3
\$\begingroup\$

I have been experimenting with C++ and OpenGL, and I am currently writing a program that renders the surface of a planet; specifically, our moon. It's a modified version of the GPU Gems implementation of Geometry Clipmaps which is normalized to the unit sphere and then offset by a heightmap in the terrain's vertex shader. On small scale scale this works rather well:

Rendering with a planet radius of 1,000 (heights exaggerated for clarity)

Radius = 10,000; side of the slope is still temporally smooth

However, I want to scale this up to the size of the actual moon. Unfortunately, when I do so, stepping artifacts appear:

Stepping artifacts on the side of a large crater

Now I am (almost) certain that this is simply an issue with floating point precision; 32-bit floats just simply don't have enough precision for planets of this size. However, I am in an interesting situation where I cannot simply change one of my numbers to a double to gain more precision. My vertex shader code is this:

layout (location = 0) in vec3 VertexPosition;

uniform mat4 MVP; //projection and camera rotation matrix
uniform mat4 PLM; //scale and translation matrix
uniform dmat4 SurfaceRotationMatrix; //transforms vertices to be the correct position on surface

uniform samplerCube bump_map; //heightmap cubemap

uniform dvec3 PlayerLocation;

uniform double PlanetRadius;
uniform double TerrainHeightScalar;

void main(){

    //find the world space position of the flat grid (high precision)
    dvec4 worldSpacePosition = PLM * vec4(VertexPosition, 1.0);

    ...

    //offset the y position by the planets radius
    worldSpacePosition.y += PlanetRadius;

    //vertex position in relation to camera (located at origin)
    dvec4 OriginNormal = normalize(worldSpacePosition); 
    //vertex position in relation to planets surface
    dvec4 surfaceHighP = SurfaceRotationMatrix * OriginNormal;  
    //also calculate surface normals at this point
    dvec4 tangentHighP = SurfaceRotationMatrix * vec4(1.0, 0.0, 0.0, 1.0);
    dvec4 bitangentHighP = dvec4(cross(surfaceHighP.xyz, surfaceHighP.xyz), 1.0);

    // vvv I think this is the problem vvv
    // dvec4 surfaceHighP has to be cast to a vec3 (float) in order for the 
    // shader to compile
    double height = texture(bump_map, vec3(surfaceHighP.xyz)).x; 

    //set position by multiplying the normal by the distance the point should be from the surface
    worldSpacePosition.xyz = (PlanetRadius + height * TerrainHeightScalar) * OriginNormal.xyz; 

    //move the position back to the origin
    worldSpacePosition.y -= PlanetRadius;

    //offset y position by the player's height (both statements are split for best precision)
    worldSpacePosition.y -= PlayerLocation.y;

    //final position: multiply by projection matrix
    gl_Position = MVP * vec4(worldSpacePosition);
}

OpenGL's texture() function only seems to take float parameters as an input. This makes me believe that this stepping is a result of the cast down to a lower-precision vector, where the program cannot distinguish the tiny change in angle between each grid position. At first I thought it might have been a texture precision error, so I tried switching the heightmap texture to an 8-bit copy but I didn't see any difference in step size or quantity. The texture filtering is linear, so the heights should be smoothly blended between one another, and at large scales big landforms like mountains and hills seem to have no associated problems.

Can anyone confirm that this cast in the texture() call is the reason for the stepping? If so, is there a way to pass high-precision numbers into a GLSL texture call, or, failing that, a solution to this problem that doesn't involve a complete redesign? I was thinking about maybe getting a weighted average of each neighboring "step", and then linearly interpolating between them, averaging between the areas where the float breaks down. Would something like that be possible, or is there a better alternative?

\$\endgroup\$
2
  • \$\begingroup\$ This sounds like it might be related to this previous question, "Low-Quality Bilinear Sampling". You might also find this recent question on generating smooth terrain on planetary scales useful. \$\endgroup\$
    – DMGregory
    Commented Feb 11, 2019 at 3:45
  • 1
    \$\begingroup\$ Hey @DMGregory, It turns out that your question "Low Quality Bilinear Sampling" was incredibly useful and the solution to my problem - the problem was the poor hardware interpolation rather than numerical precision. I just switched my own custom linear interpolation algorithm using doubles and the problem has gone away completely at real scale. I don't think I have enough reputation yet to answer my own question, so if you want to submit your own answer I will mark it as correct. \$\endgroup\$
    – RedRuin
    Commented Feb 20, 2019 at 23:09

1 Answer 1

0
\$\begingroup\$

You should divide the heightmap into manageable chunks, use precise integer math on the CPU to decide which one to use, load it in the shader and use local coordinates when querying the texture.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged .