UnityShader(三)编写简单的顶点片元着色器

  • Post author:
  • Post category:其他




编写简单的顶点/片元着色器

下面是一个最基础的UnityShader代码。在一个UnityShader中,Properties语义并不是必须的,我们可以不声明任何材质属性。SubShader中的渲染设置和标签设置也不是必须的,在没有声明的情况下SubShader会使用默认的渲染设置和标签设置。

Shader "Unlit/class02"  
{  
    SubShader  
    {  
        Pass  
        {  
            CGPROGRAM  
			// 下面两行代码将告诉Unity,哪个函数包含了顶点着色器,哪个函数包含了片元着色器
            #pragma vertex vert // 顶点着色器
            #pragma fragment frag // 片元着色器  
  
            // POSITION和SV_POSITION都是语义,分别指明输入的四维数和返回的四维数是什么类型  
            // POSITION存储模型空间坐标 SV_POSITION存储裁剪空间坐标  
            float4 vert(float4 v:POSITION):SV_POSITION  
            {  
				// 把顶点坐标从模型空间转换到裁剪空间
                return UnityObjectToClipPos(v);  
            }  
  			// 片元着色器中的代码返回了一个表示白色的float4类型的变量,并将输出的颜色存储到一个渲染目标中
            fixed4 frag():SV_TARGET
            {  
                return fixed4(1,1,1,1);  
            }  
            ENDCG  
        }  
    }  
}

最后的效果显示为一个白色的球


在上面的代码中,我们用

POSITION

语义获取到了模型的顶点位置,但如果要获取到更多的模型数据怎么办呢?同样的,有时我们也想从顶点着色器输出一些数据,如法线、纹理坐标等传递给片元着色器,这时又该怎么办呢?

我们很容易想到,可以将参数封装成一个类或结构体。Unity Shader中没有类的概念,但结构体是有的。

下面是结构体的应用:

Shader "Unlit/class03"  
{  
    SubShader  
    {  
        Pass  
        {  
            CGPROGRAM  
            #pragma vertex vert  
            #pragma fragment frag  
  
            // 输入结构体  
            struct a2v  
            {  
                float4 v:position;// 用模型的顶点填充v变量  
                float3 n:normal;// 用模型的法线填充n变量  
                float4 texcoord: TEXCOORD0;// 用模型的第一套uv填充texcoord  
                };  
            // 输出结构体  
            struct v2f  
            {  
                // 将裁剪空间中的位置信息存入pos变量  
                float4 pos:SV_POSITION;  
                // 将颜色信息存入color变量  
                fixed3 color:COLOR0;  
            };  
              
            v2f vert(a2v param)  
            {  
                v2f o;  
                o.pos = UnityObjectToClipPos(param.v);  
                // param.n包含了顶点的法线方向,分量范围是[-1,1]  
                // 下面的代码将这个范围映射到了[0,1]  
                o.color = param.n * 0.5 + fixed3(0.5,0.5,0.5);  
                return o;  
            }  
  
            fixed4 frag(v2f param):SV_Target  
            {  
                return fixed4(param.color,1);  
            }  
            ENDCG  
        }  
    }  
}

上面的代码其实就是将法线方向的分量转换成颜色,然后输出到屏幕上。需要注意的一点是,顶点着色器的输出结构中必须包含一个

SV_POSITION

的语义,否则,渲染器将无法获取裁剪空间中的顶点坐标,当然也就无法把顶点渲染到屏幕上。另外,顶点着色器是逐顶点调用,而片元着色器是逐片元调用,因此片元着色器的输入实际上是把顶点着色器的输出进行插值后得到的结果。

效果如图:


如果我们想直接在Inspector面板上直接控制模型的颜色,又该如何做呢?这就要用回之前讲过的Properties语义块。

Shader "Unlit/class03"  
{  
    Properties{  
        _Color ("Color Tint",Color) = (1,1,1,1)  
    }  
    SubShader  
    {  
        Pass  
        {  
            CGPROGRAM  
            #pragma vertex vert  
            #pragma fragment frag  
  
            // 在CG代码中需要定义一个与属性名称和类型都匹配的变量  
            fixed4 _Color;   
            // 输入结构体  
            struct a2v  
            {  
                float4 v:position;// 用模型的顶点填充v变量  
                float3 n:normal;// 用模型的法线填充n变量  
                float4 texcoord: TEXCOORD0;// 用模型的第一套uv填充texcoord  
                };  
            // 输出结构体  
            struct v2f  
            {  
                // 将裁剪空间中的位置信息存入pos变量  
                float4 pos:SV_POSITION;  
                // 将颜色信息存入color变量  
                fixed3 color:COLOR0;  
            };  
              
            v2f vert(a2v param)  
            {  
                v2f o;  
                o.pos = UnityObjectToClipPos(param.v);  
                // param.n包含了顶点的法线方向,分量范围是[-1,1]  
                // 下面的代码将这个范围映射到了[0,1]  
                o.color = param.n * 0.5 + fixed3(0.5,0.5,0.5);  
                return o;  
            }  
  
            fixed4 frag(v2f param):SV_Target  
            {  
                fixed3 color = param.color;  
                color *= _Color.rgb;  
                return fixed4(color,1);  
            }  
            ENDCG  
        }  
    }  
}

