there don’t seem to be a lot of decent shader examples floating around on the interwebs. there are some for the shader code, but few for the c code to set it up.
source code: http://paste.uni.cc/20519
so, here’s the basic idea:
- include and init glew
- make a vertex shader object and a fragment shader object (and optionally a geometry shader)
- get shader code into strings (char*)
- attach the strings to the shaders
- compile the shaders
- make a program object
- attach the shader objects to the program objects
- link the program
- delete/free things you’re done with (shaders, strings)
- tell opengl to use the program
for this example, the shader is going to draw the mandelbrot set. that’s the set {z| z = z*z + c does not diverge}, where z and c are complex numbers
the pixels correspond to ‘c’, and you can guess weather the point will diverge by iterating z=z*z+c some number of times, and seeing if |z| goes above 2.
i’ll assume you can figure out how to get shader code into a string, but if not, take a look at the source code (at the bottom).
these create your shader and program objects:
GLuint glCreateShader(GLuint type), where type is GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, or GL_GEOMETRY_SHADER
GLuint glCreateProgram()
attach code to shaders:
if your strings are nul terminated, lengths can be NULL.
void glShaderSource(GLuint shader, int number_of_strings, char** strings, int* string lengths)
attach shaders to program:
void glAttachShader(GLuint program, GLuint shader)
compile/link:
void glCompileShader(GLuint shader)
void glLinkProgram(GLuint program)
get compile/link errors:
these copy up to max_length of the log into the output string, and nul terminate it.
void glGetShaderInfoLog(GLuint shader, int max_length, int* chars_read, char* output)
void glGetProgramInfoLog(GLuint program, int max_length, int* chars_read, char* output)
use a linked program:
void glUseProgram(GLuint program)
now, onto the shaders:
for the vertex shader, we need a centre position, and a zoom level as inputs, and a complex number as ouput.
uniform vec2 pos;
uniform float zoom;
out vec2 c;
the calculation is pretty simple: use the standard matrix transform on the vertices, and use the raw vertex inputs to get the complex number:
void main() {
gl_Position = ftransform();
c = pos + gl_Vertex.xy * zoom;
}
the fragment shader has a bit more meat in it. it needs to get the complex number from the vertex shader, and the number of iterations from the opengl program.
in vec2 c;
uniform int depth;
and it needs a function to multiply complex numbers. (a+ib)(c+id) = ac-bd + i((a+b)(c+d) – ac – bd)
vec2 c_mult(vec2 a, vec2 b) {
return vec2(a.x * b.x - a.y * b.y,
(a.x+a.y)*(b.x+b.y) - a.x*b.x - a.y*b.y);
}
and a function to get the number of iterations before the mandelbrot thing diverges (between 0 and 1)
if |z| is above 2, the series diverges, so you can check for dot(z, z) > 4, to save a sqrt operation.
float mandel(int depth, vec2 c) {
int i = depth;
vec2 z = vec2(0.0, 0.0);
while(i --> 0) {
if(dot(z, z) < 4.0) {
z = c_mult(z, z) + c;
}
else break;
}
return 1.0 - float(i)/float(depth);
}
and the main function needs to set the colour based on the result. i have it just multiplying purple, but you could sue a fancier function
void main() {
gl_FragColor = mandel(depth, c) * vec4(1.0, 0.0, 1.0, 1.0);
}
in the opengl code, you’re going to need to pass pos, zoom, and depth to the shaders.
you need:
GLuint glGetUniformLocation(GLuint program, char* field_name)
void glUniform1f(GLuint location, GLfloat value)
void glUniform1i(GLuint location, GLint value)
void glUniform2fv(GLuint location, int array_length, GLfloat* values)
array_length should be 1. if your uniform variable is an array, it can be higher.
and that’s it. you just draw a rectangle or something, and it will have the set drawn on it.