着色器阶段输入/输出的自动管理
高级 程序员
编写 HLSL 着色器时,你必须精确定义顶点属性,并小心地将它们传递通过最终着色器的各个阶段。
下面是一个使用顶点颜色的简单 HLSL 着色器示例。
struct VS_IN
{
float4 pos : POSITION;
float4 col : COLOR;
};
struct PS_IN
{
float4 pos : SV_POSITION;
float4 col : COLOR;
};
PS_IN VS( VS_IN input )
{
PS_IN output = (PS_IN)0;
output.pos = input.pos;
output.col = input.col;
return output;
}
float4 PS( PS_IN input ) : SV_Target
{
return input.col;
}
technique10 Render
{
pass P0
{
SetGeometryShader( 0 );
SetVertexShader( CompileShader( vs_4_0, VS() ) );
SetPixelShader( CompileShader( ps_4_0, PS() ) );
}
}
想象一下,你想为模型添加法线并相应地调整最终颜色。你就必须修改计算颜色的代码,并调整中间结构,以便将该属性从顶点着色器传递到像素着色器。你还必须注意所使用的语义。
**代码:**修改后的 HLSL 着色器
struct VS_IN
{
float4 pos : POSITION;
float4 col : COLOR;
float3 normal : NORMAL;
};
struct PS_IN
{
float4 pos : SV_POSITION;
float4 col : COLOR;
float3 normal : TEXCOORD0;
};
PS_IN VS( VS_IN input )
{
PS_IN output = (PS_IN)0;
output.pos = input.pos;
output.col = input.col;
output.normal = input.normal;
return output;
}
float4 PS( PS_IN input ) : SV_Target
{
return input.col * max(input.normal.z, 0.0);
}
technique10 Render
{
pass P0
{
SetGeometryShader( 0 );
SetVertexShader( CompileShader( vs_4_0, VS() ) );
SetPixelShader( CompileShader( ps_4_0, PS() ) );
}
}
这个例子很简单。实际项目中的着色器要多得多,因此一次改动可能意味着要重写大量着色器、结构等等。
概括来说,添加一个新属性需要你从顶点输入开始,更新到你想使用该属性的最后一个阶段为止的所有阶段和中间结构。

SDSL
SDSL 提供了一种便捷的方式在着色器的不同阶段之间传递参数。流(stream)变量:
- 是变量
- 像任何着色器成员一样定义,并带有 stream 关键字
- 使用 streams 前缀访问(省略它会导致编译错误)。当 stream 有歧义(同名)时,应在变量前给出着色器名称(即
streams.<my_shader>.<my_variable>)
Streams 将 attribute、varying 和 output 的概念归并为一个概念。
- attribute 是在顶点着色器中先读取后写入的 stream。
- varying 是存在于着色器各阶段之间的 stream。
- output 是先赋值后读取的 stream。
可以把 streams 当作全局对象,你可以在任何地方访问它们,而无需将它们作为函数的参数传递。
Note
你不必为这些变量创建语义;编译器会自动创建。不过请记住,共享相同语义的变量会在最终着色器中被合并。当你想在局部使用某个 stream 变量而不必继承声明它的着色器时,这种行为很有用。
声明 stream 之后,你可以在着色器的任何阶段访问它。着色器编译器会处理好一切。这些变量只需像其他变量一样从调用代码(即继承层次结构)中可见即可。
**代码:**stream 的定义与使用:
shader BaseShader
{
stream float3 myVar;
float3 Compute()
{
return streams.myVar;
}
};
**代码:**stream 的限定
shader StreamShader
{
stream float3 myVar;
};
shader ShaderA : BaseShader, StreamShader
{
float3 Test()
{
return streams.BaseShader.myVar + streams.StreamShader.myVar;
}
}
SDSL 着色器示例
让我们看看与第一个示例相同的 HLSL 着色器,但用 SDSL 来编写。
**代码:**用 SDSL 编写的相同着色器
shader MyShader : ShaderBase
{
stream float4 pos : POSITION;
stream float4 col : COLOR;
override void VSMain()
{
streams.ShadingPosition = streams.pos;
}
override void PSMain()
{
streams.ColorTarget = streams.col;
}
};
现在让我们添加法线计算。
**代码:**修改后的 SDSL 着色器
shader MyShader : ShaderBase
{
stream float4 pos : POSITION;
stream float4 col : COLOR;
stream float3 normal : NORMAL;
override void VSMain()
{
streams.ShadingPosition = streams.pos;
}
override void PSMain()
{
streams.ColorTarget = streams.col * max(streams.normal.z, 0.0);
}
};
在 SDSL 中,添加新属性只需将其加入 streams 池,并在需要的地方使用即可。
