0

渲染一堆分层纹理的有效方法是什么?我有一些半透明的纹理矩形,我在 3D 空间中随机定位并从后到前渲染它们。

目前我调用 d3dContext->PSSetShaderResources() 在每次调用 d3dContext->DrawIndexed() 之前为像素着色器提供新纹理。我有一种感觉,我在每次绘制之前将纹理复制到 GPU 内存。我可能有 10-30 个 ARGB 纹理,每个纹理大约 1024x1024 像素,它们与我在屏幕上渲染的 100-200 个矩形相关联。我的 FPS 在 100 时还可以,但在 200 左右变得相当糟糕。我可能在其他地方效率低下,因为这是我的第一个半严肃的 D3D 代码,但我强烈怀疑这与来回复制纹理有关。30*1024*1024*4 是 120MB,对于应该针对任何 Windows 8 设备的 Metro 风格应用来说,这有点高。所以把它们都放在那里可能有点费力,但也许我至少可以以某种方式缓存一些?有任何想法吗?

*编辑 - 添加了一些代码片段

常量缓冲区

struct ModelViewProjectionConstantBuffer
{
    DirectX::XMMATRIX model;
    DirectX::XMMATRIX view;
    DirectX::XMMATRIX projection;
    float opacity;
    float3 highlight;
    float3 shadow;
    float textureTransitionAmount;
};

渲染方法

void RectangleRenderer::Render()
{
    // Clear background and depth stencil
    const float backgroundColorRGBA[] = { 0.35f, 0.35f, 0.85f, 1.000f };
    m_d3dContext->ClearRenderTargetView(
        m_renderTargetView.Get(),
        backgroundColorRGBA
        );

    m_d3dContext->ClearDepthStencilView(
        m_depthStencilView.Get(),
        D3D11_CLEAR_DEPTH,
        1.0f,
        0
        );

    // Don't draw anything else until all textures are loaded
    if (!m_loadingComplete)
        return;

    m_d3dContext->OMSetRenderTargets(
        1,
        m_renderTargetView.GetAddressOf(),
        m_depthStencilView.Get()
        );

    UINT stride = sizeof(BasicVertex);
    UINT offset = 0;

    // The vertext buffer only has 4 vertices of a rectangle
    m_d3dContext->IASetVertexBuffers(
        0,
        1,
        m_vertexBuffer.GetAddressOf(),
        &stride,
        &offset
        );

    // The index buffer only has 4 vertices
    m_d3dContext->IASetIndexBuffer(
        m_indexBuffer.Get(),
        DXGI_FORMAT_R16_UINT,
        0
        );

    m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    m_d3dContext->IASetInputLayout(m_inputLayout.Get());

    FLOAT blendFactors[4] = { 0, };
    m_d3dContext->OMSetBlendState(m_blendState.Get(), blendFactors, 0xffffffff);

    m_d3dContext->VSSetShader(
        m_vertexShader.Get(),
        nullptr,
        0
        );

    m_d3dContext->PSSetShader(
        m_pixelShader.Get(),
        nullptr,
        0
        );

    m_d3dContext->PSSetSamplers(
        0,                          // starting at the first sampler slot
        1,                          // set one sampler binding
        m_sampler.GetAddressOf()
        );

    // number of rectangles is in the 100-200 range
    for (int i = 0; i < m_rectangles.size(); i++)
    {
        // start rendering from the farthest rectangle
        int j = (i + m_farthestRectangle) % m_rectangles.size();

        m_vsConstantBufferData.model = m_rectangles[j].transform;
        m_vsConstantBufferData.opacity = m_rectangles[j].Opacity;
        m_vsConstantBufferData.highlight = m_rectangles[j].Highlight;
        m_vsConstantBufferData.shadow = m_rectangles[j].Shadow;
        m_vsConstantBufferData.textureTransitionAmount = m_rectangles[j].textureTransitionAmount;


        m_d3dContext->UpdateSubresource(
            m_vsConstantBuffer.Get(),
            0,
            NULL,
            &m_vsConstantBufferData,
            0,
            0
            );

        m_d3dContext->VSSetConstantBuffers(
            0,
            1,
            m_vsConstantBuffer.GetAddressOf()
            );

        m_d3dContext->PSSetConstantBuffers(
            0,
            1,
            m_vsConstantBuffer.GetAddressOf()
            );

        auto a = m_rectangles[j].textureId;
        auto b = m_rectangles[j].targetTextureId;
        auto srv1 = m_textures[m_rectangles[j].textureId].textureSRV.GetAddressOf();
        auto srv2 = m_textures[m_rectangles[j].targetTextureId].textureSRV.GetAddressOf();
        ID3D11ShaderResourceView* srvs[2];
        srvs[0] = *srv1;
        srvs[1] = *srv2;

        m_d3dContext->PSSetShaderResources(
            0,                          // starting at the first shader resource slot
            2,                          // set one shader resource binding
            srvs
            );

        m_d3dContext->DrawIndexed(
            m_indexCount,
            0,
            0
            );
    }
}

