Spliting terrain vertex buffer into 2 buffers

Started by
6 comments, last by kauna 10 years, 1 month ago

Hello,

I am currently working on a terrain generation (so common problem that you're probably bored already :)). I have recently read on gamedev.net topic (can't find it anymore :/) that there's a possibility to have one buffer containing X and Z positions (which is always the same - real positioning will be done later by using translation) and send only Y values to the shader. I was wondering about such method for a while, but I can't find any way to merge these buffers in a shader. If I sent to shader only Y values how can I obtain X and Z values?

One thing that came up to my mind while writing this question was to send X and Z values through the constant buffer. Is this the correct way?

Thank you for any help.

Advertisement

If you are using D3D11, then you don't need the XZ / XY buffer at all since you can reconstruct the position from the vertex index (check SV_VertexID). Also, under D3D11/10 you may store the height data inside a texture and use vertex shader texture read with the same vertex index. So practically you don't need any vertex buffers for height map terrain rendering! With the hardware tesselator, you don't even need an index buffer.

Otherwise, to answer your question, you'll need to define your vertex declaration to read data from 2 streams, ie. the stream 0 with XY/XZ position and height from the stream 1.


 
Something along these lines
 
 D3D11_INPUT_ELEMENT_DESC TerrainVertex_desc[] =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "HEIGHT", 0, DXGI_FORMAT_R32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };
   
This declaration assumes first buffer to contain a 2-component vector and a 1-component data from the second stream.

The input structure for the vertex shader:

struct VS_INPUT
{
    float2 vPosition : POSITION;
    float Height : HEIGHT;
};
 
Inside the shader to create the position vector:
 
VS_OUT PS_Main(in VS_INPUT Input)
{
float4 LocalPosition = float4(Input.vPosition.x, Input.vPosition.y, Input.Height, 1.0f);

...
}
 

Cheers!

I really really appreciate wasting time to answer my question and giving the example smile.png. The SV_VertexID looks very promising.

I actually shouldn't put my data to a texture as I am generating an infinite terrain, so I think sending a texture to the shader will be heavier than sending an array of Y values (and additionaly I would need to sample the texture inside the shader).

I am pre-calculating index buffers and switching between them if it's neccessary when creating LOD effect (unfortunately it has popping effects).

But still I am wondering what the performance impact will be during translating the chunks (from my investigation translation will be called like 50-100 times). I think the best way will be to use constant buffer to put there some origin position and add it to the final vertex position in the shader, but I would need to update constant buffer 50-100 times during one frame or maybe put there 100x origins at once, but which origin use for which chunk... aww! I don't know now I will try to find the best way smile.png

Hi,

Updating a vertex buffer or updating a texture will have approximately the same effect, you still need to upload data to some GPU resource. Using a texture as heightmap gives you advantage of taking multiple random samples inside the shader (good for normal generation in the shader). A typical hardware won't take any performance hit from using vertex shader texture fetch.

For the translation of chunks, you may collect all the visible terrain tiles at once and update your constant buffer once per frame. You may create a several buckets based on which indices you need to use with each tile. SV_InstanceID helps you to use correct translation with each tile.

Cheers!

The terrain tiles are not instanced*, every tile has its own vertex buffer, so SV_InstanceID won't help me, but I will play with that and check different approaches.

*I don't even see the point of instancing it as every tile has another geometry, altough number of vertices is the same,

Hi,

- the point of instancing is pretty clear. You may reduce the amount of draw calls by some hundred easily (depending how your terrain is tiled).

- the advantage of using a texture is clear too - it allows random access and allows you to use instancing with a terrain.

- as you stated - the grids share a certain amount of geometry, and the meshes are "different" but the shader code to draw them is the same regardless of the terrain form.

With tesselator hardware, I'm able to draw all my terrain from zero - infinity with 2 draw calls (because I use 2 different shaders for different detail levels).

Cheers!

I wonder how do you do the frustum culling with only 2 draws? In shader? It must be inefficient to cull by a vertex.

Frustum culling poses no problems, since every frame I collect all the visible terrain nodes / tiles and add their drawing parameters to a list (or two lists in this case). The visibility test is done on CPU and the data is put inside a std::vector container.

The data is uploaded to a constant buffers and then I execute an instanced drawing command. Nothing special here.

Cheers!

This topic is closed to new replies.

Advertisement