Advertisement

Relative to Camera rendering.

Started by May 18, 2018 12:16 AM
3 comments, last by Mihumihu 6 years, 8 months ago

Hello fellow programmers,

For a couple of days now i've decided to build my own planet renderer just to see how floating point precision issues

can be tackled. As you probably imagine, i've quickly faced FPP issues when trying to render absurdly large planets.

 

I have used the classical quadtree LOD approach;

I've generated my grids with 33 vertices, (x: -1 to 1, y: -1 to 1, z = 0).

Each grid is managed by a TerrainNode class that, depending on the side it represents (top, bottom, left right, front, back),

creates a special rotation-translation matrix that moves and rotates the grid away from the origin so that when i finally

normalize all the vertices on my vertex shader i can get a perfect sphere.


T = glm::translate(glm::dmat4(1.0), glm::dvec3(0.0, 0.0, 1.0));
R = glm::rotate(glm::dmat4(1.0), glm::radians(180.0), glm::dvec3(1.0, 0.0, 0.0));
sides[0] = new TerrainNode(1.0, radius, T * R, glm::dvec2(0.0, 0.0), new TerrainTile(1.0, SIDE_FRONT));
	
T = glm::translate(glm::dmat4(1.0), glm::dvec3(0.0, 0.0, -1.0));
R = glm::rotate(glm::dmat4(1.0), glm::radians(0.0), glm::dvec3(1.0, 0.0, 0.0));
sides[1] = new TerrainNode(1.0, radius, R * T, glm::dvec2(0.0, 0.0), new TerrainTile(1.0, SIDE_BACK));

// So on and so forth for the rest of the sides

As you can see, for the front side grid, i rotate it 180 degrees to make it face the camera and push it towards the eye;

the back side is handled almost the same way only that i don't need to rotate it but simply push it away from the eye.

The same technique is applied for the rest of the faces (obviously, with the proper rotations / translations).

The matrix that result from the multiplication of R and T (in that particular order) is send to my vertex shader as `r_Grid'.


// spherify
vec3 V = normalize((r_Grid * vec4(r_Vertex, 1.0)).xyz);

gl_Position = r_ModelViewProjection * vec4(V, 1.0);

The `r_ModelViewProjection' matrix is generated on the CPU in this manner.


// No the most efficient way, but it works.
glm::dmat4 Camera::getMatrix() {
  	// Create the view matrix
  	// Roll, Yaw and Pitch are all quaternions.
	glm::dmat4 View = glm::toMat4(Roll) *  glm::toMat4(Pitch) *  glm::toMat4(Yaw);
  
  	// The model matrix is generated by translating in the oposite direction of the camera.
	glm::dmat4 Model = glm::translate(glm::dmat4(1.0), -Position);
  
  	// Projection = glm::perspective(fovY, aspect, zNear, zFar);
	// zNear = 0.1, zFar = 1.0995116e12
	return Projection * View * Model;
}

I managed to get rid of z-fighting by using a technique called Logarithmic Depth Buffer described in this article; it works amazingly well, no z-fighting at all, at least not visible.

Each frame i'm rendering each node by sending the generated matrices this way.


// set the r_ModelViewProjection uniform
// Sneak in the mRadiusMatrix which is a matrix that contains the radius of my planet.
Shader::setUniform(0, Camera::getInstance()->getMatrix() * mRadiusMatrix);

// set the r_Grid matrix uniform i created earlier.
Shader::setUniform(1, r_Grid);

grid->render();

My planet's radius is around 6400000.0 units, absurdly large, but that's what i really want to achieve;

Everything works well, the node's split and merge as you'd expect, however whenever i get close to the surface

of the planet the rounding errors start to kick in giving me that lovely stairs effect.

I've read that if i could render each grid relative to the camera i could get better precision on the surface, effectively

getting rid of those rounding errors.

 

My question is how can i achieve this relative to camera rendering in my scenario here?

I know that i have to do most of the work on the CPU with double, and that's exactly what i'm doing.

I only use double on the CPU side where i also do most of the matrix multiplications.

As you can see from my vertex shader i only do the usual r_ModelViewProjection * (some vertex coords).

 

Thank you for your suggestions!

 


r_Grid * vec4(r_Vertex, 1.0)

is what's giving you grief.  In order to avoid fp32 precision errors you can never allow an fp32 calculation to contain an excessively large value.


r_ModelViewProjection * vec4(V, 1.0);

Is taking an already stairsteppy V and transforming it to projection space.  What you need is a GridModelViewProjection matrix that is calculated as doubles before being converted to floats.  How to pull off the normalize trick after that is a good question...

Bikeshedding bonus:  Personally, I prefer to name my mats in the style of ProjectionFromGrid rather than GridModelViewProjection.  That way it connects nicely when I write out "projectionPosition = ProjectionFromGrid * gridPosition"

Advertisement
On 5/23/2018 at 8:48 PM, corysama said:


r_Grid * vec4(r_Vertex, 1.0)

is what's giving you grief.  In order to avoid fp32 precision errors you can never allow an fp32 calculation to contain an excessively large value.



r_ModelViewProjection * vec4(V, 1.0);

Is taking an already stairsteppy V and transforming it to projection space.  What you need is a GridModelViewProjection matrix that is calculated as doubles before being converted to floats.  How to pull off the normalize trick after that is a good question...

Bikeshedding bonus:  Personally, I prefer to name my mats in the style of ProjectionFromGrid rather than GridModelViewProjection.  That way it connects nicely when I write out "projectionPosition = ProjectionFromGrid * gridPosition"

First of all, thank you for taking your time to read my long post; second of all, it worked, even though i had to rethink a few things.

I quit normalizing the vertices on the GPU, and i started creating a VBO for each node's grid on the CPU, projecting each vertex on an imaginary sphere before uploading everything in the VBO. This way, it was easy to apply the method you described and now i can render absurdly large planets with no visible fp32 precision problems at all!

It may not be the best solution, however with some caching involved, i believe i can optimize it a little.

Thank you again!

dawn.png

What about to divide each vertex of your grid(s) to 2xf32? And then to make some tricks in a vertex shader...I don't know...to improve precision?

This topic is closed to new replies.

Advertisement