移植 GLSL

重要的 API

转到创建和配置缓冲区及着色器对象的代码之后,应该将这些着色器中的代码从 OpenGL ES 2.0 的 GL 着色器语言 (GLSL) 移植到 Direct3D 11 的高级着色器语言 (HLSL)。

在 OpenGL ES 2.0 中,着色器在执行之后使用内部函数(如 gl_Position、gl_FragColor 或 gl_FragData[n],其中 n 是特定呈现目标的索引)返回数据。 在 Direct3D 中,没有特定的内部函数,着色器以其各自的 main() 函数的返回类型返回数据。

要在着色器阶段之间进行内插的数据(如顶点位置或法线)通过使用 varying 声明进行处理。 但是,Direct3D 没有此声明;在着色器阶段之间传递的所有数据都必须使用 HLSL 语义进行标记。 所选择的特定语义指示数据的用途及含义。 例如,将要在各个片段着色器之间进行插值的顶点数据声明为:

float4 vertPos : POSITION;

float4 vertColor : COLOR;

其中 POSITION 是用于标识顶点位置数据的语义。 POSITION 还是一个特殊情况,因为进行内插后,像素着色器不能对其进行访问。 因此,必须使用 SV_POSITION 指定像素着色器的输入并且将内插的顶点数据放置到该变量中。

float4 position : SV_POSITION;

可以在着色器的主体 (main) 方法上声明语义。 对于像素着色器,SV_TARGET[n] 表示呈现目标,主体方法上需要使用它。 (没有数字后缀的 SV_TARGET 默认情况下将呈现目标索引设置为 0。)

还要注意,需要使用顶点着色器来输出 SV_POSITION 系统值语义。 该语义将顶点位置数据解析为坐标值,其中 x 介于 -1 和 1 之间,y 介于 -1 和 1 之间,z 除以原始齐次坐标 w 值 (z/w) ,并且 w 为 1 除以原始的 w 值 (1/w)。 像素着色器使用 SV_POSITION 系统值语义检索屏幕上的像素位置,其中 x 介于 0 和呈现目标宽度之间,y 介于 0 和呈现目标高度(每个偏移 0.5)之间。 功能级别 9_x 像素着色器不能从 SV_POSITION 值读取。

常量缓冲区必须使用 cbuffer 进行声明,并且必须与用于查找的特定起始寄存器相关联。

Direct3D 11:HLSL 常量缓冲区声明

cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
  matrix mvp;
};

在此处,常量缓冲区使用寄存器 b0 来包含打包的缓冲区。 所有寄存器都采用 b# 格式。 有关常量缓冲区、寄存器以及数据打包的 HLSL 实现的详细信息,请阅读着色器常量 (HLSL)

Instructions

步骤 1:移植顶点着色器

在我们的简单 OpenGL ES 2.0 示例中,顶点着色器具有三个输入:一个常量模型视图投影 4x4 矩阵和两个 4 坐标矢量。 这两个矢量包含顶点位置及其颜色。 着色器将位置矢量转换为透视坐标,并将其分配给 gl_Position 内部函数以进行光栅化。 还需要将顶点颜色复制到 varying 变量以在光栅化期间进行内插。

OpenGL ES 2.0:立方体对象的顶点着色器 (GLSL)

uniform mat4 u_mvpMatrix; 
attribute vec4 a_position;
attribute vec4 a_color;
varying vec4 destColor;

void main()
{           
  gl_Position = u_mvpMatrix * a_position;
  destColor = a_color;
}

现在,在 Direct3D 中,常量模型视图投影矩阵包含在寄存器 b0 上打包的一个常量缓冲区中,特别是,顶点位置和颜色都使用各自相应的 HLSL 语义进行了标记:POSITION 和 COLOR。 由于我们的输入布局指示这两个顶点值的特定排列,因此你将创建一个结构来包含它们并在着色器主体函数 (main) 上将其声明为输入参数的类型。 (还可以将它们指定为两个参数,但这样做可能会很麻烦。)还需要为此阶段指定一个输出类型(该类型包含内插的位置和颜色),并需要将其声明为顶点着色器的主体函数的返回值。

