Heightmap Terrain Rendering

        We will begin with the simple example of rendering terrain using heightmaps. A heightmap is a grayscale image, containing the heights of the terrain. The shade of gray that a pixel has determines the height of the terrain at that point, white being the highest, and black being the lowest. When using floating point textures, the principle is the same. Each pixel contains a floating point value, 0.0f being the lowest height, and 1.0f being the highest. Building the terrain mesh from the heightmap texture could be easily done without using vertex textures, by reading the heights at loading time, when building the vertex buffer. As this operation is done only once (at loading) and not at every frame ( in the vertex shader), using vertex textures does not have any performance gains for this particular case. However, this is the simplest and most clear example that shows how Vertex Texture Fetch is used.

Let us begin by creating a new project (for Windows or Xbox360). Add to the project the files Camera.cs and Grid.cs. Then go to the game class (Game1.cs ), and add the VTFTutorial namespace to the using statements.

 

 

using VTFTutorial;

Now, add two member to the Game1 class, one for the camera component, and one for the grid, and instantiate them in the  constructor of the class. The camera will be added to the Components list, and for the grid, we will set the properties CellSize and Dimension to 4 and 256 respectively. You can try setting the CellSize to any value you want, to see what happens. We will talk about what happens when changing the Dimension property later. Now, in the LoadGraphicsContent function, call grid.LoadGraphicsContent().

 

 

Camera camera;
Grid grid;

public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    content = new ContentManager(Services);

    camera = new Camera(this);
    this.Components.Add(camera);

    grid = new Grid(this);
    grid.Dimension = 256;
    grid.CellSize = 4;
}

protected override void LoadGraphicsContent(bool loadAllContent)
{

    grid.LoadGraphicsContent();

}

We have the geometry, and now we need to create the effect file that will process and render the terrain. Add a new folder to the solution, and name it Shaders. Then Add -> New Item… and create a new text file named VTFDisplacement.fx. Once that is done, open it, and we will begin writing some HLSL (High Level Shading Language) code.

We need three parameters, the world, view and projection matrices. Next, we’ll add the heightmap texture (named displacementTexture, because it tells us how much the vertices will be displaced from the XZ plane), and the sampler for it. This sampler will be used in order to read the height data from the heightmap, inside the vertex shader. In the sampler, we use Point for all the filters, because Linear and Anisotropic are not supported for vertex textures. No error will be generated if we try them, but it will still only use Point filtering.

 

 

float4x4 world;                        // world matrix
float4x4 view;                         // view matrix
float4x4 proj;                          // projection matrix  
float maxHeight = 128;           //maximum height of the terrain

texture displacementMap;       // this texture will point to the heightmap
sampler displacementSampler = sampler_state      //this sampler will be used to read (sample) the heightmap

        Texture = <displacementMap>;
        MipFilter = Point;
        MinFilter = Point;
        MagFilter = Point;
        AddressU = Clamp;
        AddressV = Clamp;
};

Next, the shader structures for the Vertex Shader input and output. For the input, we have the position, and the texture coordinates. For the output, we have the new transformed position of the vertex, and we also pass the world position of the vertex to the pixel shader through the worldPos field. We will use this to color the terrain based on it’s height. The passed texture coordinates are not used for now.

 

 

struct VS_INPUT {
    float4 position : POSITION;
    float4 uv : TEXCOORD0;
};
struct VS_OUTPUT
{
    float4 position  : POSITION;
    float4 uv : TEXCOORD0;
    float4 worldPos : TEXCOORD1;
};

The code for the vertex shader looks like this:

 

 

