Jump to content
  • Advertisement
Sign in to follow this  
Xardes

Computing smooth normals for procedually generated terrain

This topic is 1001 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey guys,

 

I recently implemented a way of procedurally generating my Terrain by following this Tutorial: https://rendermeapangolin.wordpress.com/2015/05/26/screen-space-grid/

 

This works quite well. If you have a look a the screenshot at the very end of this Tutorial, you can see, that the shading is some sort of flat shading. I would like to achieve a smoother shading. I tried to compute per-vertex normals within the vertex shader by averaging the normals of all faces that contain the vertex. This creates a smoother shading, however either I still have mistakes in my computation or it doesn't work so well with the Screen Space grid, because The normals have no.. "smooth transition" as I move my camera, but rather change their values drastically.

 

Currently I do the following:

 

//----------------------------------------------------------------

// Sample the heightmap(2048^2)

float height[5];
height[0] = texture( myTextureSampler, finalPos.xz + vec2(-1.0/2048.0, 0.0)).r;
height[1] = texture( myTextureSampler, finalPos.xz + vec2(0.0, -1.0/2048.0)).r;
height[2] = texture( myTextureSampler, finalPos.xz + vec2(0.0, 0.0)).r;
height[3] = texture( myTextureSampler, finalPos.xz + vec2(1.0/2048.0, 0.0)).r;
height[4] = texture( myTextureSampler, finalPos.xz + vec2(0.0, 1.0/2048.0)).r;

 

vec3 n[4];
vec3 l0;
vec3 l1;
    
// Bottom left Triangle
l0 = vec3(finalPos.x - 1.0/2048.0, height[0], finalPos.z) - vec3(finalPos.x, height[2], finalPos.z);
l1 = vec3(finalPos.x, height[1], finalPos.z - 1.0/2048.0) - vec3(finalPos.x, height[2], finalPos.z);
n[0] = normalize(cross(l0,l1));
if(n[0].y <0)
n[0] = -n[0];
    
// Upper right Triangle
l0 = vec3(finalPos.x + 1.0/2048.0, height[3], finalPos.z) - vec3(finalPos.x, height[2], finalPos.z);
l1 = vec3(finalPos.x, height[4], finalPos.z + 1.0/2048.0) - vec3(finalPos.x, height[2], finalPos.z);
n[1] = normalize(cross(l0,l1));
if(n[1].y <0)
n[1] = -n[1];
  
// Upper left Triangle
l0 = vec3(finalPos.x - 1.0/2048.0, height[0], finalPos.z) - vec3(finalPos.x, height[2], finalPos.z);
l1 = vec3(finalPos.x, height[4], finalPos.z + 1.0/2048.0) - vec3(finalPos.x, height[2], finalPos.z);
n[2] = normalize(cross(l0,l1));
if(n[2].y <0)
n[2] = -n[2];
    
// Bottom right Triangle
l0 = vec3(finalPos.x + 1.0/2048.0, height[3], finalPos.z) - vec3(finalPos.x, height[2], finalPos.z);
l1 = vec3(finalPos.x, height[1], finalPos.z - 1.0/2048.0) - vec3(finalPos.x, height[2], finalPos.z);
n[3] = normalize(cross(l0,l1));
if(n[3].y <0)
n[3] = -n[3];

 

// Average normal
oNormal = normalize((n[0] + n[1] + n[2] + n[3])/4);

//---------------------------------------------------------------------------------

 

The shader is certainly smooth now, but the normals have no smooth transition to one another. I suppose, that happens, because the vertices are more dense near the camera than further away and have not that uniform 1.0/2048.0 distance? Any Ideas, how I can realize a completely smooth shading, while procedurally generating Terrain as explained within the Tutorial?

 

Thank you very much!

 

Xardes

Share this post


Link to post
Share on other sites
Advertisement

Screen shots would be informative, to illustrate the exact issue you are experiencing.

 

In the absence of visuals, my best guess would be that you are experiencing the issue explained towards the end of the tutorial, namely that you need to manually implement mipmapping using textureGrad().

Share this post


Link to post
Share on other sites

Screen shots would be informative, to illustrate the exact issue you are experiencing.

 

In the absence of visuals, my best guess would be that you are experiencing the issue explained towards the end of the tutorial, namely that you need to manually implement mipmapping using textureGrad().

Share this post


Link to post
Share on other sites

Screenshots don't add much information, as static images don't really demonstrate the problem. See this Video instead:

 

Please compare the lighting. The lighting of the leafs is smooth. The Terrain not. Its not flat shading, but the "transition of normals" isn't smooth. Still it looks better than before. I would like to achieve lighting that is as smooth as the lighting of the leafs.

 

Right now, I do the following (Changed since creating this thread):

1. Compute the world Space pos (ray tracing interception with xz plane) of the Screen Space vertex to proccess.

2. Compute the world space pos (ray tracing interception with xz plane) of the 4 Adjacent vertices (x/y +1/0, -1/0, 0/+1, 0/-1).

3. Compute the 4 world space triangle normals.

