Archive for the ‘shaders’ tag
Added a new effect to Tutorial 5: a single-pass surface and wireframe rendering effect. Note how the wireframe is anti-aliased, fades into the surface color, and displays without z-fighting.
The effect derives directly from the shader in the previously mentioned OpenGL 4.0 Shading Cookbook. The implementation there, according to the author, derives from the one presented in this nVidia whitepaper. I won’t go into detail of the effect (since the explanation is available both in the whitepaper and the book) and will only briefly comment on the implemenation.
In short, it uses a geometry shader to compute the distance of each fragment from each edge of each triangle. The fragment shader then uses those distances to determine whether to use the surface shading color or the wireframe edge color. A mix() call is rather than a discrete choice to antialias the edges. Because it’s a single pass shader, there’s no chance of z-fighting.
Setting the “ViewportMatrix”
A quick note since the OpenGL 4.0 Shading Cookbook does not explain how to set up the “ViewportMatrix”. It’s pretty simple, but just to clarify, here’s the code to set up the viewport matrix:
GLint viewport; gl->getIntegerv(GL_VIEWPORT, viewport); float halfWidth = float(viewport) / 2.0f; float halfHeight = float(viewport) / 2.0f; glm::mat4 m( glm::vec4(halfWidth, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, halfHeight, 0.0f, 0.0f), glm::vec4(0.0f, 0.0f, 1.0f, 0.0f), glm::vec4(halfWidth, halfHeight, 0.0f, 1.0f) ); gl->uniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(m));
The principle is quite simple. Prior to the viewport matrix transformation, OpenGL coordinates in eye space range from -1 to 1 in x, y, and z. Therefore, these values get scaled by half the width/height and offset by half the width/height. This maps -1 to 0 (e.g. -halfWidth + halfWidth = 0) and 1 to the full width (halfWidth + halfWidth = width). The z values do not get scaled or offset since window coordinate retain the same range as eye coordinates.
One More Image…
One last image of a slightly tweaked version of the shader that fades the wireframe intensity based on the diffuse intensity. Also, snow
I added a quick, cheap attempt at snow to Tutorial 5. The primary purpose is to demonstrate point sprites.
Below is a quick vimeo clip of the results:
What and What’s Next
The effect is achieved by sending point list (GL_POINTS) of about size 800 and using a geometry shader to create a screen-aligned, texture-mapped, alpha-blended quad for each point.
A extremely simple particle system is in effect here that resets the z position of the sprite when it falls below 0 (I feel like it’s almost an abuse of language in general to use a specialized term like “particle system” for as simple an update loop as that!). The point sprite shader derives directly from the code provided in OpenGL 4.0 Shading Language Cookbook by David Wolff (which I recommend, by the way – it’s a pretty decent book if you’re like me and hadn’t paid a lot of attention to what’s been happening since GLSL first was introduced).
Next it’d be great get the “Utah sky model” working in the tutorial, if I still had my copy of More OpenGL Game Programming around (which is a pretty good book, but I don’t endorse too highly – too much API reference material or other information easily found on the web is needlessly listed) or still had link to the web page that describes the sky shader code that was later included in the book. Or maybe I could just do a little reading here to add some nice sky effects…
Oh, and adding some first-person navigation and physics would be pretty nice as well if I get back into working with Bullet (I’m not sure I really liked the design I used with the LxMorrowind sample to implement physics – and there were some bugs in there that I never quite understood).
Another minor OpenGL lesson learned during the implementation: glDepthMask() affects calls to glClear(). That makes sense, but I wish I had thought of it before spending a while in the debugger wondering why my shader wasn’t working
An hour here, an hour there, LxEngine is gradually adopting the new material system. The refactoring is a work-in-progress and (this being a hobby project) some percentage of the older code has been unapologetically broken. I’m almost around the bend such that the advantages – other than simply adding cleaner, more logic code – will start coming online. For now, here’s the Standford Bunny run with a two-pass blur/color-inversion shader on top of the first pass’ quilt-like shader…
So much more still to do: better caching, paging and load on demand, loading materials entirely from file, inheritance of parameters, dynamically generating dependent data, sharing a common architecture with ray-tracer, etc., etc., etc.
(I also kicked off a $60 experiment with a small SSD to see if it helps with development on my 4 year old desktop – should arrive next week.)
See the progress here or click on the image below.
For various reasons, one of them being to test LxEngine with “real” data, I’ve been experimenting with loading and displaying the game data from Bethesda Softwork’s 2002 game, The Elder Scrolls III: Morrowind (buy it here on Steam). There’s a fair amount of information out there about the Morrowind file formats – as it is a highly moddable game. I’ve been using NifTools to parse the actual models and been using custom code for the ESM/BSA parsing (neither are very complicated formats).
The primary purpose of the project has been to test out LxEngine with dated, but production-quality data and data formats. The experiment thus far has been serving it’s purpose. It has raised questions like, “Hey, what should the engine do when the current cell has 17 lights and the current shader only supports 8 at a time?” The LxEngine project has hardly been lacking in TODOs, but in any case, this is helping identify the necessities versus the niceties.
A secondary purpose of the project is to learn a bit more about how Morrowind works, so that potentially as a side-effect of working on my own goals produce some useful contribution to the OpenMW project. (This project certainly isn’t meant to compete with OpenMW – the goals here are to demo some basic rendering, physics, sound, etc. from Morrowind to test out LxEngine’s capabilities. The goal of the OpenMW project is to produce a fully playable game with full fidelity to the original.)
As for the current progress, here are some screenshots:
Update: Texture Mapping
Adding texture mapping involved a couple core changes:
- Adding UVs and texture samplers to the LxEngine GLSL shader builder. This is less complex than some of the existing features of the shader builder, but hadn’t yet been added. The support is somewhat minimal and will require revisiting for multi-texturing.
- Adding DDS texture format support to the GL Rasterizer. DDS stands for DirectDraw Surface, i.e. a Microsoft DirectX format, that furthermore has some strange patent issues, which seems to bode poorly but video cards usually handle this format natively. There’s a EXT_texture_compression_s3tc OpenGL extension that allows DDS format data to be passed more or less directly to the card. There’s a simple nVidia tutorial showing how to do this.
- Passing DDS streams from within a BSA understood by the TES3 loader to the LxEngine Rasterizer which knows nothing about Morrowind format data. This was the fun one – which actually still requires a bit of work – abstracting the LxEngine rasterizer from the texture data source in a flexible, general way that both (a) allows the Rasterizer to know nothing about BSA files while the BSA loader knows nothing about OpenGL and (b) still streams the data directly from a disk-based std::istream to OpenGL without any superfluous copies. The Rasterizer now allows textures to be created with a “type” and “acquire callback”. In this case, the type is a stream and the callback is over in the TES3 loader: the only shared concept necessary is the std::istream.
And after a couple bug fixes (like, ehem, remembering to open the binary DDS stream in std::ios::binary mode)…
Next, I need to add multisampling support to the renderer: these screenshots would look so much better with it enabled!
The shader builder from a while ago has been resurrected/rewritten and integrated into LxEngine. I prioritized this as I wanted LxEngine Tutorial 3 to have use procedural materials to produce a somewhat advanced look – given the naive objectives of Tutorial 1 (i.e. get a window up) and Tutorial 2 (i.e. draw something as basic as possible).
The shader builder is not complete yet, but the core architecture is done. With the core architecture in place, advancing the code is largely a matter of adding support for more parameters and adding convenience via simple bits of intelligence in the builder. Eventually, I’d like the shader builder to be a stand-alone component that any GLSL application can use, that is to say make it usable outside LxEngine. Of course, the application using it will inevitably have to adapt to some of the variable naming, but that’s more or less an unavoidable part of interface with any code that has named arguments.
Below is the demonstration screen shot of a cube with an unlit, nested spherical checker map. And immediately following is the XML + JSON code that’s the input to the shader generator.
I’ll just fly through the how…
The graph is essentially a collection of functions, each with one output and N inputs. The functions in the above are “solid”, “checker”, and “spherical”. Each of these functions is packaged a snippet of GLSL code (i.e. the actual function code) and a chunk of JSON annotation that describes the functions data types and default values. (These function + annotation pairs are not shown above.)
The builder then merely walks the JSON graph collects the set of functions being used, creates a tree of calls where each parameter is either (a) the default because it was unspecified in the material description, (b) a hard-coded value in the shader because a hard-coded value was used in the material description, or (c) the result of another function call – i.e. which has it’s value generated by recursively repeating the process.
Now, part (b) is inefficient at the moment as two identical shaders with different parameterizations will generate two different shaders because of the hard-coding; however, that’s easily fixable and on the todo list. (The fix basically is to generate uniforms for all hard-coded values and modify the builder to output a shader + a set of uniform values. This would then be used along with a simple cache mechanism so the same shader gets returned in case of identical graphs.)
Flat shading uses the same normal across the whole face of an object. This is useful, for example, when rendering a cube: the normal should be even across each face with a hard edge between each of the six faces. Smooth shading on the other hand is useful for a sphere: the normal is blended across each sample on the face, giving the appearance of a smooth curve even though the sphere is composed of a discrete tessellation.
In a OpenGL fixed function pipeline, the glShadeModel() function can be used to control the shading on a per-object basis. It cannot be changed within a glBegin() / glEnd() block, however. In a GLSL shader based pipeline, the “flat” keyword (and deprecated “varying” keyword) can be used to control the interpolation of a particular attribute between shader stages: but the toggling between the interpolation types requires separate shaders (i.e. must be the same across the entire object). Furthermore, using the “flat” keyword requires setting up the provoking vertex correctly for the model – which means more processing on the loaded mesh before it can be rendered.
An interesting case is a cylinder: the caps should be shaded flat with a uniform normal, but the length of the cylinder should be smooth like a sphere. How can this be rendered? One option is to break the object into two sub-objects: one for the caps and one for the length of the cylinder. However, I wanted to render the object via single shader and a single object.
The input data…
LxEngine loads .blend models from Blender directly (no import/export – the .blend format is supported natively in LxEngine). One feature in Blender is to control smooth/flat shading on a per-face rather than per-model basis. This is stored as a flag on the face data in the .blend file (i.e. in Blender SDNA terms: the 8-bit “flag” field in the “mface” array of a “Mesh”). Therefore, Blender can be used to create flat shaded caps on a cylinder and smooth shading on its length. The input data is available.
One solution requires GLSL 1.50 or greater, but is quite simple:
- Create a 1D texture with a single float channel of width = number of faces for each object
- For each texel, set the value to 0.0 if the face should be flat and 1.0 if the face should be smooth shaded
- Query the texture in the geometry shader for each face via (gl_PrimitiveIDIn + .5) / textureSize(sampler, 0)
- If the value of the sample is < 1.0, then compute a flat normal for the face and pass that to the next stage for all the face’s vertices
(The above is the principle: it may be prohibitively expensive to literally create a unique 1D texture for all objects in the scene.)
In the image below, the cylinder is a single mesh (i.e. vertex array object) but is partially flat shaded and partially smooth shaded. The spheres are fully smooth shaded, the cubes fully flat shaded, and the cylinders are a mix. The shading model data comes directly from the .blend file – no extra work on the artist’s or programmer’s behalf.
A nice side-effect of this processing is that the shading reflects what occurs in Blender: no additional information needs to be tagged to the object – what the artist created in Blender should be reflected in the LxEngine renderer.
(TBD…still working on preventing WordPress from butchering posted code snippets)
Nothing too exciting…
The rasterizer sample now loads all the major geometric primitives supported by the ray-tracing sample. The code changes required a handful of minor fixes here and there (as the same code was now being used in a different context, thus exposing some holes in the original code), but otherwise was fairly trivial.
The current code uses a simple smooth-shading, normal shader as lights and materials are not yet implemented in the sample. Also, there’s some disparity in the camera setup, which I suspect is due to an error in the ray tracing camera setup code as the rasterizer camera setup is quite concise.
Unused Uniform Variables in GLSL
In GLSL, if a uniform variable is declared but never used, then glGetUniformLocation() will return -1 (i.e. not found) for that variable. This is good to know since (1) it saves some time if you’re doing incremental development and try testing your code when it’s setting the variable but not yet using its value, and (2) it means that in dynamically constructed shaders, it’s okay to include “extra” uniforms that may not be used – the shader compilation will remove the unused variable – and as long as the client code first checks for the presence of the uniform in the final program, some client-side setup can likewise be skipped.
Update: Phong Shading (Initial Imp.)
The code has been updated to read and use the lights from the scene files to produce simple Phong shading. It is not a full implementation yet, but the basic GLSL parameter passing in place.
Unfortunately, the geometry below is all currently flat-shaded. I need to add code to detect the flat versus smooth faces in the .blend file and update the geometry shader to respect that flag.
Update v2: IJW Code Reuse
Sure there are still visual fidelity issues, but it’s great to see that the LxEngine code base is achieving its goal of being highly reusable without a confusing level of abstraction / generalization in the components themselves.
Nothing technically astounding to report: only some regular progress.
I’ve been moving away from OGRE and towards coding my own OpenGL-based renderer. Why? Because I realized that, as much as I want LxEngine to utilize the work of others and avoid reinventing the wheel, this is also currently a hobby project and, bottom-line, I enjoy writing custom graphics code.
That out of the way, I’ve been very interested in terrain lately. Between the Lithosphere demo (see earlier blog entry) and unfortunately downloading and instantly getting a minor addiction to Minecraft, I’m fascinated by what a procedural terrain engine theoretically could do.
Step 1: Height maps. These seem to have a nice balance between simplicity (e.g. path-finding and collision detection) and capacity to display something interesting. Here’s a heightmap generated from a sin + cos wave with a checker pattern and fake diffuse lighting:
Video was probably overkill for such a simple example, but I thought I’d use this as an excuse to try out CamStudio (a free, open source screen recording app). Pretty simple to use. I recommend it.
A bit more work and some head-scratching to really understand Perlin noise, and the result is this heightmap:
Thanks to these pages for the information on Perlin noise:
- Malcolm Kesson’s page on Improved Perlin noise
- NVidia’s GPU Gems Chapter 5 (authored by Ken Perlin)
- Ken Perlin’s slides on Noise Machine
Perlin Noise And Turbulence by Paul Bourke
Added automatic generation of additional tiles and a cheap GLSL fog effect: