HeadsUp Live Mandelbrot ZoomPosted: April 17, 2012
To kickoff the usage of shaders, I figured I go back to the Mandelbrot example. In this particular case, showing a static image isn’t that exciting, so I figured I’d produce a little movie clip instead. So, what you see here is some typical OpenGL code written, that uses a fragment shader to do the actual work. I borrowed the fragment shader from here.
This is really getting interesting. The fragment shader itself had zero changes, because it’s GLSL code, so nothing to change. The code wrapping changed in the usual sorts of way. Here is how the shader program is setup:
function setup_shader(fname) local fp = io.open(fname, "r"); local src_buf = fp:read("*all"); local src_array = ffi.new("char*", ffi.cast("char *",src_buf)); local sdr = ogm.glCreateShader(GL_FRAGMENT_SHADER_ARB); ogm.glShaderSource(sdr, 1, src_array, nil); ogm.glCompileShader(sdr); local prog = ogm.glCreateProgram(); ogm.glAttachShader(prog, sdr); ogm.glLinkProgram(prog); ogm.glUseProgram(prog); return prog; end
There’s only one slightly tricky line in here. A shader is a program, and that program gets compiled on the GPU by the vendor’s OpenGL GLSL compiler. So, you’ve got to get the text of that program over to the GPU. The API for doing that is:
void glShaderSource (GLuint shader, GLsizei count, GLchar* *string, const GLint *length);
It’s the “GLchar **string” that’s the only challenge here. Basically, the function expects an array of pointers to strings. So, using the LuaJIT ffi, this turns out to be achievable with the following:
local src_array = ffi.new("char*", ffi.cast("char *",src_buf));
It maybe looks like a bit of a magical incantation, but once it’s done, you’re good to go. From then on out, it’s standard stuff. Notice the usage of ‘ogm’. That’s the alias for the OglMan table, which is used to pull in all the extensions you could care to use. It really was brain dead easy to do this. Whenever the LuaJIT compiler complained about not being able to find something, I just put “ogm.” in front of it, until all complaints were solved, and the program finally ran.
And the result in this case is a nice fly through of a mandelbrot set. Julia sets can be added just as easily by changing the .glsl file that I’m loading into the fragment shader.
This bodes well. It will be a small matter to wrap this stuff up in a couple of convenience objects so that I won’t have to make all those GLSL Calls explicitly.
One of the hardest parts to deal with typically is the setting of ‘uniform’ variables. This is the way in which you communicate values from the outside world into the shader code. I’m thinking Lua will help me do that in such a way that’s fairly natural, and doesn’t take a lot of code. Maybe I can use the same trick I did with OglMan (implement __index and __newindex). If I could do that, then it would look the same as setting/getting properties on an object to interact with your GLSL shader. And that would be a fine thing indeed as then the code would just slip right into the rest of the Lua code, without looking dramatically different. Never mind that the underlying code is actually running on the GPU.
At any rate, there you go. Live zooming on a Mandelbrot set, utilizing the GPU for acceleration, all written in Lua (except for the shader code of course). I wonder if the shader code could be written in Lua as well, and then just converted…