nythrix's picture

Planetary Scale Terrain

Hi!
Has anyone experience in planetary scale terrain rendering?
My problem is float precision. Earth's semi major axis is 6378137m which is 7 digits. On top of that I need at least centimeter precision on the surface so that's another 2 digits. 9 valid digits just don't fit into float. For example the camera jumps wildly instead of orbiting smoothly around the planet (as for double).

What do you suggest apart from using double?

P.S.: I couldn't find a good place for this post, so I dropped it here. Feel free to move it around.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Kamujin's picture

You are fighting 2 problems both of which I have imperfect answers to.

1) The loss of precision in your floats. I deal with this by internally tracking all locations in vectors made from decimal types. I then covert all locations into a difference from a point of view of the observer and cast the results down to floats. From these I build my modelView matricies.

To simplify consider this example...
observer location x=0, y=0, z=1,000,000,000,000,000,000,000,000
object location x=0, y=0, z=1,000,000,000,000,000,000,000,005.02

If you convert these z coordinates to a float, they would both be equal, but if you subtract them first and consider your observer as the origin the the coordinates look like this...
observer location x=0, y=0, z=0
object location x=0, y=0, z=5.02

This can easily fit into a float.
You still lose precision as you view things that are very far away, but they don't tend to "jump" on your screen due to perspective.

2) Planetary rendering. High detail when close transitioning to low detail when in orbit. I solve this by dynamically generating the planet mesh in real-time based on the observer's location.

http://www.robotechmmo.com/index.php?topic=screenshots
If you look at some of the screenshots from around Feb 2007, you can see the windows client which has the most complete implementation of the above described techniques. It allows for smooth transitions from the ground to orbit without "zoning".

nythrix's picture

Hi and thanks for the response.

[precision loss]
This is an interesting technique. Basically, you're faking camera-to-object translation by sending the deltas for rendering, am I right? I'm a bit concerned about speed, though. Do you recalculate all the vertices?

[LOD]
I'm already playing with terrain LOD even though the engine will never face ingame high orbit view. I'm going to restrict my editor camera below 20km and game camera goes even lower. I'm just testing extreme cases right now.

Very cool project you're building there Kamujin!

Kamujin's picture

[precision loss]

No you don't have to do it per vertex. Your vertices should be in model space. You can compute your modelViewProjection matrix with the "observer centric" method described above without any significant impact to render time. In short, you can take the vertices from model space all the way into observer centric view space, then into projection space with a single well constructed matrix.

Its kind of like relativity. For example, the origin of my solar system would logically be the center of the sun. Assume my observer at standing on Pluto. When its time to compute my modelViewProject matrix, I compute the location of everything as if it the origin were my observer standing on Pluto. This leaves the highest amount of precision available to objects that are near my observer standing on Pluto. The increased workload is like 3 decimal subtractions and 3 decimal to float type casts per model (not per vertex).

There is also a space compression technique that I use to reduce z-fighting.

Maybe some code will help. Here is my DecVector3 structure.

namespace RelativeSpace
{
    public struct DecVector3 : IEquatable<DecVector3>
    {
        public readonly decimal X;
        public readonly decimal Y;
        public readonly decimal Z;
 
        public static readonly DecVector3 origin = new DecVector3();
 
		public const float compressionMinDistance = Astro.JupiterRadius * 3.0f;
 
        public const float preCompressionMaxDistance = Astro.PlutoDistance;
		public const float preCompressionRange = preCompressionMaxDistance - compressionMinDistance;
 
        public const float postCompressionMaxDistance = Astro.JupiterRadius * 10.0f;
		public const float postCompressedRange = postCompressionMaxDistance - compressionMinDistance;
 
        public DecVector3(decimal X, decimal Y, decimal Z)
        {
            this.X = X;
            this.Y = Y;
            this.Z = Z;
        }
 
        public DecVector3(double X, double Y, double Z)
        {
            this.X = (decimal)X;
            this.Y = (decimal)Y;
            this.Z = (decimal)Z;
        }
 
        public DecVector3(DecVector3 location)
        {
            this.X = location.X;
            this.Y = location.Y;
            this.Z = location.Z;
        }
 
        public DecVector3(Vector3 location)
        {
            this.X = (decimal)location.X;
            this.Y = (decimal)location.Y;
            this.Z = (decimal)location.Z;
        }
 
        public override string ToString()
        {
            return string.Format("{0} {1} {2}", X, Y, Z);
        }
 
        public static DecVector3 Parse(string value)
        {
            string[] array = value.Split(' ');
            return new DecVector3(decimal.Parse(array[0]), decimal.Parse(array[1]), decimal.Parse(array[2]));
        }
 
        public Matrix4 GetTranslationMatrix()
        {
			return Matrix4.Translation((float)(X - Camera.PointOfView.Position.X), (float)(Y - Camera.PointOfView.Position.Y), (float)(Z - Camera.PointOfView.Position.Z));
        }
 
        public Matrix4 GetCompressMatrix()
        {
			double distance = Distance(Camera.PointOfView.Position);
			if(distance <= compressionMinDistance) return Matrix4.Identity;
 
			double normalizedDistance = (distance - compressionMinDistance) / preCompressionRange;
			double compressedDistance = compressionMinDistance + (normalizedDistance * postCompressedRange);
 
			return Matrix4.Scale((float)(compressedDistance / distance));				
        }
 
