将 OpenGL ES 2.0 着色器管道与 Direct3D 进行比较

重要的 API

从概念上来说,Direct3D 11 着色器管道与 OpenGL ES 2.0 中的着色器管道非常相似。 但是,就 API 设计而言,用于创建和管理着色器阶段的主要组件是两个主要接口 ID3D11Device1ID3D11DeviceContext1 的一部分。 本主题尝试在这些接口中将常用的 OpenGL ES 2.0 着色器管道 API 模式映射到 Direct3D 11 同等模式。

查看 Direct3D 11 着色器管道

着色器对象是使用 ID3D11Device1 接口上的方法创建的,如 ID3D11Device1::CreateVertexShaderID3D11Device1::CreatePixelShader

Direct3D 11 图形管道由 ID3D11DeviceContext1 接口的实例管理,并且包含以下阶段:

  • 输入装配器阶段。 输入装配器阶段为管道提供数据(三角形、直线和点)。 支持此阶段的 ID3D11DeviceContext1 方法的前缀为“IA”。
  • 顶点着色器阶段 - 顶点着色器阶段处理顶点,通常执行诸如转换、换肤以及照明之类的操作。 顶点着色器始终获取一个输入顶点并生成一个输出顶点。 支持此阶段的 ID3D11DeviceContext1 方法的前缀为“VS”。
  • 流输出阶段 - 流输出阶段将管道中的基元数据沿着到光栅器的路线流到内存中。 数据可以流出和/或传递到光栅器中。 流出到内存的数据可以作为输入数据再次循环回到管道,或者从 CPU 读回。 支持此阶段的 ID3D11DeviceContext1 方法的前缀为“SO”。
  • 光栅器阶段 - 光栅器剪辑基元,为像素着色器准备基元并确定如何调用像素着色器。 你可以通过告知管道没有像素着色器(通过 ID3D11DeviceContext::PSSetShader 将像素着色器阶段设置为 NULL)并禁用深度和模具测试(在 D3D11_DEPTH_STENCIL_DESC 中将 DepthEnable 和 StencilEnable 设置为 FALSE)来禁用光栅化。 禁用后,与光栅化有关的管道计数器将无法更新。
  • 像素着色器阶段 - 像素着色器阶段接收基元的插值数据并生成每像素数据,如颜色。 支持此阶段的 ID3D11DeviceContext1 方法的前缀为“PS”。
  • 输出合并器阶段 - 输出合并器阶段将各种类型的输出数据(像素着色器值、深度和模具信息)与呈现器目标的内容以及深度/模具缓冲区组合在一起,以生成最终的管道结果。 支持此阶段的 ID3D11DeviceContext1 方法的前缀为“OM”。

(还有针对几何图形着色器、外壳着色器、曲面细分以及域着色器的阶段,但由于在 OpenGL ES 2.0 中这些阶段没有类似内容,因此我们不会在这里讨论它们。)有关适用于这些阶段的方法的完整列表,请参阅 ID3D11DeviceContextID3D11DeviceContext1 参考页面。 ID3D11DeviceContext1 扩展了 Direct3D 11 的 ID3D11DeviceContext

创建着色器

在 Direct3D 中,在编译和加载着色器资源之前不会创建着色器资源,而是在加载 HLSLis 时创建该资源。 因此,没有与 glCreateShader 直接类似的函数,glCreateShader 创建特定类型的已初始化着色器资源(如 GL_VERTEX_SHADER 或 GL_FRAGMENT_SHADER)。 着色器是在使用特定的函数(如 ID3D11Device1::CreateVertexShaderID3D11Device1::CreatePixelShader)加载 HLSL 之后创建的,并且采用类型和编译的 HLSL 作为参数。

OpenGL ES 2.0 Direct3D 11
glCreateShader 成功加载编译的着色器对象并将这些对象作为缓冲区传递给 CSO 之后调用 ID3D11Device1::CreateVertexShaderID3D11Device1::CreatePixelShader

 

编译着色器

必须在通用 Windows 平台 (UWP) 应用中将 Direct3D 着色器预先编译为已编译的着色器对象 (.cso) 文件,并使用其中一个 Windows 运行时文件 API 对其进行加载。 (桌面应用可以在运行时通过文本文件或字符串来编译着色器。)CSO 文件通过隶属于 Microsoft Visual Studio 项目的任何 .hlsl 文件生成,文件名相同,只是文件的扩展名为 .cso。 请确保交付你的程序包时附带这些文件!

