Sign in to follow this  

Procedural Planet - GPU normal map artifacts

This topic is 1170 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,

 

I'm going to share some technical background first before I describe my normal map problem.

 

The planet is generated using cube to sphere mapping. I have QuadTree for each cube face to handle LOD and I have 18x18 quad mesh in each patch/node. Everything on the CPU side is in double float precision.

 

For each patch, I pass the patch corners (cube space coordinates) to the position map shader. The shader generates a position map by transforming each texel to cube space and then to object sphere space before it enters as an input to the noise function. Once I get the height value for a texel, I then store the texel's (object sphere space) * height in the RGB channels. So basically each texel in the position map has its sphere object space XYZ coordinates.

 

Here's the position map shader:

// Patch coordinates in cube space
vec3 xNW = v0;
vec3 xNE = v1;
vec3 xSW = v2;


// Get the direction, from the origin (top-left) to the end vector (bottom-right).
vec3 xDirection = xNE - xNW;
vec3 zDirection = xSW - xNW;


// Scale the distance by the texture coordinate (which is between 0 and 1).
xDirection *= vTextureCoord.x;
zDirection *= vTextureCoord.y;


// Get current position on the cube face patch
vec3 pos = xNW + xDirection + zDirection;


// Map the cube coordinate to sphere
pos = getSpherePos(pos);


float height = fBm3(pos, 0.05, 3.0, 0.7, 1.0, 1.0);
gl_FragColor = vec4(pos * height, 1.0);

Then in the main shader I generate normals by sampling the position map. Here's the shader:

vec3 getNormal() {
	vec2 tpos = vPositionMapUV;

	float one = 1.0 / 128.0;

	vec3 currentPos = getPosMap(vec2(tpos.x, tpos.y));


	vec3 e0 = getPosMap(vec2(tpos.x - one, tpos.y)) - currentPos;
	vec3 e2 = getPosMap(vec2(tpos.x , tpos.y - one)) - currentPos;

	vec3 n0 = cross(e0, e2);

	return normalize(n0);
}

When I reach around depth 15 I start to see artifacts on the normal map, lots of noise and stripes. Here's a picture:

iGlA4je.png

 

 

I think the problem is because I'm out of double float precision in the GPU, the vertices are probably like 0.00000005 units apart from each other. I have no clue on how to overcome the issue. 

 

Any help is greatly appreciated.

 

Thanks!

Share this post


Link to post
Share on other sites

Now I'm just thinking out loud about one possible solution. I have one idea in mind that I would like to share and I would love to get feedback.

 

So, at depth ~12 I would start re-using the normal map from depth 12 for all the children. Then for the children I would interpolate the low res normal map to keep the main features that were seen from space (big mountains and valleys, etc). And another thing I would do is generate local noise for the children, which is seeded by local factors, such as height, slope, etc which is not dependent on the same planet coordinate system, but rather some local coordinate system to avoid the GPU precision problem.

 

Has anyone done anything similar before? Isn't this how Outerra does it? They have some pre-defined normal map and to make it interesting at low level, they generate local noise.

 

Thanks!

Share this post


Link to post
Share on other sites
Hello raRaRa,
 
We are on the same boat ! I'm working on a procedural planet engine, too.
My process is close to yours, as I'm using quadtree and having exactly the same artifacts starting LOD 15.
I did not need to post a screenshot because yours show exactly the same problem.
 
We can exchange some stuff for resolving this.
 
Some differences between our projects. I'm using 6 grids (one for each faces of the cube). Coordinates are, for the moment only in single float precision.
The grid size varies on parameters, but I'm using often 256x256.
I did not pass the 4 corners to the shaders, only an offset and a scale, based on the node to render.
The grids are allways the same in memory, they change size and position based on the node.
I'm using an heightmap and a normalmap for each node, which are computed one time per node, between frames, in a "work queue".
LOD is based on CDLOD algorithm, using for each node, a parent normal map for morphing purpose.
 
I thought, like you, using a local noise for high LOD levels, but did not try for the moment. 

My doubts are about jittering (end of float precision), and I'll try to render grids based on eye position rather than the planet center. Like descibed in this book : : http://www.virtualglobebook.com/, did you try ?

 

Sorry for my poor english and good luck ;)

Edited by chrisendymion

Share this post


Link to post
Share on other sites

Your GLSL code shows that you use FP32, not FP64, so those artifacts are expected at LODs >= 15.

 

One solution is to use FP64 ( dvec3 ), however not all GPUs support all the operations you need for procedural noise.

 

In the I-Novae engine, I ended up using an hybrid solution: FP64 shader emulation. You can read more on it on this page:

https://thasler.com/blog/?p=93

 

For performance reasons many operations still work in FP32 ( only the inputs to the procedural function, ie. the planet position, is using double emulation ), but that alone significantly improves the precision on the lowest LOD levels. In my tests, I can go up to level 18 with that "trick".

 

Y.

Share this post


Link to post
Share on other sites

This topic is 1170 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this