Planet rendering issues

Started by
4 comments, last by Hawkblood 7 years ago

Hello fellow game programmers,

I'm currently working on a planet rendering application.

I am using the famous 6 quadtree method, where each face is made of an adaptive quadtree.

I'm using only one VBO for the grid and around 8 IBOs for stitching the chunks of different LOD.

The problem i'm currently having is that whenever i get close to the surface everything starts to jiggle so i assume that i'm having some floating point precision issues.

The thing is, i can't find a way out of this. My planets are really huge (Earth huge) and i don't want to give up on that.

These are the steps that i'm following to create my planet:

1. Create a cube out of 6 grids (the grids are managed by the quadtree)

2. The GPU rotates, scales and translates the chunks and then normalizes everything to inflate the cube into a sphere

3. Multiply the vertex with the scale matrix to get it to the desired size.

I believe the problem is with scaling the children chunks

For instance, if the size of the root chunk is 6371000.0, it's children are scaled down to 3185500.0 and translated to NW, NE, SE and SW.

Maybe for the ground level chunks, the size is so small that floats start going crazy.

Vertex Shader code.


vec3 roundChunkVertex = normalize((r_ChunkTransform * vec4(r_Vertex, 1.0)).xyz);
gl_Position = r_Camera * r_Scale * vec4(roundChunkVertex, 1.0);

r_ChunkTransform is a matrix that rotates, translates and scales the chunk.


r_ChunkTransform = R * T * S; 

Where R is the rotation matrix.


switch (side)
    {
        case TS_TOP:
            R = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
            break;
        case TS_BOTTOM:
            R = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(-1.0f, 0.0f, 0.0f));
            break;
        case TS_LEFT:
            R = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(0.0f, -1.0f, 0.0f));
            break;
        case TS_RIGHT:
            R = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
            break;
        case TS_FRONT:
            R = glm::rotate(glm::mat4(1.0f), glm::radians(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
            break;
        case TS_BACK:
            R = glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0.0f, 1.0f, 0.0f));
            break;
    }

T is the translation matrix that "offsets" the chunk within it's parent.


glm::dmat4 T = glm::translate(glm::mat4(1.0f), glm::vec3(offset, 0.0f));

S scales the grid on the x and y axes.


glm::dmat4 S = glm::scale(glm::mat4(1.0f), glm::vec3(scalar, scalar, 1.0f));

This is the split function


void TerrainNode::split()
{
    this->leaf[NW] = new TerrainNode(this, side, NW, glm::vec2(-.5, 0.5) * (float)scalar + offset, radius, scalar / 2.0);
    this->leaf[NE] = new TerrainNode(this, side, NE, glm::vec2(0.5, 0.5) * (float)scalar + offset, radius, scalar / 2.0);
    this->leaf[SE] = new TerrainNode(this, side, SE, glm::vec2(0.5, -.5) * (float)scalar + offset, radius, scalar / 2.0);
    this->leaf[SW] = new TerrainNode(this, side, SW, glm::vec2(-.5, -.5) * (float)scalar + offset, radius, scalar / 2.0);
        
    this->isSplit = true;
}

I attached a screenshot of the issue.

Thank you.

Advertisement

The thing is, i can't find a way out of this. My planets are really huge (Earth huge) and i don't want to give up on that. ... Maybe for the ground level chunks, the size is so small that floats start going crazy.

This is one of the many reasons why nearly no games do it. The precision issues are one of many deal-breakers. Floating point gives you roughly six decimal digits of precision, seven if you limit yourself to one-way conversions. The radius of the earth is about six million meters, give or take, which you point out.

For a single-precision floating point number in the range from about 4 million to about 16 million, the smallest increment is a full integer.

That is demonstrated by stair-stepping when you are look at anything close to those small sizes. In fact, it looks exactly like the stair-stepping you are showing in your image.

It is work and some people on the site have done it, but effectively you need to break your world not just into a spatial grid, but into multiple origins. I've read of approaches where they completely rebuild both the scale and the origin after traveling far enough, so you're always in a good range near the middle if floating point values.

There are other reasons many games avoid planetary scale, creating fun content is hard enough, and large open-world games struggle to fill an area equal to a real-world city. No games I know of have filled a real-world continent with compelling content. The biggest ones (WoW, GTA5, Dragon Age 3, Skyrim, etc) are still within a hundred square miles, some as low as twenty square miles.

It is work and some people on the site have done it, but effectively you need to break your world not just into a spatial grid, but into multiple origins. I've read of approaches where they completely rebuild both the scale and the origin after traveling far enough, so you're always in a good range near the middle if floating point values.

For the 90% solution, just calculate your vertex positions using doubles, and then make them local to the quad-tree chunk (by subtracting the chunk centre).

Each chunk will have plenty of precision even with single-precision floats (since the chunks are smaller at higher details), and the model-view-projection matrix concatenation causes a smaller and smaller range of depth values as the camera approaches the surface...

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Look into the multi-frustum technique used by Patrick Cozzi and the Cesium crew. Their book and presentations on terrain rendering are worth reading as well.

I've coded a planet visualization engine some years ago and I can say that it's possible to deal with floats.

You can use an 6378.0f value for earth radius and subtract the camera position to each render chunk in order to gain precision at near distances.

(just read swiftcoder... This solution is similar but using long doubles)

You can get around the problem by creating your own vector using "long double" values. This will mean you will need to create your own structure that handles the math that the vector structure uses and then modify your noise code to handle it. When you generate the vertex buffer for each LOD you will need to use a center value and add the offset values of each of the vertices to get a location for each vertex (using floats). The verts very far away will have the same problem as before, but they will be so far that the player can't see them. All the while preserving your noise generation precision.

This topic is closed to new replies.

Advertisement