将 GLSL 移植

重要的应用程序接口(API)

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

在 OpenGL ES 2.0 中,着色器使用内部函数(如 gl_Positiongl_FragColorgl_FragData[n] 等内部函数(其中 n 是特定呈现目标的索引)返回数据。 在 Direct3D 中,没有特定的内部函数,着色器返回数据作为其各自的 main() 函数的返回类型。

要在着色器阶段(如顶点位置或法线)之间进行插值的数据,是通过使用 varying 声明来处理的。 但是,Direct3D 没有此声明;相反,要在着色器阶段之间传递的任何数据都必须使用 HLSL 语义进行标记。 选择的特定语义指示数据的目的,并且是。 例如,可以将你希望在片段着色器之间进行内插的顶点数据声明为:

float4 vertPos : POSITION;

float4 vertColor : COLOR;

WHERE 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 除以原始 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)。

说明书

步骤 1:移植顶点着色器

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

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 会被填充,然后提供给片元着色器(Pixel Shader)。

步骤 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 结构声明匹配,并且组件标量类型在两个声明中都匹配。
  • 在呈现器C++代码中,在常量缓冲区声明中使用 DirectXMath 类型来确保适当的数据打包。
  • 有效使用常量缓冲区的最佳方法是,根据它们的更新频率将着色器变量组织成常量缓冲区。 例如,如果有一些每帧更新一次的统一数据,以及仅在相机移动时更新的其他统一数据,请考虑将这些数据分离为两个单独的常量缓冲区。
  • 忘记应用的语义或未正确应用的语义将是着色器编译 (FXC) 错误最早的来源。 仔细检查它们! 文档可能会有点混乱,因为许多较旧的页面和示例引用 Direct3D 11 之前的不同版本的 HLSL 语义。
  • 请确保您知道针对每个着色器的目标 Direct3D 功能级别。 功能级别 9_* 的语义不同于 11_1 的语义。
  • SV_POSITION语义将关联的后插位置数据解析为坐标值,其中 x 介于 0 和呈现目标宽度之间,y 介于 0 和呈现目标高度之间,z 除以原始同质坐标 w 值 (z/w),w 被原始 w 值 (1/w) 除以 1。

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

移植着色器对象

传输顶点缓冲区和数据

在屏幕上绘图