Go and OpenGL: A GLSL Material Viewer

Johan Svensson
dotcrossdot
Published in
6 min readDec 14, 2019
Blinn Phong with color.

This is an ongoing project that I’ve been working on a for a while. It’s an OpenGL shader/material viewer. I’ve been wanting to set up an OpenGL sandbox project, and I’ve also been interested in trying out the Go programming language. Therefore I decided to try and kill to birds with one stone, and write the application from scratch in Golang. The application probably doesn’t have the best OpenGL abstractions, nor the cleanest Go code, but I’ll try and explain how it’s set up in the following post.

The full source code and associated assets are available on GitHub:

Application overview

First of all I had a few simple goals when I started making this. I wanted the application to have the following abilities:

  • Ability to load and render 3D models (obj. files).
  • Ability to draw the objects with GLSL shaders.
  • Ability load, compile and reapply new shaders to the objects at runtime.
  • Ability to modify the uniforms of the created shaders through a simple UI.

This is the basic code structure of the application:

A shader struct is loaded from two files, a shader.vert and shader.frag file. The shader struct’s associated functions will parse the files for material uniforms and keep track of some meta data like file path and shader name etc.

The objModel struct can read and parse and .obj file, and create a data array from it. The allowed format of the .obj files is rather strict at the moment. It need to contain (and only contain) vertex data, uv data and normal data. The application unfortunately cannot load custom user-made models at the moment. It only loads the ones that comes withe the application (in the Assets folder).

The vertex array of the model, along with the material, is passed into the renderer struct via the setData function:

func (r *renderer) setData(verts []float32, material material)

Once the data is set for the renderer, a draw call can be issued, like so:

modelRenderer.issueDrawCall(model, view, projection, cameraPos)

Writing and loading GLSL shaders

Cell shading with color.

To load a GLSL shader, simply write the file paths to the .frag and .vert file in the textboxes under “Shader Programs” and press “Compile”. If the compilation is successful a GUI will be drawn from the Material struct in the shader. If not, then the error output will be shows instead. The values in the GUI can be modified and the result will be shown on the model when pressing “Apply”.

The image above shows the GUI and result of the shader called cellShadeColor. It does pretty much what it says… cell shading and color.

The cellShadeColor.frag program contains the following uniforms:

// Material uniforms exposed in the GUI
struct Material {
vec3 color;
float specPower;
sampler2D cellRampDiffuse;
sampler2D cellRampSpecular;
};
uniform Material material;// Non exposed uniforms
uniform mat4 modelMatrix;
uniform vec3 cameraWorldPos;

color, specPower, cellRampDiffuse and cellRampSpecular are defined in the Material struct in the shader and can therefore be modified through the GUI:

The non exposed uniforms are not material specific, and are always set from the renderer, regardless if you are using them or not. These include:

uniform mat4 modelMatrix; 
uniform mat4 viewMatrix;
uniform mat4 projMatrix;
uniform mat4 MVP;

The full shader source code and a few more example shaders are available in the GitHub repository.

GUI

The GUI in the application is handled by a separate package called imgui-go. This is a Go-wrapper for the Dear Imgui framework. It’s basically an immediate mode GUI framework for Go that works with OpenGL. It’s pretty easy to set up, and the full source code is available here:

Some of the code in the project is directly taken from the examples in this repository (e.g. the GUI renderer and some GUI input stuff). It’s been repackaged a bit but it’s basically uses the same setup.

Some strange vertex animation.

Suggestion for improvements / TODOs

  • The application has a dependency to the models in the “Assets” folder at the moment. It would be nice if it was packaged in a better way.
  • Add the ability to load custom .obj files. These would need to be parsed and examined for what kind data they contain, so that the layout of the OpenGL buffers can be modified per object.
  • Calculate tangents and binormals when parsing the .obj files. This would allow for writing normal mapping shader.
  • Allow for multiple objects to be drawn on the screen, and allow for alpha blending.

Thoughts on the Go programming language

I have to say that I really enjoy programming in Go. What I perhaps like most about it is the narrowed down feature set. Having no classes and polymorphism etc really makes you simplify your designs. Instead of focusing on architecture and structure, you spend most of your time solving the problem at hand. However, I can imagine that this could be a problem when working in more large scale projects.

Unfortunately I didn’t really find a good reason to use go routines and channels in this project. This is a bit of a shame, since Go is famous for its parallelism. Perhaps I’ll be able to do this next time!

Another pretty fantastic thing about Go is the build pipeline. With a single command you can build an .exe or .dll file from your code (no runtime needed) that can easily be distributed. Its also blazingly fast! Keep in mind though that in order to build this project you need a gcc compiler on your machine, since the Go OpenGL package is basically a wrapper around a C library.

When it comes to OpenGL and Go I’m I’m not 100% sure its a good fit. A lot of high level graphics related stuff is (in my opinion) often easier to think about in terms of objects. A renderer is an object, a material is an object, a texture is an object etc. The Go version of this is having structs with pointer receiver functions, which feels a bit less clean. I have to admit though that most of the code lines in this project were related to drawing the GUI. The actual OpenGL stuff could be done with very little Go code.

Sources

--

--