VS_OUTPUT Transform(VS_INPUT In)
{
    VS_OUTPUT Out = (VS_OUTPUT)0;                                  //initialize the output structure
    float4x4 viewProj = mul(view, proj);                                        //compute View * Projection matrix
    float4x4 worldViewProj= mul(world, viewProj);                      //finally, compute the World * View * Projection matrix

    // this instruction reads from the heightmap, the value at the corresponding texture coordinate
    // Note: we selected level 0 for the mipmap parameter of tex2Dlod, since we want to read data exactly as it appears in the heightmap
    float height = tex2Dlod ( displacementSampler, float4(In.uv.xy , 0 , 0 ) );
    // with the newly read height, we compute the new value of the Y coordinate
    // we multiply the height, which is in the (0,1) range by a value representing the Maximum Height of the Terrain
    In.position.y = height * maxHeight;
    //Pass the world position the the Pixel Shader
    Out.worldPos = mul(In.position, world);
    //Compute the final projected position by multiplying with the world, view and projection matrices                                                      
    Out.position = mul( In.position , worldViewProj);
    Out.uv = In.uv;
    return Out;
}

The tex2Dlod instruction reads the height from the displacementTexture, using the texture coordinates we set when building the grid in Grid.cs . Before the input vertex’s position is multiplied with all the matrices, we assign the height we’ve just read to the Y coordinate of the vertex. This way, the terrain is shaped according to the heightmap, and only then transformed using the world, view and projection matrices. All this is done on the graphics processor.

Let’s carry on, with the pixel shader. We simply draw the terrain, colored according to the height: white on top, and black at the bottom. The technique has one pass, using these two shaders. The compile target is vs_3_0 and ps_3_0, because, as pointed out earlier, VTF is a feature of the Shader Model 3.0

 

 

float4 PixelShader(in float4 worldPos : TEXCOORD1) : COLOR
{
        return worldPos.y / maxHeight;
}
technique GridDraw
{
    pass P0
    {
        vertexShader = compile vs_3_0 Transform();
        pixelShader  = compile ps_3_0 PixelShader();
    }
}

Now that we’re done with the effect file, let’s get back to the Game class. Add a new folder named Textures to the project, and inside it, add the height1.dds file from the resources archive, and set the content processor to Texture(mipmapped) . We need to add a member to the class, to hold the effect file, and a member for the texture.

 

 


        Effect gridEffect;
        Texture2D displacementTexture;

In the LoadGraphicsContent, we need to load the effect and the texture, using these lines

 

 

gridEffect = content.Load<Effect>("Shaders\\VTFDisplacement");
displacementTexture = content.Load<Texture2D>("Textures\\height1");

In the Draw function, we add some code to set the effect parameters, and to render the grid. We leave our terrain in the center of the world, so the world matrix is set to Identity, while the view and projection matrix are retrieved from the camera component.

 

 

protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
            graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
            gridEffect.Parameters["world"].SetValue(Matrix.Identity);
            gridEffect.Parameters["view"].SetValue(camera.View);
            gridEffect.Parameters["proj"].SetValue(camera.Projection);
            gridEffect.Parameters["maxHeight"].SetValue(128);
            gridEffect.Parameters["displacementMap"].SetValue(displacementTexture);

            gridEffect.Begin();
            foreach (EffectPass pass in gridEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                grid.Draw();
                pass.End();
            }
            gridEffect.End();
            base.Draw(gameTime);
        }

At this point, you should be able to run the program, and see something like this.

simpleTerrain

        Bilinear Filtering

Until now, everything is ok. Let’s see what happens if we try to make the terrain larger, and set the grid.CellSize to 8, grid.Dimension to 512, and the maxHeight to 512.

largeTerrain

That looks BAD! But what’s causing it? The heightmap’s size is 256 by 256. When the dimension of the grid was 256, each pixel of the texture corresponded to exactly one vertex in the grid. After we set the dimension to 512, each pixel in the texture now corresponds to TWO vertices in the grid, so every two vertices will have the same height. If we had bilinear filtering, the GPU would have automatically computed a mean value of 4 of the surrounding pixels and the result would have been smooth, not in steps, like it currently is. But since vertex textures do not support bilinear filtering, we will have to do it manually in the vertex shader. So let’s open the VTFDisplacement.fx file, and add the following code.

 

 

