Jump to content
  • entries
    9
  • comments
    52
  • views
    25,187

Up! (Part 1)


Rastar

2,940 views

 Share

blog-0765424001392241105.pngI feel a bit like I'm spamming the community blog... but of course that won't stop me from doing it again! biggrin.png With the basic mesh generated it's now time for diving into shader programming. I will displace the vertices in the vertical using a heightmap, and then apply a global color and normal map to make the terrain look more... terrainy. I'll explain the shader line by line. All of this will actually be very basic (so, shadmar and klepto: Move along, nothing to see here... whew, we're amongst ourselves now!). But it will still be so lengthy that I've split the post in two parts. I've put the files for this in my Dropbox folder (due to the large textures it's about 100MB) at https://www.dropbox.com/s/clvbremwdjhdp9c/Terrain.zip. Just extract the folder into your project root and attach the MTTerrain_v2.lua script to a pivot entity. All textures were generated using World Machine (it's from their Tutorial 4 file). Side note: You are not allowed to use them commercially, but I guess you didn't plan to anyway... If you enter a "Total terrain size" of 16384 and a "Vertical scale" of 2625 you have the dimensions that the terrain was generated for.

 

Leadwerks 3.1 provides access to four different stages of the shader pipline: vertex, tessellation, geometry and fragment shader (the tessellation stage actually consists of two programmable parts, the control and evaluation shaders). At least the vertex and the fragment stage have to be present for your shader to compile and produce any output on the screen, and that's what I'll be using here.

 

1) The vertex shader is the first programmable stage. It is executed for every vertex of the rendered model. It cannot produce any geometry or drop it, but can be used to change vertex positions, transform between different spaces, calculate colors and lighting and much more.

 

2) The fragment shader is executed for every fragment (potential pixel on your screen). Its job is to produce the final color value for each fragment.

 

For a short summary of the shader stages see the OpenGL wiki. Leadwerks shaders are programmed in GLSL, a special C-like shading language. I won't go into detail about syntax, data types and so on, but rather focus on two things that have always confused me (and still do): How data flows in, between and out of shaders; and the various transformations between coordinate spaces. So, here is the vertex shader for the terrain (I have stripped it down to almost the bare minimum that I need):

 

#version 400
#define MAX_INSTANCES 256

 

It starts easy enough with the minimum GLSL version number required by this shader. For the more recent releases, the GLSL and OpenGL version numbers are identical. Then it defines the maximum number of (hardware) instances for a model. Frankly, I don't know what happens when you exceed that limit and how large that number can be - maybe somebody can chime in?

 

//Uniforms
uniform vec4 materialcolordiffuse;
uniform sampler2D texture0;//diffuse+height map
uniform mat4 projectioncameramatrix;
uniform mat4 camerainversematrix;
uniform instancematrices { mat4 matrix[MAX_INSTANCES];} entity;

 

Then a couple of uniforms follows. Uniform values are identical across vertices. The values above are defined and filled by Leadwerks. The first two are defined in the material editor as the diffuse color and diffuse (first) texture. The next three are Leadwerks internals and lead us into the wonderful world of coordinate transformations:

  1. Starting with the last, this is an array of entity transforms, ie the values for position, rotation and scale of an entity. This is hardware instancing at play: Instead of rendering the same model with numerous draw calls, the different entity matrices of those models are stored in a uniform buffer and then passed to the shader who renders them in a single draw call. By multiplying a vertex with such a matrix it is transformed into world space (from local to global coordinates).
  2. The camerainversematrix (aka "view matrix") transforms vertices into camera space, ie into a space with the camera at the origin and typically looking down the z axis.
  3. Finally, the projectioncameramatrix both transforms into camera space and then into "clip space", the final perspective view.

 

uniform float terrainSize;
uniform float verticalScale;

 

The next two lines are uniforms that I have defined myself and that are set by the MTTerrain.lua script like this

 

shader:SetFloat("terrainSize", self.terrainSize)
shader:SetFloat("verticalScale", self.verticalScale)

 

They contain the overall size of the terrain (in meters) and its maximum height (also in meters).

 

//Attributes
in vec3 vertex_position;
in vec4 vertex_color;
//Outputs
out vec4 ex_color;
out vec2 ex_texcoords0;
out mat3 nmat;

 

And finally the per-vertex (non-uniform) inputs and outputs of the vertex shader follow. The "ins" are predefined and filled by Leadwerks (they contain the object-space vertex position and vertex color). The "outs" are arbitrary variable names that are then provided to later shader stages and can be used there by a simple naming convention. So an "out vec4 ex_color" can be accessed in another shader by defining "in vec4 ex_color".

 

Then the magic happens: Every shader stage needs a void main(void) function that does the actual processing (though you can define additional functions to structure your code).

 

mat4 entitymatrix = entity.matrix[gl_InstanceID];
mat4 entitymatrix_=entitymatrix;
entitymatrix_[0][3]=0.0;
entitymatrix_[1][3]=0.0;
entitymatrix_[2][3]=0.0;
entitymatrix_[3][3]=1.0;

vec4 modelvertexposition = entitymatrix_ * vec4(vertex_position,1.0);

 

First, the entity matrix of the current instance (identified by gl_InstanceID) is retrieved from the array and stored in the variable entitymatrix. Now, as a little trick Leadwerks stores a per-entity color in the (otherwise unused) fourth column of that matrix - the entitymatrix_ variable is relieved from that burden (if you select an entity in the scene explorer and switch to the Appearance tab - the "Diffuse" down there - that's it!). Then, (object-space) vertex position is transformed into world space.

 

ex_texcoords0 = modelvertexposition.xz / terrainSize + 0.5;

 

The (first) UV coordinates of a mesh are passed into the shader in a uniform called vertex_texcoord0, but that is of no use here: I want to apply a color and normal map that covers the entire terrain (not just a single mesh patch), so I have to calculate them myself. Since the terrain is centered on the origin, after dividing the vertex position by the terrain size an addition of 0.5 gives the correct range from 0.0 to 1.0.

 

modelvertexposition.y = (texture(texture0, ex_texcoords0)).a * verticalScale;

 

Tadaa - this innocent looking line does all the magic of the vertex displacement. Using our calculated UV coordinates, it samples the alpha channel of the diffuse texture (where I have encoded the height map), multiplies that value (which again will be between 0.0 and 1.0) with the maximum height and displaces each vertex by the result in the y direction. Simple.

 

gl_Position = projectioncameramatrix * modelvertexposition;

 

Every vertex shader must return the vertex position in the gl_Position variable, in this case in clip space coordinates.

 

nmat = mat3(camerainversematrix[0].xyz,camerainversematrix[1].xyz,camerainversematrix[2].xyz);//39
nmat = nmat * mat3(entitymatrix[0].xyz,entitymatrix[1].xyz,entitymatrix[2].xyz);//40
ex_color = vec4(entitymatrix[0][3],entitymatrix[1][3],entitymatrix[2][3],entitymatrix[3][3]);
ex_color *= vec4(1.0-vertex_color.r,1.0-vertex_color.g,1.0-vertex_color.b,vertex_color.a) * materialcolordiffuse;

 

Almost done... Finally a matrix for transforming normals into camera space and a vertex color value is calculated and exported.

 

What's next

I know, I know, that's not really a joy to wrap your head around. Part 2 goes quickly ove the fragment shader (will be much shorter) and then present the final result. Stamina!

  • Upvote 10
 Share

4 Comments


Recommended Comments

Thanks for this great tutorial. I can't stop repeating that we need official shader manual and tutorials. This one comes as an excellent substitution.

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...