Thursday, August 30, 2012

GPU based Geometry Clipmapping II

Here it is, the second part of our series: the basic shader to adjust position and scaling of the sections. In our case, the shader just performs all translation and scalations for us. To get started, we will first render everything in wireframe mode. Lets first start with the shader.
At first we have to declare all required variables.
float4x4 View;
float4x4 Projection;
float3 CameraPos;
float LODscale; //Level Of Detail for scalation
float2 Position; //Position in the ring of sectors

Those variables are needed for our shader. We don't need any World Matrix because we will calculate the matrix by ourselves.
Now it gets somehow tricky, because we need to write a vertex shader which transforms the single vertices to their according position. But while creating a matrix, we have to keep something in mind: A normal identity matrix looks like this:

But HLSL takes the arguments in a different order. Not each single row but each single column. So the initializer has to look something like this:
So for our transform matrix we have to use another order.









Keeping this in mind, we can start to write our Vertex Shader.
float4 mainVS(float2 pos : POSITION) : POSITION{
 float4x4 worldMatrix = float4x4(
  scale, 0, 0, 0,
  0, scale, 0, 0,
  0, 0, scale, 0,
  -Position.x * 32 * LODscale+ CameraPos.x, 0, Position.y * 32 * LODscale+ CameraPos.z, 1);
 float4 worldPos = mul(float4(pos.x, 0.0f, pos.y, 1.0f), worldMatrix);
 float4 viewPos = mul(worldPos, View);
 float4 projPos = mul(viewPos, Projection);
 
 return projPos;
}

As you can see, we have flipped the matrix to work correctly. And we also have to use the double amount of faces to multiply the position with (16 faces -> 32 in shader). The Position parameter is given in sectors, so we have to multiply it with 16 faces.
With this shader it is quite easy to generate geometry clipmaps. As the last part of our shader, we have to write the pixel shader:
float4 wireframe_PS() : COLOR {
 return float4(1.0, 1.0, 1.0, 1.0);
}

The only thing the code above is doing, is to set the color to white.
Now we will define the wireframe render technique.
technique WireframeWhite {
 pass p0 {
  CullMode = None;
  FillMode = Wireframe;
  VertexShader = compile vs_3_0 wireframe_VS();
  PixelShader = compile ps_3_0 wireframe_PS();
 }
}

This snippet just causes the compiler to render everything wireframe.
Now we have build the following shader:

float4x4 View : View;
float4x4 Projection : Projection;
float3 CameraPos : CameraPosition;
float LODscale; //The LOD ring index 0:highest x:lowest
float2 Position; //The position of the part in the ring

float4 wireframe_VS(float2 pos : POSITION) : POSITION{
 float4x4 worldMatrix = float4x4(
  scale, 0, 0, 0,
  0, scale, 0, 0,
  0, 0, scale, 0,
  -Position.x * 32 * LODscale+ CameraPos.x, 0, Position.y * 32* LODscale+ CameraPos.z, 1);
 float4 worldPos = mul(float4(pos.x, 0.0f, pos.y, 1.0f), worldMatrix);
 float4 viewPos = mul(worldPos, View);
 float4 projPos = mul(viewPos, Projection);
 
 return projPos;
}

float4 wireframe_PS() : COLOR {
 return float4(1.0, 1.0, 1.0, 1.0);
}

technique WireframeWhite {
 pass p0 {
  CullMode = None;
  FillMode = Wireframe;
  VertexShader = compile vs_3_0 wireframe_VS();
  PixelShader = compile ps_3_0 wireframe_PS();
 }
}

Now we can turn to the code. As said before, each ring has 12 sectors and the inner one has 16. To determine the position we pass to the shader, we use a simple array:
private readonly Vector2[] _positionIndex = new Vector2[]
            {
                new Vector2(1, -1),
                new Vector2(0.5f, -1),
                new Vector2(0, -1),
                new Vector2(-0.5f, -1),
                new Vector2(1, -0.5f),
                new Vector2(-0.5f, -0.5f),
                new Vector2(1, 0),
                new Vector2(-0.5f, 0),
                new Vector2(1, 0.5f),
                new Vector2(0.5f, 0.5f),
                new Vector2(0, 0.5f),
                new Vector2(-0.5f, 0.5f),
                new Vector2(0.5f, -0.5f),
                new Vector2(0, -0.5f),
                new Vector2(0.5f, 0),
                new Vector2(0, 0)
            };   

The first 12 items determine the ring around the centered 4 sectors which are placed at the end of the array. This is because we can easily decrement the rendered sectors from 16 to 12 without changing anything.
As the last missing part, we have to render our sectors for each LOD.
        public void Draw(GameTime gameTime)
        {
            _terrain.CurrentTechnique = _terrain.Techniques["WireframeWhite"];
            _terrain.Parameters["View"].SetValue(MainGame.Instance.Camera);
            _terrain.Parameters["Projection"].SetValue(MainGame.Instance.Projection);
            _terrain.Parameters["CameraPos"].SetValue(MainGame.Instance.CameraPosition);
            _terrain.Parameters["Heightmap"].SetValue(_heightmap);
            _terrain.Parameters["texel"].SetValue(texelsize);

            int sectors = 16;
            foreach (EffectPass pass in _terrain.CurrentTechnique.Passes)
            {


                for (int l = 0; l < LOD; l++)
                {
                    _terrain.Parameters["LODscale"].SetValue(1 << l);

                    for (int i = 0; i < sectors; i++)
                    {
                        _terrain.Parameters["Position"].SetValue(_positionIndex[i]);
                        pass.Apply();
                        Main.Instance.Device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, _vertices, 0, _vertices.Length, _indices, 0, _indices.Length / 3, VertexPosition.VertexDeclaration);
                    }
                    sectors = 12;
                }
            }
        }
As you can see, we start with 16 sectors at LOD=0 to a fixed constant value of LODs. For each LOD we cycle trough 12 sectors because after the first LOD the sectors are set to 12. And we also bitshift the current LOD to get the expotential offset. To improve the level of detail, simply increase the vertices used. This sample has shown how to write a geometry clipmapping shader easily. See you for trick III, heightmap implementation!

No comments:

Post a Comment