问题 为什么HLSL有语义?


在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;
}

我明白了 foo 和 bar 在里面 VS_OUTPUT 连接到 foo 和 bar 在 main 在片段着色器中。我没有得到的是为什么我手动选择语义来携带数据。为什么像GLSL一样,DirectX只能找出放置数据的位置并在连接着色器时连接它?

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

我认为语义可以暗示意义,没有意义 foo 要么 bar 但他们也可以模糊意义,如果 foo 不是TEXCOORD和 bar 不是颜色。我们没有将语义放在C#或C ++或JavaScript变量上,为什么HLSL需要它们呢?


3971
2018-02-27 09:26


起源



答案:


简单地说,(旧)glsl使用这种变量来命名变量(请注意,变量现已弃用)。

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

如果您通过foo2重命名foo,则需要在可能的所有着色器(以及最终的着色器)中替换此名称。使用语义,您不需要这个。

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

例如:

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

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;
};

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

但是,您可以交换到可能需要法线的新像素着色器,而无需另外使用Vertex Shader。您也可以仅交换PixelShader,从而节省一些API调用(单独的着色器对象 扩展名是OpenGL的等价物)。

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

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

OpenGL还提供 布局限定符 这大致相当(它在技术上有点不同,但是或多或少遵循相同的概念)。

希望有所帮助。


11
2018-02-28 18:10



这实际上毫无意义。如果在某些着色器中使用TEXCOORD2而在其他着色器中使用TEXCOORD0则它们也不匹配,因此您仍然需要按名称进行匹配,无论它是您自己的变量名还是选择语义名。 - gman


答案:


简单地说,(旧)glsl使用这种变量来命名变量(请注意,变量现已弃用)。

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

如果您通过foo2重命名foo,则需要在可能的所有着色器(以及最终的着色器)中替换此名称。使用语义,您不需要这个。

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

例如:

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

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;
};

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

但是,您可以交换到可能需要法线的新像素着色器,而无需另外使用Vertex Shader。您也可以仅交换PixelShader,从而节省一些API调用(单独的着色器对象 扩展名是OpenGL的等价物)。

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

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

OpenGL还提供 布局限定符 这大致相当(它在技术上有点不同,但是或多或少遵循相同的概念)。

希望有所帮助。


11
2018-02-28 18:10



这实际上毫无意义。如果在某些着色器中使用TEXCOORD2而在其他着色器中使用TEXCOORD0则它们也不匹配,因此您仍然需要按名称进行匹配,无论它是您自己的变量名还是选择语义名。 - gman


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

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

  1. 语义的数量受您使用的DirectX版本的限制,因此可能会耗尽。

  2. 语义名称有点误导。你会看到很多这类变量(看看posWorld变量):

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

您将看到posWorld变量和TEXCOORD0语义根本不相关。但它很好,因为我们不需要将纹理坐标传递给TEXCOORD0。但是,这可能会导致那些只使用着色器语言的人之间产生一些混淆。


3
2017-10-01 17:25