Example of Procedural Planet generation?
Hi,
Does any one have share-able example of "Procedural Planet" generation script (pref. C#) or anything explains how game makers simulate "endless" space environment?
I want to toy with this technique to learn something new.
Thanks :)
This is an extremely broad topic. I doubt it that anyone would have a script (probably more than 1) to share. Generating planets would be more complex than just generating terrain since the planets are round.
Firstly you would have to generate the planets themselves, then the terrain on the planet using perlin noise or some other algorithm, then textures, then LOD $$anonymous$$odels, then occlusion culling of some sort and this and that.
If you like coding and all that comes with it I suggest you look around and get familiar with the concepts like instantiating objects, generating terrain and maybe clouds. Take it one thing at a time and you won't get too overwhelmed. Focus on the task at hand.
Good luck!
I am interested in Procedural Planets for my game as well would like to use my current models rather than making whole new models though
Answer by Uncasid · Oct 05, 2012 at 04:44 AM
Last edit for this, but it should be placed at the top.. someone has done what I was suggesting here, and when you purchase, they give you the source: http://u3d.as/content/vander-imerso-nunes/etherea1-ethereal-planets/2Ph
well.. lets see.. i have done this sort of thinng before..
i used fbm for generating height map on the fly, used spherical harmonics to get the map to look correct at the poles, and used geoclipmapping to dothe planet.. now, when i added streets and such i used l-systems.
details.. i generated several textures for the planets, 1 for height, 1 for diffuse, 1 for normals, then some via masks for roads..
geoclipmapping, i tried to do spherical geo clipmapping, but that was a bit over my head and just couldn't get it to work. what this does is take a center point that is under your camera and make it maximum detail, then at a specific distance you have another level that is less detail which merges with the points from full detail section. you do this for 5 or so levels each with progressively less detail until you hit a flat surface. now the terrain data is stored in a toroidal array, which means you move all the verts / faces, NOT the camera (called camera space).
now, to tell you the truth.. it is a LOT of work getting this done.. google the specific words there and it will get you on your way :). also to note, i didn't do this in unity. if you have a hard time finding the stuff i am talking about, write back here and i will give you the links (i am on ipad and too lazy to get all the details for you ;) )
Edit (Not on the iPad anymore and not as lazy): the first one is on clipmaps, and this is a very good resource on how to do them. He talks about a few different kinds of clipmaps, and how to do them on the GPU http://www.cg.tuwien.ac.at/research/publications/2008/fruehstueck-2008-gpu/fruehstueck-2008-gpu-paper.pdf (Proof of concept here, it is possible in unity: http://www.youtube.com/watch?index=5&list=UU3twJEOXuPUgYkhIn-4TPug&feature=plcp&hl=id&v=0OS9Dh94x1c&gl=ID)
Now for Fractal Brownian Motion (think of it as an advanced version of perlin noise), this link is quite math intensive, but isn't as hard as it looks. The think about fbm is that it has octaves, which gives the impression that you are zooming in. http://harrisd.net/papers/Id/MandelbrotVanNess1968SIAM.pdf (this is fbm implemented) http://www.allegro.cc/forums/thread/604600
Now for l-systems.. These are actually quite easy to understand as well. The tree tool in unity already uses this type of system :) http://www.cs.purdue.edu/homes/aliaga/cs197-10/papers/p_cities.pdf
crap, had to edit again, forgot the spherical harmonics part: Spherical harmonics is used to bend textures at the poles to prevent a texture artifact that comes with texturing a sphere (http://jwhigham.files.wordpress.com/2010/05/uniformcloudsplanet.jpg)
as you can see, this is sort of a square peg in a round hole sort of problem.. You need to spherize your square texture to make it work. There are a few different methods to make this work, like the one used to fix the picture above: http://jwhigham.wordpress.com/2010/05/21/clouding-the-issue/ The problem you have is that you need to do this on the fly, as you are generating planets on the fly using a seed (I assume you are going for a galaxy, correct?) here is the math for you: http://www.ppsloan.org/publications/StupidSH36.pdf
now, I will be the first to admit, this took me a long while to understand, so here is a start on a cpu based implementation of the perlin noise + SH (can't give you everything, else you wouldn't learn :D) The code below was done in XNA, but the theory is the same no matter which engine you use
/* --------------------------------------------------------------------------------------------
* - perlinPlanet
* --------------------------------------------------------------------------------------------
* Built by: Uncasid
* Last Updated: 10/13/10 4:00pm MST
* Description:
* This class is used to build a planet based on perlin noise.
*
* Requirements:
* + Content (mesh) for sphere
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Planet
{
class cpuPerlinPlanet : GameObjects._Prototypes.aBaseRenderableWorldObject
{
#region Fields
//Old planet
//private Primitives.cPrimSphere PlanetMesh;
private Model PlanetMesh;
private Effect shader;
private Effect shaderPerlin;
private Effect DepthAndNormalsEffect;
//private Texture2D PlanetTex;
private Texture2D PlanetNrmTex;
private Texture2D PlanetSpecTex;
private Texture2D CloudTex;
private Texture2D gTexture;
private cAtmosphere Atmosphere;
private float PlanetSize;
//private DepthStencilBuffer tmpBuffer;
//private DepthStencilBuffer cachedBuffer;
private Controllers.Lights.LightTypes.genericLight _Light;
#endregion
private int Seed;
//Texture2D heightTexture_;
//Texture2D colorTexture_;
Texture2D normalTexture_;
Texture2D WaterTex, GrassTex, RockTex, SnowTex, WaterNormTex, GrassNormTex, RockNormTex, SnowNormTex;
Texture2D AlphaMap;
const int MapShift = 11;
const int MapSize = (1 << MapShift);
const int MapAnd = (MapSize - 1);
const float SeaThreshold = 0.8f;
const float HillsThreshold = 0.85f;
const float MountainThreshold = 0.9f;
const float SnowThreshold = 0.999f;
const float NormalMapStrength = 4.0f;
public float rotationSpeedX { get; set; }
public float rotationSpeedY { get; set; }
public float rotationSpeedZ { get; set; }
/// <summary>
/// Constructs a new Planet,
/// with the specified size and tessellation level.
/// </summary>
public cpuPerlinPlanet(int seed, Controllers.Lights.LightTypes.genericLight Light, float diameter, cAtmosphere aAtmosphere)
: base()
{
//, Game game, GraphicsDevice GraphicsDevice
Seed = seed;
//_game = game;
PlanetSize = diameter;
Atmosphere = aAtmosphere;
//_GraphicsDevice = GraphicsDevice;
_Light = Light;
position = new MathLib.Vector3_64(0d, 0d, 0d);
rotation = new Quaternion(0, 0, 0, 1);
scale = new Vector3(diameter, diameter, diameter);
}
/// <summary>
/// Load the content
/// </summary>
public override void LoadContent()
{
// Create the perlin texture for the planet
GenerateTextures(Seed);
shader = GameEngine.mContentManager.Load<Effect>("Content/Shaders/Mesh/PlanetShader");
DepthAndNormalsEffect = GameEngine.mContentManager.Load<Effect>("Content/Shaders/Mesh/PlanetNormalsDepth");
//PlanetTex = colorTexture_;
PlanetNrmTex = normalTexture_;
PlanetSpecTex = GameEngine.mContentManager.Load<Texture2D>("Content/Textures/Planet/earthmap10k_SPEC");
WaterTex = GameEngine.mContentManager.Load<Texture2D>("Content/Textures/Planet/Generic/EarthWater");
GrassTex = GameEngine.mContentManager.Load<Texture2D>("Content/Textures/Planet/Generic/EarthGrass");
RockTex = GameEngine.mContentManager.Load<Texture2D>("Content/Textures/Planet/Generic/EarthRock");
SnowTex = GameEngine.mContentManager.Load<Texture2D>("Content/Textures/Planet/Generic/EarthSnow");
WaterNormTex = GameEngine.mContentManager.Load<Texture2D>("Content/Textures/Planet/Generic/EarthWater_NRM");
GrassNormTex = GameEngine.mContentManager.Load<Texture2D>("Content/Textures/Planet/Generic/EarthGrass_NRM");
RockNormTex = GameEngine.mContentManager.Load<Texture2D>("Content/Textures/Planet/Generic/EarthRock_NRM");
SnowNormTex = GameEngine.mContentManager.Load<Texture2D>("Content/Textures/Planet/Generic/EarthSnow_NRM");
PlanetMesh = GameEngine.mContentManager.Load<Model>("Content/Models/Planet50k");
CloudTex = Atmosphere.CloudTex;
gTexture = Atmosphere.texture;
// Set the depth buffer
//this.cachedBuffer = new DepthStencilBuffer(shader.GraphicsDevice, Core.getInstance().GraphicsDevice.Viewport.Width, Core.getInstance().GraphicsDevice.Viewport.Height, DepthFormat.Depth16);
}
/// <summary>
/// Generate the textures for the perlin planet
/// </summary>
/// <param name="seed"></param>
private void GenerateTextures(int seed)
{
Random rand = new Random(seed);
// generate a height map
float[] h = new float[MapSize * MapSize];
// use midpoint displacement in a breadth-first manner
float distance = 1.0f;
int countdown = 2;
for (int generation = 0; generation <= MapShift; ++generation)
{
// for each generation, perturb the lower-right corner of successively
// smaller divided squares
int add = (1 << (MapShift - generation));
for (int y = add; y <= MapSize; y += add)
{
for (int x = add; x <= MapSize; x += add)
{
h[((y & MapAnd) * MapSize) + (x & MapAnd)] += (generation == 0) ? 0.5f : ((float)(rand.NextDouble() - 0.5) * distance);
}
}
if (generation < MapShift)
{
System.Diagnostics.Debug.Assert(add > 1);
int off = add >> 1;
// average the edges, and the midpoint of the square
for (int y = 0; y < MapSize; y += add)
{
for (int x = 0; x < MapSize; x += add)
{
float h00 = h[((y & MapAnd) * MapSize) + (x & MapAnd)];
float h01 = h[((y & MapAnd) * MapSize) + ((x + add) & MapAnd)];
float h10 = h[(((y + add) & MapAnd) * MapSize) + (x & MapAnd)];
float h11 = h[(((y + add) & MapAnd) * MapSize) + ((x + add) & MapAnd)];
h[((y & MapAnd) * MapSize) + ((x + off) & MapAnd)] = (h00 + h01) * 0.5f;
h[(((y + off) & MapAnd) * MapSize) + (x & MapAnd)] = (h00 + h10) * 0.5f;
h[(((y + add) & MapAnd) * MapSize) + ((x + off) & MapAnd)] = (h10 + h11) * 0.5f;
h[(((y + off) & MapAnd) * MapSize) + ((x + add) & MapAnd)] = (h01 + h11) * 0.5f;
h[(((y + off) & MapAnd) * MapSize) + ((x + off) & MapAnd)] = (h00 + h01 + h10 + h11) * 0.25f;
}
}
}
if (countdown > 0)
--countdown;
else
distance = distance * 0.5f;
}
// now, filter the edges of the texture because the poles are smaller area than the equator
for (int i = 0; i < MapSize / 4; ++i)
{
float f = 0;
int bins = i;
float weight = 1 - i / (MapSize / 4);
int curbin = 0;
int curcnt = 0;
int binoffset = 0;
int off = i * MapSize;
for (int j = 0; j < MapSize; ++j)
{
int bin = j * bins / MapSize;
if (bin != curbin)
{
f /= curcnt;
for (int q = binoffset; q < j; ++q)
h[off + q] = h[off + q] * (1 - weight) + f * weight;
f = 0;
curcnt = 0;
curbin = bin;
binoffset = j;
}
f += h[off + j];
++curcnt;
}
f /= curcnt;
for (int q = binoffset; q < MapSize; ++q)
h[off + q] = h[off + q] * (1 - weight) + f * weight;
off = (MapSize - i - 1) * MapSize;
f = 0;
curbin = 0;
curcnt = 0;
binoffset = 0;
for (int j = 0; j < MapSize; ++j)
{
int bin = j * bins / MapSize;
if (bin != curbin)
{
f /= curcnt;
for (int q = binoffset; q < j; ++q)
h[off + q] = h[off + q] * (1 - weight) + f * weight;
f = 0;
curcnt = 0;
curbin = bin;
binoffset = j;
}
f += h[off + j];
++curcnt;
}
f /= curcnt;
for (int q = binoffset; q < MapSize; ++q)
h[off + q] = h[off + q] * (1 - weight) + f * weight;
}
Color[] c = new Color[MapSize * MapSize];
Color[] c2 = new Color[MapSize * MapSize];
for (int y = 0; y < MapSize; ++y)
for (int x = 0; x < MapSize; ++x)
{
float height = h[y * MapSize + x];
if (height < SeaThreshold)
c2[y * MapSize + x].R = 255;
else c2[y * MapSize + x].R = 0;
if (height >= SeaThreshold && height < HillsThreshold)
c2[y * MapSize + x].G = 255;
else c2[y * MapSize + x].G = 0;
if (height >= HillsThreshold && height < MountainThreshold)
c2[y * MapSize + x].B = 255;
else c2[y * MapSize + x].B = 0;
if (height >= SnowThreshold)
c2[y * MapSize + x].A = 255;
else c2[y * MapSize + x].A = 0;
c[y * MapSize + x] = HeightToColor(h[y * MapSize + x], y);
}
//if (colorTexture_ != null)
// colorTexture_.Dispose();
//colorTexture_ = new Texture2D(_game.GraphicsDevice, MapSize, MapSize, 0, TextureUsage.AutoGenerateMipMap, SurfaceFormat.Color);
//colorTexture_.SetData<Color>(c);
if (AlphaMap != null)
AlphaMap.Dispose();
AlphaMap = new Texture2D(GameEngine.mGraphicsDevice, MapSize, MapSize, false, SurfaceFormat.Color);
AlphaMap.SetData<Color>(c2);
for (int y = 0; y < MapSize; ++y)
for (int x = 0; x < MapSize; ++x)
c[y * MapSize + x] = CalcNormal(ref h, x, y);
if (normalTexture_ != null)
normalTexture_.Dispose();
normalTexture_ = new Texture2D(GameEngine.mGraphicsDevice, MapSize, MapSize, false, SurfaceFormat.Color);
normalTexture_.SetData<Color>(c);
PostprocessPipeline.PostProcessComponent Blur = new PostprocessPipeline.GaussBlurComponent(_Light);
Blur.LoadContent();
Blur.Draw(new GameTime());
AlphaMap = Blur.outputRT.RenderTarget;
}
/// <summary>
/// Calculate the normals
/// </summary>
/// <param name="h">Height data</param>
/// <param name="x">X position</param>
/// <param name="y">Y position</param>
/// <returns></returns>
private Color CalcNormal(ref float[] h, int x, int y)
{
float du = (h[x + y * MapSize] - h[((x + 1) & MapAnd) + y * MapSize]) * NormalMapStrength;
float dv = (h[x + y * MapSize] - h[x + ((y + 1) & MapAnd) * MapSize]) * NormalMapStrength;
float z = (float)Math.Abs(du) + Math.Abs(dv);
if (z > 1)
{
du /= z;
dv /= z;
}
float dw = (float)Math.Sqrt(1.0f - du * du - dv * dv);
return new Color(new Vector3(du * 0.5f + 0.5f, dv * 0.5f + 0.5f, dw));
}
/// <summary>
/// Create a color based on the height
/// </summary>
/// <param name="h"></param>
/// <param name="latitude"></param>
/// <returns>Color</returns>
private Color HeightToColor(float h, int latitude)
{
float latScale = (100.0f + Math.Abs(latitude - MapSize / 2)) / 200.0f;
float w;
if (h < SeaThreshold)
{
w = SeaThreshold - h;
if (w > 1) w = 1;
return Color.Lerp(Color.Blue, Color.DarkBlue, w);
}
// make things colder outside of the equator
h = SeaThreshold + (h - SeaThreshold) * latScale;
if (h < MountainThreshold)
{
w = (MountainThreshold - h) / (MountainThreshold - SeaThreshold);
return Color.Lerp(Color.DarkGreen, Color.DarkKhaki, w);
}
if (h < SnowThreshold)
{
w = (SnowThreshold - h) / (SnowThreshold - MountainThreshold);
return Color.Lerp(Color.LightGray, Color.DarkGray, w);
}
w = h - SnowThreshold;
if (w > 0.5f) w = 0.5f;
return Color.Lerp(Color.LightCyan, Color.White, w + 0.5f);
}
/// <summary>
/// Update the rotation of the planet
/// </summary>
/// <param name="cameraPosition"></param>
/// <param name="gameTime"></param>
internal override void Update(GameTime gameTime)
{
Vector3 axisX = new Vector3(1, 0, 0);
Vector3 axisY = new Vector3(0, 1, 0);
Vector3 axisZ = new Vector3(0, 0, 1);
axisX = Vector3.Transform(axisX, Matrix.CreateFromQuaternion(this.rotation));
axisY = Vector3.Transform(axisY, Matrix.CreateFromQuaternion(this.rotation));
axisY = Vector3.Transform(axisZ, Matrix.CreateFromQuaternion(this.rotation));
this.rotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axisX, this.rotationSpeedX * gameTime.ElapsedGameTime.Milliseconds) * Quaternion.CreateFromAxisAngle(axisY, this.rotationSpeedY * gameTime.ElapsedGameTime.Milliseconds) * Quaternion.CreateFromAxisAngle(axisZ, this.rotationSpeedZ * gameTime.ElapsedGameTime.Milliseconds) * this.rotation);
base.Update(gameTime);
}
/// <summary>
/// Draws the planet, using the specified effect. Unlike the other
/// Draw overload where you just specify the world/view/projection matrices
/// and color, this method does not set any renderstates, so you must make
/// sure all states are set to sensible values before you call it.
/// </summary>
public override void Draw(GameTime gameTime)
{
// Set important renderstates.
//RenderState renderState = shader.GraphicsDevice.RenderState;
//renderState.AlphaTestEnable = false;
//Set renderstates for opaque rendering.
//renderState.AlphaBlendEnable = false;
//renderState.CullMode = CullMode.CullCounterClockwiseFace;
// Updated renderstates for stencil buffer
//renderState.DepthBufferEnable = true;
//renderState.DepthBufferFunction = CompareFunction.LessEqual;
//renderState.DepthBufferWriteEnable = true;
// Cache the current depth buffer
//this.tmpBuffer = shader.GraphicsDevice.DepthStencilBuffer;
//shader.GraphicsDevice.DepthStencilBuffer = this.cachedBuffer; //new DepthStencilBuffer(shader.GraphicsDevice, Core.getInstance().GraphicsDevice.Viewport.Width, Core.getInstance().GraphicsDevice.Viewport.Height, DepthFormat.Depth16);
//shader.GraphicsDevice.DepthStencilBuffer = new DepthStencilBuffer(shader.GraphicsDevice, Core.getInstance().GraphicsDevice.Viewport.Width, Core.getInstance().GraphicsDevice.Viewport.Height, DepthFormat.Depth15Stencil1);
// Set our custom depth buffer
//shader.GraphicsDevice.DepthStencilBuffer = shadowDepthBuffer;
shader.Parameters["World"].SetValue(this.myWorldMatrix);
shader.Parameters["WorldIT"].SetValue(this.myWorldInverseTranspose);
shader.Parameters["View"].SetValue(GameEngine.mCameraManager.activeCamera.myViewMatrix);
shader.Parameters["Projection"].SetValue(GameEngine.mCameraManager.activeCamera.myProjectionMatrix);
//shader.Parameters["PlanetTex"].SetValue(PlanetTex);
shader.Parameters["Splat1"].SetValue(WaterTex);
shader.Parameters["Splat2"].SetValue(GrassTex);
shader.Parameters["Splat3"].SetValue(RockTex);
shader.Parameters["Splat4"].SetValue(SnowTex);
shader.Parameters["AlphaMap"].SetValue(AlphaMap);
shader.Parameters["CloudDiffuse"].SetValue(CloudTex);
shader.Parameters["gTex"].SetValue(gTexture);
shader.Parameters["ReflectionMap"].SetValue(PlanetSpecTex);
shader.Parameters["AtmosphereHeight"].SetValue(Atmosphere.AtmosphereSize);
shader.Parameters["elaspedtime"].SetValue((float)gameTime.TotalGameTime.TotalSeconds * 3);
shader.Parameters["LightPos"].SetValue(_Light.position_32);
shader.Parameters["FarPlane"].SetValue(GameEngine.mCameraManager.activeCamera.maximumDepth);
Texture2D LightBuffer = LightPrePass.Manager.GetLightBuffer();
shader.Parameters["LightBuffer"].SetValue(LightBuffer);
Vector2 HalfPixel = new Vector2
(
0.5f / (float)LightPrePass.Manager.LightingBuffer.ScreenSize.X,
0.5f / (float)LightPrePass.Manager.LightingBuffer.ScreenSize.Y
);
shader.Parameters["HalfPixel"].SetValue(HalfPixel);
// Draw the model. A model can have multiple meshes, so loop.
foreach (ModelMesh mesh in PlanetMesh.Meshes)
{
foreach (ModelMeshPart part in mesh.MeshParts)
{
part.Effect = shader;
}
// Draw the mesh, using the effect set above.
mesh.Draw();
}
base.Draw(gameTime);
// Set the buffer back to it's original
//shader.GraphicsDevice.DepthStencilBuffer = tmpBuffer;
}
/// <summary>
/// Draw the planet
/// </summary>
/// <param name="gameTime"></param>
/// <param name="effect"></param>
/// <param name="WorldViewProjection"></param>
public override void Draw(GameTime gameTime, Effect effect, Matrix WorldViewProjection)
{
effect.Parameters["WorldViewProj"].SetValue(WorldViewProjection);
// Draw the model. A model can have multiple meshes, so loop.
foreach (ModelMesh mesh in PlanetMesh.Meshes)
{
foreach (ModelMeshPart part in mesh.MeshParts)
{
part.Effect = effect;
}
// Draw the mesh, using the effect set above.
mesh.Draw();
}
base.Draw(gameTime);
}
public void DrawNormalsAndDepth()
{
DepthAndNormalsEffect.Parameters["World"].SetValue(myWorldMatrix);
DepthAndNormalsEffect.Parameters["WorldIT"].SetValue(this.myWorldInverseTranspose);
DepthAndNormalsEffect.Parameters["View"].SetValue(GameEngine.mCameraManager.activeCamera.myViewMatrix);
DepthAndNormalsEffect.Parameters["Projection"].SetValue(GameEngine.mCameraManager.activeCamera.myProjectionMatrix);
DepthAndNormalsEffect.Parameters["FarPlane"].SetValue(GameEngine.mCameraManager.activeCamera.maximumDepth);
DepthAndNormalsEffect.Parameters["Splat1Norm"].SetValue(WaterNormTex);
DepthAndNormalsEffect.Parameters["Splat2Norm"].SetValue(GrassNormTex);
DepthAndNormalsEffect.Parameters["Splat3Norm"].SetValue(RockNormTex);
DepthAndNormalsEffect.Parameters["Splat4Norm"].SetValue(SnowNormTex);
DepthAndNormalsEffect.Parameters["AlphaMap"].SetValue(AlphaMap);
// Draw the model. A model can have multiple meshes, so loop.
foreach (ModelMesh mesh in PlanetMesh.Meshes)
{
foreach (ModelMeshPart part in mesh.MeshParts)
{
part.Effect = DepthAndNormalsEffect;
}
// Draw the mesh, using the effect set above.
mesh.Draw();
}
}
}
}
Hopefully this is more useful for you, and good luck!
Answer by Kos-Dvornik · Mar 03, 2014 at 08:08 AM
With sources: http://kostiantyn-dvornik.blogspot.com/2014/02/huge-amazing-unity-terrain-tutorial.html