4

I'm writing a game in Visual Studio 2010, using the XNA 4.0 framework. I have a 3D terrain model generated from a height map. What I'm trying to accomplish is to tint this model in a given radius around a certain point, the end goal being to display to the player the radius in which a unit can move in a given turn. The method I'm using to draw the model at the moment is this:

void DrawModel(Model model, Matrix worldMatrix)
    {
        Matrix[] boneTransforms = new Matrix[model.Bones.Count];
        model.CopyAbsoluteBoneTransformsTo(boneTransforms);

        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.World = boneTransforms[mesh.ParentBone.Index] * worldMatrix;
                effect.View = camera.viewMatrix;
                effect.Projection = camera.projectionMatrix;


                effect.EnableDefaultLighting();
                effect.EmissiveColor = Color.Green.ToVector3();
                effect.PreferPerPixelLighting = true;

                // Set the fog to match the black background color
                effect.FogEnabled = true;
                effect.FogColor = Color.CornflowerBlue.ToVector3();
                effect.FogStart = 1000;
                effect.FogEnd = 3200;
            }

            mesh.Draw();
        }
    }

Also, in case it's relevant, I followed this tutorial http://create.msdn.com/en-US/education/catalog/sample/collision_3d_heightmap to create my heightmap and terrain.

Thanks in advance for any help!

Dangerbunny
  • 81
  • 1
  • 3
  • 10

2 Answers2

3

You can use a shader to achieve that...

you only would need to pass as argument the world position of the center and the radius, and let the pixel shader receive the pixel world position interpolated from the vertex shader as a texture coord... then only have to check the distance of the pixel position to the center and tint it with a color if the pixel position is in range...

Blau
  • 5,742
  • 1
  • 18
  • 27
  • I haven't worked with shaders before. Could you explain what you mean by passing the pixel shader an argument? Is the shader an object? Thanks – Dangerbunny Aug 09 '12 at 00:56
  • A shader is the code executed in the GPU and associated with the effect, you can code your own effect... though you should learn the shader basics before, here you can find some info about a shader for doing what you want made by myself http://stackoverflow.com/questions/8000164/how-to-draw-a-circle-on-3d-terrain-in-xna – Blau Aug 09 '12 at 10:30
  • This works perfectly, thank you! Also I can't believe I didn't see the other post, thats pretty much the exact same question I had X) – Dangerbunny Aug 09 '12 at 21:02
0

The technique you are looking for is called decaling.

You have to extract the part of the terrain, where the circle will be drawn, apply an appropriate texture to that part and draw it blending it with the terrain.

For the case of a terrain based on a uniform grid, this will look like the following:

You have the center position of the decal and its radius. Then you can determine min and max row/col in the grid, so that the cells include every drawn region. Create a new vertex buffer from these vertices. Positions can be read from the heightmap. You have to alter the texture coordinates, so the texture will be placed at the right position. Assume, the center position has coordinate (0.5, 0.5), center position + (radius, radius) has coordinate (1, 1) and so on. With that you should be able to find an equation for the texture coordinates for each vertex.

Extracted grid

In the above example, the top left red vertex has texture coordinates of about (-0.12, -0.05)

Then you have the subgrid of the terrain. Apply the decal texture to it. Set an appropriate depth bias (you have to try out some values). In most cases, a negative SlopeScaleDepthBias will work. Turn off texture coordinate wrapping in the sampler. Draw the subgrid.

Here's some VB SlimDX Code I wrote for that purpose:

Public Sub Init()
    Verts = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2) ^ 2
    Tris = (Math.Ceiling(2 * Radius / TriAngleWidth) + 1) ^ 2 * 2

    Dim Indices(Tris * 3 - 1) As Integer
    Dim curN As Integer
    Dim w As Integer
    w = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2)
    For y As Integer = 0 To w - 2
        For x As Integer = 0 To w - 2
            Indices(curN) = x + y * w : curN += 1
            Indices(curN) = x + (y + 1) * w : curN += 1
            Indices(curN) = (x + 1) + (y) * w : curN += 1
            Indices(curN) = x + (y + 1) * w : curN += 1
            Indices(curN) = (x + 1) + (y + 1) * w : curN += 1
            Indices(curN) = (x + 1) + y * w : curN += 1
        Next
    Next

    VB = New Buffer(D3DDevice, New BufferDescription(Verts * VertexPosTexColor.Struct.SizeOfBytes, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, VertexPosTexColor.Struct.SizeOfBytes))
    IB = New Buffer(D3DDevice, New DataStream(Indices, False, False), New BufferDescription(4 * Tris * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 4))
End Sub

