This example shows how to create and use custom OpenGL shader programs in Cornerstone. We will create a widget whose texture is computed procedurally in the fragment shader.
To be able to render anything we have to create widget, because widgets are the only thing Cornerstone draws at all. Since we want to use our own shaders, we override renderContent-function.
We will use five additional values in our shaders. These values are stored as attributes of the class for handy access using CSS-files. Also GLSL program and status flag are stored as members.
We feed our uniform values to shader program by defining our own uniform buffer format. Since we will be using drawPrimitiveT-function, it is necessary to have fields projMatrix, modelMatrix, color and depth with the appropriate types in our uniform block. The rest of the fields are used in our shaders for custom effects.
One must pay special attention to the memory layout of the data elements: Matrix4 and Vector4 data types must come first in the struct, as they need to be aligned to 16-byte boundaries. After the 16-byte elements, we can introduce the smaller data elements, such as the 4-byte floating point values. The other important thing to keep in mind when using uniform blocks, is that the order of the defined struct must match exactly to the block defined in shaders. The definition of uniform block used in shaders is shown below:
Note that the layout qualifiers are not optional when using Cornerstone renderer! The name of the uniform block can be chosen freely as long as it's consistent across all the shaders attached to the program.
In the constructor we initialize our widget's attributes and load and attach shaders to the GLSL program.
In the latter part of the constructor we set vertex description for the shader program. This description defines what attributes vertices have and which order the attributes have in the corresponding struct.
The order of the vertex description's added attributes must match to the struct defining vertex attributes. Otherwise attributes will get mixed. The definition of the vertex struct is following:
Actually in this case we wouldn't need to define the struct of vertex attributes because the one we defined is equivalent to Luminous::BasicVertexUV. The values are mapped to the shader by the strings used in Luminous::VertexDescription. These strings must match with the incoming attribute names used in shaders. Below is shown definition of inputs to a vertex shader:
The vertex shader transforms given vertices first to the eye space and then to the clip space. Eye space corresponds to the pixels of the screen where origo is located at the top left corner. Finally texture coordinates are copied to the fragment shader.
In the fragment shader we create the texture by first calculating weights for the colors using uniform variable modulator. After this we take a weighted sum of the colors passed as uniforms. The alpha value of the fragment is set to 1. By default Cornerstone renderer uses the first active output in fragment shader as an output variable. In this case the output binds to frag_color.
The actual rendering of the widget is done in renderContent-function. The rendering is started by creating a rendering command by calling Luminous::RenderContext::drawPrimitiveT. Function takes six parameters which are described by the following snippet.
The return value of drawPrimitiveT has type Luminous::RenderContext::RenderBuilder. The actual data for rendering is piped to the renderer through this object. Since the object is a templated type, we can access the structs defined earlier through this object.
That is all we need for custom rendering. After RenderBuilder is created and supplied with the corresponding data, the renderer has all the information it needs. We don't actually need to execute any separate call for rendering. The main program just creates three widgets and setups some animations for them to make example more interesting. The full C++-source code of the example is shown below: