[译]Unity3D Shader教程(三)变量

[译]Unity3D Shader教程(三)变量

2021年6月20日 0 作者 老王

原文链接:
Shader Tutorials by Ronja


1 Summary

概要。
在明确了着色器部分是如何组合在一起的,以及在实际着色器代码之外的 shaderlab 的粗略轮廓之后,我们来看看我们的着色器需要哪些变量来运行,以及如何将这些变量添加到我们的代码中。这些变量包括我们为每个材质设置的变量、网格数据变量的一部分以及从顶点传递到片段的数据。

2 Object Data

模型数据。
我们在解释着色器流程时所说的模型数据和上面的网格数据其实不是同一种东西。在底层的程序上,模型数据被定义为将要被渲染的内容的数据流,但我们将其想象为网格会更容易思考。在大多数情况下,模型数据必须包括顶点位置和顶点之间构成的三角形。除此之外,模型数据还包括顶点法线、uv 坐标和顶点颜色。每个顶点的数据(所以除了三角形之外的所有数据)在渲染流水线中的顶点阶段处理。位置和方向都在模型空间(或者说局部空间)中,所以无论对象如何缩放或移动,我们都可以使用相同的顶点数据而无需额外处理。
在Unity着色器中,模型数据通常以名为 appdata(如果你知道为什么,请告诉我,我很好奇) 的结构传递给顶点着色器。数据名可以为任何名称,但必须添加标识符来告诉 unity 哪个变量应该填充哪些数据,例如像这样:

struct appdata{
  float4 vertex : POSITION;
  float2 uv : TEXCOORD0;
};

(上面这个结构的意思解释如下:
上面:后的POSITION和TEXTURE官方的名字是语义,这是由语法规定好的。每个语义表示不同的模型数据。
比如POSITION就表示模型数据的顶点位置(模型空间),TEXCOORD0就表示该顶点的uv0坐标。
所以上面的结构体就表示,将模型数据的顶点位置(模型空间)填充到vertex这个变量中,将uv0坐标填充到uv这个变量中。
也就是说,变量: 语义 其实是一个固定的语法)
常用的顶点数据的语义可以见官方的文档

3 Interpolators

插值。
在顶点阶段修改数据后,光栅化器使用修改后的数据来绘制像素。 为此,我们必须在本地位置中设置它在屏幕上的位置(在裁剪空间中),以便光栅化器知道在哪里绘制它,我们使用 SV_POSITION语义来达到这个目的。 除此之外,其他任何数据都是可选的,片段着色器可以使用它们来生成颜色,包括从纹理和照明效果中读取颜色。对于两个的顶点之间生成的像素,还需要对顶点着色器的输出数据进行插值。顶点色器的输出数据的结构体常用描述符为v2f(顶点到片段)。
(这段没怎么说清楚, 上一节2 Object Data中的appdata结构体是顶点着色器的输入,这里的v2f是顶点着色器的输出,同时v2f也是片元着色器的输入。v2f结构体中必须包含SV_POSITION语义标记的变量,此语义表示此顶点对应于裁剪空间中的位置,说白了就是顶点的局部坐标经过MVP变换后得到的坐标。除了SV_POSITION语义标记的变量外,v2f结构体中的其他变量都是可有可无的。)
v2f结构的示例如下:

// 从顶点着色器传递给片元着色器的数据,同时光栅器会对数据进行插值
struct v2f{
  float4 position : SV_POSITION;
  float2 uv : TEXCOORD0;
};

4 Output color

输出颜色。
我们最常使用简单的 4维向量(fixed4)表示输出颜色(各维度分别表示表示输出颜色的红/绿/蓝/alpha 通道)。 将片元着色器标记为 SV_Target 语义来告诉编译器将片元着色器的输出作为屏幕上的绘制颜色。

5 Uniform data

除了着色器中渲染网格部分的数据(即上面的appdata、v2f)之外,还有我们为每个材质设置的数据,这些数据通常被称为uniform data(统一),因为它在整个绘制过程中是不变的(而且顶点着色器与片元着色器都可以访问这些数据)。uniform data可以是数字、纹理、矢量等等。 当前正在渲染的相机以及正在渲染的对象的变换的矩阵也是uniform data的一部分,但 幸运的是,这部分数据是由 Unity 自动设置的,所以我们现在不必担心。
要定义新的uniform data,我们只需将变量添加到我们在所有结构体或函数之外的着色器程序中。

