evalapply



Tutorial 0. Setting up a GLUT Window in Common Lisp




Motivation

I was initially planning on showing people how to use the more advanced features of OpenGL in Common Lisp; namely VBOs, shaders, 3d object model loading, terrain generation etc.. This was mainly due to the new OpenGL 3 spec, which deprecates a lot of old OpenGL functionality, and in order to keep up with the times, these new extensions need to be understood. But I googled around and found absolutely no cl-opengl tutorials! So I thought I’d start from the beginning. If I get positive feedback I’ll keep writing until everything is covered. For those of you who prefer to look at code, there is good reference material in cl-opengl’s darcs repository in the /examples folder.

Pre requisites

Note that I have only tested this code on SBCL Linux, I know that you can get it to work on CLISP on M$ Windows, since this is what I used to use. On Windows you will also have to install ASDF, as it does not come pre packaged with CLISP. This involves downloading asdf.lisp and placing it in a sub-directory in your CLISP directory, and loading it with (load “path/to/asdf/asdf.lisp”). You may also add this line to your .clisprc init file so that CLISP loads it automatically when it starts up. You may also need to obtain the freeglut library, the one that I am currently using is version 2.4.0.

There are some good tutorials available which demonstrate how to set up SLIME and Emacs, so I’m not going to cover how you set up your environment(unless people really want this). You’ll need to download the libs and reference them in a common folder which can be seen by asdf:*central-repository*, in order for #’asdf:operate to work on the libs that I describe. On Windows you will need to create shortcuts to your .asd files, on linux you can use ln -s -f [TARGET] [SYMLINK] to create a symlink to the libs.

Unfortunately cl-opengl does not have easily downloadable tarballs, so you will need to get darcs and use the command: darcs get http://www.common-lisp.net/cl-opengl/darcs/cl-opengl to obtain the library.

If everything is set up correctly, including the libraries using asdf is easy:

