为啥 HLSL 有语义?

Posted

技术标签:

【中文标题】为啥 HLSL 有语义?【英文标题】:Why does HLSL have semantics?为什么 HLSL 有语义? 【发布时间】:2014-02-27 09:26:20 【问题描述】:

在 HLSL 中,我必须使用语义将信息从顶点着色器传递到片段着色器。在 GLSL 中不需要语义。语义的客观好处是什么?

示例:GLSL

顶点着色器

varying vec4 foo
varying vec4 bar;

void main() 
  ...
  foo = ...
  bar = ...

片段着色器

varying vec4 foo
varying vec4 bar;

void main() 
  gl_FragColor = foo * bar;

示例:HLSL

顶点着色器

struct VS_OUTPUT

   float4  foo : TEXCOORD3;
   float4  bar : COLOR2;


VS_OUTPUT whatever()

  VS_OUTPUT out;

  out.foo = ...
  out.bar = ...

  return out;

像素着色器

void main(float4 foo : TEXCOORD3,
          float4 bar : COLOR2) : COLOR

    return foo * bar;

我看到VS_OUTPUT 中的foobar 如何连接到片段着色器中main 中的foobar。我不明白为什么我要手动选择语义来承载数据。为什么,像 GLSL 一样,DirectX 不能在链接着色器时找出将数据放在哪里并连接它?

手动指定语义是否有一些更具体的优势,还是只是从汇编语言着色器时代遗留下来的?选择 TEXCOORD4 而不是 COLOR2 或 BNORMAL1 是否有一些速度优势?

我知道语义可以暗示意义,foobar 没有意义,但如果foo 不是 TEXCOORD 并且 bar 不是颜色,它们也可以掩盖意义。我们不会将语义放在 C#、C++ 或 javascript 变量上,那么为什么 HLSL 需要它们?

【问题讨论】:

【参考方案1】:

简单地说,(旧)glsl 使用这个变量命名变量(请注意,现在不推荐使用变量)。

语义的一个明显好处是,您不需要在阶段之间使用相同的变量名称,因此 DirectX 管道通过语义而不是变量名称进行匹配,并且只要您具有兼容的布局,就会重新排列数据。

如果将 foo 重命名为 foo2,则可能需要在所有着色器(以及最终的后续着色器)中替换此名称。有了语义,你就不需要这个了。

此外,由于您不需要完全匹配,因此可以更轻松地分离着色器阶段。

例如:

你可以有一个这样的顶点着色器:

struct vsInput

float4 PosO : POSITION;
float3 Norm: NORMAL;
float4 TexCd : TEXCOORD0;
;

struct vsOut

float4 PosWVP : SV_POSITION;
float4 TexCd : TEXCOORD0;
float3 NormView : NORMAL;
;

vsOut VS(vsInput input)
 
     //Do you processing here

还有一个像这样的像素着色器:

struct psInput

    float4 PosWVP: SV_POSITION;
    float4 TexCd: TEXCOORD0;
;

由于顶点着色器输出提供了像素着色器所需的所有输入,因此这是完全有效的。法线将被忽略并且不会提供给您的像素着色器。

但是您可以切换到可能需要法线的新像素着色器,而无需另一个顶点着色器实现。您也可以仅交换 PixelShader,从而节省一些 API 调用(Separate Shader Objects 扩展名是 OpenGL 等效项)。

因此在某些方面,语义为您提供了一种在阶段之间封装 In/Out 的方法,并且由于您引用其他语言,这相当于使用属性/设置器/指针地址...

在速度方面,根据命名没有区别(您几乎可以以任何方式命名语义,当然系统名称除外)。不同的布局位置确实意味着成功(管道将为您重新组织,但也会发出警告,至少在 DirectX 中)。

OpenGL 还提供了大致相同的Layout Qualifier(它在技术上有点不同,但遵循或多或少相同的概念)。

希望对您有所帮助。

【讨论】:

这实际上没有任何意义。如果您在某些着色器中使用 TEXCOORD2 而在其他着色器中使用 TEXCOORD0,它们也不会匹配,因此无论是您自己的变量名称还是选择语义名称,您仍然不得不按名称进行匹配。 @gman 同意,但我猜着色器程序经常使用由标准语义(例如 NORMAL)标记的构造,这与这很好。也许图形程序员真正想要的是所有程序都必须以相同方式使用的标准化变量名?考虑到已经存在多少着色器程序,这将是一件很难推出的事情,所以也许语义是实际上可以提供的第二好的东西? 有趣的是,这些语义在所有现代 GPU api(vulkan、metal...)中都消失了,它只是按位置匹配,因此没有更多标准化的名称。 @gman 这与现代 api 无关,它只是一个 d3d 的东西,并且在 d3d12/shader model 6+ 编译器功能中仍然存在。在内部,它将语义映射到位置,但您是否需要现代 d3d 领域的语义。【参考方案2】:

正如 Catflier 上面提到的,使用语义可以帮助解耦着色器。

但是,我可以想到语义的一些缺点:

    语义数量受您使用的 DirectX 版本限制,可能会用完。

    语义名称有点误导。你会经常看到这类变量(查看 posWorld 变量):

    struct v2f float4 position : POSITION; float4 posWorld : TEXCOORD0;

您将看到 posWorld 变量和 TEXCOORD0 语义根本不相关。但这很好,因为我们不需要将纹理坐标传递给 TEXCOORD0。但是,这可能会在那些刚刚学习着色器语言的人之间造成一些混淆。

【讨论】:

【参考方案3】:

我更喜欢在变量之后写: NAME,而不是在之前写layout(location = N) in。 此外,与 Vulkan 不同,如果您更改顶点输入的顺序,则无需更新 HLSL 着色器。

【讨论】:

GLSL 不需要layout(location = N)。这仅适用于想要硬编码的人。没有它,驱动程序将选择位置,您在运行时查找它们。此外,任何现代引擎都将生成着色器,因此选择位置将由引擎决定,使用名称或数字这样做很简单,但可以说计算机在数字方面比名称更好,所以名称除了更多开销外什么也不买.

以上是关于为啥 HLSL 有语义?的主要内容,如果未能解决你的问题,请参考以下文章

为啥恰好一次语义不可行?

为啥 MIX 依赖在依赖的语义版本之前有一个“~>”?

在执行语义分割任务时我应该减去图像均值吗?为啥或者为啥不?

“语义”是啥意思?为啥“移动语义”这样命名,而不是任何其他术语?

JS 语义:为啥私有和公共类属性似乎重新排序?

Unity Shader Lab - HLSL