//texture and transforms of the texture
sampler2D _MainTex;
float4 _MainTex_ST;

//tint of the texture
fixed4 _Color;

一旦这些变量存在,我们就可以通过代码(Material.SetType)设置它们。 但是要在Inspector面板中看到它们,我们必须在文件顶部将它们声明为属性(properties)。 属性的语法为 _Variable ("Inspector Name", Type) = DefaultValue,同时最前面还可以添加属性绘制特性(property drawers)。 纹理是一个特殊情况,因为它们不仅将纹理写入您提供的变量中,而且还将生成一个 _Variable_ST的变量(ST 代表缩放Scale和平移Translate,旧版本的uniform data被称为平铺tiling和偏移offset)。
例如,上面例子中对应的属性部分如下:

Properties{
  _Color ("Tint", Color) = (0, 0, 0, 1)
  _MainTex ("Texture", 2D) = "white" {}
}

6 Spaces?

空间。
在谈论着色器中的位置时,通常会说明这个位置所在的空间,即模型(局部)/世界/观察/屏幕/裁剪空间。
模型(局部)空间坐标是对象空间中的坐标。 对象空间中的 0 位于该对象的原点。如果对象旋转或缩放,对象空间中的坐标也随之旋转或缩放。顶点坐标以模型空间的形式保存在文件中,并上传到 GPU。
世界空间坐标是与其他一切相关的坐标。世界空间中的0点,其实是我们人为指定的。如果我们说位置,但没有说明是那个空间,我们通常认为是在世界空间。
观察空间是模型相对于相机的位置。裁剪空间是应用投影矩阵后的位置,因此这些位置是基于相机缩放的,如果您进行透视投影,在世界空间中距离相机更远的对象在裁剪空间中会更小。屏幕空间是应用了我们渲染所需的一些技巧后的位置,因此大多数时候您可以忽略观察和裁剪空间并尝试获取屏幕空间坐标(并且有很好的实用函数,因此您不必了解矩阵乘法)。

7 Sources

源码。
所有教程都在底部链接了生成的着色器的来源。 由于我们现在只是在分析,我现在只是将完整着色器的代码放在这里。
https://github.com/ronja-tutorials/ShaderTutorials/blob/master/Assets/001-004_basic_unlit/basic_unlit.shader

Shader "Tutorial/001-004_Basic_Unlit"{
  //show values to edit in inspector
  Properties{
    _Color ("Tint", Color) = (0, 0, 0, 1)
    _MainTex ("Texture", 2D) = "white" {}
  }

  SubShader{
    //the material is completely non-transparent and is rendered at the same time as the other opaque geometry
    Tags{ "RenderType"="Opaque" "Queue"="Geometry" }

    Pass{
      CGPROGRAM

      //include useful shader functions
      #include "UnityCG.cginc"

      //define vertex and fragment shader functions
      #pragma vertex vert
      #pragma fragment frag

      //texture and transforms of the texture
      sampler2D _MainTex;
      float4 _MainTex_ST;

      //tint of the texture
      fixed4 _Color;

      //the mesh data thats read by the vertex shader
      struct appdata{
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
      };

      //the data thats passed from the vertex to the fragment shader and interpolated by the rasterizer
      struct v2f{
        float4 position : SV_POSITION;
        float2 uv : TEXCOORD0;
      };

      //the vertex shader function
      v2f vert(appdata v){
        v2f o;
        //convert the vertex positions from object space to clip space so they can be rendered correctly
        o.position = UnityObjectToClipPos(v.vertex);
        //apply the texture transforms to the UV coordinates and pass them to the v2f struct
        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        return o;
      }

      //the fragment shader function
      fixed4 frag(v2f i) : SV_TARGET{
          //read the texture color at the uv coordinate
        fixed4 col = tex2D(_MainTex, i.uv);
        //multiply the texture color and tint color
        col *= _Color;
        //return the final color to be drawn on screen
        return col;
      }

      ENDCG
    }
  }
  Fallback "VertexLit"
}