Please note that with the availability of the BETA versions of Processing (0085+), this tutorial is now obsolete, as Processing now offers a JOGL based alternative rendering engine (w00t!). Its methods however, although untested with versions larger than 0068, should still be applicable. I'm keeping this tutorial online for the dozen or so people in the world who'd like to either stick with 0068 for the time being or prefer a more hands on approach to OpenGL within Processing.
If you are inexperienced with either OpenGL or Processing, my recommendation would be to start with what Processing already provides.
This is a quick solution for using Processing 0068 with the OpenGL bindings provided by JOGL. As the Processing team is working on integrating JOGL into the Processing graphics engine anyway, this is not to be considered a permanent solution. Consider it more as a quick hack for those of us who can't wait to use hardware accelerated full screen-capable 2d and 3d rendering with everybody's favorite set of libraries and the simplicity that comes with Processing.
This tutorial expects you to have experience in Processing and at least some experience in OpenGL, as I am not aiming to teach you OpenGL. If that's what you're looking for, please check the resources section at the end of this page. Having a grasp of Object Oriented Programming is of benefit and if you've done a little Java you'll have an even easier time, but both are not really required.
At the very end of the tutorial, I will present you a framework sketch which has all the functionality you'll need set up for you, so if you're keen on diving straight into the code, you can be my guest. Just make sure you'll read the warnings in here before you give in to your urge.
A few disclaimers first:
* While I'm not a newbie to 3d programming, I am a newbie to OpenGL, I am a newbie to JOGL and I can't claim to have a deep and caring relationship with java awt or swing. This doesn't mean the techniques presented here will not work. But it does mean that you should take everything I say with a grain of salt, and it also means that those who are more seasoned in the above mentioned APIs are most probably going to come up with better ways to do things. So please think of this as a work in progress. If you'd like to point out an improvement or correct an error of mine, feel free to e-mail me..
* I'm on a Windows XP system and I currently don't have access to a Mac. I also don't have the time to get into Linux. The techniques in this tutorial have only been tested on configurations similar to mine. If you port them over to a different platform and run into any interesting issues, e-mail me and I'll do my best to point them out here.
* I cannot be responsible for any harm any of this does to your machine or data. You're messing with your graphics hardware. You do want to save all currently open files before you run any of my code.
There are several different ways of getting stuck here, so making the call where to turn to for help may be a bit tricky:
If you're stuck with OpenGL specific code, I probably can't help you, but there are thousands of people who can. Find a good OpenGL forum or check the resources section of this page.
Your problem may also be related to JOGL, rather than the techniques presented here. I agree that this is rather hard to tell. You should still search the JOGL board before posting on the Processing forums or contacting me.
If it's clear that the problem lies with this tutorial, please re-read the part and see if there's anything you missed. I anticipate that in quite a few cases, the JOGL files will not be where they should be. Please read the following section carefully. If your problem persists, write me an e-mail or post in my thread on the Processing board. Please don't be upset if it may take a little while for me to get back to you.
**** What you will need! ****
JOGL is not an official part of Java but a library written by afficionados. This means that you'll need some additional classes and platform specific OpenGL bindings. Please go to the JOGL project's website and find the download section. You need two files, preferably from the lates release build: jogl.jar and the platform specific jogl-natives-*.jar.
Once you have obtained the necessary .jar files, open up Processing and create a new sketch. Find the sketch's folder on your harddisk and add a folder named "code" to it. Copy the jar files there and unpack them. You can use any software that can read zip files to do so; if it won't unpack jar files, try changing the files' extension to .zip. You should now have three things in your code folder: a folder named "net", a folder named "META-INF" and the platform specific native libraries (two .dll files if you're on Windows). Delete the META-INF folder and we're ready to go.
**** Setting up an OpenGL Window ****
What we will do is set up a new window with an openGL component (a GLCanvas) on which we will do all the rendering. I've tried a couple of things to add an OpenGL canvas to the standard processing window, but didn't get it to run. But even if we did, the way things are set up right now, I don't think we could get Processing to draw to the same canvas that OpenGL uses, so our best option is to start with a nice new window anyway.
Before we dive into the code, let's talk a bit about how JOGL handles things. JOGL needs to make sure that the OpenGL context of the JOGL component is current before calling any OpenGL functions on it. The way this is best done is to write a GLEventListener and add it to our JOGL component. The GLEventListener specifies the methods init, display, reshape and displayChanged. In terms of Processing, init and display are equivalent to setup() and loop(). Whenever display is called, you can safely assume that the OpenGL context is current and everything is sweet.
If this confused you, don't worry. The important thing to understand is that we will now have twoloop functions, the familiar loop(), and the display function within which all rendering to the openGL window should take place.
Now that we have these issues out of the way, let's have a look at some code. The first program we will look at will show us the bare bones code to get something running. It will also demonstrate how to use the loop() and display() functions. display() will draw a simple quad with a color gradient. loop() will change a variable which will be used to determine the colors of the quad. This may sound trivial, but if you can get this to run, you have everything you need to use things like Sonia or video input to alter the quad (or anything more complex you'd like to draw).
The source code for our first program is here.
Please copy it into your sketch and make sure your sketch's code folder contains the jogl classes (within the "net" folder and its subfolders) and the native libraries. Try to run the sketch.
Fancy, isn't it? Let's go over the code in detail.
This is all our Processing specific stuff. Everything else in the setup function will handle the creation of our openGL window. I've set the size for our Processing window here, but really I need not have bothered, since anything of interest will take place in the other window anyway. You could however still draw to the standard window with Processing's drawing functions; use this window for example to draw diagnostics stuff and the likes.
// set up our window
oglFrame = new Frame("Our very own OpenGL Window!");
oglFrame.setSize( 800, 600 );
renderer = new JOGLRenderer();
The oglFrame is the window which will hold our GLCanvas. This is pretty much Java specific stuff. If you'd like to learn more about it, you might want to look into awt and/or Swing programming, tutorials being available at java.sun.com. Chances are that you will not run into this again in your Processing work. One thing that is noteable, though, is that it is possible to use JOGL with awt or Swing components, enabling you to give your OpenGL window an interface with buttons, text fields and what have you.
The "renderer" object is an instance of the JOGLRenderer class which we will create later on, and within which we will write all our rendering code. If you know nothing about classes and interfaces and the likes you can look for a tutorial about Object Oriented Design on the Processing or Java websites (in fact such tutorials are all over the web). Or you could simply substitute any theory I write here with "blah blah" and just mess with the code. You will probably get what you want either way.
// set the properties of our openGL component...
GLCapabilities glCaps = new GLCapabilities();
glCaps.setRedBits(8); // 32 bit color resolution
glCaps.setBlueBits(8);
glCaps.setGreenBits(8);
glCaps.setAlphaBits(8);
glCaps.setDoubleBuffered(true); // double buffered
canvas = GLDrawableFactory.getFactory().createGLCanvas( glCaps );
// add the renderer to the component and the component to our window.
canvas.addGLEventListener(renderer);
oglFrame.add( canvas );
Here's where we create our OpenGL canvas and set it's properties. setRedBits(), setBlueBits() etc. specify the color depth of the canvas. I'll assume your hardware is somewhat current and use 8 bits per channel. We also want our canvas to be double buffered. What this means is that all our rendering will be done to an invisible offscreen buffer which will only be shown after we have finished rendering. For our example, this doesn't really matter, but if we'd draw a lot of geometry without double buffering, your screen would flicker very noticeably, as it would get updated while the program is still drawing your scene.
There are a whole bunch of other properties you can set on your OpenGL canvas using the glCaps object, such as steroscopic rendering or the number of bits for the stencil buffer. Check out the GLCapabilities class in the JOGL documentation if you want to know more.
We cannot instantiate our GLCanvas directly but get it from a Factory object to which we pass our requested capabilities. Once we have our canvas, we add our event listeners to it and add the canvas to our window. Until now, the only event listener we have is our JOGLRenderer, but later on we will learn how to get input from the mouse and keyboard by writing the respective event listeners. This is another thing that differs slightly from what you're used to in Processing, but don't worry, it's basically a matter of copy and paste.
// let the program exit if our openGL window is shut down.
oglFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
This will add an event listener to our window which will shut down the program if the OpenGL window is closed. Just leave it in there and don't mind.
And that's it. Time to show the world our OpenGL window!
// show our openGL window!
oglFrame.setVisible(true);
oglFrame.setFocusable(true);
oglFrame.requestFocus();
setFocusable and requestFocus should in theory give the OpenGL window the focus. On my machine they don't and the standard Processing window still has it; if it's the same on your machine, you can take these two lines out if you want to.
Now that we have set up things, let's take a look at our loop function:
void loop() {
// ----------------- Add your own stuff here!
ourVariable += 0.1;
// ------------------------------------------
canvas.display();
}
Not much here, is there?
One thing that is important is that you call canvas.display() in the loop. What this does is tell the GLCanvas component to redraw itself, calling the display() method of our renderer in the process. If we left this out (try it!), our rendering code would only get executed when the component felt the need to update itself, that is for example, when we grabbed the Processing window and dragged it over the OpenGL window.
If we didn't call the display function of the canvas object but tried to call the display function of our JOGLRenderer directly, it would not be certain that the GLCanvas' OpenGL context would be current when our rendering code gets executed, which means the results would probably not be very nice.
Other than that, you could put stuff into the loop() function as usual, as long as you do yourself a favor and keep all of your OpenGL rendering code in the JOGLRenderer's display method. You might as well put the code you'd have put into loop() into display() as well, but I think it's easier to start with what we're familiar with, and I haven't thoroughly tested using Processing's libraries from within display() (although I don't see why there should be a problem).
Now let's go and finally take a look at our JOGLRenderer!
As mentioned before, JOGLRenderer specifies four methods in order to implement the GLEventListener interface (again, if this last sentence confuses you, substitute with "blah blah"):
The init function should do all the OpenGL setup stuff... clear the screen, set up perspective, create display lists and so on. One thing that you should *not* do within init is to draw anything. I haven't tried it to see what happens, but the JOGL user's guide says you're going to have a bad time, and I'm really all for good times. Keep your drawing code to the display function.
The reshape function gets called any time you resize the OpenGL window. I've intentionally left this one empty for you to try out what happens when you don't do anything to react to resizing. Actually looks pretty nice, doesn't it? Once you get tired of the effect, you may want to add a glClear to the reshape method and perhaps change the perspective according to the new window proportions. displayChanged() has not been implemented at the moment, so we best leave it alone.
Time to get to the part where you call some actual OpenGL functions...
JOGL wraps up all OpenGL function calls neatly in the GL Class, and all GLU functions in the GLU class. You can get instances of these classes from within your init, display and reshape methods by calling drawable.getGL() and drawable.getGLU() respectively.
The JOGL user's guide mentions that it is good practise to acquire these objects every time display is called, rather than store them in fields. I have stored them in fields in the past and have not had problems with it, but I believe it's best to play it safe here, so we create gl and glu everytime we need them.
Now, anytime we want to call an OpenGL function, we call the function on our gl object: glColor3f(1.0, 1.0, 1.0); becomes gl.glColor3f(1.0, 1.0, 1.0);
Anytime we need an OpenGL constant, we will find it defined statically in the GL class: glBegin(GL_QUADS); becomes gl.glBegin(GL.GL_QUADS);
Please note the capitalisation here. You don't necessarily need to understand what a static variable is, just try to remember that functions are called by gl.glFunction() and constants are referred to as GL.GL_CONSTANT.
Same thing goes for the GLU library: glu.gluFunction()
but GLU.GLU_CONSTANT
I mentioned before that I'm not going to teach you OpenGL (check the resources at the bottom of this page for some starting points), so I won't go into details on this code, but it's commented and if you've been working with Processing, you'll notice some similarities. (I think this is a good place to thank Ben and Casey for adopting OpenGL paradigms for Processing's drawing API... They seemed alien to me at first, but when I started learning OpenGL, I appreciated the familiar ground I was walking on so much more!)
// ----------------- Add your own stuff here!
gl.glShadeModel(GL.GL_SMOOTH); // enable smooth shading
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // set the background color
gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST); // nice perspective correction
gl.glEnable(GL.GL_DEPTH_TEST); // enable the z-Buffer
gl.glDepthFunc(GL.GL_LEQUAL); // the z comparison function
gl.glClearDepth(1.0f); // set the depth the z-Buffer will be cleared to
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT ); // clear the screen
glu.gluPerspective(45.0f,(float)(800)/(float)(600),0.1f,100.0f); // and set up a perspective.
// ------------------------------------------
Here's for the init function... We don't actually need everything in there for our simple example (in fact we don't need anything z-Buffer related at all), but oh my.
public void display(GLDrawable drawable)
{
// -------- this is our render loop!
GL gl = drawable.getGL(); // gl should be fetched like this in every frame...
// ----------------- Add your own stuff here!
float a = sin(ourVariable)/2+0.5;
float b = sin(ourVariable*1.3)/2+0.5;
float c = sin(ourVariable*1.9)/2+0.5;
gl.glBegin( GL.GL_QUADS ); // we will draw a rectangle
gl.glColor3f(a, 0.5, c);
gl.glVertex3f( -0.3, 0.3f, -1 );
gl.glColor3f(0.5, 1-a, b);
gl.glVertex3f( -0.3, -0.3f, -1 );
gl.glColor3f(c, 1-c, 1);
gl.glVertex3f( 0.3, -0.3f, -1 );
gl.glColor3f(c, b, a);
gl.glVertex3f( 0.3, 0.3f, -1 );
gl.glEnd();
// ------------------------------------------
}
And here's our whole render loop (our display() function, to be more precise)! As you can probably figure, those a, b and c variables are solely there to impress you with some flashy color-shifting. :)
**** Windowed vs. Full Screen ****
Up to this point, we have already learned how to set up a window for OpenGL rendering. Time to go Fullscreen Exclusive! (Weeeeee!) The first thing you want to do before continuing with this part is to save any files you currently have open and possibly close all programs you have running, except for Processing! I've had a couple of crashes where I couldn't go back from Fullscreen to Windowed Mode. We want to minimize the damage. :) Also, I'd like to gently remind you that I'm on a WinXP machine. I haven't tested it on Mac, Linux or Irix. Do not yell at me.
Now that I have you wondering if you'll ever want to try this, please create a new sketch with the code folder containing the jogl files and copy the source code from here into it. Run it AND DO NOT press escape to quit. Quit by hitting alt+F4 or whatever shortcut normally closes applications on your system. You can press escape later on when we've written some key event listeners.
Did it work? Great. Let's take a look at what has changed since our last sketch:
There is now a boolean variable named fullscreen, which I set to true at the beginning of setup. I thought it would be nice to have something to quickly flag applications as non-fullscreen for testing.
The code to actually go to Fullscreen Exclusive mode is as following:
if (fullscreen) {
oglFrame.setUndecorated(true); // We don't want any title bar for this window
GraphicsEnvironment ge=null;
GraphicsDevice gd=null;
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gd = ge.getDefaultScreenDevice();
if( gd.isFullScreenSupported() )
{
gd.setFullScreenWindow(oglFrame);
}
}
This is basically Java code which says "Set the oglFrame as the fullscreen window!". To quit fullscreen mode, we do the same thing but call gd.setFullScreenWindow(null); instead. All this will work with Java 1.4+, so if it won't compile, this may be a possible place to start looking for reasons.
If you'd like to know more about the Java behind this, here's a pretty good explanation.
Since we go to fullscreen at the start of our application, we must go back when we exit it. Thus, we have to modify the window listener code that executes when we want to close the program:
// let the program exit if our openGL window is shut down.
oglFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
if (fullscreen) {
GraphicsEnvironment ge=null;
GraphicsDevice gd=null;
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gd = ge.getDefaultScreenDevice();
if( gd.isFullScreenSupported() )
{
gd.setFullScreenWindow(null);
}
}
System.exit(0);
}
});
Pretty straightforward, and if it's not, you really don't have to care about it, because you can just leave this code as it is. As I mentioned, we quit fullscreen mode by calling gd.setFullScreenWindow(null);
One more thing I have added is a call to gl.glClear in the display method which will clear the screen before each frame. When I left it out, I got very odd results. Incidentally, calling gl.glClear at the start of the display function is another way of handling the resizing issues that come with windowed mode. But I know that very often, we don't want to clear the screen at all but want to keep drawing to the same image until the end of time, which is why the next part of this tutorial is about retaining what's on the screen...
**** Retaining what's on the Screen ****
Not clearing the screen can be a bit trickier than you'd expect, and again I sure wish someone would send me their spare G5 so I could test this on another platform - but with a bit of trial and error, we should get it to work. One thing you might try if you follow everything I do here and the damn screen is still cleared is to mess with your graphics card settings. With my ATI 9700, going into the OpenGL settings of the driver and playing with the parameters for antialiasing miraculously helped.
Still, before you do any of this, just try if it works automagically anyway.
I've modified our program to do something different this time, as drawing the same quad at the same position over and over again clearly doesn't demonstrate our screen retaining skills very well. So we will now draw one random quad per frame.
Here's the modified display function:
public void display(GLDrawable drawable)
{
// -------- this is our render loop!
GL gl = drawable.getGL(); // gl should be fetched like this in every frame...
// ----------------- Add your own stuff here!
float a = sin(ourVariable)/2+0.5;
float b = sin(ourVariable*1.3)/2+0.5;
float c = sin(ourVariable*1.9)/2+0.5;
gl.glBegin( GL.GL_QUADS ); // we will draw a rectangle
gl.glColor3f(a, 0.5, c);
gl.glVertex3f( random(-1,1), random(-1,1), random(-3,-2) );
gl.glColor3f(0.5, 1-a, b);
gl.glVertex3f( random(-1,1), random(-1,1), random(-3,-2) );
gl.glColor3f(c, 1-c, 1);
gl.glVertex3f( random(-1,1), random(-1,1), random(-3,-2) );
gl.glColor3f(c, b, a);
gl.glVertex3f( random(-1,1), random(-1,1), random(-3,-2) );
gl.glEnd();
// ------------------------------------------
}
As you can see, I've taken out the gl.glClear function and positioned the vertices of the quad randomly.
One thing we might want to do in case we're not going fullscreen is to change the reshape function so that it will clear the screen when the application is resized.
// -------- this will be called whenever our openGL window is resized!
GL gl = drawable.getGL(); // gl should be fetched like this in every frame...
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT ); // clear the screen
Leaving stuff on the screen when resizing really doesn't make much sense at all, as there's no way the image will look the same afterwards (well, except for copying everything to a texture in every frame and placing the texture on screen after resize, I guess), and the excitement over the artifacts caused by reshaping a window probably wears off after a while. (I'm not there yet, but I guess I will be anytime soon :) )
While this may already achieve what we want for windowed applications, switching to fullscreen results in a very harsh flicker on my machine. One solution that is simple and applicable for me is to switch off double buffering. The way I see it, having two buffers which the application alternately draws to gets in the way of leaving things on the screen. So our next step would be to deactivate double buffering in our setup function:
One downside to single buffering is that the screen may update while you are still drawing, but in most cases, this resembles what you want to disable double buffering for anyway, so I haven't spent much time looking for a solution. If you come up with one, let me know and I'll mention it here.
Another thing that's a bit awkward is that apparently the call to gl.glClear in the init function of our renderer is ignored when working with only one single buffer. What can we do about this? Well, considering that all of what we're doing here is a temporary solution until Processing hits the 1.0, adding another hack is ugly but forgiveable in my book. Let's add a flag which will check whether it's the first time display is being called, and if so have the screen cleared. Yes, it's clumsy, but it works.
if (isFirstFrame) {
isFirstFrame = false;
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT ); // clear the screen
}
I have added this code to the display function, right after gl is acquired. I have also declared the isFirstFrame variable at the beginning of the JOGLRenderer class, making it a field of JOGLRenderer and initialised it to true in JOGLRenderer's init function.
Time to go fullscreen. Before you edit the fullscreen flag in setup to true: If you've skipped this tutorial's part about going fullscreen, go there now and read the warning.
If you've followed all of this and it worked, good for you! If it didn't and you found a way to make it, please let me know.
Here is the source code for this part.
**** Keyboard and Mouse Handling ****
Let's try to get some input into our program! While MIDI input, audio input or computer vision can all be nicely done using the libraries that have been written for Processing, the conventional methods that wrap up event handling will only work with the standard application window and not with our OpenGL canvas. The way we have to go is to write our own Event Listeners and add them to our GLCanvas component. Not to worry, if you're already familiar with Java, you'll know how these things work, and if not, I'll try to wrap everything up nicely and show you where to place things!
Again, for this part, we will change our programm. It will now let the user draw triangles at different z-depths using the mouse. Pressing the space bar will clear the screen.
Perhaps we should look at the end of the source code for this part of the tutorial first. There, you will find two new classes, one KeyInputHandler and one MouseInputHandler. Both extend Java event listeners, allowing you to write your own event handling code.
Both classes define functions for event handling that resemble the functions you are used to from Processing. keyPressed() will execute when a key is pressed with the OpenGL window in focus and mousePressed does the same thing for the mouse. Just put anything you would have put into the usual mousePressed, mouseMoved, keyReleased, etc. methods into the methods in this class, after the comments that say "Add your own stuff here". Anything before these comments is there so the variableskeyPressed, mousePressed etc. are updated as well, giving you as much compatibility to your familiar way of working with Processing as possible.
The only thing my Event Listeners handle differently are the mouse positions. First of all, if I had stored these in the mouseX and mouseY variables that you are used to, they would have been overwritten by Processing and not worked reliably, so I changed their names to oglMouseX and oglMouseY respectively. Use these variables when you are dealing with mouse positions in the OpenGL window. I have also added two variables named oglPMouseX and oglPMouseY to replace Processing's pmouseX and pmouseY.
The second thing that must be noted is the difference between coordinate systems in Java/Processing and OpenGL. In Processing, the coordinate system's center - that is the coordinates (0,0) - is in the top left corner of your window and the x and y coordinates extend positively to the width and height of the window. In OpenGL this is mostly not the case. In our example the coordinate center of the OpenGL world is at the center of the OpenGL window with x and y coordinates extending from about -1 to +1 at near z-Values. There are also cases where the coordinate center is at the bottom left of the window. If all of this is confusing to you, you may want to read up on OpenGL at one of the resources provided at the bottom of this page.
Anyway, I figured that I'd leave the conversion for you to do yourself since it may be a different one from situation to situation. As in Processing, oglMouseX and oglMouseY for now refer to pixel distances from the top left corner of the window.
Now that we have our Event Listeners, we must add them to our GLCanvas component.
I have added two new objects at the top of our program: the KeyInputHandler "keyHandler" and the MouseInputHandler "mouseHandler". Both are initialised in the setup function. Now scroll down a bit and locate the line canvas.addGLEventListener(renderer); (still in the setup function). This is where we added our renderer to the canvas. Below this line, I have inserted three lines of code which add the two other listeners:
And that's it, mouse and key events are now delegated to the functions in our event handling classes.
The rest of what has changed since the last part of this tutorial is an example of how to use these event handling capabilities. Let's go through the changes quickly:
In the loop() function, ourVariable (the one that handles our color shifting, if you remember) will only be incremented if the mouse is pressed.
if (mousePressed) {
ourVariable += 0.1;
}
At the beginning of the program I have added a float variable named ourZ. ourZ will loop from -3 to -1 and will be used to draw the triangles with varying depth.
And here's the updated display() function:
ourZ += 0.02;
if (ourZ > -1) {ourZ = -3;}
if (mousePressed) { // If the mouse is pressed...
float a = sin(ourVariable)/2+0.5;
float b = sin(ourVariable*1.3)/2+0.5;
float c = sin(ourVariable*1.9)/2+0.5;
// mouse coordinates are pixel distances from the top left corner of the window.
// we will need to convert them to floating point values ranging from -1 to 1
float x = ((float) oglMouseX / (float) drawable.getSize().getWidth())*2-1;
float y = -(((float) oglMouseY / (float) drawable.getSize().getHeight())*2-1);
gl.glBegin( GL.GL_TRIANGLES ); // draw a triangle
gl.glColor3f(a, 0.5, c);
gl.glVertex3f( x, y+0.1, ourZ );
gl.glColor3f(0.5, 1-a, b);
gl.glVertex3f( x-0.1, y-0.1, ourZ );
gl.glColor3f(c, 1-c, 1);
gl.glVertex3f( x+0.1, y-0.1, ourZ );
gl.glEnd();
}
Whenever the mouse is pressed, a triangle is drawn at approximately the mouse coordinates with ourZ as the z-depth. Take a look at the conversion of the oglMouseX and oglMouseY into the x and y floats! Here, the x coordinate of the mouse cursor is converted from 0 ... width to -1 ... 1 and the y coordinate from 0 ... height to 1 ... -1.
if (keyPressed && key == ' ') { // If the spacebar is pressed...
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT ); // clear the screen
}
This piece of code will clear the screen. You should be able to use keyPressed and key just as you normally would. One thing to remember is to only execute OpenGL functions from within the renderer's init(), display or reshape functions (or functions that are only called from within these).
Oh, and I almost forgot:
I've added some code to the KeyInputHandler that should finally enable you to press escape to quit your application. That still doesn't mean I am responsible for anything that happens when you use Fullscreen mode!
**** Textures! ****
I have two suggestions, as to how to go about adding textures to your OpenGL applications:
If you have a bit of background in java and you're not particularly keen on updating your textures as your program is running - that is if you want to use static textures loaded from files as opposed to procedural textures, video input and the likes - my advice would be to take a look at this tutorial posted on the JOGL forums by a fellow named gregorypierce. He presents source code for a TextureManager class that can take care of all your texturing issues. I'm sure he knows much more about what he's doing than I do, and his classes handle more advanced features such as mip-mapping and enable you to load transparent PNGs as well as JPGs.
On the other hand, if you want to update your textures continuously, such as with generative or video textures, you can use two functions that I quickly wrote up. These are handy for converting BImages or arrays of pixels into textures on the fly, so you have something more familiar to work with.
Before I'll send you to the source code for this part, there's a couple of things I should probably remind you of:
* Always have your texture image dimensions in powers of 2. 64*64, 128*128, 256*256, 512*512, 128*512 etc. If you want to draw to the standard Processing window and use its pixels array as a texture, set its size to 256*256 or something like that. I know this is troublesome when you want to use video input for textures, but you'll have to find a way of working around that.
* For all I know, uploading image data to the graphics card is not a fast process. With the creation of Java ByteBuffers I perform to achieve this, there is going to be even more overhead involved. If you update a lot of your textures on a per frame basis, don't expect your usual thousand frames per second. :)
That being said, here is the source code for this part. As always, you will have to have your jogl files in your code folder, and you'll also want to put a file named texture.jpg into your data folder. If you're in a hurry, you can grab this one.
The new functions have been added at the end. makeTexture(int textureID, BImage img) converts a BImage to a texture and makeTexture(int textureID, int[] imageData, int w, int h) converts an array of pixels (in this case you'll also have to specify the height and width of your array).
Please note the paramter textureID in there. It specifies the "slot" your texture will be saved to, so that you can retrieve it later on if you need it again, using gl.glBindTexture(GL.GL_TEXTURE_2D, textureID);. Whenever you call makeTexture, your new texture is automatically bound, so if you're confused and don't want to bother, you could always use just any same old integer larger than 0 as your textureID.
The example for this part is pretty straightforward. At the beginning of the application, I create a BImage named ourTexture which is uploaded to the graphics card in the renderer's init function which also enables texturing. In the display function, we'll draw our trusty old colored quad, only this time, texture mapping has been added and calls to gl.glTexCoord2f are being made. One thing you may have noticed is that I switched the texture coordinates vertically to make up for OpenGL's coordinate system. While the approach gregorypierce used in his code is to flip the image before the texture is created from it, my functions have been written with people in mind who'd like to update their textures as often as possible, so I didn't want to add more overhead than I already have.
**** Reading Back Pixels and Saving Your OpenGL Renderings ****
The final part of this tutorial will enable you to copy the image data from your renderings back into memory and save them as PNGs. Again, please note that reading from and writing to the graphics card are always rather slow operations and they are not exactly accelerated by doing them with java.
The code for this part gives you two new functions to use: copyFrame() will copy the contents of the current frame into an integer array. saveFrameAsPNG will take a string specifying the filename as an argument and save the contents of the current frame to disk as a PNG file.
The functions are basically an adaptation of code by keving, posted to the JOGL forum in this thread.
Our example is the same as in the last part with the addition that pressing "s" on your keyboard will now save the frame. Please note that the copy and save functions use the gl object and thus may only be used when the OpenGL context is current, i.e. from within the display function!
And that's all! We now have a framework for most of our OpenGL desires to be used from within Processing! A complete base sketch, cleared of any example specific drawing code can be found here, to be used as you wish. Have fun!
**** Resources ****
If you'd like me to add a link, please let me know.
** JOGL resources ** https://jogl.dev.java.net/
This is the website of the JOGL project. Get your latest JOGL binaries here!
** OpenGL resources ** http://nehe.gamedev.net/
Probably the most often referenced OpenGL tutorials on the web. 48 awesome lessons ranging from "Hello World" to pixel shaders.
**** Porting selected Processing examples to OpenGL ****
The examples all use the framework sketch constructed in the tutorial. Refer to the beginning of each example for some additional comments, such as where example specific code is located. Also note again that I'm not responsible for whatever havok these may wreak. :)
form: Points and Lines form: Shape Primitives
These first two examples show you how to draw some primitive shapes with JOGL. They'll also give you some ideas of how to handle OpenGL's coordinate system.
form: Vertices 3D
This example introduces you to 3d rendering with OpenGL. It shows you how to switch to perspective view and how to deal with translation and rotation. It also uses the OpenGL equivalents of push() and pop().
drawing: Continuous Lines
This example lets the user doodle using the mouse. What is tricky about this is again the conversion between Processing and OpenGL coordinates, an additional problem being that the window's title bar counts as part of the canvas' height... see the comments in the sketch for more details.
Update: Marius Watz suggests calling oglFrame.pack() after oglFrame.add(canvas) in the setup() method. Unfortunately calling oglFrame.pack() will crash the application on my machine. You might still want to try it out, if your target system is a single machine in an installation situation.
image: Displaying
Demonstrates loading and displaying a BImage as a texture. Remember to use images whose height and width are powers of two.
image: Transparency
This example demonstrates how to use GL_BLEND to draw a semi-transparent image. It also shows how to retrieve stored textures via glBindTexture.
image: Linear Image
I've implemented this example by moving only texture coordinates, in order to illustrate how some effects can be achieved more efficiently by using tools provided by OpenGL. (The rest of the example is actually not implemented, but it's not hard to figure out)
transform: Push and Pop
This is a somewhat advanced example, using lights, push and pop operations and drawing code (constructing a box, as there are no OpenGL primitives for box).