在上面的代码中,我们在Properties语义块中定义了一个属性 _Color ,此时Inspector面板上就会出现一个颜色拾取器。在片元着色器中,我们使用 _Color 中的rgb值控制由顶点着色器传入的颜色值。此时当我们调整Inspector面板中的颜色属性时,物体的颜色也会随之改变。

效果如下:

下面给出ShaderLab属性类型与CG变量类型的对应关系:

ShaderLab属性类型 CG变量类型
Color、Vector float4、half4、fixed4
Range、Float float、half、fixed
2D sampler2D
Cube samplerCube
3D sampler3D

float、half、fixed的区别:

类型 精度
float 最高精度浮点型,通常用32位存储
half 中等精度浮点型,通常用16位存储
fixed 最低精度浮点型,通常用11位存储

很多时候我们不需要亲自编写一些变量或函数,Unity已经帮我们内置了很多有用的变量与函数,我们只需要引入它们就可以直接使用。Unity内置着色器可以在官网下载源码。

其实我们很多常用的变量与方法(如UnityObjectToClipPos方法)都是从UnityShaderUtilities.cginc中定义的,在UnityShader编译时,Unity会自动将这个库文件引入。

下面将我们前面写的输入结构体替换为UnityCG.cginc库文件中的appdata_base结构体:

...省略

CGPROGRAM  
#pragma vertex vert  
#pragma fragment frag  
// 引入Shader库  
#include "UnityCG.cginc"  
  
// 在CG代码中需要定义一个与属性名称和类型都匹配的变量  
fixed4 _Color;   
// 输出结构体  
struct v2f  
{  
    // 将裁剪空间中的位置信息存入pos变量  
    float4 pos:SV_POSITION;  
    // 将颜色信息存入color变量  
    fixed3 color:COLOR0;  
};  
  
// 使用 UnityCG.cginc 中的 appdata_base 结构体  
v2f vert(appdata_base param)  
{  
    v2f o;  
    o.pos = UnityObjectToClipPos(param.vertex);  
    // param.n包含了顶点的法线方向,分量范围是[-1,1]  
    // 下面的代码将这个范围映射到了[0,1]  
    o.color = param.normal * 0.5 + fixed3(0.5,0.5,0.5);  
    return o;  
}  
  
fixed4 frag(v2f param):SV_Target  
{  
    fixed3 color = param.color;  
    color *= _Color.rgb;  
    return fixed4(color,1);  
}  
ENDCG
...省略

在上面的UnityShader代码中,我们经常用到SV_POSITION、COLOR0等关键字,这些其实是CG/HLSL提供的

语义

。语义实际上就是一个赋值给Shader输入和输出的字符串,这个字符串表达了这个参数的含义。

从应用阶段传递模型数据给顶点着色器时Unity支持的常用语义:

语义 描述
POSITION 模型空间中的顶点位置,通常是float4类型
NORMAL 顶点法线,通常是float3类型
TANGENT 顶点切线,通常是float4类型
TEXCOORDn(如TEXCOORD0、TEXCOORD1等) 该顶点的纹理坐标,TEXCOORD0表示第一组纹理坐标,以此类推。通常是float2或float4类型
COLOR 顶点颜色,通常是fixed4或float4类型

从顶点着色器传递数据给片元着色器时Unity使用的常用语义:

语义 描述
SV_POSITION 裁剪空间中的顶点坐标,结构体中必须包含一个用该语义修饰的变量。等同于DX9中的POSITION
COLOR0 通常用于输出第一组顶点颜色,但不必需
COLOR1 通常用于输出第二组顶点颜色,但不必需
TEXCOORD0~TEXCOORD7 通常用于输出纹理坐标,但不必需

片元着色器输出时Unity支持的常用语义

语义 描述
SV_TARGET 输出值将会存储到渲染目标(render target)中。等同于DX9中的COLOR语义



版权声明:本文为LWR_Shadow原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。