How to Build and Draw a simple Triangle




Remark: in the following article, words GLSL Hacker and GeeXLab are swappable! They represent the same software with a different name…


Today we are going to see how to build and render a simple scene that includes a camera, a triangle, a reference grid and a GPU program (in GLSL). GLSL Hacker supports both Lua and Python programming languages. We will use Lua for this article. So let’s go!

Article index:

Note: the overview of GLSL Hacker can be useful to understand the concept of INIT and FRAME scripts

1 – The Camera (INIT script)

Here is the code to create and initialize a perspective camera. The camera is the core element of the viewing pipeline and we need a perspective camera to see the 3D world. The camera has a field of view (fov) of 60 degrees and its position is X=2, Y=2 and Z=4.

winW, winH = gh_window.getsize(0)
 
local aspect = winW / winH
camera_params = { fov=60, znear=1.0, zfar=1000.0 }
camera = gh_camera.create_persp(camera_params.fov, 
                                aspect, 
                                camera_params.znear, 
                                camera_params.zfar)
gh_camera.set_viewport(camera, 0, 0, winW, winH)
gh_camera.set_position(camera, 2, 2, 4)
gh_camera.set_lookat(camera, 0, 0, 0)
gh_camera.set_upvec(camera, 0, 1, 0, 0)

2 – The Triangle (INIT script)

GLSL Hacker has a built-in function that creates a triangle (gh_mesh.create_triangle). This function works fine but I prefer to show you how to build a triangle manually using a more flexible way. The triangle is the most simple mesh and is made up of 3 vertices:

triangle = gh_mesh.create_v2()
local num_vertices = 3
local num_faces = 0 -- non-indexed rendering
gh_mesh.alloc_mesh_data(triangle, num_vertices, num_faces)

gh_mesh.create_v2 creates a generic mesh. gh_mesh.alloc_mesh_data allocates memory for storing the vertices (and the faces if we build an indexed mesh). After the call to gh_mesh.alloc_mesh_data, we have a mesh with enough room to store 3 vertices. Let’s see now how to initialize the position and color of these vertices:

gh_mesh.set_vertex_position(triangle, 0, -1, -1, 0, 1)
gh_mesh.set_vertex_position(triangle, 1, 0, 1, 0, 1)
gh_mesh.set_vertex_position(triangle, 2, 1, -1, 0, 1)
 
gh_mesh.set_vertex_color(triangle, 0, 1, 0, 0, 1)
gh_mesh.set_vertex_color(triangle, 1, 0, 1, 0, 1)
gh_mesh.set_vertex_color(triangle, 2, 0, 0, 1, 1)

That’s all for the triangle.

3 – The Reference Grid (INIT script)

The reference grid is a helper object that allows to easily visualize the ground. The creation and initialization of the grid is really simple:

grid = gh_utils.grid_create()
gh_utils.grid_set_geometry_params(grid, 10, 10, 20, 20)

More parameters to customize the grid have been covered in THIS ARTICLE.

4 – The GPU Program (INIT script)

We live today in full shader world so we need a GPU program to draw (render) 3D objects on the screen. To draw both the triangle and the grid, all we need is a GPU program that can deal with the position and color of objects vertices. This GPU program has a vertex shader to transform the geometry (the vertices) and a pixel shader to draw the pixels of the primitives (here a triangle and lines).

Here is the source code of the GPU program in GLSL (the OpenGL Shading Language) for an OpenGL 3.0 demo:

<gpu_program name="vertex_color_prog" >
    <raw_data_vs><![CDATA[	 
#version 130 // To support OpenGL 3.0 drivers (Gallium on Linux...)
in vec4 gxl3d_Position;
in vec4 gxl3d_Color;
uniform mat4 gxl3d_ModelViewProjectionMatrix;
out vec4 Vertex_Color;
void main()
{
  gl_Position = gxl3d_ModelViewProjectionMatrix * gxl3d_Position;
  Vertex_Color = gxl3d_Color;
}
  ]]></raw_data_vs>
    <raw_data_ps><![CDATA[	 
#version 130 // To support OpenGL 3.0 drivers (Gallium on Linux...)
in vec4 Vertex_Color;
out vec4 FragColor;
void main()
{
  FragColor = Vertex_Color;
}
    ]]></raw_data_ps>
</gpu_program>

Remark: I used the version 130 in this GLSL program because it’s supported by any OpenGL 3.2+ implementation but and that’s the important thing, it’s supported by some Gallium drivers on Linux. Those drivers do not support OpenGL 3.2 (version 150) but only OpenGL 3.0 (version 130). A good example of such a driver is the default driver that is installed with Linux Mint 17 when a NVIDIA GeForce card is present.

If you want to test this demo on a Raspberry Pi, you have to use this GPU program (OpenGL ES 2.0):

<gpu_program name="vertex_color_prog" >
    <raw_data_vs><![CDATA[	 
attribute vec4 gxl3d_Position;
attribute vec4 gxl3d_Color;
uniform mat4 gxl3d_ModelViewProjectionMatrix;
varying vec4 Vertex_Color;
void main()
{
  gl_Position = gxl3d_ModelViewProjectionMatrix * gxl3d_Position;
  Vertex_Color = gxl3d_Color;
}
  ]]></raw_data_vs>
    <raw_data_ps><![CDATA[	 
varying vec4 Vertex_Color;
void main()
{
  gl_FragColor = Vertex_Color;
}
    ]]></raw_data_ps>
</gpu_program>

To get the handle to the GPU program in the Lua code, just include this instruction:

vertex_color_prog = gh_node.getid("vertex_color_prog")

5 – Scene Rendering (FRAME script)

All the previous steps took place in the INIT script. Now let’s see the FRAME script that will perform the real drawing of the scene:

-- Apply the params of the main camera and clear the color 
-- and depth buffers.
--
gh_camera.bind(camera)
gh_renderer.clear_color_depth_buffers(0.2, 0.2, 0.2, 1.0, 1.0)
gh_renderer.set_depth_test_state(1)
 
 
-- Bind vertex_color_prog to make it the active GPU program.
--
gh_gpu_program.bind(vertex_color_prog)
 
 
-- Render the triangle and the grid
--
gh_object.render(triangle)
gh_object.render(grid)

Simple isn’t it?

6 – The Demo

The demo is available in GLSL Hacker GeeXLab code sample pack in the host_api/Triangle/ folder. You can download both GLSL Hacker GeeXLab and the code sample pack from THIS PAGE.

The demo is available in two flavors: GL3 for desktop systems (Windows, Linux and OS X) and GLES2 for the Raspberry Pi.