float textureSize = 256.0f;        //size of the texture - these two would be set by the application in a real application
float texelSize =  1.0f / 256.0f; //size of one texel

float4 tex2Dlod_bilinear( sampler texSam, float4 uv )
{

        float4 height00 = tex2Dlod(texSam, uv);
        float4 height10 = tex2Dlod(texSam, uv + float4(texelSize, 0, 0, 0));
        float4 height01 = tex2Dlod(texSam, uv + float4(0, texelSize, 0, 0));
        float4 height11 = tex2Dlod(texSam, uv + float4(texelSize , texelSize, 0, 0));

        float2 f = frac( uv.xy * textureSize );

        float4 tA = lerp( height00, height10, f.x );
        float4 tB = lerp( height01, height11, f.x );

        return lerp( tA, tB, f.y );
}

This is the code for bilinear filtering. It samples the four pixels nearest to the current coordinates, and interpolates between them, to obtain the average height that the vertex should have. To use this function, in the vertex shader, replace

 

 

float height = tex2Dlod ( displacementSampler, float4(In.uv.xy , 0 , 0 ) );

with

 

 

float height = tex2Dlod_bilinear( displacementSampler, float4(In.uv.xy,0,0));

Now, for Dimension = 512, cellSize = 8 and maxHeight = 512, the terrain looks like this, which is much better:

bilinear

        Bonus: Texturing

Let us add some textures to the ground. A very good way to do this is to use a pixel shader to blend between several textures (ex: sand, grass, rock, snow), based on some weights, computed according to the height of each vertex. Riemers has a nice tutorial about this here, but in his implementation, the weights used to blend the textures are computed on the CPU, when loading the heightmap. We’ll see how to do this on the GPU. This code is based on Riemers’ formulas, so I take this opportunity to give credit where credit is due.

First, add sand.dds, grass.dds, rock.dds and snow.dds to the project, in the Textures folder. (Note: These textures are at a low resolution, to reduce the download size. Having these textures at thigh resolution greatly improves the quality of the rendering.) Then, open VTFDisplacement.fx and add four texture parameters, and samplers for each of them.

 

 

texture sandMap;
sampler sandSampler = sampler_state
{
    Texture   = <sandMap>;
    MipFilter = Linear;
    MinFilter = Linear;
    MagFilter = Linear;
    AddressU  = Wrap;
    AddressV  = Wrap;
};

texture grassMap;
sampler grassSampler = sampler_state
{
    Texture   = <grassMap>;
    MipFilter = Linear;
    MinFilter = Linear;
    MagFilter = Linear;
    AddressU  = Wrap;
    AddressV  = Wrap;
};

texture rockMap;
sampler rockSampler = sampler_state
{
    Texture   = <rockMap>;
    MipFilter = Linear;
    MinFilter = Linear;
    MagFilter = Linear;
    AddressU  = Wrap;
    AddressV  = Wrap;
};

texture snowMap;
sampler snowSampler = sampler_state
{
    Texture   = <snowMap>;
    MipFilter = Linear;
    MinFilter = Linear;
    MagFilter = Linear;
    AddressU  = Wrap;
    AddressV  = Wrap;
};

Now, we need to add some output parameters for the vertex shader. For each vertex, the output will contain four weights that specify what amount of each texture (sand, grass, rock and snow) should be used for the vertex. Thus, a vertex that has a small height, will only have sand, so the weight of sand will be 1, while the weights of the others will be zero. As the height increases, we have to make a transition from sand to grass, so the sand weight will decrease, while the grass weight will increase. Since each weight is a number between 0.0 and 1.0, we can pack these 4 floats inside a single float4 parameter. The new Output structure looks like this:

 

 