;; ensure asdf is available
(require 'asdf)

;; load the cl-glut library and its dependencies (cl-opengl, cl-glu, cffi ..)
(asdf:operate 'asdf:load-op :cl-glut)

This will load cl-glut and all of its dependencies, including cl-opengl, cffi etc. Ignore the warnings, if you get errors then it is most likely because you are using the wrong version of glut, or that you are missing one of the dependencies.

Step 0. Setting up a window

To create a cl-glut window we’ll need to create a class which derives from cl-gluts base window class named glut:window:

;; our window class
(defclass gl-window (glut:window) ()
  (:default-initargs :width 640 :height 480 :title "Tutorial 0"
    :mode '(:double :rgba :depth)))

Here as you can see we are specifying the default width, height and title for our window.

The :mode alist contains symbols which specify various default attributes for our window. In our case we want our window to be double buffered, support RGBA colours, and support the depth buffer.

If you wish to find out which methods can be overridden, or which methods are available in the base class, you can call (apropos ‘glut:window).

Step 1. Matrices and OpenGL

Before I get on to showing the code, I will talk a bit about matrices, and how they are handled in OpenGL, I am not going to cover the entirety of matrices as this is far too long for a simple tutorial on setting up a window. If you wish to learn more about matrices, google is your friend. :)

Matrices are important to understand as they can be used to perform operations on 3d objects, they are used everywhere in OpenGL under the hood, and help to understand how the camera works. You can think of the graphics card as a very fast matrix calculator. (More on this in a later tutorial)

A matrix is a set of numbers arranged either in row major form, or column major form. What this means is, say you have an array of numbers initialized to 1 2 3 4 … :

(defvar *number-array*
  (let ((number-array (make-array (* 4 4))))
    (loop for i below (- (* 4 4) 1) do
	 (setf (aref number-array i) i))))

So:

[1  2  3  4
 5  6  7  8
 9  10 11 12
 13 14 15 16]

In row major form, the numbers would be read out 1 2 3 4 5 6 7 … .

In column major form they would be read out 1 5 9 13 2 6 10 … .

It is important to know this because OpenGL treats matrices as if they were in column major form, and so any matrix that you pass to OpenGL should abide by this convention.

Here is a very simple but special 4×4 matrix known as the identity matrix:

 X Y Z W
[1 0 0 0
 0 1 0 0
 0 0 1 0
 0 0 0 1]

This matrix is special because it has the property that if you multiply it by another 4 by 4 matrix, it will give back the same matrix. Whenever you see a call to gl:load-identity in my code, it is loading the identity matrix onto the current matrix stack. This ensures that we are using the identity matrix, and then multiplying any other matrix(for example a translation matrix to transform an object), by a matrix which returns itself, and not the resulting matrix of a previous computation.

The function gl:matrix-mode allows you to specify which matrix stack you are currently using. OpenGL has 3 matrix stacks, stacks are LIFO(Last In First Out) data structures. You can think of them as a series of boxes, the top box being the “current” matrix that is being used by OpenGL. The three available matrix stacks in OpenGL are :projection, :modelview, and :texture.

Note that you can load custom matrices for any operation that you wish to perform in OpenGL, but this is not usually the best way of doing things. For example, gl:translate, gl:rotate, gl:scale may all be defined and multiplied as custom matrices by the current modelview matrix in order to transform the 3d scene, but making use of these in-built functions results in less system memory being used(since a 4 by 4 array of numbers uses up memory). It is important to understand the theory behind OpenGL transformations, but it is better to use the in built transformation functions for these purposes.

The projection matrix stack

The projection matrix is a matrix which represents the view frustum. There are two main types of projection used in OpenGL, orthographic, and perspective. There are three functions which you should be concerned about for setting up your perspective. gl:ortho, for setting up an orthographic projection matrix, and gl:frustum or glu:perspective for a perspective projection matrix.

Once you have called gl:matrix-mode, and set the current matrix stack, you can load your own matrices using gl:load-matrix, or multiply the current matrix(the matrix which is currently at the top of the stack) by a custom matrix.

To give you an example of this, if I was mazochistic enough to want to write my own perspective projection matrix, I could load it like this:

(defun create-and-load-projection-matrix (left right top bottom near far)
  (let ((custom-projection (make-array (* 4 4) :initial-element 0)))
    ...
    (gl:load-matrix custom-projection)))

You can load your own matrices in OpenGL with the gl:load-matrix command, and you can also use gl:mult-matrix to multiply the current matrix by your own custom made matrix.

The modelview matrix stack

Modelview is the matrix stack which contains all of those matrix operations which pertain to the 3d scene. For example if you wish to move an object around in 3d space, you must apply a transformation matrix to the current modelview matrix.

Step 2. Resizing the Window

Next we’ll need to define a method which sets up our perspective, and enables us to resize the window. For this task we will override glut:reshape. To find out more about methods you may use (describe #’glut:reshape).

;; resize the view
(defmethod glut:reshape ((w gl-window) width height)
  ;; ensure divide by zero never occurs
  (when (= height 0) (setf height 1))

  ;; set up the projection matrix
  (gl:matrix-mode :projection)
  (gl:load-identity)
  (gl:frustum -1.0 1.0 -1.0 1.0 1.0 100.0)

  ;; set up the viewport with our new width and height
  (gl:matrix-mode :modelview)
  (gl:load-identity)
  (gl:viewport 0 0 width height))

Here i’m ensuring that there are no divide by zero errors, by setting the height to 1 whenever it is 0. Then I am setting up a projection matrix with a near value of 1.0, and a far value of 100.0. This will ensure that when our window is resized, the projection matrix will still display our scene correctly. You can change these values however you like, some people also prefer the function #’gl:perspective, you may use this if you are more accustomed to it.

Next we’ll set up the state of our window. The method display-window is used to display the window, the :before symbol is used to indicate that we wish this method to be called before the window is displayed.

;; our display initialization method
(defmethod glut:display-window :before ((w gl-window))
  (gl:clear-color 0 0 0 0)
  (gl:depth-func :less)
  (gl:shade-model :smooth)
  ;; (gl:cull-face :back)
  (gl:front-face :ccw))

In order for our window to draw stuff, we need to override the glut:display method, as can be seen below.

;; our render method
(defmethod glut:display ((w gl-window))
  (gl:clear :color-buffer :depth-buffer)
  (gl:load-identity)

  ;; translate the camera -2.0 on the z axis
  (gl:translate 0.0 0.0 -2.0)

  ;; draw a basic triangle
  (gl:begin :triangles)
  (gl:vertex -0.5 -0.5 0.0)
  (gl:vertex 0.5 -0.5 0.0)
  (gl:vertex 0.0 0.5 0.0)
  (gl:end)

  ;; swap the gl buffers
  (glut:swap-buffers))

Here we are clearing the color and depth buffers, translating back by -2.0(this is due to the way that I set up the view frustum, and may not be necessary depending on how you overrode the reshape method). Then we are drawing a simple colourless triangle, and swapping the buffers so that our triangle is flipped to the primary screen buffer.

Note that using gl:begin … gl:end etc is deprecated in the OpenGL 3 specification, so you are encouraged to use other means of drawing, such as vertex buffer objects, which loads vertex and index data directly into the graphics card memory. However, for the purposes of this tutorial, or if you are a beginner to OpenGL, it is better to start with these deprecated commands, as they are simpler to use and understand.

Step 3. Handling keys

There’s nothing worse than having a program that you can’t quit! So we’ll define a keyboard handling method, so that you can at the very least press Escape to quit the application.

;; keyboard handler
(defmethod glut:keyboard ((w gl-window) key x y)
  (case key
    (#\Escape (glut:destroy-current-window))))

Finally, we’ll need a function that acts as the entry point of our application. I’ve called it start but you may call it whatever you like. All this does is instantiate the gl-window class that we created before, and displays it with the glut:display-window method.

;; application entry point
(defun start ()
  (glut:display-window (make-instance 'gl-window)))

(start)

Et voila, we now have a camera, a window, and a rendered triangle, whoopee.

Credits: Someone has already reviewed and criticized this tutorial, thanks _3b on #lisp! :)

Entire code:

;; load the cl-glut library and its dependencies (cl-opengl, cl-glu, cffi ..)
(asdf:operate 'asdf:load-op :cl-glut)

;; our window class
(defclass gl-window (glut:window) ()
  (:default-initargs :width 640 :height 480 :title "Tutorial 0"
    :mode '(:double :rgba :depth)))

(defvar *custom-identity* (make-array (* 4 4) :initial-element 0))

(defmacro mx4x4-ref (mx i j)
  `(aref ,mx ,(+ (* j 4) i)))

(defun setup-identity ()
  (setf (mx4x4-ref *custom-identity* 0 0) 1
	(mx4x4-ref *custom-identity* 1 1) 1
	(mx4x4-ref *custom-identity* 2 2) 1
	(mx4x4-ref *custom-identity* 3 3) 1))

;; resize the view
(defmethod glut:reshape ((w gl-window) width height)
  ;; ensure divide by zero never occurs
  (when (= height 0) (setf height 1))

  ;; set up the projection matrix
  (gl:matrix-mode :projection)
;;  (gl:load-identity)
  (gl:load-matrix *custom-identity*)
  (gl:frustum -1.0 1.0 -1.0 1.0 1.0 100.0)

  ;; set up the viewport with our new width and height
  (gl:matrix-mode :modelview)
  (gl:load-matrix *custom-identity*)
  (gl:viewport 0 0 width height))

;; our display initialization method
(defmethod glut:display-window :before ((w gl-window))
  (gl:clear-color 0 0 0 0)
  (gl:depth-func :less)
  (gl:shade-model :smooth)
  ;; (gl:cull-face :back)
  (gl:front-face :ccw))

;; our render method
(defmethod glut:display ((w gl-window))
  (gl:clear :color-buffer :depth-buffer)
  ;;(gl:load-identity)
  (gl:load-matrix *custom-identity*)

  ;; translate the camera -2.0 on the z axis
  (gl:translate 0.0 0.0 -2.0)

  ;; draw a basic triangle
  (gl:begin :triangles)
  (gl:vertex -0.5 -0.5 0.0)
  (gl:vertex 0.5 -0.5 0.0)
  (gl:vertex 0.0 0.5 0.0)
  (gl:end)

  ;; swap the gl buffers
  (glut:swap-buffers))

;; keyboard handler
(defmethod glut:keyboard ((w gl-window) key x y)
  (case key
    (#\Escape (glut:destroy-current-window))))

;; application entry point
(defun start ()
  (glut:display-window (make-instance 'gl-window)))

(start)


Comments

  1. retupmoca says:

    Found the problem – you need to use gl:load-identity instead of loading the *custom-identity* you had – which is all 0′s in your example code, as well.

    Posted 3 years, 4 months ago
  2. cmalune says:

    I changed the code in the tutorial so that it now uses (gl:load-identity), thanks for this. I thought it would be useful to show people how to load custom matrices, but in the tutorial code its probably best to do things normally. :)

    Posted 3 years, 4 months ago


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.