4. Compute the vertex average

 

Code:

    vec3 finalPos[5];
    vec3 camera_dir = normalize( ( inverse(projectionMatrix) * vec4(aPosition.x, aPosition.z, 1.0, 1.0) ).xyz );
    vec3 world_dir = normalize( inverse(modelViewMatrix) * vec4(camera_dir, 0) ).xyz;
    float t = camPos.y / -world_dir.y;
    finalPos[0] = camPos + t*world_dir;
    

    // 256x256 is the screen Space grid resolution
    camera_dir = normalize( ( inverse(projectionMatrix) * vec4(aPosition.x + 1.0/256.0, aPosition.z, 1.0, 1.0) ).xyz );
    world_dir = normalize( inverse(modelViewMatrix) * vec4(camera_dir, 0) ).xyz;
    t = camPos.y / -world_dir.y;
    finalPos[1] = camPos + t*world_dir;
   
    camera_dir = normalize( ( inverse(projectionMatrix) * vec4(aPosition.x - 1.0/256.0, aPosition.z, 1.0, 1.0) ).xyz );
    world_dir = normalize( inverse(modelViewMatrix) * vec4(camera_dir, 0) ).xyz;
    t = camPos.y / -world_dir.y;
    finalPos[2] = camPos + t*world_dir;
    
    camera_dir = normalize( ( inverse(projectionMatrix) * vec4(aPosition.x, aPosition.z + 1.0/256.0, 1.0, 1.0) ).xyz );
    world_dir = normalize( inverse(modelViewMatrix) * vec4(camera_dir, 0) ).xyz;
    t = camPos.y / -world_dir.y;
    finalPos[3] = camPos + t*world_dir;
    
    camera_dir = normalize( ( inverse(projectionMatrix) * vec4(aPosition.x, aPosition.z - 1.0/256.0, 1.0, 1.0) ).xyz );
    world_dir = normalize( inverse(modelViewMatrix) * vec4(camera_dir, 0) ).xyz;
    t = camPos.y / -world_dir.y;
    finalPos[4] = camPos + t*world_dir;
    
    float height[5];
    height[0] = 8.0 * texture( myTextureSampler, finalPos[0].xz / 128.0 ).r;
    height[1] = 8.0 * texture( myTextureSampler, finalPos[1].xz / 128.0 ).r;
    height[2] = 8.0 * texture( myTextureSampler, finalPos[2].xz / 128.0 ).r;
    height[3] = 8.0 * texture( myTextureSampler, finalPos[3].xz / 128.0 ).r;
    height[4] = 8.0 * texture( myTextureSampler, finalPos[4].xz / 128.0 ).r;
    
    float sx = height[1] - height[2];
    float sz = height[3] - height[4];

    oPos = vec3(finalPos[0].x, height[0], finalPos[0].z);
    

    // This is passed to the fragment shader!
    oNormal = normalize(vec3(sx,2.0/255.0,sz));

 

Any Ideas, how I can get it more smooth/what I am doing wrong?

Share this post


Link to post
Share on other sites
Its hard to tell because of the complex colours on your terrain (rendering it with just light, and no colour would be helpful). However, two questions spring to mind: are you using textureGrad() to sample the mimaps in your texture data? and are you snapping the sampling points to the nearest texel center? (this is usually done in shadow mapping to prevent the shadows from 'swimming')

Share this post


Link to post
Share on other sites

Alright another video:

 

The color of the fragments in the video is as follows:

 

    vec3 green = vec3(0.0, 0.75, 0.0);
    vec3 grey = vec3(0.2, 0.2, 0.2);

    vec3 col = mix(green, grey, oPos.y*0.1);

    // Final fragment color

    oFragColor = vec4(col, 1.0);

 

That means the color is more green in low areas and less green in higher ares. As you can see, the 3D world position generated by the heightmap is continuous (displayed to the limit of RGB space of course). Therefore the sampling of the heightmap is done correctly, is it not? I mean, if I just compute the normals correctly out of these world space coordinates, I should get smooth lighting, right?

 

If you want I will add another video with just diffuse lighting, I just wanted to make sure, everything is fine until here.

 

Thanks again.

Share this post


Link to post
Share on other sites

i had the same problem, calculation of normalmaps in shader didin't work at all, i had to move that calculation to cpu and then uploaded that to vertexbuffer or texture, then shader could interpolate properly

 

 

edit: i found more readable code i think