        public Matrix4 GetWorldRelativeMatrix()
        {
            return GetTranslationMatrix();
        }
 
		public Matrix4 GetCompressedWorldRelativeMatrix()
		{
			return GetTranslationMatrix() * GetCompressMatrix();
		}
 
        public double Distance(DecVector3 other)
        {
            return Math.Sqrt((double)(((other.X - X) * (other.X - X)) + ((other.Y - Y) * (other.Y - Y)) + ((other.Z - Z) * (other.Z - Z))));
        }
 
        public double Length
        {
            get
            {
                return Math.Sqrt((double)((X * X) + (Y * Y) + (Z * Z)));
            }
        }
 
        public Vector3 FloatVector
        {
            get
            {
                return new Vector3((float)X, (float)Y, (float)Z);
            }
        }
 
        public static DecVector3 Lerp(DecVector3 v0, DecVector3 v1, decimal interpolationCoef)
        {
            return new DecVector3
            (
            v0.X + (interpolationCoef * (v1.X - v0.X)),
            v0.Y + (interpolationCoef * (v1.Y - v0.Y)),
            v0.Z + (interpolationCoef * (v1.Z - v0.Z))
            );
        }
 
        public static DecVector3 Normalize(DecVector3 vec)
        {
            decimal magnitude = (decimal)Math.Pow((double)((vec.X * vec.X) + (vec.Y * vec.Y) + (vec.Z * vec.Z)), 0.5);
            return new DecVector3(vec.X / magnitude, vec.Y / magnitude, vec.Z / magnitude);
        }
 
		public bool Equals(DecVector3 other)
		{
			return this == other;
		}
 
		public static bool operator ==(DecVector3 left, DecVector3 right)
		{
			return left.X == right.X
			&& left.Y == right.Y
			&& left.Z == right.Z;
		}
 
		public static bool operator !=(DecVector3 left, DecVector3 right)
		{
			return left.X != right.X
			|| left.Y != right.Y
			|| left.Z != right.Z;
		}
 
		public override bool Equals (object o)
		{
			if (o is DecVector3 == false) return false;
			return this == (DecVector3)o;
		}
 
		public override int GetHashCode ()
		{
			return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode();
		}
 
        public static DecVector3 operator *(DecVector3 vec, decimal value)
        {
            return new DecVector3(vec.X * value, vec.Y * value, vec.Z * value);
        }
 
        public static DecVector3 operator /(DecVector3 vec, decimal value)
        {
            return new DecVector3(vec.X / value, vec.Y / value, vec.Z / value);
        }
 
        public static DecVector3 operator +(DecVector3 left, DecVector3 right)
        {
            return new DecVector3(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
        }
 
        public static DecVector3 operator +(DecVector3 left, Vector3 right)
        {
            return new DecVector3(left.X + (decimal)right.X, left.Y + (decimal)right.Y, left.Z + (decimal)right.Z);
        }
 
        public static DecVector3 operator -(DecVector3 left, DecVector3 right)
        {
            return new DecVector3(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
        }
 
        public DecVector3 GetDirection(DecVector3 dest)
        {
            return DecVector3.Normalize(new DecVector3(dest.X - X, dest.Y - Y, dest.Z - Z));
        }
 
        public void YawPitch(DecVector3 loc, out double yaw, out double pitch)
        {
            double diffX = (double)(loc.X - X);
            double diffY = (double)(loc.Y - Y);
            double diffZ = (double)(loc.Z - Z);
            pitch = Math.Atan2(-diffY, Math.Sqrt(diffX * diffX + diffZ * diffZ));
            yaw = Math.Atan2(diffX, diffZ);
        }
 
        public Quaternion PointAt(DecVector3 loc)
        {
            double diffX = (double)(loc.X - X);
            double diffY = (double)(loc.Y - Y);
            double diffZ = (double)(loc.Z - Z);
            double pitch = Math.Atan2(-diffY, Math.Sqrt(diffX * diffX + diffZ * diffZ));
            double yaw = Math.Atan2(diffX, diffZ);
 
			return Quaternion.FromAxisAngle(Vector3.UnitY, (float)yaw) * Quaternion.FromAxisAngle(Vector3.UnitX, (float)pitch);			
        }
 
        public double DotProduct(DecVector3 other)
        {
            return (double)((X * other.X) + (Y * other.Y) + (Z * other.Z));
        }
 
        public Vector3 CrossProduct(DecVector3 B)
        {
            return new Vector3((float)((Y * B.Z) - (B.Y * Z)), (float)((Z * B.X) - (B.Z * X)), (float)((X * B.Y) - (B.X * Y)));
        }
    }
}
nythrix's picture

There you go! Code worth a million words :-D
Right now I'm rewriting my terrain code. Since the planet (or it's visible half) will never be rendered at once I should be able to fit into float after all.

Thank you for your time!

hshutao's picture

hi, nythrix

http://code.google.com/p/renderterrain. maybe helpful
would you contribut source code?

hshutao

nythrix's picture


would you contribut source code?

I'm afraid my terrain code looks as good as my bedroom right now. None of them can be shown to the public. My real life takes up too much time so please be patient.

You can have a look here for details on what I'm implementing.