一、问题
当继续编写D3D12后续教程示例的过程中,在调用ID3D12Device::CreateGraphicsPipelineState时遇到如下的错误,调用失败,无法创建管线状态对象:
D3D12 ERROR: ID3D12Device::CreateGraphicsPipelineState: Rasterization Unit is enabled (PixelShader is not NULL or Depth/Stencil test is enabled and RasterizedStream is not D3D12_SO_NO_RASTERIZED_STREAM) but position is not provided by the last shader before the Rasterization Unit. [ STATE_CREATION ERROR #682: CREATEGRAPHICSPIPELINESTATE_POSITION_NOT_PRESENT]
这个错误的提示,起初让我一脸懵逼,它提示的本意是说设置了光栅化阶段,但是后续的阶段并没有提供Position流变量,然后我想到的就是仔细检查了提供给ID3D12Device::CreateGraphicsPipelineState函数的所有参数,一个个比对,居然在同样参数的情况下,一个例子可以创建成功,另一个例子死活不行。百思不得其解!
二、解决
接着没有办法只好去问度娘和CSDN,结果是一无所获,于是大胆的翻墙出去古狗一下,居然发现在微软示例的问题列表里也有人遇到了跟我一样的问题,而回答只是简单的说要用SV_POSITION,突然间我反应过来原来是Shader文件写的有问题,错误的初始版本如下:
struct VSInput
{
float4 position : POSITION0; //顶点位置
float4 normal : NORMAL0; //法线
float2 texuv : TEXCOORD0; //纹理坐标
};
struct PSInput
{
float4 position : POSITION0; //位置
float4 normal : NORMAL0; //法线
float2 texuv : TEXCOORD0; //纹理坐标
};
PSInput VSMain(VSInput vin)
{
PSInput vout;
// 最终变换到视空间
vout.position = mul(vout.position, mxMVP);
// 向量仅做世界矩阵变换
vout.normal = mul(vin.normal, mxWorld);
// 纹理坐标原样输出
vout.texuv = vin.texuv;
return vout;
}
问题就在于用于Vertex Shader阶段输出的Position变量,语义依然是:POSITION0,需要改成SV_POSITION0如下:
struct VSInput
{
float4 position : POSITION0; //顶点位置
float4 normal : NORMAL0; //法线
float2 texuv : TEXCOORD0; //纹理坐标
};
struct PSInput
{
float4 position : SV_POSITION0; //位置
float4 normal : NORMAL0; //法线
float2 texuv : TEXCOORD0; //纹理坐标
};
PSInput VSMain(VSInput vin)
{
PSInput vout;
// 最终变换到视空间
vout.position = mul(vout.position, mxMVP);
// 向量仅做世界矩阵变换
vout.normal = mul(vin.normal, mxWorld);
// 纹理坐标原样输出
vout.texuv = vin.texuv;
return vout;
}
三、简析
结合ID3D12Device::CreateGraphicsPipelineState报错的提示,并结合SV_语义文法前缀的含义,其实这个问题就很好理解了,本质上其实POSITION只是个语法层面的语义,主要用于从C/C++代码中传递顶点位置数据到Shader中,或者说是从CPU内存中传递到GPU显存中的一个标识,GPU并不过多解读其含义,只是简单的知道它修饰的变量是个“位置”,甚至GPU只是理解为是一个普通向量,这主要是为了Shader编写的灵活性,比如通常用一些语义文法传递一些其它含义的辅助变量到GPU渲染管线中,或者在渲染管线各阶段间传递一些其他含义的向量变量,这是一个很常用的技巧,比如下面的Shader片段:
struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 viewDir : TEXCOORD1;
fixed4 color : COLOR;
};
其中的TEXCOORD1语义文法修饰的变量viewDir,本质上就不是一个纹理坐标了,而是一个视见方向向量,用于一些特殊渲染效果的计算,如果此时GPU非要过分解读这个变量为纹理坐标,那么相关的计算也就没法进行了,从而限制了Shader编写的灵活性,也就失去了Shader编写实现特效的可编程管线本身的意义了。
所以如果GPU过分解读,反倒限制了这些向量变量的使用,最终怎么计算这些向量变量则由程序员通过Shader来控制,另外,又因为光栅化过程其实是个非可编程的固化阶段,所以光栅化过程是没法直接“理解和运用”这些向量变量的(其实本质是说,GPU没法把这些普通的向量放到专用的管线寄存器中供固定阶段使用),因此这个“位(xiang)置(liang)”是不能用来直接当做光栅化之后齐次投影坐标空间中的位置的,或者直白的说纯固化的光栅化阶段只能理解它是个“向量”(),但不能进一步理解它为顶点在投影空间中的“真实位置”,而只有SV_前缀语义文法修饰的变量才是指出最终真正的顶点“位置”,当然它是指齐次投影坐标空间中“真实位置”。而SV前缀的本意就是System Value,也就是说只有SV_前缀的语义文法修饰的变量,才会被GPU正确的理解为它能理解的真正含义的向量变量,或者说GPU的一些固定阶段就会进一步理解其含义(也就是放到专用寄存器中),并进行固化过程的运算,比如光栅化阶段,或者是Tesselator阶段。同理,这样的深入“理解”也导致最终被这些SV_前缀修饰的向量变量含义和用法基本也就被固定了,这样也就使得这些向量变量就不能像没修饰定义的向量变量那样可以被灵活使用了(也就是专用寄存器中的变量不能用于一般用途)。
最终,通过这个本来让我一脸懵逼的错误,从而深入的理解了POSITION和SV_POSITION语义修饰符之间的本质差异了。故成文,算是对这个问题的一个备忘,并分享给各位,也让大家深刻理解这个shader编写中的“小玄机”。
暂无评论内容