vec3 getvertNormal(int x, int y, float dome_sizeX, float dome_sizeZ)
{
int i = x;
int j = y;
int	  jMIN = j - 1;
int	  jMAX = j + 1;
int	  iMIN = i - 1;
int	  iMAX = i + 1;

if ( i-1 < 0 ) iMIN = TerraDim-1;
if ( j-1 < 0 ) jMIN = TerraDim-1;
if ( i > TerraDim-1 ) iMAX = 0;
if ( j > TerraDim-1 ) jMAX = 0;
float transX = dome_sizeX*(1.0/float(TerraDim));
float transZ = dome_sizeZ*(1.0/float(TerraDim));

vec3 vertex_position = vec3(float(x)*transX, 0.0, float(y)*transZ);
//terrain[y*TerraDim + x].v
//hpdata[y*TerraDim*4 + x*4 + 0] = Byte(tmp * 255.0);
float h2 = float(hpdata[ j*TerraDim * 4 + iMAX * 4 + 0 ])/255.0;
float h3 = float(hpdata[ j*TerraDim * 4 + iMIN * 4 + 0 ])/255.0;
float h4 = float(hpdata[ jMAX*TerraDim * 4 + i * 4 + 0 ])/255.0;
float h5 = float(hpdata[ jMIN*TerraDim * 4 + i * 4 + 0 ])/255.0;

float MIN_HEIGHT = vertex_position.y;
float dst = HEIGHT_SCALE;

vec3 minxy = vertex_position + vec3(-transX,MIN_HEIGHT+dst*h3, 0.0);
vec3 xmaxy = vertex_position + vec3(0.0, MIN_HEIGHT+dst*h4, transZ);
vec3 maxxy = vertex_position + vec3(transX, MIN_HEIGHT+dst*h2, 0.0);
vec3 xminy = vertex_position + vec3(0.0, MIN_HEIGHT+dst*h5, -transZ);


vec3 n1 = Normal(vertex_position, minxy, xmaxy, false);
vec3 n2 = Normal(vertex_position, xmaxy, maxxy, false);
vec3 n3 = Normal(vertex_position, maxxy, xminy, false);
vec3 n4 = Normal(vertex_position, xminy, minxy, false);
return Normalize( (n1+n2+n3+n4)/4.0 );

}

void GenerateNormals()
{
	GetAABB();

for (int y=0; y < TerraDim; y++)
for (int x=0; x < TerraDim; x++)
{
normals[y*TerraDim + x] = getvertNormal(x, y,absnf( max.x-min.x),absnf(max.z-min.z));
terrain[y*TerraDim + x].n = normals[y*TerraDim + x];
}
SendToGPU();
}

 

 

 

 

 

 

 

original code

OCEAN_GRID_SIZE is the terrain size
SIM_GRID_SIZE is the dimension of a grid
NormalMapVertexTranslation = float(OCEAN_GRID_SIZE)*(1.0/float(SIM_GRID_SIZE));
vec3 CalculateNormalMap(int x, int y)
{
int minx = x - 1;
int miny = y - 1;
int maxx = x + 1;
int maxy = y + 1;

if (minx < 0) minx = SIM_GRID_SIZE-1;
if (miny < 0) miny = SIM_GRID_SIZE-1;

if (maxx > SIM_GRID_SIZE-1) maxx = 0;
if (maxy > SIM_GRID_SIZE-1) maxy = 0;

vec3 p = AOS[ ArrI(x,y) ].v; //get the position of the vertex (x, z)
p.y = 0.0; //this is actual height i fetch that after calculating heights for 4 connected verts (up down left right - may wrote that in wrong order)

vec3 minxy = p + vec3(-NormalMapVertexTranslation,heightmap[ ArrI(minx, y) ] / NORMAL_SCALE, 0.0);
vec3 xmaxy = p + vec3(0.0, heightmap[ ArrI(x, maxy) ] / NORMAL_SCALE, NormalMapVertexTranslation);
vec3 maxxy = p + vec3(NormalMapVertexTranslation, heightmap[ ArrI(maxx, y) ] / NORMAL_SCALE, 0.0);
vec3 xminy = p + vec3(0.0, heightmap[ ArrI(x, miny) ] / NORMAL_SCALE, -NormalMapVertexTranslation);

p.y = heightmap[ ArrI(x, y) ] / NORMAL_SCALE; //now apply height to p

//calc all normals - false here means do not normalize.
vec3 n1 = Normal(p, minxy, xmaxy, false);
vec3 n2 = Normal(p, xmaxy, maxxy, false);
vec3 n3 = Normal(p, maxxy, xminy, false);
vec3 n4 = Normal(p, xminy, minxy, false);

//and get average of it
return Normalize( (n1+n2+n3+n4)/4.0 );
}
Edited by WiredCat

Share this post


Link to post
Share on other sites

Alright. 2 Videos this time.

 

The first shows diffuse lighting, when I compute the normals in the fragment shader in exactly the same way it is done in the tutorial (but with 0.25 ambient light):

 

vec3 normal = normalize(cross( dFdx(oPos.xyz), dFdy(oPos.xyz)));

float diffuse = max(dot(normalize(normal),normalize(vec3(0.0,15.0,15.0))),0.25);

 

Video:

 

The second one compute the normals in vertex shader and only the lighting in fragment shader with the code posted earlier.

 

Video:

 

@WiredCat I don't get, why this wouldn't work in shader.. But I guess computing an appropriate normal map would be an elegant work around. Also saves all the computation in the shader. If I do it via CPU over multiple frames once the player reaches the end of the current area, that shouldn't be too difficult. Thank you, I will try that. o/

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!