directx-11hlslrendertarget3d-texture

Rendering to a full 3D Render Target in one pass


Using DirectX 11, I created a 3D volume texture that can be bound as a render target:

D3D11_TEXTURE3D_DESC texDesc3d;
// ...
texDesc3d.Usage     = D3D11_USAGE_DEFAULT;
texDesc3d.BindFlags = D3D11_BIND_RENDER_TARGET;

// Create volume texture and views
m_dxDevice->CreateTexture3D(&texDesc3d, nullptr, &m_tex3d);
m_dxDevice->CreateRenderTargetView(m_tex3d, nullptr, &m_tex3dRTView);

I would now like to update the whole render target and fill it with procedural data generated in a pixel shader, similar to updating a 2D render target with a 'fullscreen pass'. Everything I need to generate the data is the UVW coordinates of the pixel in question.

For 2D, a simple vertex shader that renders a full screen triangle can be built:

struct VS_OUTPUT
{
    float4 position : SV_Position;
    float2 uv: TexCoord;
};

// input: three empty vertices
VS_OUTPUT main( uint vertexID : SV_VertexID )
{
    VS_OUTPUT result;
    result.uv = float2((vertexID << 1) & 2, vertexID & 2);
    result.position = float4(result.uv * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
    return result;
}

I have a hard time wrapping my head around how to adopt this principle for 3D. Is this even possible in DirectX 11, or do I have to render to individual slices of the volume texture as described here?


Solution

  • Here is some sample code doing it with pipeline version. You basically batch N triangles and route each instance to a volume slice using Geometry Shader.

    struct VS_OUTPUT
    {
        float4 position : SV_Position;
        float2 uv: TexCoord;
        uint index: SLICEINDEX;
    };
    
    VS_OUTPUT main( uint vertexID : SV_VertexID, uint ii : SV_InstanceID )
    {
        VS_OUTPUT result;
        result.uv = float2((vertexID << 1) & 2, vertexID & 2);
        result.position = float4(result.uv * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
        result.index= ii;
        return result;
    }
    

    Now you need to call DrawInstanced with 3 vertices and N instances where N is your volume slices count

    Then you assign triangles to GS like this:

    struct psInput
    {
        float4 pos : SV_POSITION;
        float2 uv: TEXCOORD0;
    uint index : SV_RenderTargetArrayIndex; //This will write your vertex to a specific slice, which you can read in pixel shader too
    };
    
    [maxvertexcount(3)] 
    void GS( triangle VS_OUTPUT input[3], inout TriangleStream<psInput> gsout )
    {       
    psInput output;
    for (uint i = 0; i < 3; i++)
    {
        output.pos = input[i].pos;
        output.uv = input[i].uv;
        output.index= input[0].index; //Use 0 as we need to push a full triangle to the slice
        gsout.Append(output);
    }
    gsout.RestartStrip();
    }
    

    Now you have access to slice index in your pixel shader:

    float4 PS(psInput input) : SV_Target
    {
    //Do something with uvs, and use slice input as Z
    }
    

    Compute shader version (don't forget to create a UAV for your volume), and numthreads here is totally arbirtary

    [numthreads(8,8,8)]
    void CS(uint3 tid : SV_DispatchThreadID)
    {
         //Standard overflow safeguards
    
         //Generate data using tid coordinates
    } 
    

    Now instead you need to call dispatch with width/8, height/8, depth/8