Public Sub Update()
    Dim Vertex(Verts - 1) As VertexPosTexColor.Struct
    Dim curN As Integer
    Dim rad As Single 'The decal radius
    Dim height As Single
    Dim p As Vector2
    Dim yx, yz As Integer
    Dim t As Vector2 'texture coordinates
    Dim center As Vector2 'decal center
    For y As Integer = Math.Floor((center.Y - rad) / TriAngleWidth) To Math.Floor((center.Y - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1
        For x As Integer = Math.Floor((center.X - rad) / TriAngleWidth) To Math.Floor((center.X - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1
            p.X = x * TriAngleWidth
            p.Y = y * TriAngleWidth

            yx = x : yz = y
            If yx < 0 Then yx = 0
            If yx > HeightMap.GetUpperBound(0) Then yx = HeightMap.GetUpperBound(0)
            If yz < 0 Then yz = 0
            If yz > HeightMap.GetUpperBound(1) Then yz = HeightMap.GetUpperBound(1)
            height = HeightMap(yx, yz)
            t.X = (p.X - center.X) / (2 * rad)  + 0.5
            t.Y = (p.Y - center.Y) / (2 * rad) + 0.5
            Vertex(curN) = New VertexPosTexColor.Struct With {.Position = New Vector3(p.X, hoehe, p.Y), .TexCoord = t, .Color = New Color4(1, 1, 1, 1)} : curN += 1
        Next
    Next
    Dim data = D3DContext.MapSubresource(VB, MapMode.WriteDiscard, MapFlags.None)
    data.Data.WriteRange(Vertex)
    D3DContext.UnmapSubresource(VB, 0)
End Sub

And here's the according C# code.

public void Init()
{
    Verts = Math.Pow(Math.Ceiling(2 * Radius / TriAngleWidth) + 2, 2);
    Tris = Math.Pow(Math.Ceiling(2 * Radius / TriAngleWidth) + 1, 2) * 2;

    int[] Indices = new int[Tris * 3];
    int curN;
    int w;
    w = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2);
    for(int y = 0; y <= w - 2; ++y)
    {
        for(int x = 0; x <= w - 2; ++x)
        {
            Indices[curN] = x + y * w ; curN += 1;
            Indices[curN] = x + (y + 1) * w ; curN += 1;
            Indices[curN] = (x + 1) + (y) * w ; curN += 1;
            Indices[curN] = x + (y + 1) * w ; curN += 1;
            Indices[curN] = (x + 1) + (y + 1) * w ; curN += 1;
            Indices[curN] = (x + 1) + y * w ; curN += 1;
        }
    }

    VB = new Buffer(D3DDevice, new BufferDescription(Verts * VertexPosTexColor.Struct.SizeOfBytes, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, VertexPosTexColor.Struct.SizeOfBytes));
    IB = new Buffer(D3DDevice, new DataStream(Indices, False, False), new BufferDescription(4 * Tris * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 4));
}

public void Update()
{
    VertexPosTexColor.Struct[] Vertex = new VertexPosTexColor.Struct[Verts] ;
    int curN;
    float rad; //The decal radius
    float height;
    Vector2 p;
    int yx, yz;
    Vector2 t; //texture coordinates
    Vector2 center; //decal center
    for(int y = Math.Floor((center.Y - rad) / TriAngleWidth); y <= Math.Floor((center.Y - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1; ++y)
        for(int x = Math.Floor((center.X - rad) / TriAngleWidth); x <= Math.Floor((center.X - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1; ++x)
        {
            p.X = x * TriAngleWidth;
            p.Y = y * TriAngleWidth;

            yx = x ; yz = y;
            if( yx < 0)
                yx = 0;
            if (yx > HeightMap.GetUpperBound(0))
                yx = HeightMap.GetUpperBound(0);
            if (yz < 0)
                yz = 0;
            if (yz > HeightMap.GetUpperBound(1))
                yz = HeightMap.GetUpperBound(1);
            height = HeightMap[yx, yz];
            t.X = (p.X - center.X) / (2 * rad)  + 0.5;
            t.Y = (p.Y - center.Y) / (2 * rad) + 0.5;
            Vertex[curN] = new VertexPosTexColor.Struct() {Position = new Vector3(p.X, hoehe, p.Y), TexCoord = t, Color = New Color4(1, 1, 1, 1)}; curN += 1;
        }
    }
    var data = D3DContext.MapSubresource(VB, MapMode.WriteDiscard, MapFlags.None);
    data.Data.WriteRange(Vertex);
    D3DContext.UnmapSubresource(VB, 0);
}
Nico Schertler
  • 32,049
  • 4
  • 39
  • 70
  • Okay, I've looked around for how to accomplish such a technique but I haven't come upon a good resource. Could you suggest a way to implement this or a resource to that end? Thanks – Dangerbunny Aug 05 '12 at 20:58
  • It's really hard to find information about that. I added explanations for the case you use a regular grid. Hope, it helps. – Nico Schertler Aug 05 '12 at 23:04
  • I'm with you as far as creating a VertexBuffer, which I assume is filled with the Vector3's of each vertex on the entire heightmap right? After that you've lost me X) How do I access the texture coordinates of the model? I'm sorry I'm a beginner in the realm of 3D. Also I don't know VB, I'm working with c# atm. Thanks for your patience you've been a big help already – Dangerbunny Aug 06 '12 at 21:27
  • The texture coordinates of the decal don't have anything to do with the texture coordinates of the model. They have to be set so the decal texture is at the correct position. If you're unsure about them, just set them in the range of [0, 1] at the beginning and adjust them at the end. Try to look through the VB code. It's very similar to C#, although syntax is sometimes different. `Dim` is a variable declaration, `Indices()`is an array. – Nico Schertler Aug 07 '12 at 08:27
  • So should I be creating an entirely new set of vertices/indices with positions based on my heightmap, applying the texture to them, and then drawing that on top of my existing model? Or am I actually altering the texture of my existing model? Also, I'm having difficulty understanding the concept how the texture coordinates differ from the heightmap positions. What exactly are texture coordinates? – Dangerbunny Aug 08 '12 at 16:50
  • The first version is the correct one. Texture coordinates define, which part of the texture are drawn where on the model. (0,0) is the top left corner of the texture, (1, 1) is the bottom right corner. – Nico Schertler Aug 08 '12 at 19:03
  • Thanks for all the help Nico, you've definitely greatly expanded my knowledge. However shader's are the way to go rather than constructing a whole new series of vertices/indices. Thanks though! – Dangerbunny Aug 09 '12 at 21:00
  • Yes, that's the far simpler method. Fine, if that is sufficient. If you want to do something more general (e.g. applying a lot of circles or using different textures), then you should reconsider decals. – Nico Schertler Aug 10 '12 at 08:47