struct VS_OUTPUT
        {
            float4 position  : POSITION;
            float4 uv : TEXCOORD0;
            float4 worldPos : TEXCOORD1;
            float4 textureWeights : TEXCOORD2;     // weights used for multitexturing
        };

Let’s move forward, and at the bottom of the vertex shader, we’ll add the following code

 

 

float4 TexWeights = 0;
exWeights.x = saturate( 1.0f - abs(height - 0) / 0.2f);
exWeights.y = saturate( 1.0f - abs(height - 0.3) / 0.25f);
exWeights.z = saturate( 1.0f - abs(height - 0.6) / 0.25f);
exWeights.w = saturate( 1.0f - abs(height - 0.9) / 0.25f);
loat totalWeight = TexWeights.x +
                            TexWeights.y +
                            TexWeights.z +
                            TexWeights.w;
exWeights /=totalWeight;
ut.textureWeights = TexWeights;

Each component of the textureWeights vector corresponds to a certain texture, X for sand, Y for grass, Z for rock and W for snow. Each of the texture has an area where it becomes more visible, reaches a maximum of visibility, and then slowly fades, to leave room for the next texture. The last instruction normalizes the values, so the total sum of the weights for each vertex is 1. Otherwise, we would get darker or lighter areas in the transitions.

When sampling the textures in the Pixel Shader, we will multiply the texture coordinates by a value chosen by us (8 in this case), in order to have the texture repeated across the terrain. If we choose a value too low, the textures will be stretched over the whole terrain, and will look bad when viewing the terrain close. If we choose a value too high, the repetitive patter will be visible when viewing the terrain from above. Feel free to experiment with this value. A technique called detail texturing can also be applied to combine these textures with a very detailed texture when we are close to the ground, but I will not cover this technique here.

Finally, in the Pixel Shader, we read the colors from the four textures, and combine them using the weights, in order to achieve the transitions we were looking for.

 

 

float4 PixelShader(in float4 uv : TEXCOORD0, in float4 weights : TEXCOORD2) : COLOR
{
         float4 sand = tex2D(sandSampler,uv * 8);
         float4 grass = tex2D(grassSampler,uv * 8);
         float4 rock = tex2D(rockSampler,uv * 8 );
         float4 snow = tex2D(snowSampler,uv * 8 );
         return sand * weights.x + grass * weights.y + rock * weights.z + snow * weights.w;
}

The final terrain should look like this:

textured

This concludes the first chapter of this tutorial. In this chapter we saw how to use vertex textures to render a terrain from a heightmap, and texture it with multiple textures, everything running on the GPU. We also saw how bilinear filtering is implemented in the Vertex Shader. At this point you may wonder why would you want to move all these calculations on the GPU, where they are done every frame, instead of doing them once, at loading, on the CPU. In the following chapters, we will see how vertex textures can be used to dynamically morph a terrain, and how to add deformations to it. Normally, these things would consume a lot of CPU power, but we will do it all on the GPU, leaving the processor free for whatever you may need it: gameplay, physics, A.I., etc.

