Usage Tutorial

This page should serve as tutorial and also as reference to Ruby bindings for OpenGL language. It is assumed that you have basic understanding of both OpenGL and Ruby.

If you are new to OpenGL, you can start by visiting OpenGL homepage, reading the OpenGL Programming Guide (also known as Red Book) or going to NeHe’s tutorials page.

If you are new to Ruby, the ruby-lang website contains lots of documentation and manuals for Ruby.

Note: While older gem versions packaged GLU and GLUT together with OpenGL, they are now standalone libraries.

Table of Contents

Basics:

Advanced stuff:

API reference:

Naming conventions

You can import opengl by calling:

require 'opengl'

The functions and constants are named the same as their C counterparts:

require 'opengl'
require 'glu'
require 'glut'
# ...
Gl.glFooBar( Gl::GL_FOO_BAR )
Glu.gluFooBar( Glu::GLU_FOO_BAR )
Glut.glutFooBar( Glut::GLUT_FOO_BAR )

This is the ‘full’ syntax, useful when you are expecting name clashes with other modules, or just want to be formal ;) More often, you will want to use the ‘C-style’ syntax, which you can accomplish by using include to export the module functions and constants to global namespace:

require 'opengl'
require 'glu'
require 'glut'
include Gl,Glu,Glut
...
glFooBar( GL_FOO_BAR )
gluFooBar( GLU_FOO_BAR )
glutFooBar( GLUT_FOO_BAR )

Finally, you can use the ‘old’ syntax:

require 'opengl'
require 'glu'
require 'glut'
# ...
# Note the missing prefixes in functions and constants
# and also capitalization of module names
GL.FooBar( GL::FOO_BAR )
GLU.FooBar( GLU::FOO_BAR )
GLUT.FooBar( GLUT::FOO_BAR )

This syntax was used by previous opengl gem versions; some people also consider it as being more in the spirit of OO programming. It has one downside though - due to Ruby’s naming scheme, you cannot use constants which begins with number, e.g. GL_2D would under this syntax be (GL::)2D which is illegal.

All three variants of syntax will continue to be supported in future, so it’s up to you which one you choose to use.

The rest of this tutorial will use the C syntax.

Calling syntax

Function parameters

For most types the ruby syntax follows the C API. If needed, ruby will do automatic parameter conversion to required type if possible. Example:

glVertex3f( 1.0, 1.0, 1.0 )  # matches C syntax
glVertex3f( 1, 1, 1 )        # equivalent to the above
glVertex3f( "string", 1, 1 ) # raises TypeError exception

Arrays are passed/received as Ruby arrays:

vertex = [ 1, 1, 1 ]
glVertex3fv( vertex )

For functions with multiple parameter-number variations (glVertex, glColor…) we define ‘overloaded’ functions, as in:

glVertexf( 1, 1 )       # will call glVertex2f()
glVertexf( 1, 1, 1 )    # will call glVertex3f()
glVertexf( 1, 1, 1, 1 ) # will call glVertex4f()
glVertexi( 1, 1 )       # will call glVertex2i()
# ... and so on

Return values

In C, OpenGL functions rarely return values directly, instead you pass in pointer to preallocated buffer and they will fill it with values; sometimes you have to even query how big buffer you’ll need to allocate. Ruby does this all for you, returning either single value or array:

glColor4f( 1.0, 1.0, 1.0, 1.0 )
# ...
color = glGetDoublev(GL_CURRENT_COLOR)
p color # will be [1.0,1.0,1.0,1.0]

Matrices

Matrices are passed and received as ruby array, or as ruby Matrix objects:

matrix_a = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
matrix_b = [ [  0, 1, 2, 3 ],
             [  4, 5, 6, 7 ],
             [  8, 9,10,11 ],
             [ 12,13,14,15 ] ]
matrix_c = Matrix.rows( [ [  0, 1, 2, 3 ],
                          [  4, 5, 6, 7 ],
                          [  8, 9,10,11 ],
                          [ 12,13,14,15 ] ] )
# ...
glLoadMatrixf(matrix_a)
glLoadMatrixf(matrix_b) 
glLoadMatrixf(matrix_c) # same result

You may also create your own matrix class and pass it this way, provided that it is convertible to array (has to_a method).

Note that as OpenGL uses column-major notation for matrices, you may need to call transpose() when working with row-major matrices or arrays in ruby.

