首页 > 解决方案 > 为什么顺序在着色器中很重要?

问题描述

速记

这个问题有C++标签,因为与DirectXin合作的开发人员C++比在C#. 我不相信这个问题与这两种语言直接相关,而是与所使用的类型(据我所知完全一样)或DirectX它本身以及它如何编译着色器有关。如果在其中工作的人C++知道更好且更具描述性的答案,那么我更喜欢这个答案而不是我自己的答案。我了解两种语言,但C#主要使用。


概述

HLSL着色器中,当设置我的常量缓冲区时,我遇到了一个相当奇怪的问题。有问题的原始常量缓冲区设置如下:

cbuffer ObjectBuffer : register(b0) {
    float4x4 WorldViewProjection;
    float4x4 World;
    float4x4 WorldInverseTranspose;
}

cbuffer ViewBuffer : register(b1) {
    DirectionalLight Light;
    float3 CameraPosition;
    float3 CameraUp;
    float2 RenderTargetSize;
}

如果我交换b0andb1寄存器,渲染不再有效(e1)。如果我不理会这些寄存器,WorldWorldViewProjection一次又一次地交换顺序,则渲染不再起作用(e2)。但是,只需在文件中移动ViewBuffer上述内容而不进行其他修改,它就可以正常工作。ObjectBufferHLSL

现在,我希望寄存器放置非常重要,并且第一个寄存器b0需要该缓冲区中给定的三个属性,并且我知道HLSL常量缓冲区需要在 16 字节块中。然而,这给我留下了一些问题。


问题

鉴于HLSL期望常量缓冲区位于 16 字节块中的事实;

类型与本质上是数组数组的类型不float4x4一样吗?Matrix

[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[    TOTAL   ] = 64 bytes

由于 afloat本身是 4 个字节,这意味着 afloat4是 16 个字节,因此 afloat4x4是 64 个字节。那么,如果尺寸保持不变,为什么顺序很重要呢?

标签: c#c++directx-11hlslsharpdx

解决方案


速记

我目前正在进一步分析这个问题,以便我可以给出更详细和准确的答案。当我发现更多细节时,我将更新问题和答案以尽可能准确地反映。


基本答案

上述问题的确切问题(在发布时未知)是HLSL缓冲区与其表示不匹配C#;因此,变量的重新排序导致着色器失败。但是,我仍然不确定为什么类型相同。我在路上学到了一些其他的东西来寻求答案,并决定把它们贴在这里。


为什么订单很重要

经过一些进一步的研究和测试,我仍然不能 100% 确定类型都相同的原因。总的来说,我相信这可能是由于预期cbuffer的类型和struct. 在这种情况下,如果您cbuffer期望 a boolfirst 然后 a float,那么重新排列会导致问题。

cbuffer MaterialBuffer : register(b0) {
    bool HasTexture;
    float SpecularPower;
    float4 Ambient;
    ...
}
// Won't work.
public struct MaterialBuffer {
    public float SpecularPower;
    public Vector2 padding2;
    public bool HasTexture;
    private bool padding0;
    private short padding1;
    public Color4 Ambient;
    ...
}
// Works.
public struct MaterialBuffer {
    public bool HasTexture;
    private bool padding0;
    private short padding1;
    public float SpecularPower;
    public Vector2 padding2;
    public Color4 Ambient;
    ...
}

我投入了一些研究工作来测试类型字节大小的差异,这似乎并没有真正改变任何东西,但我将在这里发布我对常见基本类型的发现:

1 Byte  : bool, sbyte, byte
2 Bytes : short, ushort
4 Bytes : int, uint, float
8 Bytes : long, ulong, double
16 Bytes: decimal

您必须意识到用于构造更复杂类型的基本类型。例如说你有Vector2一个X属性和一个Y属性。如果这些由float类型表示,那么您将需要在下一个属性之前进行 8 字节填充,除非您有其他东西可以帮助达到 16 字节。但是,如果它们由double类型或decimal类型表示,那么大小是不同的,您需要注意这一点。


注册分配

我能够解决注册问题;这也对应于C#设置缓冲区时的一侧。在设置缓冲区时,您为这些缓冲区分配索引,并且HLSL预期使用相同的索引。

// Buffer declarations in HLSL.
cbuffer ViewBuffer : register(b0)
cbuffer CameraBuffer : register(b1);
cbuffer MaterialBuffer : register(b2);

// Buffer assignments in C#.
context.VertexShader.SetConstantBuffer(0, viewBuffer);
context.VertexShader.SetConstantBuffer(1, cameraBuffer);
context.VertexShader.SetConstantBuffer(2, materialBuffer);

上述代码将按预期工作,因为缓冲区已分配给正确的寄存器。但是,例如,如果我们将相机的缓冲区更改为 8,则cbuffer必须将其分配给寄存器b8才能正常工作。出于这个确切原因,下面的代码不起作用。

cbuffer CameraBuffer : register(b1)
context.VertexShader.SetConstantBuffer(8, cameraBuffer);

推荐阅读