OpenGL ES 2.0 Direct3D 11
glCompileShader 不适用。 将着色器编译为 Visual Studio 中的 .cso 文件,并将这些文件包含在你的程序包中。
对编译状态使用 glGetShaderiv 不适用。 如果编译中存在错误,请从 Visual Studio 的 FX 编译器 (FXC) 中查看编译输出。 如果编译成功,则会创建一个相应的 CSO 文件。

 

加载着色器

如创建着色器的部分中所述,Direct3D 11 会在相应的 CSO 文件加载到缓冲区中并传递到下表中的其中一个方法时创建着色器。

OpenGL ES 2.0 Direct3D 11
ShaderSource 在成功加载编译的着色器对象之后调用 ID3D11Device1::CreateVertexShaderID3D11Device1::CreatePixelShader

 

设置管道

OpenGL ES 2.0 具有“着色器程序”对象,该对象包含多个用于执行的着色器。 各个着色器都与该着色器程序对象相连接。 但是,在 Direct3D 11 中,你可以直接使用呈现上下文 (ID3D11DeviceContext1) 并在其上创建着色器。

OpenGL ES 2.0 Direct3D 11
glCreateProgram 不适用。 Direct3D 11 不使用着色器程序对象抽象。
glLinkProgram 不适用。 Direct3D 11 不使用着色器程序对象抽象。
glUseProgram 不适用。 Direct3D 11 不使用着色器程序对象抽象。
glGetProgramiv 使用你创建的 ID3D11DeviceContext1 参考。

 

使用静态的 D3D11CreateDevice 方法创建 ID3D11DeviceContext1ID3D11Device1 的实例。

Microsoft::WRL::ComPtr<ID3D11Device1>          m_d3dDevice;
Microsoft::WRL::ComPtr<ID3D11DeviceContext1>  m_d3dContext;

// ...

D3D11CreateDevice(
  nullptr, // Specify nullptr to use the default adapter.
  D3D_DRIVER_TYPE_HARDWARE,
  nullptr,
  creationFlags, // Set set debug and Direct2D compatibility flags.
  featureLevels, // List of feature levels this app can support.
  ARRAYSIZE(featureLevels),
  D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for UWP apps.
  &device, // Returns the Direct3D device created.
  &m_featureLevel, // Returns feature level of device created.
  &m_d3dContext // Returns the device's immediate context.
);

设置视区

在 Direct3D 11 中设置视口与在 OpenGL ES 2.0 中设置视口的方法非常相似。 在 Direct3D 11 中,使用一个已配置的 CD3D11\_VIEWPORT 调用 ID3D11DeviceContext::RSSetViewports

Direct3D 11:设置视口。

CD3D11_VIEWPORT viewport(
        0.0f,
        0.0f,
        m_d3dRenderTargetSize.Width,
        m_d3dRenderTargetSize.Height
        );
m_d3dContext->RSSetViewports(1, &viewport);
OpenGL ES 2.0 Direct3D 11
glViewport CD3D11_VIEWPORTID3D11DeviceContext::RSSetViewports

 

配置顶点着色器

在 Direct3D 11 中配置顶点着色器是在加载该着色器时完成的。 使用 ID3D11DeviceContext1::VSSetConstantBuffers1 将 uniform 作为常量缓冲区传递。

OpenGL ES 2.0 Direct3D 11
glAttachShader ID3D11Device1::CreateVertexShader
glGetShaderiv、glGetShaderSource ID3D11DeviceContext1::VSGetShader
glGetUniformfv、glGetUniformiv ID3D11DeviceContext1::VSGetConstantBuffers1

 

配置像素着色器

在 Direct3D 11 中配置像素着色器是在加载该着色器时完成的。 使用 ID3D11DeviceContext1::PSSetConstantBuffers1 将 Uniform 作为常量缓冲区传递。

OpenGL ES 2.0 Direct3D 11
glAttachShader ID3D11Device1::CreatePixelShader
glGetShaderiv、glGetShaderSource ID3D11DeviceContext1::PSGetShader
glGetUniformfv、glGetUniformiv ID3D11DeviceContext1::PSGetConstantBuffers1

 

生成最终结果

管道完成后,会在后台缓冲区中绘制着色器各个阶段的结果。 在 Direct3D 11 中,与 Open GL ES 2.0 一样,这涉及到调用绘图命令将结果输出为后台缓冲区中的颜色映射,然后将该后缓冲区发送到显示器。

