While working on a small mobile project, I've started to look for ways to render infinite terrain. Modern mobile GPUs come with very slow memory, low bandwidth, but reasonable processing power. So procedural content does not only have interesting gameplay value, but also performance advantages.

I constructed simple terrain mesh:

void rePTerrainRenderable::load()
{
    reGroup* group = new reGroup;
    reVec3 offset(-columns()*size().x/2, 0, -rows()*size().y/2);
    for ( size_t x=0; x<columns(); x++)
        for ( size_t y=0; y<rows(); y++) // Each "cell" is made of 2 faces
        {
            float x1 = x*size().x;
            float y1 = y*size().y;
            float x2 = (x+1)*size().x;
            float y2 = (y+1)*size().y;
            reFace face1, face2;
            face1.vertices[0] = reVertex(reVec3(x1, 0, y2)+ offset);
            face1.vertices[1] = reVertex(reVec3(x2, 0, y1)+ offset);
            face1.vertices[2] = reVertex(reVec3(x1, 0, y1)+ offset);            
            face2.vertices[0] = reVertex(reVec3(x1, 0, y2)+ offset);
            face2.vertices[1] = reVertex(reVec3(x2, 0, y2)+ offset);
            face2.vertices[2] = reVertex(reVec3(x2, 0, y1)+ offset);                    
            group->faces.push_back(face1);
            group->faces.push_back(face2);
        }
    mesh->groups.push_back(group);
    group->bufferChanged = true;
}

We need pseudo random values changing in a smooth way, pseudo random since we need continuity. There are various noise implementations for GLSL, I've chosen snoise.

Here is the vertex shader, generally speaking how jagged the surface is defined by amount of change of input, so by dividing input vector we are smoothing the surface, adding a second as multiplier just increases variation.

Vertex shader is very simple:

vec4 calculateVertex(vec4 pos)
{
    vec2 loc = vec2(pos.x, pos.z);
    pos.y = snoise(loc/50.0f)*3*snoise(loc/100.0f);
    return pos;
}

Calculating normals requires taking your vertex and extending in both +x, -x, +z, -z directions, after that we end up with 4 triangles. Cross product of edges gives us the triangle normal. Average of these normals can be used as approximate vertex normal we can interpolate.

vec3 calculateNormal(vec4 pos)
{
    float s = .01; // it should be small enough to be precise, 
                   // big enough to match visible surface
    vec3 l =  calculateVertex(pos + vec4( -s, 0,  0, 0)) - pos; 
    vec3 t  = calculateVertex(pos + vec4(  0, 0, -s, 0)) - pos; 
    vec3 b =  calculateVertex(pos + vec4(  0, 0,  s, 0)) - pos; 
    vec3 r =  calculateVertex(pos + vec4(  s, 0,  0, 0)) - pos; 
    vec3 n1 = cross(r, t);
    vec3 n2 = cross(l, b);
    vec3 n3 = cross(t, l);
    vec3 n4 = cross(b, r);
    return normalize((normalize(n1)+normalize(n2)+normalize(n3)+normalize(n4))/4.0f);
}

Comments

comments powered by Disqus