he is a beedso framerr.

TABLE OF CONTENTS
Introduction to JOGL
OpenGL is a cross-platform, language-independent, industrial standard API for producing 3D (and 2D) computer graphics. Graphics cards that claim OpenGL-compliance make use of the hardware acceleration when possible to speed up the graphics rendering process. OpenGL competes with Direct3D on Microsoft Windows platform. The OpenGL mother site is at .
The JOGL (Java Bindings for the OpenGL) allows Java applications to access the OpenGL API for graphics programming. In other words, it is simply a wrapper library for Java application to use OpenGL API. JOGL is open-source and currently maintained by &JogAmp& (Java on Graphics, Audio, Media and Processing) @ . JogAMP provides JOGL (3D graphics), JOAL (Java Bindings for OpenAL for 3D Audio) and JOCL (Java Bindings for OpenCL - a Common
Language for Graphics Processors).
Alternatives to JOGL include open-source LWJGL (Light-Weight Java Game Library) @ .
This tutorial assumes that you have sufficient knowledge on OpenGL, but new to JOGL. To learn OpenGL, find a good OpenGL book (e.g., the
Red book &OpenGL Programming Guide& or Blue Book &OpenGL Superbible&). Nehe production
maintains an excellent OpenGL Tutorials (@ ). You may also read my OpenGL tutorials.
JOGL 2 supports OpenGL 1.3 to 4.0 and OpenGL ES 1.x and 2.x. JOGL integrates with the AWT, Swing and SWT. It also provides its own native windowing toolkit called NEWT.
This guide is meant for JOGL 2.0 (tested on rc8), which is not compatible with the older (and obsoleted) JOGL 1.x. For JOGL 1.x, read &&.
Setting Up
JOGL 2.0_rc8
&Downloading and installing JOGL& @ .
Step 0: Install JDK
Install JDK, an IDE such as Eclipse/NetBeans or a programming text editor. You need a working Java programming environment to write JOGL programs.
Step 1: Download JOGL
Download the latest &stable& release from JogAMP @
(or from JogAMP @
&rA Builds/Downloads &rA Current &rA zip). Select &jogamp-all-platforms.7z&, which contains the JOGL and gluegen JAR-files, Java Native Library (JNI) for all the platforms (e.g., Win32, Win64, Linux, Mac OS), and source-files.
You are also recommended to download the &javadocs& (jogl-javadoc.7z and gluegen-javadoc.7z), &demos& (jogl-demos.7z) and sources.
Step 2: Setup JOGL
Unzip &jogamp-all-platforms.7z& (you can unzip &.7z& file using WinRAR, WinZip, or ).
The jogl's and gluegen's jar-files are kept in the &jar& sub-directory.
The Java Native JNI Libraries
(&.dll& for Windows, &.so& for Linux, or &.jnilib& for MacOS) are kept in the &lib& sub-directories.
Create a JOGL binary directory, says &jogl-2.1& - I shall denote the binary directory as $JOGL_HOME. Create sub-directories &jar&, &lib&, &src& (optional), &javadoc& (optional) under the $JOGL_HOME.
Copy the necessary jar-file, native libraries of your operating platform, and source-files into the appropriate sub-directories. Read &&.
For example, for Win64, copy &jar\gluegen-rt.jar&, &jar\jogl.all.jar&, &jar\gluegen-rt-natives-windows-amd64.jar& and &jar\jogl-all-natives-windows-amd64.jar& into &jar&; &lib\windows-amd64\gluegen-rt.dll&, &lib\windows-amd64\jogl_desktop.dll&, &lib\windows-amd64\nativewindow_awt.dll&, &lib\windows-amd64\nativewindow_win32.dll&, &lib\windows-amd64\newt.dll& into &lib&; and
&gluegen-java-src.zip&, &jogl-java-src.zip& into &src&. Unzip the javadocs downloaded into &javadoc&.
Read the &jogl.README.txt&.
This step is optional, but it is good to organize the JOGL JAR-files and Java Native Libraries for your operating platform in a single directory.
Step 3a: Customize for Eclipse 4.3
We shall first create a Eclipse's User Library called &jogl-2.1&, which specifies the jar-files, native libraries, javadoc, and source files for the JOGL API. All the JOGL projects can then include this user library in its build path.
From &Window& menu
=> Preferences
=> Build Path => User Libraries
=> In &User library name&, enter &jogl-2.1&.
In &User Library& dialog => Select &jogl-2.1&
=> &Add External JAR...& => Navigate to &$JOGL_HOME\jar&, and select &gluegen-rt.jar& and
&jogl.all.jar&.
Expand the &jogl.all.jar& node, select &Native library location: (none)& &rA &Edit...& &rA External Folder... &rA select &$JOGL_HOME\lib& to provide the path for the native library code (such as &jogl_desktop.dll& for Windows).
Repeat for &gluegen-rt.jar& (for &gluegen-rt.dll&).
(Optional But Recommended) Expand the &jogl.all.jar& node again
=> Select &Javadoc location& &rA &Edit...&
Specify the javadoc's path (either file: or http:) in &Javadoc URL& if you use an unzip version of the javadoc.
Specify the javadoc's archive file (either zip or jar) in &Javadoc in archive& if you use a zip file.
Choose &Validate&, which search for an &index.html& file.
This is needed for Eclipse to display javadoc information about classes and methods.
(Optional But Recommended) You may provide the source files by editing &Source attachment& &rA &Edit...& &rA &External File...& &rA Select the source file in zip form. Source is needed only if you are interested to debug into the JOGL source codes.
For EACH JAVA PROJECT created that uses JOGL, right-click on the project &rA &Build Path& &rA &Add Libraries& &rA Select &User Library& => Check &jogl-2.1&.
Read && if you encounter error &SEVERE: java.lang.UnsatisfiedLinkError: no xxx in java.library.path&.
Step 3b: Customize for NetBeans 7.0 (To Check!)
There was a so-called &NetBeans
OpenGL Pack&, but it seems to be out-dated and does not support JOGL 2 (?!).
We shall create our own JOGL Library as follows:
From &Tool& &rA &Library& &rA Click &New Libraries...& &rA Enter &jogl2.0&.
Click &Add JAR/Folder...& &rA Select &jogl.all.jar& and &gluegen-rt.jar&.
Under the &JavaDoc& tab &rA Select the JOGL's javadoc. You could use the zip version for better performance.
Under the &Source& tab &rA Select the JOGL's source. You could use the zip version for better performance.
For EACH of the JOGL project, include the JOGL library. Right-click on the project &rA &Properties& &rA &Library& &rA Under &Compile& tab &rA &Add Libraries...& &rA Choose the library &jogl2.0& created earlier.
You also need to include the native library path for each of the project. Right-click the project => &Set Configuration& => &Customize...& => &Run& => In &VM options&, enter &-Djava.library.path=xxx&, where xxx is directory path (e.g., d:\bin\jogl2.0\lib), that contains the Java Native JNI Libraries (&*.dll& for Windows, &*.so& for Linux or &*.jnilib& for MacOS).
Read && if you encounter error &SEVERE: java.lang.UnsatisfiedLinkError: no xxx in java.library.path&.
Step 3c: Customize for JDK/Editor
You need to modify two environment variables - CLASSPATH and PATH. Read
on how to set these environment variables.
Modify the CLASSPATH environment variable to include the full-path filenames of &jogl.all.jar& and &gluegen-rt.jar&, for example,
shell& set classpath=.;$JOGL_HOME\lib\jogl.all.$JOGL_HOME\lib\gluegen-rt.jar
where $JOGL_HOME denotes the JOGL installed directory. Take note that you should include the current working directory '.'.
Modified the PATH environment variable to include the full path to the JOGL's &lib& directory for accessing the native libraries (e.g., &jogl_xxx.dll&, &gluegen-rt.dll&), for example,
shell& set path=$JOGL_HOME\......
Alternatively, you could include the directory path of the native libraries in Java system's property &java.library.path&, via the VM command-line option -Djava.library.path=pathname, for example,
shell& java -Djava.library.path=d:\bin\jogl2.0\lib myjoglapp
Read && if you encounter error &SEVERE: java.lang.UnsatisfiedLinkError: no xxx in java.library.path&.
Getting Started with JOGL 2.1
OpenGL Drawable: GLCanvas and GLJPanel
An OpenGL drawable is a surface (or canvas) for graphics rendering. JOGL provides two drawables in package javax.media.opengl.awt: GLCanvas and GLJPanel.
GLCanvas: A heavyweight AWT component which is a subclass of java.awt.Canvas.
You can create a GLCanvas via the default constructor GLCanvas(), which construct a new GLCanvas component with a default set of OpenGL capabilities, using the default OpenGL capabilities selection mechanism, on the default screen device. For example,
GLCanvas canvas = new GLCanvas();
JFrame frame = new JFrame();
frame.getContentPane().add(canvas);
canvas.addGLEventListener(.....);
GLJPanel: A lightweight Swing component which is a subclass of javax.swing.JPanel.
Again, You can create a GLJPanel via the default constructor GLJPanel(), which construct a new GLJPanel component with a default set of OpenGL capabilities, using the default OpenGL capabilities selection mechanism. For example,
GLJPanel canvas = new GLJPanel();
JFrame frame = new JFrame();
frame.getContentPane().add(canvas);
canvas.addGLEventListener(.....);
The GLCanvas is a heavyweight AWT component which supports hardware acceleration. It is designed as the primary widget for JOGL applications. On the other hand, GLJPanel is a swing-compatible lightweight component, which supports hardware acceleration but is not as fast as GLCanvas. GLJPanel is intended to provide 100% swing integration when the heavyweight GLCanvas cannot be used. Both the GLCanvas and GLJPanel implement a common interface GLAutoDrawable (which in turn implements the interface GLDrawable). These interfaces define the common behaviors expected on GLCanvas and GLJPanel, so that applications can switch between them with minimal code changes.
Beside supporting AWT, Swing and SWT, JOGL 2 also provide its own Native Window Toolkit called NEWT via drawable GLWindow (in package com.jogamp.newt.opengl). I shall discuss NEWT later.
GLEventListener and GLAutoDrawable
The interface GLEventListener (in package javax.media.opengl) declares the following 4 OpenGL event handlers:
init(GLAutoDrawable drawable): called by the
drawable immediately after the OpenGL context is initialized. It can be used to perform one-time initialization tasks such as setting up of lights and display lists. init() runs only once.
dispose(GLAutoDrawable drawable): called by the drawable before the OpenGL context is destroyed. It can be used to release all OpenGL resources, such as memory buffers.
display(GLAutoDrawable drawable): called by the drawable to
render OpenGL graphics. It is the most important method.
reshape(GLAutoDrawable drawable, int x, int y, int width, int height): called by the drawable when it is first set to visible, and during the first repaint after the it has been resized. It is used to set the view port and projection mode, and view volume.
All these methods are call-back methods. When an OpenGL event is posted on the event-queue, the graphics sub-system calls back the corresponding handler. An OpenGL renderer shall implement the GLEventListener interface.
Recall that GLCanvas and GLJPanel are GLAutoDrawable. The interface GLAutoDrawable defines these abstract methods to add or remove a GLEventListener:
public void addGLEventListener(GLEventListener listener)
public void removeGLEventListener(GLEventListener listener)
For example,
GLCanvas canvas = new GLCanvas();
canvas.addGLEventListener(renderer);
Whenever an OpenGL event is fired (e.g., init, display), GLCanvas or GLJPanel invokes the corresponding handler
of all its registered GLEventListener(s) (such as init() and display()) with itself as the argument for GLAutoDrawable, as illustrated.
To perform animation, we need an animator to drive the drawable's display() method in a loop to refresh the display regularly. JOGL provides two animator classes: Animator and FPSAnimator (in package com.jogamp.opengl.util). The commonly-used FPSAnimator can drive the display() of the given drawable at the specified number of frame per seconds (fps). For example,
GLCanvas canvas = new GLCanvas();
FPSAnimator animator = new FPSAnimator(canvas, 60);
animator.start();
animator.pause();
animator.resume();
animator.stop();
animator.isStarted();
animator.isAnimating();
animator.isPause();
The commonly-used constructors are:
FPSAnimator(GLAutoDrawable drawable, int fps)
FPSAnimator(GLAutoDrawable drawable, int fps, boolean scheduleAtFixedRate)
Animator(GLAutoDrawable drawable)
OpenGL Graphics Context
In order to perform rendering, an so-called OpenGL rendering context is required. You can retrieve the graphics context from a drawable as follow:
javax.media.opengl.GL
gl = drawable.getGL();
javax.media.opengl.GL2 gl = drawable.getGL().getGL2();
javax.media.opengl.GL3 gl = drawable.getGL().getGL3();
javax.media.opengl.GL4 gl = drawable.getGL().getGL4();
// Others: GL2GL3, GL2bc, GL4bc, GLES1, GLES2, GL2ES1, GL2ES2
[TODO] To Revise.
JOGL 2.0/2.1 Program Templates
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoD
import javax.media.opengl.GLEventL
import javax.media.opengl.awt.GLC
import javax.media.opengl.glu.GLU;
import com.jogamp.opengl.util.FPSA
import static javax.media.opengl.GL.*;
import static javax.media.opengl.GL2.*;
@SuppressWarnings(&serial&)
public class JOGL2Setup_GLCanvas extends GLCanvas implements GLEventListener {
private static String TITLE = &JOGL 2.0 Setup (GLCanvas)&;
private static final int CANVAS_WIDTH = 640;
private static final int CANVAS_HEIGHT = 480;
private static final int FPS = 60;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
GLCanvas canvas = new JOGL2Setup_GLCanvas();
canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
final FPSAnimator animator = new FPSAnimator(canvas, FPS, true);
final JFrame frame = new JFrame();
frame.getContentPane().add(canvas);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
new Thread() {
public void run() {
if (animator.isStarted()) animator.stop();
System.exit(0);
}.start();
frame.setTitle(TITLE);
frame.pack();
frame.setVisible(true);
animator.start();
private GLU
public JOGL2Setup_GLCanvas() {
this.addGLEventListener(this);
public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
glu = new GLU();
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl.glClearDepth(1.0f);
gl.glEnable(GL_DEPTH_TEST);
gl.glDepthFunc(GL_LEQUAL);
gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
gl.glShadeModel(GL_SMOOTH);
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
GL2 gl = drawable.getGL().getGL2();
if (height == 0) height = 1;
float aspect = (float)width /
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL_PROJECTION);
gl.glLoadIdentity();
glu.gluPerspective(45.0, aspect, 0.1, 100.0);
gl.glMatrixMode(GL_MODELVIEW);
gl.glLoadIdentity();
public void display(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.0f, -6.0f);
gl.glBegin(GL_TRIANGLES);
gl.glVertex3f(0.0f, 1.0f, 0.0f);
gl.glVertex3f(-1.0f, -1.0f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, 0.0f);
gl.glEnd();
public void dispose(GLAutoDrawable drawable) { }
Try running the above program, which will show a white triangle on a black screen.
In this template:
I follow the design of &&, where the GUI &Component& (GLCanvas or GLJPanel) and &Container& (JFrame, Frame, JApplet, Applet) are clearly separated. The &Component& can be easily plugged into any of the &Container&.
The main class extends GLCanvas (Component) to provide the OpenGL graphics rendering canvas. It also implements GLEventListener and provides handlers for init(), display(), dispose() and reshape().
The main() method:
Allocates a GLCanvas component.
Allocates an Animator to drive the display() method of the GLCanvas in a loop to refresh the display.
Allocates a top-level container (Swing's JFrame or AWT's Frame), and adds the GLCanvas component into the container..
Using AWT's Frame as Top-Level Container
The above template uses Swing's JFrame as the top-level container. To use AWT's Frame as the top-level window, modify the main() method as follows:
public static void main(String[] args) {
GLCanvas canvas = new GLCanvas();
final Frame frame = new Frame();
frame.add(canvas);
[TODO] Check whether GLCanvas is better to use with AWT's Frame or Swing's JFrame if there is no other light-weight components.
Separating the Component and Container Classes
Alternatively, you could separate the renderer Component and the top-level Container into two classes:
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoD
import javax.media.opengl.GLEventL
import javax.media.opengl.awt.GLC
import javax.media.opengl.glu.GLU;
import static javax.media.opengl.GL.*;
import static javax.media.opengl.GL2.*;
@SuppressWarnings(&serial&)
public class JOGL2Setup_Renderer extends GLCanvas implements GLEventListener {
private GLU
public JOGL2Setup_Renderer() {
this.addGLEventListener(this);
public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
glu = new GLU();
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl.glClearDepth(1.0f);
gl.glEnable(GL_DEPTH_TEST);
gl.glDepthFunc(GL_LEQUAL);
gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
gl.glShadeModel(GL_SMOOTH);
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
GL2 gl = drawable.getGL().getGL2();
if (height == 0) height = 1;
float aspect = (float)width /
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL_PROJECTION);
gl.glLoadIdentity();
glu.gluPerspective(45.0, aspect, 0.1, 100.0);
gl.glMatrixMode(GL_MODELVIEW);
gl.glLoadIdentity();
public void display(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.0f, -6.0f);
gl.glBegin(GL_TRIANGLES);
gl.glVertex3f(0.0f, 1.0f, 0.0f);
gl.glVertex3f(-1.0f, -1.0f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, 0.0f);
gl.glEnd();
public void dispose(GLAutoDrawable drawable) { }
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.media.opengl.awt.GLC
import com.jogamp.opengl.util.FPSA
@SuppressWarnings(&serial&)
public class JOGL2Setup_RendererMain extends JFrame {
private static String TITLE = &JOGL 2.0 Setup (GLCanvas)&;
private static final int CANVAS_WIDTH = 640;
private static final int CANVAS_HEIGHT = 480;
private static final int FPS = 60;
public JOGL2Setup_RendererMain() {
GLCanvas canvas = new JOGL2Setup_Renderer();
canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
final FPSAnimator animator = new FPSAnimator(canvas, FPS, true);
this.getContentPane().add(canvas);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
new Thread() {
public void run() {
if (animator.isStarted()) animator.stop();
System.exit(0);
}.start();
this.setTitle(TITLE);
this.pack();
this.setVisible(true);
animator.start();
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new JOGL2Setup_RendererMain();
In this approach, all the graphics rendering codes are kept in the renderer (Component) class. The main (Container) class can be left untouched.
To use the light-weight GLJPanel instead of the heavy-weight GLCanvas, simply change all reference of GLCanvas to GLJPanel.
public class JOGL2Setup_GLJPanel extends GLJPanel implements GLEventListener {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
GLJPanel canvas = new JOGL2Setup_GLJPanel();
GLJPanel canvas = new GLJPanel();
Running as an Applet
As we followed the Component-based Architecture in our class design, we can plug the GLCanvas Component into other top-level Container, such as JApplet or Applet easily.
To run the program as an applet, you can simply add an applet launching class (as follow) to launch the main renderer class. Recall that an applet extends javax.swing.JApplet (or java.awt.Frame) and is managed via methods init() (instead of main() of an application), start(), stop() and destroy().
import java.lang.reflect.InvocationTargetE
import javax.media.opengl.awt.GLC
import javax.swing.JA
import javax.swing.SwingU
import com.jogamp.opengl.util.FPSA
@SuppressWarnings(&serial&)
public class JOGL2Setup_Applet extends JApplet {
private static final int FPS = 60;
public void init() {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
GLCanvas canvas = new JOGL2Setup_GLCanvas();
getContentPane().add(canvas);
animator = new FPSAnimator(canvas, FPS, true);
animator.start();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
public void destroy() {
if (animator.isStarted()) animator.stop();
public void start() {
animator.start();
public void stop() {
if (animator.isStarted()) animator.stop();
The main() method in the renderer class is ignored by applet.
Running in Full-Screen Mode
Games are often run in full-screen mode without decoration (title, status, scroll bars). The JOGL 2.0 program template for full screen mode operation is as follows:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAnimatorC
import javax.media.opengl.GLAutoD
import javax.media.opengl.GLEventL
import javax.media.opengl.awt.GLC
import javax.media.opengl.glu.GLU;
import com.jogamp.opengl.util.FPSA
import static javax.media.opengl.GL.*;
import static javax.media.opengl.GL2.*;
@SuppressWarnings(&serial&)
public class JOGL2Setup_GLCanvasFullScreen extends GLCanvas
implements GLEventListener, KeyListener {
private static final int FPS = 60;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
GLCanvas canvas = new JOGL2Setup_GLCanvasFullScreen();
FPSAnimator animator = new FPSAnimator(canvas, FPS, true);
JFrame frame = new JFrame();
frame.getContentPane().add(canvas);
frame.setUndecorated(true);
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
frame.setVisible(true);
animator.start();
private GLU
public JOGL2Setup_GLCanvasFullScreen() {
this.addGLEventListener(this);
this.addKeyListener(this);
this.setFocusable(true);
this.requestFocus();
public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
glu = new GLU();
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl.glClearDepth(1.0f);
gl.glEnable(GL_DEPTH_TEST);
gl.glDepthFunc(GL_LEQUAL);
gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
gl.glShadeModel(GL_SMOOTH);
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
GL2 gl = drawable.getGL().getGL2();
if (height == 0) height = 1;
float aspect = (float)width /
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL_PROJECTION);
gl.glLoadIdentity();
glu.gluPerspective(45.0, aspect, 0.1, 100.0);
gl.glMatrixMode(GL_MODELVIEW);
gl.glLoadIdentity();
public void display(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.0f, -6.0f);
gl.glBegin(GL_TRIANGLES);
gl.glVertex3f(0.0f, 1.0f, 0.0f);
gl.glVertex3f(-1.0f, -1.0f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, 0.0f);
gl.glEnd();
public void dispose(GLAutoDrawable drawable) { }
public void keyTyped(KeyEvent e) {}
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
switch (keyCode) {
case KeyEvent.VK_ESCAPE:
new Thread() {
public void run() {
GLAnimatorControl animator = getAnimator();
if (animator.isStarted()) animator.stop();
System.exit(0);
}.start();
public void keyReleased(KeyEvent e) {}
To operate in full-screen mode, set the frame to:
final JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
The &window-close& button is no longer available in the full-screen mode. Hence, we use the ESC key to exit the program. The renderer, hence, also implements the KeyListener interface and provide KeyEvent handlers, such as keyPressed() for processing the ESC key..
Note: Read the &Graphics Programming& section on how to switch between full-screen mode and windowed mode, if desired.
Example 1: Rotating 2D Shapes
Modify the GLEventListener handlers init(), display() and reshape() of the
template (GLCanvas or GLJPanel) as follows:
public class JOGL2Ex1Rotate2D extends GLJCanvas implements GLEventListener {
private float angle = 0.0f;
public void display(GLAutoDrawable drawable) {
render(drawable);
private void render(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
float sin = (float)Math.sin(angle);
float cos = (float)Math.cos(angle);
gl.glBegin(GL_TRIANGLES);
gl.glColor3f(1.0f, 0.0f, 0.0f);
gl.glVertex2d(-cos, -cos);
gl.glColor3f(0.0f, 1.0f, 0.0f);
gl.glVertex2d(0.0f, cos);
gl.glColor3f(0.0f, 0.0f, 1.0f);
gl.glVertex2d(sin, -sin);
gl.glEnd();
private void update() {
angle += 0.01f;
public void init(GLAutoDrawable drawable) { }
public void dispose(GLAutoDrawable drawable) { }
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { }
Using GLJPanel on Swing's JFrame.
Use GLCanvas on AWT's Frame.
Use GLCanvas on AWT's Applet.
Using GLJPanel on Swing's JApplet.
Observe and compare the results.
GLEventListener Handlers
: called by the drawable immediately after the OpenGL graphics context is initialized. It can be used to perform one-time initialization tasks such as setting up of lights and display lists. The init() runs only once.
public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
// glu = GLU.createGLU();
gl.glShadeModel(GLLightingFunc.GL_SMOOTH);
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepth(1.0f);
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glDepthFunc(GL.GL_LEQUAL);
gl.glHint(GL2ES1.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
: called by the drawable when the drawable is first set to visible, and during the first repaint after the canvas has been resized.
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
GL2 gl = drawable.getGL().getGL2();
height = (height == 0) ? 1 :
float aspect = (float)width /
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
gl.glLoadIdentity();
glu.gluPerspective(45.0f, aspect, 0.1f, 100.0f);
gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
gl.glLoadIdentity();
: called by the drawable to perform OpenGL graphics rendering.
public void display(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
: called by the drawbale before the OpenGL context is destroyed. It can be used to release all OpenGL resources, such as memory buffers.
public void disposed(GLAutoDrawable drawable) {
Example 2: Rotating 3D Shapes
The following example show a color-pyramid and color-cube (Nehe Lesson #5).
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoD
import javax.media.opengl.GLEventL
import javax.media.opengl.awt.GLC
import javax.media.opengl.glu.GLU;
import com.jogamp.opengl.util.FPSA
import static javax.media.opengl.GL.*;
import static javax.media.opengl.GL2.*;
@SuppressWarnings(&serial&)
public class JOGL2Ex2Rotate3D_GLCanvas extends GLCanvas implements GLEventListener {
private static String TITLE = &Rotating 3D Shapes (GLCanvas)&;
private static final int CANVAS_WIDTH = 320;
private static final int CANVAS_HEIGHT = 240;
private static final int FPS = 60;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
GLCanvas canvas = new JOGL2Ex2Rotate3D_GLCanvas();
canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
final FPSAnimator animator = new FPSAnimator(canvas, FPS, true);
final JFrame frame = new JFrame();
frame.getContentPane().add(canvas);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
new Thread() {
public void run() {
if (animator.isStarted()) animator.stop();
System.exit(0);
}.start();
frame.setTitle(TITLE);
frame.pack();
frame.setVisible(true);
animator.start();
private GLU
private float anglePyramid = 0;
private float angleCube = 0;
private float speedPyramid = 2.0f;
private float speedCube = -1.5f;
public JOGL2Ex2Rotate3D_GLCanvas() {
this.addGLEventListener(this);
public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
glu = new GLU();
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl.glClearDepth(1.0f);
gl.glEnable(GL_DEPTH_TEST);
gl.glDepthFunc(GL_LEQUAL);
gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
gl.glShadeModel(GL_SMOOTH);
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
GL2 gl = drawable.getGL().getGL2();
if (height == 0) height = 1;
float aspect = (float)width /
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL_PROJECTION);
gl.glLoadIdentity();
glu.gluPerspective(45.0, aspect, 0.1, 100.0);
gl.glMatrixMode(GL_MODELVIEW);
gl.glLoadIdentity();
public void display(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glTranslatef(-1.6f, 0.0f, -6.0f);
gl.glRotatef(anglePyramid, -0.2f, 1.0f, 0.0f);
gl.glBegin(GL_TRIANGLES);
gl.glColor3f(1.0f, 0.0f, 0.0f);
gl.glVertex3f(0.0f, 1.0f, 0.0f);
gl.glColor3f(0.0f, 1.0f, 0.0f);
gl.glVertex3f(-1.0f, -1.0f, 1.0f);
gl.glColor3f(0.0f, 0.0f, 1.0f);
gl.glVertex3f(1.0f, -1.0f, 1.0f);
gl.glColor3f(1.0f, 0.0f, 0.0f);
gl.glVertex3f(0.0f, 1.0f, 0.0f);
gl.glColor3f(0.0f, 0.0f, 1.0f);
gl.glVertex3f(1.0f, -1.0f, 1.0f);
gl.glColor3f(0.0f, 1.0f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, -1.0f);
gl.glColor3f(1.0f, 0.0f, 0.0f);
gl.glVertex3f(0.0f, 1.0f, 0.0f);
gl.glColor3f(0.0f, 1.0f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, -1.0f);
gl.glColor3f(0.0f, 0.0f, 1.0f);
gl.glVertex3f(-1.0f, -1.0f, -1.0f);
gl.glColor3f(1.0f, 0.0f, 0.0f);
gl.glVertex3f(0.0f, 1.0f, 0.0f);
gl.glColor3f(0.0f, 0.0f, 1.0f);
gl.glVertex3f(-1.0f, -1.0f, -1.0f);
gl.glColor3f(0.0f, 1.0f, 0.0f);
gl.glVertex3f(-1.0f, -1.0f, 1.0f);
gl.glEnd();
gl.glLoadIdentity();
gl.glTranslatef(1.6f, 0.0f, -7.0f);
gl.glRotatef(angleCube, 1.0f, 1.0f, 1.0f);
gl.glBegin(GL_QUADS);
gl.glColor3f(0.0f, 1.0f, 0.0f);
gl.glVertex3f(1.0f, 1.0f, -1.0f);
gl.glVertex3f(-1.0f, 1.0f, -1.0f);
gl.glVertex3f(-1.0f, 1.0f, 1.0f);
gl.glVertex3f(1.0f, 1.0f, 1.0f);
gl.glColor3f(1.0f, 0.5f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, 1.0f);
gl.glVertex3f(-1.0f, -1.0f, 1.0f);
gl.glVertex3f(-1.0f, -1.0f, -1.0f);
gl.glVertex3f(1.0f, -1.0f, -1.0f);
gl.glColor3f(1.0f, 0.0f, 0.0f);
gl.glVertex3f(1.0f, 1.0f, 1.0f);
gl.glVertex3f(-1.0f, 1.0f, 1.0f);
gl.glVertex3f(-1.0f, -1.0f, 1.0f);
gl.glVertex3f(1.0f, -1.0f, 1.0f);
gl.glColor3f(1.0f, 1.0f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, -1.0f);
gl.glVertex3f(-1.0f, -1.0f, -1.0f);
gl.glVertex3f(-1.0f, 1.0f, -1.0f);
gl.glVertex3f(1.0f, 1.0f, -1.0f);
gl.glColor3f(0.0f, 0.0f, 1.0f);
gl.glVertex3f(-1.0f, 1.0f, 1.0f);
gl.glVertex3f(-1.0f, 1.0f, -1.0f);
gl.glVertex3f(-1.0f, -1.0f, -1.0f);
gl.glVertex3f(-1.0f, -1.0f, 1.0f);
gl.glColor3f(1.0f, 0.0f, 1.0f);
gl.glVertex3f(1.0f, 1.0f, -1.0f);
gl.glVertex3f(1.0f, 1.0f, 1.0f);
gl.glVertex3f(1.0f, -1.0f, 1.0f);
gl.glVertex3f(1.0f, -1.0f, -1.0f);
gl.glEnd();
anglePyramid += speedP
angleCube += speedC
public void dispose(GLAutoDrawable drawable) { }
Modify the program to run as an applet.
Modify the program to run in full screen mode.
JOGL's NEWT (Native Windowing Toolkit) - GLWindow
Besides relying on AWT/Swing, JOGL provides its own windowing toolkit called NEWT. The base class for NEWT is GLWindow. To allocate a GLWindow:
GLProfile glp = GLProfile.getDefault();
GLCapabilities caps = new GLCapabilities(glp);
GLWindow window = GLWindow.create(caps);
window.addGLEventListener(......);
Let's rewrite Example 1 using JOGL's NEWT.
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoD
import javax.media.opengl.GLC
import javax.media.opengl.GLEventL
import javax.media.opengl.GLP
import com.jogamp.newt.event.WindowA
import com.jogamp.newt.event.WindowE
import com.jogamp.newt.opengl.GLW
import com.jogamp.opengl.util.FPSA
public class JOGL2NewtDemo implements GLEventListener {
private static String TITLE = &JOGL 2 with NEWT&;
private static final int WINDOW_WIDTH = 640;
private static final int WINDOW_HEIGHT = 480;
private static final int FPS = 60;
private double theta = 0.0f;
public JOGL2NewtDemo() {}
public static void main(String[] args) {
GLProfile glp = GLProfile.getDefault();
GLCapabilities caps = new GLCapabilities(glp);
GLWindow window = GLWindow.create(caps);
final FPSAnimator animator = new FPSAnimator(window, FPS, true);
window.addWindowListener(new WindowAdapter() {
public void windowDestroyNotify(WindowEvent arg0) {
new Thread() {
public void run() {
animator.stop();
System.exit(0);
}.start();
window.addGLEventListener(new JOGL2NewtDemo());
window.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
window.setTitle(TITLE);
window.setVisible(true);
animator.start();
public void display(GLAutoDrawable drawable) {
render(drawable);
private void render(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glClear(GL.GL_COLOR_BUFFER_BIT);
float sine = (float)Math.sin(theta);
float cosine = (float)Math.cos(theta);
gl.glBegin(GL.GL_TRIANGLES);
gl.glColor3f(1, 0, 0);
gl.glVertex2d(-cosine, -cosine);
gl.glColor3f(0, 1, 0);
gl.glVertex2d(0, cosine);
gl.glColor3f(0, 0, 1);
gl.glVertex2d(sine, -sine);
gl.glEnd();
private void update() {
theta += 0.01;
public void init(GLAutoDrawable drawable) { }
public void dispose(GLAutoDrawable drawable) { }
public void reshape(GLAutoDrawable drawable, int x, int y, int weight, int height) { }
OpenGL Profile and Capabilities
[TODO] Revised
You can create drawables to support different OpenGL profiles and capabilities:
First, construct a GLProfile, which specifies the target profile of your application, e.g., OpenGL2, OpenGL3, OpenGL4, OpenGL|ES1, OpenGL|ES2, etc. The profiles are beyond the scope of this tutorial. Read && if you are interested. We shall use the following statement to get the default profile, which best reflects your running platform.
GLProfile glp = GLProfile.getDefault();
Next, construct a GLCapabilities, based on your chosen profile, which maintain a set of OpenGL capabilities.
GLCapabilities caps = new GLCapabilities(glp);
Now, you can construct your desired drawing canvas: GLCanvas for AWT, GLJPanel for Swing, and GLWindow for Newt.
All these canvas are GLDrawable.
GLCanvas canvas = new GLCanvas(caps);
this.add(canvas);
GLJPanel panel = new GLJPanel(caps);
this.setContentPane(panel);
GLWindow window = GLWindow.create(caps);
window.setVisible(true);
Nehe's JOGL 2 Port
I have ported some of the Nehe's lessons into JOGL. Refer to
for the problem descriptions.
Setting Up
Download Source Codes: &&.
Nehe's Lesson #1 &Setting Up&:
- Using GLCanvas: JOGL2Setup_GLCanvas.java.
- Using GLJPanel: JOGL2Setup_GLJPanel.java.
- Run as an applet with GLCanvas: JOGL2Setup_Applet.java.
- Run in full-screen mode: JOGL2Setup_GLCanvasFullScreen.
Expected output: blank black screen.
OpenGL Basics
I consider Lessons #2 - #8 as OpenGL basic lessons, that are extremely important! I use GLCanvas on Swing's JFrame for all these exercises, as GLJPanel does not seem to run properly.
Download Source Codes: &&.
Nehe's Lesson #2 &Basic Shape&: JOGL2Nehe02Basic2D.java.
Nehe's Lesson #3 &Color&: J2Nehe03Color.java.
Nehe's Lesson #4 &Rotation&: JOGL2Nehe04Rotation.java.
Nehe's Lesson #5 &3D Shape&: JOGL2Nehe05Shape3D.java.
Nehe's Lesson #6 &Texture&: JOGL2Nehe06Texture.java.
Nehe's Lesson #7 &Texture Filter, Lighting, and key-controlled&: JOGL2Nehe07TextureFilterLightKey.java.
Nehe's Lesson #8 &Blending&: JOGL2Nehe08Blending.java and JOGL2Nehe08Blending_FullScreen.java
&&Click imge to run the applet (not working - JNLP not supported under my server?!)
OpenGL Intermediates
Download Source Code: &&.
Nehe's Lesson #9 &Moving Bitmaps in 3D space&: J2NeheEx09Stars.java.
Nehe's Lesson #10: Building and moving in a 3D world J2NeheEx10World3D.java.
Nehe's Lesson #11 &Waving Effect&: J2NeheEx11Flag.java.
Nehe's Lesson #12 &Using Display List&: J2NeheEx12DisplayList.java.
Nehe's Lesson #13 &2D Texts&: J2NeheEx13Text2D.java.
Nehe's Lesson #14 &3D Texts&: J2NeheEx14Text3D.java.
Nehe's Lesson #16 &Fog Effect&: J2NeheEx16Fog.java.
Nehe's Lesson #18 &Quadrics&: J2NeheEx18Quadrics.java.
Nehe's Lesson #19 &Particle Engine&: (a) J2NeheEx19Particle.java, (b) J2NeheEx19Firework.java.
Nehe's Lesson #26 &Reflection&: J2NeheEx26Reflection.java.
More JOGL Ports/Examples
JOGL comes with many excellent demos. You can get the latest demo source codes from
&rA &jogl demos& &rA &src& &rA &demos&. However, many of these demos are not properly explained (?).
Gears/JGears
You can get the source codes &Gears.java& from JOGL or get the latest code from
&rA &jogl demos& &rA &src& &rA &demos& &rA &gears& &rA &Gears.java&.
&Gears.java& run the JOGL's NEWT window. [TODO] more.
There is a variation called &JGears.java&, which included 2 images and texts in the display.
[TODO] Photo-Cube
[TODO] Bouncing Balls inside a Cube
[TODO] JOGL Demos
[TODO] Redbook Examples
Deploying JOGL Applications, Applets and Web Start Apps
Deploying JOGL 2.0 WebStart App
Jarring Up JOGL App
Jar-up your JOGL application's classes and relevant resources.
Preparing JNLP file
&?xml version=&1.0& encoding=&UTF-8&?&
&information&
&title&My JOGL App&/title&
&vendor&My Company&/vendor&
&offline-allowed /&
&/information&
&resources&
&j2se version=&1.4+& href=&/products/autodl/j2se& /&
&jar href=&my_app.jar& main=&true& /&
&extension name=&jogl-all-awt& href=&http://jogamp.org/deployment/v2.0-rc8/jogl-all-awt.jnlp& /&
&/resources&
&application-desc main-class=&mydemo.myAppMain& /&
The above &extension& is meant for JOGL application. The necessary JOGL jar-files are provide by the jogamp server. Choose the appropriate JOGL version that you used to compile your application, e.g., &v2.0-rc8&.
Deploying JOGL 2.0 Applet
Reference:
Java Network Launch Protocol (JNLP) Support @
JNLPAppletLauncher @ .
Test page for JOGL Applets @ .
JOGL: Applets @ .
JNLP Applet (JDK 6u10)
Starting from JDK 6u10 Standard &JNLP Applets& are supported, in which the Java Plug-In (for web browser) provides support for launching applets directly from JNLP files. To launch an applet from a JNLP file, use the &jnlp_href& parameter in the &applet& tag.
For example, the following HTML file contains an applet which references JNLP file &my_applet.jnlp&.
&applet width=&300& height=&300& &
&param name=&jnlp_href& value=&my_applet.jnlp&&
The JNLP file &my_applet.jnlp& is as follows:
&?xml version=&1.0& encoding=&UTF-8&?&
&jnlp href=&my_applet.jnlp&&
&information&
&title&My Applet&/title&
&vendor&My Company&/vendor&
&offline-allowed /&
&/information&
&resources&
&j2se version=&1.6+& /&
&jar href=&my_applet.jar& main=&true& /&
&extension name=&jogl-all-awt& href=&http://jogamp.org/deployment/v2.0-rc8/jogl-all-awt.jnlp& /&
&/resources&
&applet-desc
name=&My Applet&
main-class=&mydemo.MyApplet&
width=&300&
height=&300&&
&/applet-desc&
The above extension is meant for JOGL app, where you should choose the appropriate JOGL version that you used to compile your application, e.g., &v2.0-rc8&.
Applet Launcher (JDK 6u10)
JDK 1.6u10 has greatly improved the efficiency of Java applet, and it is now feasible and practical to deploy a huge Java program as an applet, via a so-called &Applet Launcher&.
JNLP applet is support since JDK 6u14. However, you may need to fall back to the Applet-Launcher to support the older releases.
Depolying JOGL Applet as &Standard JNLP Applet& / &JNLPAppletLauncher&
We shall deploy JOGL applet as &JNLP Applet& if the web browser JRE Plug-in supports it (above JDK 6u14); otherwise, we fall back to JNLPAppletLauncher, as follow:
Prepare a JNLP file as follow:
&?xml version=&1.0& encoding=&utf-8&?&
&information&
&title&JOGL Test&/title&
&vendor&SomeOne&/vendor&
&/information&
&resources&
&j2se version=&1.6+&/&
&property name=&sun.java2d.noddraw& value=&true&/&
&jar href=&YourJoglApp.jar& main=&true&/&
&extension name=&jogl-all-awt& href=&http://jogamp.org/deployment/v2.0-rc8/jogl-all-awt.jnlp& /&
&/resources&
&applet-desc
name=&Test Applet&
main-class=&YourAppletClass&
width=&640&
height=&480&&
&/applet-desc&
In the &extension&'s href, set to the appropriate JOGL version that you used to compile your program, e.g., &v2.0-rc8& in my case.
Use the following &applet& tag with a reference to the JNLP file in parameter &jnlp_href&:
&applet code=&org.jdesktop.applet.util.JNLPAppletLauncher&
height=400
&http://jogamp.org/deployment/v2.0-rc8/jar/applet-launcher.jar,
http://jogamp.org/deployment/v2.0-rc8/jar/nativewindow.all.jar,
http://jogamp.org/deployment/v2.0-rc8/jar/gluegen-rt.jar,
http://jogamp.org/deployment/v2.0-rc8/jar/jogl.all.jar,
YourJOGLApp.jar&&
&param name=&codebase_lookup& value=&false&&
&param name=&subapplet.classname& value=&YourAppletClass&&
&param name=&subapplet.displayname& value=&JOGL Applet Test&&
&param name=&noddraw.check& value=&true&&
&param name=&progressbar& value=&true&&
&param name=&jnlpNumExtensions& value=&1&&
&param name=&jnlpExtension1& value=&http://jogamp.org/deployment/v2.0-rc8/jogl-core.jnlp&&
&param name=&java_arguments& value=&-Dsun.java2d.noddraw=true&&
&param name=&jnlp_href& value=&YourJnlpFile.jnlp&&
Attribute &code& is pointing at &JNLPAppletLauncher&, which is downloaded from http://jogamp.org/deployment/v2.0-rc8/jar/applet-launcher.jar, instead of your local server.
Attribute &archive& includes all the jar files for running JOGL program. Again, they are downloaded from the server. &archive& also include your JOGL JAR file.
Attributes &width& and &height& specify the width and height of your applet's display area inside the browser's window.
The name of your main applet is specified in the parameter &subapplet.classname&.
The parameter &codebase_lookup& is set to false, as this applet does not need to fetch other files from your local server's code base path.
The parameter &noddraw.check& is set to true, to check if DirectDraw is enabled and, if so, will prompt the user
to disable it for all applets. DirectDraw is incompatible with OpenGL. Disabling it is unlikely to slow
down other non-3D applets significantly.
REFERENCES & RESOURCES
JOGL mother site @ ; JOGL tutorial @ ; JOGL Wiki @ .
JOGL Developer and master repository (including source and demos) @ .
OpenGL mother site @ .
Nehe OpenGL tutorials @ .
OpenGL Programming Guide (Red book); OpenGL Superbible (Blue book)}

我要回帖

更多关于 axframercontrol 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信