Textures and other raw data

Data for textures, arrays, buffers etc. can be specified either as ruby arrays or directly as raw packed strings - strings that contains their direct memory representation (just like C arrays). If you need to convert between ruby arrays and these strings, use ruby Array#pack and String#unpack functions. Example:

# create texture, 2x2 pixels,
# 3 components (R,G,B) for each pixel as floats
texture = [
           1.0, 0.0, 0.0, # 1st pixel, red
           0.0, 1.0, 0.0, # 2nd pixel, green
           0.0, 0.0, 1.0, # 3rd pixel, blue
           1.0, 1.0, 1.0  # 4th pixel, white
          ]
# convert it to string
# f = native float representation
# * = convert all values in the array the same way
data = texture.pack("f*")
# ...
glTexImage2D(
  GL_TEXTURE_2D, # target
  0,             # mipmap level,
  GL_RGB8,       # internal format
  2, 2,          # width, height
  0,             # border = no
  GL_RGB,        # components per each pixel
  GL_FLOAT,      # component type - floats
  data           # the packed data
)

Reverse works just the same:

# ...
data = glGetTexImage( # returns the packed data as string
  GL_TEXTURE_2D, # target
  0,             # mipmap level
  GL_RGB,        # components per pixel
  GL_FLOAT       # component type
)
# now convert it to ruby array
texture = data.unpack("f*")
#...

For storage, packed strings are more memory efficient than ruby arrays, but cannot be easily changed or manipulated.

Error Checking

Starting with version 0.60.0, opengl performs automatic checking of OpenGL and GLU errors. Functions:

Gl.enable_error_checking
Gl.disable_error_checking
Gl.is_error_checking_enabled? # true/false

When the checking is enabled (default), glGetError() is executed after each OpenGL call, and should error occur, Gl::Error exception is raised:

Gl.enable_error_checking
...
begin 
  ...
  glEnable(GL_TRUE) # will raise exception
  ...
rescue Gl::Error => err
  # err.id contains the OpenGL error ID
  if (err.id == GL_INVALID_ENUM)
    puts "Oh noes! You used invalid enum!"
    ...
  end
  ...
end

Some GLU functions may also throw Glu::Error - the handling is the same as above.

It is usually good idea to leave error checking on for all your code, as OpenGL errors have habit to pop-up in unexpected places. For now there is no measurable performance hit for error checking, although this may depend on your graphic drivers implementation.

Examples

Various examples are in ‘examples’ directory of the bindings. To run them, manually pass them to ruby like:

ruby some_sample.rb

On windows, you may want to use rubyw instead, which displays the standard output window as some examples use the console for usage info etc.

If you get ‘opengl not found’ error, and you installed ruby-opengl from gems, your shell or ruby installation is probably not configured to use the gems; in that case type:

ruby -rubygems some_sample.rb

The README file in the examples directory contains some notes on the examples.

OpenGL Version and Extensions

To query for available OpenGL version or OpenGL extension, use Gl.is_available? function:

# true if OpenGL version is 2.0 or later is available
Gl.is_available?(2.0)
...
# returns true if GL_ARB_shadow is available on this system
Gl.is_available?("GL_ARB_shadow")

For list of what extensions are supported in ruby-opengl see this page

The extensions’ function names once again follows the C API. Some extensions were over time promoted to ARB or even to OpenGL core, retaining their function names just with suffix changed or removed. However sometimes the functions semantics was changed in the process, so to avoid confusion, ruby-opengl bindings will strictly adhere to the C naming, e.g. :

# will call the function from GL_ARB_transpose_matrix extension
glLoadTransposeMatrixfARB(matrix)
...
# will call the function from OpenGL 1.3
glLoadTransposeMatrixf(matrix)

Note: opengl is compiled against OpenGL 1.1, and all functions and enums from later versions of OpenGL and from extensions are loaded dynamically at runtime. That means that all of OpenGL 2.1 and supported extensions are available even if the ruby-opengl bindings are compiled on platform which lacks proper libraries or headers (like for example Windows without installed graphic drivers). This should ease binary-only distribution and application packaging.

Selection/Feedback queries

Querying selection and feedback is different from C. Example:

