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:
However, I want to scale this up to the size of the actual moon. Unfortunately, when I do so, stepping artifacts appear:
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?