Table of Contents

着色器阶段输入/输出的自动管理

高级 程序员

编写 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() ) );
	}
}

这个例子很简单。实际项目中的着色器要多得多,因此一次改动可能意味着要重写大量着色器、结构等等。

概括来说,添加一个新属性需要你从顶点输入开始,更新到你想使用该属性的最后一个阶段为止的所有阶段和中间结构。

media/hlsl_add_normal.png

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 池,并在需要的地方使用即可。

media/sdsl_add_normal.png

另请参阅