OpenGL ES 2.0 Direct3D 11
glDrawElements ID3D11DeviceContext1::DrawID3D11DeviceContext1::DrawIndexed(或 ID3D11DeviceContext1 上的其他 Draw* 方法)。
eglSwapBuffers IDXGISwapChain1::Present1

 

将 GLSL 移植到 HLSL

除了复杂的类型支持和一些整体语法之外,GLSL 和 HLSL 没有太大的不同。 很多开发人员发现将常用的 OpenGL ES 2.0 指令和定义的别名设置为其 HLSL 同等内容最便于在两者之间进行移植。 请注意,Direct3D 使用着色器模型版本来表示图形接口所支持的 HLSL 的功能集;OpenGL 有一个不同版本的 HLSL 规范。 下表给出了其他版本中为 Direct3D 11 和 OpenGL ES 2.0 定义着色器语言功能集的大概思路。

着色器语言 GLSL 功能版本 Direct3D 着色器模型
Direct3D 11 HLSL ~4.30。 SM 5.0
GLSL ES for OpenGL ES 2.0 1.40。 GLSL ES for OpenGL ES 2.0 的旧实现可能会使用 1.10 至 1.30。 使用 glGetString(GL_SHADING_LANGUAGE_VERSION) 或 glGetString(SHADING_LANGUAGE_VERSION) 检查你的原始代码,以确定这种情况。 ~SM 2.0

 

有关这两个着色器语言之间的差别以及常用语法映射的详细信息,请阅读 GLSL 到 HLSL 参考

将 OpenGL 内部函数移植到 HLSL 语义

Direct3D 11 HLSL 语义是用来标识在应用和着色器程序之间传递的值的字符串,如 uniform 或属性名称。 尽管它们可以是任何类型的字符串,但最好使用指示用法的字符串,如 POSITION 或 COLOR。 在构造常量缓冲区或缓冲区输入布局时分配这些语义。 也可以为语义中附加一个介于 0 和 7 之间的数字,以便你可以为相似的值使用不同的寄存器。 例如:COLOR0、COLOR1、COLOR2...

前缀为“SV_”的语义是系统值语义,它们是由你的着色器程序编写的;你的应用本身(在 CPU 上运行)无法修改它们。 通常,它们的值为图形管道中其他着色器阶段的输入或输出,或者完全由 GPU 生成。

此外,SV_ 语义在用于指定着色器阶段的输入或输出时有着不同的行为。 例如,SV_POSITION(输出)包含在顶点着色器阶段转换的顶点数据,而 SV_POSITION(输入)则包含在光栅化期间插入的像素位置值。

下面是常见 OpenGL ES 2.0 着色器内部函数的一些映射:

OpenGL 系统值 使用此 HLSL 语义
gl_Position POSITION(n) 针对顶点缓冲区数据。 SV_POSITION 提供像素着色器的像素位置,不能由你的应用编写。
gl_Normal NORMAL(n) 针对由顶点缓冲区提供的普通数据。
gl_TexCoord[n] TEXCOORD(n) 针对提供给着色器的纹理 UV(在某些 OpenGL 文档中为 ST)坐标数据。
gl_FragColor COLOR(n) 针对提供给着色器的 RGBA 颜色数据。 请注意,处理方式与坐标数据相同;语义只是帮助你确定它是颜色数据。
gl_FragData[n] SV_Target[n] 用于从像素着色器写入到目标纹理或其他像素缓冲区。

 

编写语义代码的方法并不同于在 OpenGL ES 2.0 中使用内部函数。 在 OpenGL 中,你可以直接访问很多内部函数,而无需进行任何配置或声明;在 Direct3D 中,必须在特定的常量缓冲区中声明一个字段才能使用某个特殊的语义,或者你将其声明为着色器的 main() 方法的返回值。

下面是常量缓冲区定义中使用的语义示例:

struct VertexShaderInput
{
  float3 pos : POSITION;
  float3 color : COLOR0;
};

// The position is interpolated to the pixel value by the system. The per-vertex color data is also interpolated and passed through the pixel shader. 
struct PixelShaderInput
{
  float4 pos : SV_POSITION;
  float3 color : COLOR0;
};

该代码定义一对简单的常量缓冲区

下面是用来定义片段着色器返回值的语义示例:

// A pass-through for the (interpolated) color data.
float4 main(PixelShaderInput input) : SV_TARGET
{
  return float4(input.color,1.0f);
}

在本例中,SV_TARGET 是呈现器目标的位置,当着色器执行完后会向其中写入像素颜色(被定义为具有四个浮点值的向量)。

有关 Direct3D 的语义使用的详细信息,请阅读 HLSL 语义