# this will create selection buffer 512*sizeof(GLuint) long
buf = glselectbuffer(512)
# enter feedback mode
glRenderMode(GL_SELECT) 
# ... draw something here 
# return to render mode
count = glRenderMode(GL_RENDER)
# at this point the buf string is freezed and contains
# the selection data, which you can recover with unpack
# function
data = buf.unpack("I*") # I for unsigned integer
# also, next call to glRenderMode(GL_SELECT) will overwrite
# the 'buf' buffer with new data

The feedback query follows the same pattern, only the data are stored as floats.

Vertex Arrays

In current state, vertex arrays are not very efficient in ruby-opengl, as it is not possible to change the array content once it is specified, and there is overhead for converting between ruby and C representation of numbers. Using display lists for static and immediate mode for dynamic objects is recommended instead.

You can specify the data the same way as texture data. Example:

normals = [0,1,0, 1,0,0, 1,1,1]
glNormalPointer(GL_FLOAT,0,normals)
...
glEnable(GL_NORMAL_ARRAY)
glDrawArrays(...)
...

This applies to all *pointer functions. glGetPointerv will return reference to the frozen string previously specified.

Buffer Objects

Once again, in current state buffer objects (VBOs in particular) are not very efficient in ruby-opengl. Unlike textures and vertex arrays, the data for buffers must be prepacked by using .pack() function, as buffers does not retain information about the storage type. Mapping of the buffer afterwards is read-only.

Like in C, buffer binding affects some functions in way that if particular buffer is bound, the related functions (for example glTexImage) take integer offset in place of data string argument. This is also true for getter functions (e.g. glGetTexImage) - instead of returning the data string, they take offset as they’re last argument (so in ruby they take one extra argument), and will write the data in the bound buffer as expected.

VBO example:

# specify 3 vertices, 2*float each
data = [0,0, 0,1, 1,1].pack("f*")
# ...
# generate buffer name
buffers = glGenBuffers(1)
# bind to the name to ARRAY buffer for vertex array
glBindBuffer(GL_ARRAY_BUFFER,buffers[0])
# here the data is specified, size is n*sizeof(float)
# note that you don't get to specify type, as buffers
# operate on byte level
glBufferData(GL_ARRAY_BUFFER,6*4,data,GL_DYNAMIC_DRAW)
# ...
# here instead of specyfing the data, you pass '0' (or
# positive integer) as offset to the bound buffer
glVertexPointer(2,GL_FLOAT,0,0)
# ...
glEnableClientState(GL_VERTEX_ARRAY)
# ...

GLUT, SDL, GLFW..

When it comes to low-level task like GL window creation, input and event handling, the usual first choice is GLUT, as it is readilly available alongside OpenGL. However both GLUT itself and its implementations have their drawbacks, and for that and other reasons there are number of replacement libraries. You can use any of them with the opengl gem (as long as there are ruby bindings for them).

Note: Older gem versions packaged GLUT with the opengl gem, however it is now a standalone gem.

Here is example for SDL:

require 'opengl'
require 'sdl'
# init
SDL.init(SDL::INIT_VIDEO)
SDL.setGLAttr(SDL::GL_DOUBLEBUFFER,1)
SDL.setVideoMode(512,512,32,SDL::OPENGL)
# ...
Gl.glVertex3f(1.0,0.0,0.0)
# ...
SDL.GLSwapBuffers()
# ...

and another example for GLFW

require 'opengl'
require 'glfw'
# init
Glfw.glfwOpenWindow( 500,500, 0,0,0,0, 32,0, Glfw::GLFW_WINDOW )
# ...
Gl.glVertex3f(1.0,0.0,0.0)
# ...
Glfw.glfwSwapBuffers()
# ...

GLUT callbacks

The GLUT callback functions are specified as Proc objects, which you can either create with lambda as:

reshape = lambda do |w, h|
  # ...
end
# ...
glutReshapeFunc( reshape )

or by conversion from normal functions:

def reshape(w,h)
  # ...
end
# ...
glutReshapeFunc( method("reshape").to_proc )

Note: An older notation you’ll see instead of lambda is proc. The PickAxe v2 notes that proc is “mildly deprecated” in favor of lambda. You’ll also sometimes see Proc.new used in place of either. Pages 359-360 of PickAxe v2 describe the differences between using lambda and Proc.new, but for our purposes either will be fine.

Internals

The directory structure follows current Ruby standards, with a few extra directories added.