像素着色器

cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    float opacity;
    float3 highlight;
    float3 shadow;
    float textureTransitionAmount;
};

Texture2D baseTexture : register(t0);
Texture2D targetTexture : register(t1);
SamplerState simpleSampler : register(s0);

struct PixelShaderInput
{
    float4 pos : SV_POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
};

float4 main(PixelShaderInput input) : SV_TARGET
{
    float3 lightDirection = normalize(float3(0, 0, -1));

    float4 baseTexelColor = baseTexture.Sample(simpleSampler, input.tex);
    float4 targetTexelColor = targetTexture.Sample(simpleSampler, input.tex);
    float4 texelColor = lerp(baseTexelColor, targetTexelColor, textureTransitionAmount);
    float4 shadedColor;
    shadedColor.rgb = lerp(shadow.rgb, highlight.rgb, texelColor.r);
    shadedColor.a = texelColor.a * opacity;
    return shadedColor;
}
4

2 回答 2

2

正如 Jeremiah 所建议的那样,您可能不会将每个帧的纹理从 CPU 移动到 GPU,因为您必须为每个帧创建新纹理或使用“UpdateSubresource”或“Map/UnMap”方法。

我认为实例化不会对这种特定情况有所帮助,因为多边形的数量非常少(我会开始担心有几百万个多边形)。您的应用程序更有可能受到带宽/填充率的限制,因为您正在执行大量纹理采样/混合(这取决于GPU 上的结构填充率、像素填充率和ROP的数量)。

为了获得更好的性能,强烈建议:

附带说明一下,您可能会在https://gamedev.stackexchange.com/上对此类问题获得更多关注。

于 2012-06-02T02:52:44.747 回答
1

我不认为您正在从 GPU 到系统内存进行任何来回复制。您通常必须显式地调用 Map(...),或者通过 blitting 到您在系统内存中创建的纹理。

一个问题是,您正在为每个纹理进行 DrawIndexed(...) 调用。如果您通过批处理将大量工作发送给 GPU,则 GPU 的工作效率最高。实现此目的的一种方法是将 n 数量的纹理设置为 PSSetShaderResources(i, ...),然后执行 DrawIndexedInstanced(...)。然后,您的着色器代码将读取每个着色器资源并绘制它们。我在这里的 C++ DirectCanvas 代码(SpriteInstanced.cpp) 中执行此操作。这可以编写大量代码,但结果非常高效(我什至在着色器中进行矩阵运算以提高速度)。

另一种可能更简单的方法是试一试 DirectXTK spritebatch。

我在这个项目中使用了它......仅用于一个简单的 blit,但它可能是一个很好的开始,可以看到使用 spritebatch 所需的少量设置。

另外,如果可能的话,尝试“atlas”你的纹理。例如,尝试将尽可能多的“图像”放入纹理中并从中进行 blit,而不是为每个纹理设置一个纹理。

于 2012-06-01T20:38:45.690 回答