Direct3D 11:立方体对象的顶点着色器 (HLSL)

cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
  matrix mvp;
};

// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
  float3 pos : POSITION;
  float3 color : COLOR;
};

// Per-vertex color data passed through the pixel shader.
struct PixelShaderInput
{
  float3 pos : SV_POSITION;
  float3 color : COLOR;
};

PixelShaderInput main(VertexShaderInput input)
{
  PixelShaderInput output;
  float4 pos = float4(input.pos, 1.0f); // add the w-coordinate

  pos = mul(mvp, projection);
  output.pos = pos;

  output.color = input.color;

  return output;
}

在光栅化期间填充输出数据类型 PixelShaderInput 并将其提供给片段(像素)着色器。

步骤 2:移植片段着色器

GLSL 中的示例片段着色器极其简单:提供具有内插颜色值的 gl_FragColor 内部函数。 OpenGL ES 2.0 会将其写入到默认的呈现目标。

OpenGL ES 2.0:立方体对象的片段着色器 (GLSL)

varying vec4 destColor;

void main()
{
  gl_FragColor = destColor;
} 

Direct3D 几乎同样简单。 唯一明显的区别是像素着色器的主体函数必须返回一个值。 由于颜色是一个 4 坐标 (RGBA) 的浮点值,因此将 float4 表示为返回类型,然后将默认呈现目标指定为 SV_TARGET 系统值语义。

Direct3D 11:立方体对象的像素着色器 (HLSL)

struct PixelShaderInput
{
  float4 pos : SV_POSITION;
  float3 color : COLOR;
};


float4 main(PixelShaderInput input) : SV_TARGET
{
  return float4(input.color, 1.0f);
}

像素在位置处的颜色将写入到呈现目标。 现在,让我们在绘制到屏幕中看一看如何显示该呈现目标的内容!

上一步

移植顶点缓冲区和数据

后续步骤

绘制到屏幕

注解

了解 HLSL 语义以及常量缓冲区的打包可以为你省去调试的麻烦,并且还可以提供优化机会。 如果有机会,请通读变量语法 (HLSL)Direct3D 11 中的缓冲区简介以及如何创建常量缓冲区。 如果没有机会通读这些内容,那么请记住下面这些有关语义和常量缓冲区的开始提示:

  • 始终要仔细检查呈现器的 Direct3D 配置代码,以确保常量缓冲区的结构与 HLSL 中的 cbuffer struct 声明相匹配,并且确保两个声明之间的组件标量类型相匹配。
  • 在呈现器的 C++ 代码中,在常量缓冲区声明中使用 DirectXMath 类型,以确保正确的数据打包。
  • 有效使用常量缓冲区的最佳方法是,根据它们的更新频率将着色器变量组织成常量缓冲区。 例如,如果你有一些每个帧都进行更新的 uniform 数据,还有仅在相机移动时更新的其他 uniform 数据,则需考虑将该数据分为两个不同的常量缓冲区。
  • 你忘记应用或应用错误的语义将是着色器编译 (FXC) 错误的最早来源。 一定要对它们进行仔细检查! 本文可能有点乱,因为很多较旧的页面和示例都参考了 Direct3D 11 之前不同版本的 HLSL 语义。
  • 确保你了解每个着色器的目标 Direct3D 功能级别。 功能级别 9_* 的语义与 11_1 的语义有所不同。
  • SV_POSITION 语义将关联的内插后位置数据解析为坐标值,其中 x 介于 0 和呈现目标宽度之间,y 介于 0 和呈现目标高度之间,z 为除以原始齐次坐标 w 值 (z/w),w 为 1 除以原始 w 值 (1/w)。

如何:将简单的 OpenGL ES 2.0 呈现器移植到 Direct3D 11

移植着色器对象

移植顶点缓冲区和数据

绘制到屏幕