Archive for the ‘tutorial’ tag
Material System
Work continues on the LxEngine material system…
The above is a simple “toon” shader on the Suzanne model. The shader code is based on the simple example provided at LightHouse3d, but bases the color on a 1d texture look-up on a slightly blurred color texture rather discrete if-else statements.
The Video
Here’s a quick video of some of the material effects:
The Code
At the highest-level, the implementation of the new shader is very simple. The new shader is defined by creating a new directory “ToonSimple” in the materials sub-directory of the media directory. This directory contains a vertex shader, a fragment shader, and JSON parameters description.
The material is then loaded in the C++ code via a call to
pRasterizer->acquireMaterial("ToonSimple")
and attached to the Instance‘s spMaterial member. LxEngine handles all the shader loading and parameter activation.
On to the details…
Vertex Shader
The vertex shader code is quite simple and uses a fixed light direction:
uniform mat4 unifProjMatrix; uniform mat4 unifViewMatrix; uniform mat3 unifNormalMatrix; in vec3 vertNormal; varying out float fragIntensity; void main() { // Keep it simple and use a fixed light direction vec3 lightDir = vec3(.5,-.5, 1.0); // The fragIntensity is effectively just the intensity of the diffuse // value from the Phong reflection model. // fragIntensity = dot(normalize(lightDir), unifNormalMatrix * vertNormal); gl_Position = unifProjMatrix * unifViewMatrix * gl_Vertex; }
The uniforms – unifProjMatrix, unifViewMatrix, unifNormalMatrix – are all “standard” LxEngine names, therefore it will automatically set the correct matrix values when activating the shader. Likewise with the attribute vertNormal; it too will be set automatically by the existing engine code. (This will be explained momentarily.)
Fragment Shader
The fragment shader is quite simple:
#version 150 #extension GL_ARB_explicit_attrib_location : enable uniform sampler1D unifTexture0; in float fragIntensity; layout(location = 0) out vec4 outColor; void main() { outColor = texture(unifTexture0, fragIntensity); }
Now the fragment shader does have an interesting detail: the uniform unifTexture0 is not a “standard” LxEngine uniform. (How could it be? The transformation matrices are common to many shaders, as are properties like the geometry’s normals, but is a texture map ever going to be “standard” enough that the engine would know what to set?)
This is a custom uniform, but it still does not require any C++ code for the engine to set it’s value properly. We’ll get to that momentarily.
Automatically setting the shader variables
The automatic setting of uniforms and attributes is done via calls to getActiveUniform and getActiveAttrib after the GLSL program is compiled. The MaterialClass class wraps the GLSL program and provides iteration functions that exemplify the use of these OpenGL calls:
void MaterialClass::iterateUniforms (std::function<void(const Uniform& uniform)> f) { int uniformCount; gl->getProgramiv(mProgram, GL_ACTIVE_UNIFORMS, &uniformCount); for (int i = 0; i < uniformCount; ++i) { Uniform uniform; char uniformName[128]; GLsizei uniformNameLength; gl->getActiveUniform(mProgram, GLuint(i), sizeof(uniformName), &uniformNameLength, &uniform.size, &uniform.type, uniformName); if (uniformNameLength >= sizeof(uniformName)) { throw lx_error_exception("GLSL program contains a uniform with too long a name size!"); } else { uniform.name = uniformName; uniform.location = gl->getUniformLocation(mProgram, uniformName); f(uniform); } } }
The LxEngine internal rasterizer code, after compiling a GLSL shader for the first time, will iterate over the uniforms and attributes to generate a set of values that need to be set whenever that material is made active. The set of “instructions” necessary to set those values is encapsulated in a std::vector<std::function<void()>> – which, in effect, allows a sort of dynamic code generation at the expense of a bit of overhead to the std::function calls. The flexibility and simplicity definitely win out over the efficiency loss for the purposes of LxEngine.
For example, below is a code snippet from the shader attribute instruction generation function (or see the latest version of the material source code for more details):
std::function<void()> Material::_generateInstruction(RasterizerGL* pRasterizer, const Attribute& attribute, lx0::lxvar& value) { ... if (attribute.name == "vertNormal") { return [=]() { auto& vboNormals = pRasterizer->mContext.spGeometry->mVboNormal; if (vboNormals) { gl->bindBuffer(GL_ARRAY_BUFFER, vboNormals); gl->vertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 0, 0); gl->enableVertexAttribArray(location); } else gl->disableVertexAttribArray(location); check_glerror(); }; }
Setting a custom uniform
The non-standard unifTexture0 uniform is set somewhat differently. The material definition – in addition to the vertex and fragment shaders – also includes a simple JSON parameter description file. In this case, it contains only one parameter:
{ parameters: { unifTexture0 : "media2/textures/gradients/1d/suncopper_1-00.png" } }
In this case, the _generateInstruction() method loops over all unrecognized uniform names and searches for a user-specified parameter value for that uniform. In this case, it finds “unifTexture0″ as both an unrecognized uniform and a value in the parameter mapping.
Since the information about the uniform also includes the data type (GL_SAMPLER_1D), LxEngine can figure out to interpret that string value as an image filename, can load that file and store it in the texture cache, and generate an instruction to set that texture when activating the material:
else if (uniform.type == GL_SAMPLER_1D) { auto filename = value.as<std::string>(); TexturePtr spTexture = pRasterizer->mTextureCache.findOrCreate(filename ); GLuint textureId = spTexture->mId; // Activate the corresponding texture unit and set *that* to the GL id return [=]() { const auto unit = pRasterizer->mContext.textureUnit++; // Set the shader uniform to the *texture unit* containing the texture (NOT // the GL id of the texture) gl->uniform1i(loc, unit); gl->activeTexture(GL_TEXTURE0 + unit); gl->bindTexture(GL_TEXTURE_1D, textureId); // Set the parameters on the texture unit gl->texParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, mFilter); gl->texParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, mFilter); gl->enable(GL_TEXTURE_1D); check_glerror(); }; }
Adding simple shaders should be simple
The point really is that adding a simple shader, like this toon shader, is simple to do. The new material system in LxEngine makes it trivial as common uniforms and attributes are automatically set up and the mechanism for specifying custom uniforms is quite easy.
The objective is an engine designed to make experimentation and research simple.
What’s Next? LxEngine Tutorial 4
I’m currently working on cleaning up and writing up a good description of “Tutorial 4″ of LxEngine. I want to add a couple more effects to make the tutorial feel a bit more substantial first (perhaps add shadow mapping?), but would also like to get a finished tutorial out the door. As a preview, the fourth tutorial will include at least the following: writing an application via Javascript, geometry generated from scripts, multipass rendering, multithreading, time-lapse events, and…well, probably more if I don’t hurry up and finish this off!
LxEngine Tutorial 1
LxEngine is still in early development (in fact, the only way to even attempt to use it right now is acquiring the source from github and building it yourself), but I thought it would be interesting to write up a first tutorial on how to use it. I had been remembering how useful the NeHe tutorials were back when I was first learning OpenGL.
Also – who would ever use an engine that isn’t documented?
I supposed the process of writing a basic tutorial might expose some/any fundamental problems with the architecture that I might be overlooking while working on more advanced features. For the most part it has: the plug-in system is a bit confused and will require future work, a basic API naming consistency issue needed fixing, and – overall – there’s still a fair amount more than I was realizing to know to get that first app up and running…
The tutorial’s here:
Feedback is very welcome and would be most helpful, even if it involves simply reading the tutorial and not bothering with the code (given that the engine is still in its infancy).


