Advertisement

Make normals from terrain in spherical form.

Started by January 16, 2025 06:10 PM
4 comments, last by Aressera 14 hours, 2 minutes ago

Folks,

I know how to make normals from terrain in flat form. However, I want to learn how to make normals in spherical form from terrain. I tried to make normals but it resulted so funny. Shadows looks shining from south instead of from sun.

dy = radius * pi/(nlat*grid);
dz = radius * pi2*cos(lat) / (nlng * grid);
dydz = dy*dz;

nml = glm::dvec3(2.0*dydz,
      dz*escale*(elev[en-ELEV_STRIDE]-elev[en+ELEV_STRIDE]),
      dy*escale*(elev[en-1]-elev[en+1]));
      
nx1 = nml.x*clat - nml.y*-slat;
ny1 = nml.x*slat + nml.y*clat;
nz1 = nml.z;
      

That is for left-handed coordinates (DirectX). I am now figuring out for right-handed coordinates (OpenGL)

nml = glm::dvec3(dx*escale*(elev[en-1]-elev[en+1])
      dy*escale*(elev[en-ELEV_STRIDE]-elev[en+ELEV_STRIDE]),
      2.0*dydz);

I googled it but it did not help so much for mapping terrain in spherical form.

Thanks,
Tim

Should be relatively easy to derive given the implicit definition of a sphere. Spherical coordinates are just an extension of polar coordinates( derived from the implicit definition of a circle within a plane.)

https://en.wikipedia.org/wiki/Spherical_coordinate_system : see the section on [Cartesian coordinates]

Given sphere centered about the origin. The normalized vector from the origin to any point of the sphere will give the normal also at that point of the sphere. Too lazy to draw a diagram right now, but if you start with a circle and then place a string or a pencil from the center of that circle to the edge and then elevate the string/pencil/ keeping the point at the center fix, you should be able to start to visualize it( I find that it helps when it comes to these sort of issue).

Advertisement

Sword7 said:
However, I want to learn how to make normals in spherical form from terrain.

I guess you use a heightmap to add terrain to a planetary sphere?
If so, it matters how you do the mapping from 2D texture space to the 3D surface of the sphere. Cube mapping would work well for example.

Anyway, what you need for normals is a tangent frame on the sphere, where up represents sphere normal which is trivial, but front and right match the U and V directions of your heightmap texture space, which isn't trivial and depends on the mapping mentioned above.

Once you have this tangent frame, you can calculate the normal like before on the plane, where the plane means texture space.
Then you can transform the normal to world space using the tangent frame.

Maybe this all sounds a bit complicated, so here is an illustation removing one dimension:

Maybe you're familiar to the concept of a tangent space from implementing normal mapping, otherwise looking this up should help.

cgrant said:
Spherical coordinates are just an extension of polar coordinates

Yeah, maybe that's the mapping he is using. Example would be the cojmon world map we use to map earth.
But that's a very bad mapping if so. It has extreme texture distortion at north and south poles, so a game would have robustness problems or at least visual glitches in those areas.
Cube mapping would avoid this problem but is more complicated.

Here is what I do in my planet generator, which uses a “cube sphere” representation of the terrain, where the terrain exists on the faces of a cube which is projected onto the sphere.

First, you want to do some warping using the tan() function so that the projection from cube to sphere has a more uniform distribution of points. This paper describes how to do that. The warping function is applied to the UV coordinates on each cube face (in range [-1,1]) before the projection to sphere. This compresses near the center of the face and expands at the corners, to counteract the distortion of projection from cube to sphere. For each point on a cube face, apply the warping function to the 2D uniform grid and then make it 3D by transforming from face UV to XYZ point on the [-1,1] cube.

Then, you need to calculate the gradient of the terrain elevation data in the face UV space (ignoring tan() warping). This should give you a U and V derivative for each point on the face. To make this work across face boundaries you need to first copy the cube face elevation data to a larger array with margins of at least 1 vertex (with margins copied in from neighboring faces). This can be difficult to do correctly because the faces are not all oriented the same way, there are 24 cases to handle (both sides of 12 edges).

Then, given the gradient in UV space, and warped XYZ positions on the cube faces, you can calculate the tangent basis and normals similar to how JoeJ describes.

Vector3f cubeXYZ = ... // warped position on [-1,1] cube
Vector3f sphereXYZ = normalize( cubeXYZ );

// Calculate vertex position using elevation
Vector3f vertexPosition = sphereXYZ * (planetRadius + elevation);

// The tangent and bitangent vectors on the cube face (constant for all points on face)
Vector3f cubeTangentXYZ = ... // e.g. (1, 0, 0) for cube +Z face
Vector3f cubeBitangentXYZ = ... // e.g. (0, 1, 0) for cube +Z face

// Calculate orthonormal basis matrix for local tangent space.
Vector3f basisN = sphereXYZ; // sphere normal
Vector3f basisB = normalize( cross( basisN, cubeTangentXYZ ) ); // bitangent
Vector3f basisT = cross( basisB, basisN ); // tangent

// Determine local tangent space of the terrain, assuming it is flat
Vector3f terrainT( 1.0f, 0.0f, gradientU );
Vector3f terrainB( 0.0f, 1.0f, gradientV );
Vector3f terrainN = normalize( cross( terrainT, terrainB ) );

// Rotate terrain normal to planet-relative space to get final normal.
Vector3f vertexNormal = basisT*terrainN.x + basisB*terrainN.y + basisZ*terrainN.z;

You will definitely want to reference this image which defines the orientations of all faces in a cube map:

Note how there is a 90 degree rotation on the X/Y face edges, and a 180 degree rotation on the Y+/Z- and Y-/Z- edges. You will need to correctly handle those rotations for smooth gradient calculation on the sphere.

Advertisement