The complete code for this chapter can be found in Chapter1.zip .

  • Pingback: Game Rendering » Bilinear Interpolation

  • Pingback: Abandoned: Terrain 2 « dev in the making

  • shadmar

    Thank you, I just did this one in 3d rad, and it works perfectly 🙂

  • hinrustjum

    Девушки джинсыкредо mp3 бесплатно пикантное видео – порно нудизм бесплатные миди учебная бесплатная библиотека. порно тв бесплатноНа мой взгляд данную проблему можно разрешить так. Тест психологический пройти, а также красивые порно фотки небритые девушки порно фото большие попки бляди щигры стриптиз красивых девушек бесплатные nokia s60 последнее поздравление драйвера видео sis650 740 3d видео бесплатно скачать бесплатные обои samsungвидео о сексе. Система работает со всеми популярными системами платежей и СМС.

  • Bracher

    Awesome stuff thanks man!

  • AsemaKhan

    Hi !
    I am trying to load your code but its not working and I am getting these errors.

    Warning 1 Member ‘Heightmap.Game1.LoadGraphicsContent(bool)’ overrides obsolete member ‘Microsoft.Xna.Framework.Game.LoadGraphicsContent(bool)’. Add the Obsolete attribute to ‘Heightmap.Game1.LoadGraphicsContent(bool)’.

    Warning 2 Member ‘Heightmap.Game1.UnloadGraphicsContent(bool)’ overrides obsolete member ‘Microsoft.Xna.Framework.Game.UnloadGraphicsContent(bool)’. Add the Obsolete attribute to ‘Heightmap.Game1.UnloadGraphicsContent(bool)’.

    Error 3 The name ‘ResourceUsage’ does not exist in the current context

    Error 4 The name ‘ResourceManagementMode’ does not exist in the current context

    Error 5 The name ‘ResourceUsage’ does not exist in the current context

    Error 6 The name ‘ResourceManagementMode’ does not exist in the current context

    Can you please tell me what I am missing ?

  • http://www.catalinzima.com Catalin Zima

    The tutorial was written for a much older version of XNA (I believe it was 2.0 at that time), and there have been some breaking changes in the new version of XNA

  • http://Website Paul C

    For those looking to use this in XNA 4, you just have to convert the height map to a valid format, like Vector4.

    I made a quick method to do this, and it seems to work:

    var OutTex = new Texture2D(GraphicsDevice, InTex.Width, InTex.Height, false, SurfaceFormat.Vector4);

    Color[] In = new Color[InTex.Width * InTex.Height];
    Vector4[] Out = new Vector4[InTex.Width * InTex.Height];
    InTex.GetData(In);
    for (int i = 0; i < InTex.Width * InTex.Height; i++)
    Out[i] = new Vector4(In[i].R / 255f, In[i].G / 255f, In[i].B / 255f, In[i].A / 255f);
    OutTex.SetData(Out);

    return OutTex;

    Thanks again Catalin for another great article, I've learn a ton from your site.

  • http://Website Barret MacIntosh

    Converting to xna 4.0:

    error 1: error X3000: syntax error: unexpected token ‘PixelShader’

    solution : change the name of it to something else (and make sure to update the technique)

    error 2 : XNA Framework HiDef profile does not support vertex texture format Color.

    if you try to use Reach you will get this error: XNA Framework Reach profile does not support vertex shader model 3.0.

    solution: right click height1.dds in the solution and select properties. Under ‘content processor’ change ‘Texture Format’ to NoChange. This is easier than Paul C’s solution.

    To be honest, I only used the textures, grid.cs and .fx files in my code. So there could be other errors.

  • http://Website Barret MacIntosh

    @AsemaKhan and the next person that needs to know…

    in the grid.cs class replace:
    draw()
    {
    Game.GraphicsDevice.SetVertexBuffer( vb );
    Game.GraphicsDevice.Indices = ib;
    Game.GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, ( Dimension + 1 ) * ( Dimension + 1 ), 0, 2 * Dimension * Dimension );
    }

    and the:
    public void LoadGraphicsContent()
    {
    vb = new VertexBuffer( Game.GraphicsDevice, VertexPositionNormalTexture.VertexDeclaration, ( Dimension + 1 ) * ( Dimension + 1 ), BufferUsage.None );
    ib = new IndexBuffer( Game.GraphicsDevice, typeof(int), 6 * Dimension * Dimension, BufferUsage.None );
    vb.SetData( vertices );
    ib.SetData( indices );
    }

    And you should be good to go!

    I could probably do a whole port, but I have no where to post it…

  • http://Website Alex

    Just thought I’d share this with you guys:

    http://www.megaupload.com/?d=9IUCNV4V

    It’s a XNA 4.0 version of this sample. Hope you guys can use it. Catalin, feel free to add this to your blog if you want, after all the code was yours to begin with :).

    Alex

  • http://Website XullrippeR

    .

  • http://Website XullrippeR

    Great tutorial!
    Now I actually understand how a terrain works 🙂

    Based on your terrain, I implemented my own terrain and now I’m coding a triangle picker, that can see on which triangle of the terrain the mouse if going over.

    But I’m stuck with this:
    Is it possible to get the new positions of the vertices, that have been moved by the shader/effect?
    My “triangle picker” works, but only with the old positions of the vertices.

    Thanks in advance!

  • http://www.andol.info andol

    is the effect file above also compatible with vs 2.0 and ps 2.0?

  • http://Website martinabinaford

    you ok steven sorry iv took so long this is there web address
    filling address , they have 20% discount now, mention miss binaford told you to ring

  • http://www.ninjasinlabcoats.com Ninja


    Catalin Zima:

    The tutorial was written for a much older version of XNA (I believe it was 2.0 at that time), and there have been some breaking changes in the new version of XNA

    Yep there most *definitely* have been changes *INDEED*…

    !!!!!FOR ANYONE LOOKING AT THIS ARTICLE IN 2012 !!!!

    XNA 4.0 has basically *BROKEN* the vertex texture sampling and requires you to keep your entire render target pipeline in a very *specific* format (HdrBlendable).
    Even then, when doing *any* form of Vertex Texture sampling, as this example shows (and is a great example), you must realize that you cannot:
    1.) Use but a very few render target formats
    2.) Do *ANY* form of alpha blending or blending of *ANY* kind.
    3.) You can *never* mix/match render targets as there is a bug in the sampler state (deep within the XNA 4 pipeline)…as in you can *try* to reset your sampler but due to the “awesome” changes in the XNA 4 refresh, basically it doesn’t catch the sampler changes until *after* the back buffer has presented…which means…unfortunately… this example no longer works under XNA 4.0 (with the refresh applied).

    Great example… but it is a shame that XNA 4.0 has been broken and will never get fixed…ever…again…due to Windows 8.

    So… if you want to try doing anything like the above…you either need:

    Windows 8 and use the DirectX 11 SDK and migrate all code to C++ (from C#)…be a registered Xbox 360 Developer so you can have the DirectX 10+ SDK and manage your own sampler states directly (no XNA crap in the way)…

    Or just not be able to do vertex shader side texture sampling!

    If you try this example under the XNA 4.0 pipeline (with refresh applied) you will find that trying to do things like simple SpriteBatch calls… will basically give you similar errors to the “XNA Framework Reach profile does not support vertex shader model 3.0. ” but it will complain about the utilization of “point sampling” vs “linear sampling”… and no… setting the sampler states like say:
    //Set to point sampling for vertex texture sampling
    for (int i = 0; i < 4; i++)
    {
    GraphicsDevice.SamplerStates[i] = SamplerState.PointClamp;
    GraphicsDevice.VertexSamplerStates[i] = SamplerState.PointClamp;
    }
    ….
    (Call your draw method with your custom effect that does vertex texture sampling)
    //Reset back to linear
    for (int i = 0; i < 4; i++)
    {
    GraphicsDevice.SamplerStates[i] = SamplerState.LinearWrap;
    GraphicsDevice.VertexSamplerStates[i] = SamplerState.LinearWrap;
    }

    It will still barf all over itself.

    Shawn H "claims" that you can try to "trick it" doing something like:
    GraphicsDevice.SamplerStates[i] = SamplerState.PointWrap;

    GraphicsDevice.SamplerStates[i] = SamplerState.LinearWrap;

    To clear the state cache… but this doesn't work at all either.

    Basically… XNA 4.0 (with refresh) no longer supports vertex texture sampling… well…that is… unless you don't mind always using an HdrBlendable surface and always have point sampling turned on…*for all of your drawing*…

    Sucks… great example…awesome example of how this used to be done…and how it could be done…in earlier versions of XNA before they wrapped it so much in "user friendly" garbage…they broke it.

    One of the many reasons Microsoft is ditching XNA and migrating back to a more C++ oriented DirectX SDK for Windows 8…

    I am sure all of us "Indie" developers will get used to having to do without C#…because in the end it is better to have more control over the hardware then to let someone try to "think for you".

    Awesome example…just sucks it can no longer be used…well…can no longer be used in any form of multi-render target design…

    🙁

  • Phillip H

    Previous poster nonwithstanding this works fine with XNA4. You need to use a SurfaceFormat.Vector4 for the heightmap, and you are restricted to PointSample for the VTF lookup, but you can happily mix and match samplers in your shader with no problems.

    One “error” in the original example with the manual linear interpolation though; the example shown doesn’t give an accurate results because Point filtering is “nearest point”. The example would work reliably if Point filtering didn’t round to the nearest point. In fact this will stretch some texel heights and squish some others. The problem isn’t likely to be noticible on this scale though, and it only really matters if your vertexes are not aligned as a multiple of your heightmap sizes.

  • Phillip H

    Suggested improvement; if you aren’t that worried about only having 255 different heights, you can save a lot of VTF lookups by packing the texel offset heights into the 4 colour channels.

    For any given Pixel use Alpha to be your point height, R to be height10, G = height01, B = height11. You need to bake this into the texture yourself, but it means a single VTF can give you all four points for your interpolation.

    To add variation, you can add a perlin noise texture and sample that as well, and the user probably wont notice the limitation of 255 different heights.

  • http://Website Ant

    I’ve started using SharpDX since XNA is now defunct. Since SharpDX won’t allow directx9 code I had to use directx10. So the shader became:

    float4x4 WorldMatrix;
    float4x4 ViewMatrix;
    float4x4 ProjectionMatrix;

    float maxHeight; //maximum height of the terrain

    Texture2D heightMap;
    Texture2D sandTexture;
    Texture2D grassTexture;
    Texture2D stoneTexture;
    Texture2D snowTexture;

    SamplerState pointSampler
    {
    MipFilter = Point;
    MinFilter = Point;
    MagFilter = Point;
    AddressU = Clamp;
    AddressV = Clamp;
    };

    SamplerState linearSampler
    {
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
    };

    struct VS_INPUT
    {
    float4 Position : SV_POSITION;
    float2 UV : TEXCOORD0;
    };

    struct VS_OUTPUT
    {
    float4 Position : SV_POSITION;
    float2 UV : TEXCOORD0;
    float4 WorldPosition : TEXCOORD1; // position in world coords
    float4 TextureWeights : TEXCOORD2; // weights used for multitexturing
    };

    VS_OUTPUT VS(VS_INPUT input)
    {
    float height = heightMap.SampleLevel(pointSampler, input.UV, 0).r;
    input.Position.y = height * maxHeight;

    VS_OUTPUT output = (VS_OUTPUT)0;
    output.Position = mul(input.Position, WorldMatrix);
    output.Position = mul(output.Position, ViewMatrix);
    output.Position = mul(output.Position, ProjectionMatrix);

    output.WorldPosition = mul(input.Position, WorldMatrix);

    float4 texWeights = 0;

    texWeights.x = saturate( 1.0f – abs(height – 0) / 0.2f);
    texWeights.y = saturate( 1.0f – abs(height – 0.3) / 0.25f);
    texWeights.z = saturate( 1.0f – abs(height – 0.6) / 0.25f);
    texWeights.w = saturate( 1.0f – abs(height – 0.9) / 0.25f);

    float totalWeight = texWeights.x + texWeights.y + texWeights.z + texWeights.w;
    texWeights /= totalWeight;
    output.TextureWeights = texWeights;
    output.UV = input.UV;

    return output;
    }

    float4 PS(VS_OUTPUT input):SV_TARGET
    {
    float4 sand = sandTexture.Sample(linearSampler, input.UV * 8.0f);
    float4 grass = grassTexture.Sample(linearSampler, input.UV * 8.0f);
    float4 stone = stoneTexture.Sample(linearSampler, input.UV * 8.0f);
    float4 snow = snowTexture.Sample(linearSampler, input.UV * 8.0f);

    return (sand * input.TextureWeights.x + grass * input.TextureWeights.y + stone * input.TextureWeights.z + snow * input.TextureWeights.w);
    }

    technique Simplest
    {
    pass Pass0
    {
    SetVertexShader(CompileShader(vs_4_0, VS()));
    SetGeometryShader(NULL);
    SetPixelShader(CompileShader(ps_4_0, PS()));
    }
    }

    The only problem is in the pixel shader with texture sampling. There seems to be a problem with texture coords * 8. If I don’t scale the texture cords the textures are rendered as expected, but when scaled the textures are stretched so much it looks like it’s rendered with solid colour. At the edges of the terrain mesh the textures are stretched into lines. I don’t know how to fix this. I have tried many different numbers and they just don’t work.

  • http://Website Ant

    I’ve got it working. The problem was SharpDX Toolkit doesn’t allow texture samples to be defined in the effect file. So that had to be done in c# code.

    One thing I would like to do is add a rock texture on slopes. How would that be added to height/weighted textures?

  • Catalin ZZ

    For slope-based rock textures, you’d need to do that normally in the pixel shader, and use the rock based on the normals coming in from the vertex shader

  • http://Website Ant

    I’m doing something like that, but how do I blend the slope texture and height textures to remove sharp lines.

    This is my pixel shader at the moment:

    float4 PS(VS_OUTPUT input):SV_TARGET
    {
    if (input.Normal.y < 0.75f)
    {
    return (stoneTexture.Sample(linearSampler, input.UV));
    }
    else
    {
    float4 sand = sandTexture.Sample(linearSampler, input.UV);
    float4 grass = grassTexture.Sample(linearSampler, input.UV);
    float4 dirt = dirtTexture.Sample(linearSampler, input.UV);
    float4 snow = snowTexture.Sample(linearSampler, input.UV);

    return (sand * input.TextureWeights.x + grass * input.TextureWeights.y + dirt * input.TextureWeights.z + snow * input.TextureWeights.w);
    }

    I would to include the slope texture in with the other textures something like: finalcolour = slope*weight + sand*uv.x … etc

  • Pingback: Large Scale Terrain | Phillip Hamlyn

  • CYY

    Hi,

    About the bilinear filter, I doubt that there’s something wrong there. frac(uv * textureSize) gives you the distance between uv and the corners of the pixel that uv locates, then you deduce the weights for bilinear filter. However, the weights should be calculated by distance between uv and the center of the surrounding pixels.

    In your code, given that the uv locates at the pixel (x, y), you calculate the filtered value always from pixels (x, y), (x+1, y), (x, y+1), (x+1, y+1), which is not always correct IMO. Suppose that frac(uv * textureSize) return a value (0.25, 0.25), which is on the upper-left side of the center of pixel (x,y), then the filtered value should accumulate the pixels (x-1, y-1), (x-1, y), (x, y-1), (x, y) multiply with the corresponding weights.

    Below is my implementation:

    float4 height00 = tex2Dlod(texSam, uv + float2(-texelSize/2, -texelSize/2));

    float4 height10 = tex2Dlod(texSam, uv + float2(-texelSize/2, texelSize/2));

    float4 height01 = tex2Dlod(texSam, uv + float2(texelSize/2, -texelSize/2));

    float4 height11 = tex2Dlod(texSam, uv + float2(texelSize/2, texelSize/2));

    float2 f = frac( uv.xy * textureSize – float2(0.5, 0.5));

    float4 tA = lerp( height00, height10, f.x );

    float4 tB = lerp( height01, height11, f.x );

    return lerp( tA, tB, f.y );

    Any idea?