跨引擎TA Shader


渲染管线

概念:GPU绘制物体的时候,标准的,流水线一样的操作

游戏引擎如何绘制物体:CPU提供绘制数据(顶点数据,纹理贴图等)给GPU,配置渲染管线(装载Shader代码到GPU,配置一次称为一次SetPassCall),并对GPU下命令(DrawCall)绘制。原来的顶点数据需要经过三次矩阵的变换。分别是映射到世界坐标,以摄像机为中心的坐标,投影坐标

纹理坐标:当一个模型建好之后,我们需要给模型上色,上色的过程相当于在一张白纸上面画画,只不过是这张白纸包住了模型,模型顶点在白纸上的坐标就是纹理坐标。

材质其实是配置shader的配置文件,CPU从材质中读取shader数据,把shader程序装载进GPU

顶点数据

模型有一个个三角形面构成,一个面包含三个顶点。
顶点数据包含模型坐标(相对于模型原点),纹理坐标,法线向量,切线向量。

流程

顶点初始化->顶点shader(编程)->Tellellation曲面化->几何shader->裁剪,投影->三角形遍历->片元着色shader(编程)->输出2D图像

顶点初始化:CPU提供顶点数据给GPU,顶点数据包括在模型中的位置,UV纹理坐标,法线,切线等。Unity定义一些装固定数据的”盒子”(SV_XXXX),顶点shader从中拿数据,其他没有固定数据的”盒子”用户可以自由使用

顶点Shader:

  1. 形状变换(可选) 你通过代码,改变顶点的模型坐标
  2. 坐标变换(绘制到准确位置),经过三次坐标变换,映射到2D平面
  3. 数据传递(由编程者决定内容),把数据传回渲染管线,有固定的数据盒子

片元着色shader:
片元是涂色的最小单位,可以看成像素点。每个片元的数据通过顶点数据插值得来,可以引入光照计算。

Unity Shader模板

创建一个unlit Shader,它是一个基础模板,可以开发任意效果:

Shader "Unlit/Shader"
{
    // 属性列表,可以在编辑器中赋值,用户传递的,不能在shader中修改,在所有着色器可以访问
    Properties
    {
        // 变量名("显示名字",类型)= 默认值
        _MainTex ("Texture", 2D) = "white" {}
    } 

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        // 细节距离,离得远不用细节
        LOD 100
        // 一个Pass是一个完整的渲染管线绘制
        Pass
        {
            CGPROGRAM    // CG代码的开始
            #pragma vertex vert   //预编译,告诉编译器顶点代码在哪
            #pragma fragment frag //预编译,告诉编译器片元着色代码在哪
            // make fog work
            #pragma multi_compile_fog
            // Unity封装的Shader API
            #include "UnityCG.cginc"
 
            struct appdata   // shader中需要使用的数据,Unity定义了一些语义描述来取对应的数据,POSITION,TEXCOORD0对应的是一个个盒子数据,这些数据会先进入顶点shader,可以直接获取
            {
                float4 vertex : POSITION;  // 模型坐标,可改
                float2 uv : TEXCOORD0;     // 开始时候存的纹理坐标,可改
            };

            struct v2f  // 给片元shader使用的数据,经过顶点shader计算后得到
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };
            // 定义用户的属性变量的数据变量,刚好对应2D中的两种数据类型
            sampler2D _MainTex;   // 图片
            float4 _MainTex_ST;   // Tilling,offset四个分量
            // 顶点shader
            v2f vert (appdata v)
            {
                v2f o;
                // 坐标空间的转换,直接转到投影空间
                o.vertex = UnityObjectToClipPos(v.vertex);
                // 纹理坐标叠加上Tilling和offset
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                // 返回数据给渲染管线
                return o;
            }

            // 片元着色shader,着色调用次数远远大于顶点shader调用次数
            // 着色好了放到SV_Target
            // 这个v2f的参数并不等于顶点里面返回的v2f,而是插值后每个片元的v2f
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

摄像机如何显示颜色

摄像机看见的颜色=物体本身的发光+反射环境中所有的光
除了太阳,灯泡等物体,大多数是不会自发光的,只会反射光,物体的颜色是因为反射了特定的颜色的光,吸收了其他颜色的光得到的。
自然界的光照是一个非常多次叠加的效果,非常复杂,因此需要简化:不考虑二次反射的光,并且不考虑其他物体的遮挡,但是考虑自己的遮挡。公式变为:
摄像机看见的颜色=物体本身的发光+本色*(直接光照反射+一次反射光+考虑自己的遮挡)+环境光

反射分为镜面反射和漫反射,而环境直接光照和一次反射光可以分为这两种类型,具体类型由材质决定。

每个光都可以用红,绿,蓝,强度表示,例如:
白光(1,1,1,1)
红光(1,0,0,1)
光照的时候,物体的颜色=本色光照
白光照在红物体上:(1,1,1,1)
(1,0,0,1)=(1,0,0,1)=红色
物体本色在建模的时候就决定好了,渲染的好的关键是调节反射
常用的终极简化:

  1. 颜色=本色,休闲小游戏常用,对光照无要求
  2. 颜色=本色*漫反射,移动端常用(兰伯特,半兰伯特)

法线与光照计算

光照计算研究方向有两种:

  1. 基于经验模型(不要求逼真,用于卡通渲染):
    漫反射有 兰伯特,半兰伯特
    镜面反射 有冯高光,布林冯高光

  2. 基于物理PBR(遵循物理定律,能量守恒,模拟真实世界,效果逼真)

法线用于反射,是光照计算的关键。每个片元的法线都可以由顶点法线插值得到。

如何增强模型细节?
方案一:做高模型(面数多,计算量大)
方案二:建一个高模型,只导出法线贴图,但是模型依旧用低模型,但是法线到高模的法线贴图中取,能做出同样的光暗细节。

一个贴图每个点都有对应的RGBA(颜色分量),法线向量可以存储在颜色分量中得到法线贴图。每个法线都是一个标准化向量,通过将三维坐标映射到0到1的范围内通过颜色分量的前三维存储。取出同理,映射和反向映射方法:
(-1,1)*0.5+0.5–>(0,1)
(0,1)*2-1–>(-1,1)

我们也可以只存储x,y,z通过标准化向量来求解得到,可以压缩存储空间。

逐顶点和逐像素光照

这部分计算发生在片元着色shader

逐像素:根据顶点的数据给每个片元插值计算法线,纹理坐标,坐标。然后将这些像素逐个计算反射。这部分计算都在片元着色,这是按照片元的法线计算的。

逐顶点:在顶点shader时候,我们算好顶点的光照颜色(直射光+反射光+环境光),然后通过插值得到每个片元的光照颜色,在片元着色shader的时候只需要将对应纹理的 本色插值的光照 就可以了,不用每个像素使用法线的计算光照了。计算量远远小于逐像素,是移动平台主要采用的方式,但效果可能不能那么细腻。

逐顶点光照:

Shader "Bycw/VertexLight"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                // 存放片元光照
                float4 allLight : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);

                // 计算我们的光照
                float3 N = normalize(UnityObjectToWorldNormal(v.normal));
                float3 L = normalize(_WorldSpaceLightPos0);
                float halfLam = dot(L, N) * 0.5 + 0.5;
                float3 halfLamLight = halfLam * _LightColor0.rgb;
                // 把光照传递给片元
                o.allLight = float4(halfLamLight, 1);
                // end

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {               
                fixed4 col = tex2D(_MainTex, i.uv);
                //片元直接本色乘光照
                col *= i.allLight;

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

逐片元请看下一小节。

兰伯特漫反射模型

漫反射,是指光照射在粗糙表明向各个方向反射的现象,在这个模型中,向各个方向反射的光强度一致,反射光的强度等于反射光的方向和法线的点乘。因此这个值是0到1的一个范围(负数置0),1代表入射光强度。也就是说光的入射方向如果正好在法线上,那么漫反射的强度等于光照强度。 这也是符合直觉的,在一个平面上,靠近光源的点是最亮的,因为光源刚好在法线上。

为了防止点乘之后的值为负,可以把这个点乘后的值乘0.5,再加0.5,这样就能把范围映射到0到1,这就是半兰伯特反射模型。

这个例子是逐片元光照,对应上一小节的逐顶点:

Shader "Unlit/Shader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // 配置模式
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            // 使用光照头文件
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                // 拿到模型的法线
                float3 normal: NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                //定义世界坐标系下的法线,因为这里是逐像素(片元),要计算好后传给片元着色shader,如果是逐顶点,就传插值的光照就行
                float3 wNormal: TEXCOORD1; 
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.wNormal = UnityObjectToWorldNormal(v.normal);

                return o;
            }
 
            fixed4 frag (v2f i) : SV_Target
            {
                // 光照方向的标准化向量
                float3 L = normalize(_WorldSpaceLightPos0);
                // 法线向量
                float3 N = i.wNornal;
                // 半兰伯特公式
                float halfLam = dot(L,N) * 0.5 + 0.5
                // 反射光计算
                float3 diff = halfLam * _LightColor0.rgb;
                // 转换单位到4维向量
                fixed4 diffColor = fixed4(diffLight.rgb,1);
                fixed4 col = tex2D(_MainTex, i.uv);
                // 本色 * 光照
                return col * diffColor;
            }
            ENDCG
        }
    }
}

冯高光和布林冯高光经验模型

Phong镜面反射模型:
镜面反射在物体表面绝对光滑的情况下,只往法线另一侧相同的夹角反射光线。但是一般没有绝对光滑的物体,因此也会往其他方向反射光线,只是强度会随着偏移反射角而减弱。假设我们定义物体的光滑度为N,标准反射角向量为R,反射点到摄像机的向量为V,则有:

$$
反射光强度= (\max(0,Dot(R,V)))^N
$$

Blin-Phone镜面反射模型:

$$
反射光强度= (\max(0,Dot(H,N)))^N
$$

其中,N是法线向量,H是视角向量和光源向量的半角向量。也就是说这个向量平分视角向量和光源入射向量,这个向量越接近法线,则视角接收的反射光越强。

Shader "Bycw/BycwPhong"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        // 属性面板,可以在编辑器中设置
        _Gloss("Gloss", Range(0, 5)) = 1
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                // 拿到模型法线
                float3 normal: NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                // 世界坐标系下法线 
                float3 wNormal: TEXCOORD1;
                // 顶点的世界坐标
                float3 worldPos: TEXCOORD2;

                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            // 属性
            float _Gloss;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                // 世界法线
                o.wNormal = normalize(UnityObjectToWorldNormal(v.normal));
                // 顶点世界坐标
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // 片元到光源的标准化向量
                float3 L = normalize(-_WorldSpaceLightPos0);
                // 法线
                float3 N = normalize(i.wNormal);
                // 反射向量
                float3 R = reflect(L, N);
                // 片元到摄像机的向量
                float3 V = normalize(_WorldSpaceCameraPos - i.worldPos);
                // float _Gloss = 1;

                // Lambert计算光强
                float halfLambert = dot(-L, N) * 0.5 + 0.5;
                float3 halfLight = halfLambert * _LightColor0.rgb;
                // Phong高光计算光强
                float phong = pow(max(0, dot(R, V)), _Gloss); 
                float3 phoneLight = phong * _LightColor0.rgb;
                // 0.3的漫反射,0.7的镜面反射
                float4 allLight = float4(phoneLight.rgb, 1) * 0.3 + float4(halfLight.rgb, 1) * 0.7;
                fixed4 col = tex2D(_MainTex, i.uv);

                return col * allLight;
            }
            ENDCG
        }
    }
}

Blin-Phong高光

Shader "Bycw/BlinPhong"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Gloss("Gloss", Range(0, 5)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float3 wNormal: TEXCOORD1; 
                float2 uv : TEXCOORD0;
                float3 worldPos: TEXCOORD2;

                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Gloss;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                
                o.wNormal = normalize(UnityObjectToWorldNormal(v.normal)); 
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 N = i.wNormal;
                float3 V = normalize(_WorldSpaceCameraPos - i.worldPos);
                float3 L = normalize(_WorldSpaceLightPos0);
                // 半角向量
                float3 H = normalize(L + V);
                // Blin-Phony光强度
                float blinPhong = pow(max(0, dot(H, N)), _Gloss);
                float halfLam = dot(L, N) * 0.5 + 0.5;
                float3 blinPhoneLight = _LightColor0.rgb * blinPhong;
                // 半兰伯特光强度
                float3 halfLamLight = _LightColor0.rgb * halfLam;
                // 两者的光照叠加
                float4 allLight = float4(blinPhoneLight, 0.5) + float4(halfLamLight, 0.5);
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                return col * allLight;
            }
            ENDCG
        }
    }
}

法线贴图

次时代中法线贴图是标配

Shader "Bycw/BycwNormal"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        // 定义法线贴图
        _NormalTex ("Normal", 2D) = "white" {}
        
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float3 wNormal: TEXCOORD1;
                float3 worldPos: TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            // 法线贴图
            sampler2D _NormalTex;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                
                // 可以不写这行
                o.wNormal = normalize(UnityObjectToWorldNormal(v.normal));
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 L = normalize(_WorldSpaceLightPos0);
                // float3 N = i.wNormal;
                // 法线向量(0,1)解包到(-1,1)得到对应片元的法线向量
                float3 N = normalize(UnpackNormal(tex2D(_NormalTex, i.uv))); 
                float3 V = normalize(_WorldSpaceCameraPos - i.worldPos);
                float3 H = normalize(V + L);

                float _Gloss = 4;
                float blinPhong = pow(max(0, dot(H, N)), _Gloss);
                float halfLam = dot(L, N) * 0.5 + 0.5;
                float3 blinPhoneLight = _LightColor0.rgb * blinPhong;
                float3 halfLamLight = _LightColor0.rgb * halfLam;
                // 镜面反射+漫反射+环境光强度
                float4 allLight = float4(blinPhoneLight, 0.5) + float4(halfLamLight, 0.5) + UNITY_LIGHTMODEL_AMBIENT;

                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                col *= allLight;

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

PBR次时代美术工作流

PBR(Physically Based Rendering)是基于物理原理来模拟计算光的反射。算法是成熟的,我们不直接实现其原理,而是调节参数。

PBR有两种工作流,可以相互转换:

  1. 金属与粗糙度工作流:主要元素是颜色贴图,金属度,粗糙度,法线贴图。金属度是模拟光线反射的强度的贴图,例如皮肤是0,盔甲是1。粗糙度是模拟漫反射的贴图,更多细节。这个工作流的优点是更容易创作,各个贴图是分开的,纹理占用内存小(金属和粗糙度贴图是灰度图),更广泛被应用,但是缺点是边缘的伪像更明显,尤其是分辨率低的时候。

  2. 反射与光泽度工作流:接近于真实物理,主要元素是漫反射,反射(RGB贴图),光泽度(RGB贴图),法线贴图。优点是边缘伪像不明显,控制灵活,缺点是灵活控制可能不遵守能量守恒破坏PBR原则,并且RGB贴图多,占用内存多。

高度贴图:类似法线贴图,有着增强细节的效果,把高度值保存起来。

Unity支持两种工作流,Cocos,Laya,UE4只支持主流的金属与粗糙度

美术提供:物体本身贴图,金属度贴图,粗糙度贴图。

在Unity中,名为Standard的shader就是基于PBR的。
其中参数如下:
Albedo:本色贴图
Metallic:金属度,可以调一个值,也可以传贴图
Smoothess:光泽度,可以调值,里面的Source选项中,可以选择粗糙度的来源(本色贴图还是金属度),因为一般金属度和粗糙度用两个通道(R和A)做到一个贴图中。
Normal Map:法线贴图
Height Map:高度贴图
Occlusion:环境遮挡
Detail Mask:细Detail Mask是一种用于控制细节纹理的遮罩或掩模。细节纹理通常用于增加表面的细微细节,例如皮肤上的皱纹、石头上的裂纹等,从而增加物体的真实感。通过定义一个遮罩图像来指定哪些区域应用细节纹理。这个遮罩图像是一个灰度图,其中不同的灰度值表示不同的遮罩强度。通常,灰度值较高的区域将更多地受到细节纹理的影响,而灰度值较低的区域则保留原始的基础材质。
Emission:自发光,可以选择颜色或贴图

渲染队列与ZTest,ZWrite

普通情况下,绘制画面时,绘制最远处的物体,然后绘制近处物体,这样就能覆盖掉远处物体。但是绘制的时候做了优化,先绘制近的,再绘制远的,如果远的被挡住了,就不进行绘制,远近不是相对于一整个物体来说的,而是相对于一个个片元。
先绘制的物体会把片元放入颜色缓存区和深度缓冲区,绘制后面的片元的时候,比较深度,如果深度比原来的小,那么就把这个新的片元放入原来的颜色缓冲区和深度缓冲区。如果不是,这个片元信息会被丢弃,称为没有通过深度测试(ZTest)。把片元信息更新到深度缓存区称为ZWrite。如果关掉深度缓存,虽然颜色会更新,但是深度不会更新。
深度测试有不同规则:
ALWAY:颜色都会通过测试
NEVER:颜色永远无法通过测试
LESS:距离摄像机越近的通过测试
EQUAL:距离相等的通过测试
GREATER:距离摄像机越远的通过测试
NOT_EQUAL:距离不相等的通过测试

如果一个物体的shader把深度测试设定为ALWAY,那么永远都能绘制到屏幕中。

绘制透明物体(第四维的透明度数值alpha不是1)的时候,可以采用不同的渲染队列,每个队列有不同的编号,场景的物体会根据队列存放。游戏引擎先绘制编号小的队列,再绘制编号大的队列,不同的队列都有不同的颜色和深度缓冲区。
不同队列直接的颜色不会被遮挡,但是会混合,所以通过不同渲染队列透明物体看其他物体时是两种物体的混合色。这个过程叫Blend
Blend有几种混合模式:

  1. 直接覆盖(默认)
  2. 透明度混合(常用)
    // 根据后面绘制物体的透明度混合,例如后面队列的透明物体Alpha为30%,则按照30%透明物体颜色和70%缓存的物体颜色绘制,src是本色,dst是颜色缓存
    Blend SrcAlpha OneMinusSrcAlpha
    // 线性叠加
    Blend One One
    // 正片叠底,相乘Multiply
    Blend DstColor Zero
    通过在透明物体shader上设置上面代码可以达到对应效果

立方体纹理采样和自制天空盒

天空盒其实就是一个立方体或球体,立方体有六个面,每个面都可以对应一个纹理贴图。如果这六个贴图在边缘处是连续的,那么就可以看成一个整体。
对于天空盒来说,其渲染颜色只由本色决定,创建天空盒(立方体或球体),包围住整个场景,并编写Shader控制渲染盒子,然后调整渲染队列顺序(调到最小),使其最先绘制。
我们需要创建一个Cubemap的纹理,它可以设置立方体的六张贴图。

Shader "Bycw/BycwSkyBox"
{
    Properties
    {
        // 这是Cube纹理,不是一般的纹理
        _MainTex ("Texture", Cube) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Cull Off     // 背对摄像机的面不会被裁掉;
        ZWrite Off   // 关掉深度缓存
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

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

            };

            struct v2f
            {
                // 立方体原点指向顶点的向量
                float3 dir : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };
            // 纹理贴图
            samplerCUBE _MainTex; 

            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                // 通过模型顶点得到向量
                o.dir = v.vertex;
                
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // i.uv变成i.dir了
                fixed4 col = texCUBE(_MainTex, i.dir);
                // fixed4 col = fixed4(1.0, 0, 0, 1.0);
                return col;
            }
            ENDCG
        }
    }
}

向前渲染管线

向前渲染:一种Unity内置的渲染管线,其包含多光源的光照计算,分为两个部分:base和additional:

  1. base:包含最重要的逐像素光源+逐顶点光源+SH光源+LightMap+环境光,称为一个Pass
  2. Additional:其他重要的逐像素光源,一个光源一个Pass

如何处理多光源光照计算:
每个光源可以设置渲染模式为:Auto,Important,Not Important
Important是逐像素光照,Not Important是逐顶点光照和SH光源的计算。Important光源的数量决定Pass的数量。因此每个重要光源都会做一次光照计算。
float4 _LightColor0:该Pass处理的重要光源的颜色
float4 _WorldSpaceLightPos0:重要光源的位置,如果是平行光,_WorldSpaceLightPos0.w为0,其余为1
float4x4 LightMatrix0:从世界空间到光源空间的变换矩阵,可以用于采样cookie和光强衰减(attenuation)纹理
unity_4LightPosX0:
unity_4LightPosY0:
unity_4LightPosZ0:仅用于BasePass,前四个非重要点光源在世界空间中的位置
float4 unity_4LightAtten0:仅用于BasePass,储存4个非重要点光源的刷减因子

float4 unity_4LightColor:仅用于BasePass,储存4个非重要点光源的颜色

Shader "Bycw/BycwForward"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        
        LOD 100

        Pass
        {
            // 向前渲染
            Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            //预编译指令,用于前向渲染计算basePass
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;

            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                //unity_LightColor存储四个非重要光源颜色,_LightColor0是本Pass唯一重要的光源
                float4 lightColor = _LightColor0 + unity_LightColor[0] + unity_LightColor[1] + unity_LightColor[2] + unity_LightColor[3];
                // 本身乘光照
                col = col * lightColor;
                return col;
            }
            ENDCG
        }

         Pass
        {
            // 一个其他重要的光源
            Tags { "RenderType"="Opaque" "LightMode"="ForwardAdd" }
            // 混合光照
            Blend One One

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // 预编译,其他重要的光源
            #pragma multi_compile_fwdadd

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;

            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // fixed4 col = fixed4(1, 1, 1, 1);
                col = col * _LightColor0;
                return col;
            }
            ENDCG
        }
    }

    
}

渲染管线设置

每个摄像机都会绘制一个场景,因此渲染管线的设置在摄像机上:

Rendering Path:
    Use Graphics Setting:默认是向前渲染,在Player Setting->Graphics->Randing Path中设置
    Forward:向前渲染
    Delay:演示渲染

重要光源数目可以在Project Setting->Quality->Pixel Light Count设置

光照衰减计算

计算方式:
点光源:球体衰减,中心是1,超过最远距离是0
聚光灯:垂直衰减和水平衰减
衰减系数可以通过计算,也可以事先存到纹理中读取(主流)

Shader "Bycw/BycwLightAtten"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        
        LOD 100

        Pass
        {
            Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // 平行光的衰减因子
                float atten = 1.0;

                col = col * _LightColor0 * atten;
                return col;
            }
            ENDCG
        }

        Pass
        {
            Tags { "RenderType"="Opaque" "LightMode"="ForwardAdd" }
            Blend One One

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdadd

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            // 需要这个头文件
            #include "AutoLight.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldPos: TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            // 重要光源;
            fixed4 frag (v2f i) : SV_Target
            {   
                
                /*     
                float atten = 1.0;
                #ifdef USING_DIRECTIONAL_LGITH
                    fixed atten = 1.0;
                #else
                    //点光源
                    #if defined(POINT)
                        // 把片元的世界坐标转移到以点光源为参考的坐标
                        float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
                        //使用点到光源的距离值的平方来取样,可以避开开方操作
                        //使用宏UNITY_ATTEN_CHANNEL来得到衰减纹理中衰减值所在的分量,以得到最终的衰减值。
                        fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
                    //聚光灯
                    #elif defined(SPOT)
                        float4 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1));
                        //角度衰减 * 距离衰减
                        // _LightTexture0是衰减系数纹理
                        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0,lightCoord.xy/lightCoord.w + 0.5).w * tex2D(_LightTextureB0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
                    #else
                        fixed atten = 1.0;
                    #endif
                #endif
                */
                // 采用Unity内置的宏计算,原理和上面代码一致
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                
                // 自己算
                // float r = distance(i.worldPos, _WorldSpaceLightPos0);
                // float atten = 1- (r / 10);
                // end

                fixed4 col = tex2D(_MainTex, i.uv);
                col = col * _LightColor0 * atten;
                return col;
            }
            ENDCG
        }
    }
}

阴影计算原理

步骤:

  1. 计算阴影区域
  2. 在阴影区域叠加颜色

计算区域的时候,光源相当于摄像机,光源看不见的地方没有阴影。Shader中会编写一个特殊的Pass,计算阴影时调用。我们需要把片元坐标转换为以光源为中心的坐标,并通过光源计算每个片元的深度信息,如果某个地方的深度比光源照到的地方大,那么就是阴影。这个深度信息会提供给引擎,通过其他shader接收阴影。

在物体属性Mesh Renderer中,可以在Lighting中调节Cast Shadows,如果关闭,物体阴影投射的pass不会被调用,也就没有阴影。Receive Shadows可以设置是否接收阴影。
物体投射阴影的时候,会产生Pass,因此DrawCall会加1

Shader "Bycw/BycwShadow"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        
        // 投影Pass, 摄像机在我们光源的位置;
        Pass {
            // 标注模式为阴影投射
            Tags {"LightMode"="ShadowCaster"}
            

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // 预编译
            #pragma multi_compile_shadowcaster

            #include "UnityCG.cginc"

            struct v2f {                
                V2F_SHADOW_CASTER; //宏相当于 float3 vec : TEXCOORD0; float4 pos : SV_POSITION
            };

            v2f vert(appdata_base v) {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
                // 相当于
                // o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz;
                //opos = UnityObjectToClipPos(v.vertex)
                return o;
            }

            float4 frag(v2f i): SV_Target {
                SHADOW_CASTER_FRAGMENT(i)
                // 相当于
                // return UnityEncodeCubeShadowDepth((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w)
                // 把深度信息传出去了
            }
            ENDCG    
        }
            
        // Base Pass
        Pass
        {
            Tags {"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                // 根据阴影坐标,你可以获取深度信息,获取阴影的颜色值;
                SHADOW_COORDS(1) // unityShadowCoord4 _ShadowCoord : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                // 算出阴影坐标
                TRANSFER_SHADOW(o); // o._ShadowCoord = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                // 根据我们的阴影的坐标,把阴影的数据获取出来,得到阴影颜色
                fixed shadow = SHADOW_ATTENUATION(i); // unitySampleShadow(i._ShadowCoord)
                // 光的颜色乘以阴影(0到1的值,表示光线在阴影区域的衰减程度或透明度)
                float4 lightColor = _LightColor0 * shadow;
                return col * lightColor;
            }
            ENDCG
        }
        

    }
    
}

GPU并发计算(compute shadder)

GPU的线程和CPU不同,线程由硬件实现,每个线程都有自己的执行上下文和寄存器,可以单独执行计算任务,而这种计算单元有成千上万个,可以实现高效的并行计算。CPU缓存大、逻辑运算ALU较少;GPU逻辑运算较多,缓存较小。相较之下,GPU有更多的运算单元,即干活的人多了。GPU 包含数千个并行计算单元,称为 CUDA 核心。这些 CUDA 核心可以同时处理多个数据流,从而实现高效的并行计算。CUDA 核心还包含了一些特殊的硬件单元,例如浮点数处理单元、整数处理单元、逻辑单元和共享内存等,可以提供快速的数学运算和数据处理能力。
GPU中,线程被组织成线程组,每个线程组包含多个线程,维度可以用numthreads(x,y,z)设置,GPU会根据线程组的大小和维度自动分配线程资源,并将计算任务分配给每个线程

案例:控制GPU计算1024个sqrt计算

GPU代码:Unity中创建Compute Shader,GPU的代码指令中有Kernel的概念,并行计算的函数入口都可以放到kernel里面。
例如下面的函数CSSqrtMain会排到kernel0,每个这样的函数都有一个KernalIndex

// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSSqrtMain

RWStructuredBuffer<float> inputData; // 用于接受CPU传递过来的数据
RWStructuredBuffer<float> outputData; // 用于返回给GPU的数据;

// 分配线程组
[numthreads(64,1,1)]
void CSSqrtMain(uint3 id : SV_DispatchThreadID)// id是三维的
{
    outputData[id.x] = sqrt(inputData[id.x]);
}

常见的读写缓存区数据类型如下:
RWBuffer:这个类型是用于读写一维数据的缓冲区类型,可以用于存储任意类型的数据,例如float、int等。可以使用SetData和GetData函数将数据写入和读出缓冲区。
RWByteAddressBuffer:这个类型也是用于读写一维数据的缓冲区类型,但它是按字节寻址的,可以用于存储任意类型的数据。可以使用SetData和GetData函数将数据写入和读出缓冲区。
RWStructuredBuffer:这个类型是用于读写结构化数据的缓冲区类型,可以用于存储自定义的结构体类型。可以使用SetData和GetData函数将数据写入和读出缓冲区。
RWTexture1D:这个类型是用于读写一维纹理数据的缓冲区类型,可以用于存储颜色或其他类型的数据。可以使用SetPixel和GetPixel函数将数据写入和读出缓冲区。
RWTexture1DArray:这个类型是用于读写一维纹理数组数据的缓冲区类型,可以用于存储颜色或其他类型的数据。可以使用SetPixel和GetPixel函数将数据写入和读出缓冲区。
RWTexture2D:这个类型是用于读写二维纹理数据的缓冲区类型,可以用于存储颜色或其他类型的数据。可以使用SetPixel和GetPixel函数将数据写入和读出缓冲区。
RWTexture2DArray:这个类型是用于读写二维纹理数组数据的缓冲区类型,可以用于存储颜色或其他类型的数据。可以使用SetPixel和GetPixel函数将数据写入和读出缓冲区。
RWTexture3D:这个类型是用于读写三维纹理数据的缓冲区类型,可以用于存储颜色或其他类型的数据。可以使用SetPixel和GetPixel函数将数据写入和读出缓冲区。

CPU传递数据需要使用ComputeBuffer:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MySqrtGPU : MonoBehaviour
{
    private static int itemCount = 1024;
    // 编辑器绑定GPU脚本
    public ComputeShader mysqrtGPUShader = null;

    ComputeBuffer inputData = null;
    ComputeBuffer outputData = null;

    void Start()
    {
        inputData = new ComputeBuffer(itemCount, sizeof(float));
        outputData = new ComputeBuffer(itemCount, sizeof(float));

        float[] rawInputData = new float[itemCount];
        for (int i = 0; i < itemCount; i++) {
            rawInputData[i] = (float)(i);
        }
        // 设置传递数据
        inputData.SetData(rawInputData);
        // 查找对应的KernalIndex
        int kernelIndex = this.mysqrtGPUShader.FindKernel("CSSqrtMain");
        // 关联数据
        this.mysqrtGPUShader.SetBuffer(kernelIndex, "inputData", inputData);
        this.mysqrtGPUShader.SetBuffer(kernelIndex, "outputData", outputData);
        // 执行对应kernelIndex的函数
        this.mysqrtGPUShader.Dispatch(kernelIndex, itemCount / 64, 1, 1);
        // 取出结果
        float[] result = new float[itemCount];
        outputData.GetData(result);

        for (int i = 0; i < itemCount; i++) {
            Debug.Log("sqrt(" + i + ") = " + result[i]);
        }
    }
}

查看Unity内置shader源码

查看Unity内置shader源码是我们学习shader的重要资源,可以在Unity官网上的Built in shaders下载

shader语法

ShaderLab+CG

Unity定义常用的基于顶点的数据盒子

以下盒子默认数据如下,可以自己灵活改变,装载任意数据类型(float,float2,float3,float4):
POSITION:位置
NORMAL:法线
TANGENT:切线
TEXCOORD0:纹理坐标
TEXCOORD(n):自己定义内容,Shader Model为2/3的时候最多有8个这样的盒子,如果是4/5,最多有16个盒子
COLOR0:颜色,此外还有COLOR1

以下是系统指定用途,最好不要改变:
SV_POSITION:顶点shader返回的顶点位置
SV_Target:片元着色器shader返回给渲染管线的颜色

顶点shader如何传递数据给着色器shader

顶点shader的有一个入口函数vert,其默认传递的参数是一个结构体appdata,在结构体中我们可以把数据盒子中的数据(基于模型的顶点数据)赋给其成员,默认的成员是传递顶点坐标的float4 vertex : POSITION和传递纹理坐标的float2 uv : TEXCOORD0;,具体什么成员变量存放什么数据完全由我们自己决定。然后我们就可以在vert函数中使用这个结构体进行计算了。
vert返回的数据默认也是一个名为v2f的结构体,这个结构体的成员变量定义了要传递给片元着色器shader的数据,存放的盒子可以自己来定,默认是vertex:ST_POSITION和uv:TEXCOORD0,其中ST_POSITION是顶点shader返回给系统的片元坐标数据,我们不要修改它,其他变量例如uv是传给着色shader的,可以自己定。在vert函数中我们需要通过appdata传进来的数据计算出v2f返回的数据。
顶点shader对传递给片元着色shader的数据v2f会经过三角形插值处理,经过插值后,每一个片元都拥有和顶点一样格式的v2f数据,然后片元着色shader会对片元进行逐个处理。

片元着色shader处理数据

片元着色shader接收顶点shader返回插值后的数据,对片元进行逐个处理。
处理时通过法线,光源位置,片元本色等信息,计算出片元应当显示的颜色。
处理完成之后,返回值是片元的颜色,通过ST_Target返回渲染管线。

CPU如何传递数据给GPU

在shader中,CPU通过在CGProgram中定义的变量把数据传递给GPU,例如:

sampler2D _MainTex;
float4 _MainTex_ST:
float3 _MyColor:
float _MyValue:

这些变量前面可以加上一个uniform,Unity中可以省略,这些数据在shader中只读不能修改,但是为了操作方便,我们可以把数据绑定到材质球的属性面板中进行修改,可以在属性列表properties中定义。

Properties
    {
        // 变量名字("显示名字",类型) = 默认值
        _MainTex ("Texture", 2D) = "white" {}
        _MyPos("MyPos", Vector) = (1.0, 1.0, 1.0, 1.0)
        _MyColor ("MyColor", Color) = (1.0, 1.0, 1.0, 1.0)
        _MyValue ("MyValue", Range(0, 1.0)) = 0.5        
    }

下面是Properties中类型对应的变量:
Color,Vector –> float4,half4,fixed4
Range,Float –> float
2D –> Sampler2D
Cube –> SamplerCube
3D –> Sampler3D

渲染管线配置

一个shader脚本可以包含多个SubShader,但是只会运行一个,如果前面的不能运行,才会运行后面的。如果所有的SubShader不能运行的时候,可以在最后指定默认的shader:

Fallback "VertexLit"

每个SubShader会包含若干个Pass,每个Pass都是一个完整的渲染流水线,Pass的渲染管线状态可以进行如下设置(如果写到SubShader则所有SubShader的Pass都用):

Cull Back|Front|off     //设置提出模式:剔除背面/正面/关闭,默认是Back
Ztest Less|Greater|LEqual|GEqual|Equal|NotEqual|Alway //设置深度测试使用的规则
Zwrite On|Off  //开启/关闭深度写入
Blend SrcFactor DstFactor //开启并设置混合模式

在pass中可以定义名字:

name "myShader"

这样就能直接使用这个名字进行调用了

标签Tag

Tag可以在SubShader或Pass中设置:
格式:Tag{“Key”=”Value” “Key2”=”Value2”}
Queue:渲染队列 Qpaque/Transparent,渲染队列的值可以在材质面板中设置,它的默认值就是基于Qpaque(2000),Tansparent(3000)的
RanderType:对着色器进行分类,不透明着色器/半透明着色器 Qpaque/Transparent
DisableBatching:指明是否对该物体使用批处理 “DisableBatching”=True
ForceNoShadowCasting:控制是否会投射阴影
IgnoreProjector:如果设置为True,物体不会受Projector影响,用于半透明
CanUsePriteAtlas:如果用于精灵,设置为False
PreviewType:材质在属性面板中如何显示,默认是球,可以设置为Plane,SkyBox
LightMode:定义该Pass在渲染流水线中的角色

Shader数据类型

浮点型

主要包括float(32)、half(16)、fixed三种精度
所有平台显卡都支持float,fixed部分显卡不支持,但是如果代码用到了就会自动用float进行替代,一般情况下我们使用float就可以了。

浮点数用于表示向量或颜色:

  1. 颜色可以由3到4个浮点数表示,float3–>rgb,float4–>rgba
  2. 向量可以分为三种(二维,三维,四维),对应于(x,y),(x,y,z),(x,y,z,w)

初始化:

float3 color = float3(1,0,0)
float4 view = float4(1,1,1,1)

访问(降维和加维):

color.r
color.g
color.b
color.rgb  // 会输出三维的浮点数
view.x
view.y
view.z
view.w
view.xy
view.xyz
float value = float4(view.xyz,1.0)

结构体

struct temp_data{
    int a;
    float2 b;
    uint c;
}
struct temp_data temp;
temp.a = 10
temp.b = float2(10.0,10.0)

纹理类型和采样函数

// 三种纹理类型
sampler2D a;
samplerCUBE b;
sampler3D c;
// 采样函数
float4 col = tex2D(a,i.uv);
float3 col2 = texCUBE(b,float3(1.0,0.0,0.0));
float3 col3 = tex3D(c,float3(1.0,0.0,0.0));

````

#### 颜色
颜色有两种描述方式,两种可以相互转换
1. 红,绿,蓝三个分量,简称RGB
2. 色相,饱和度,亮度,简称HSV

#### Bool,条件判断,循环语句

```csharp
bool isSelf = false
int mask = 0xff
int value = 0xffffffff
// 与或非:&& || !
// 位运算:左移<<  右移>>  按位或|  按位与&  按位取反~  按位异或^
if(isSelf) // 条件和循环语句与C#一致
{
    mask = mask | value;
}
else if(mask==value){
}
int i = 0;
while(i < 10){
    i++;
}
for(int k = 0;k < 10;k++){
}


#### 矩阵

矩阵是描述线性变换的数学工具,用一个矩阵乘以一个向量,我们就可以把这个向量映射到另一个向量。Unity中矩阵往往表示一个点在N维空间的变换。包括但不限于缩放,平移,旋转。在Unity中矩阵最多只有4x4。

```csharp
// 矩阵的定义
float2x2 a = float2x2(1,0,0,1);
float3x3 b = float2x2(1,0,0,1,1,0,0,1,1);
float2x3 c = float2x3(1,0,0,1,1,0);
float4 v1 = float4(1,0,0,0);
float4x4 d = float4x4(v1,v1,v1,v1);
float value = d[3][3]

常用内置函数(Cg和GLSL)

radians(degrees) // 度转弧度
degrees(radians) // 弧度转度
sin
cos
tan
asin
acos
atan
pow
exp
log:e为底
exp2:2的几次方
log2:2为底
sqrt
inversesqrt:开根号的倒数
abs
sign
floor:向下取整
ceil:向上取整
fract:取小数部分
mod:传入x,y,返回x-y*floor(x/y)
min
max
clamp
mix:传入x,y,a,返回x*(1-a) + y*a
step:传入edge,x,x大于等于edge返回1,否则返回0
smoothstep:传入edge0,edge1,x,t=clamp((x-edge0)*(edge1-edge0),0,1),返回t*t*(3-2*t)
matrixCompMult(mat x,mat y):两个矩阵对应的元素相乘,并非常识上的矩阵乘法
// 向量相关
lessThan
lessThanEqual
greaterThan
greaterThanEqual
equal
any
all
not
// 材质查找
texture2D
texture2DProj
texture2DLod
texture2DProjLod
textureCube
textureCubeLod
// 几何函数
distance
faceforward
length
normalize
reflect
refract

shader精选案例

宝石特效

效果:宝石出现在屏幕的时候,中间的部分是暗淡的,但是两边的部分是明亮的,并且旋转的时候根据视角改变亮和暗的区域。
方法:定义视向量为片元指向摄像机的向量,将这个向量与片元的法线进行点乘,然后将这个值作为明暗判断的指标,法线指向摄像机的片元越暗

Shader "Bycw/BycwStone"
{
    // CPU--->GPU, 这个里面的变量你在Shader代码里面不能修改的;
    Properties
    {
        // 变量名字("显示的名字", 类型) = 默认值
        // _MainTex ("Texture", 2D) = "white" {}
        _BaseColor("BaseColor", Color) = (1.0, 0, 0, 1.0)
        _PowerValue("Power Value", Range(0, 2)) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;

                // 片元着色需要的数据,顶点会传递给它;
                float3 wNormal: TEXCOORD1;
                float3 viewDir: TEXCOORD2;
            };

            // CPU ---。GPU, uniform修饰;
            uniform float _PowerValue;
            uniform fixed4 _BaseColor;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                // 法线:
                o.wNormal = normalize(UnityObjectToWorldNormal(v.normal));
                
                // 视方向
                float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.viewDir = normalize(WorldSpaceViewDir(worldPos));

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = _BaseColor;
                float value = dot(i.viewDir, i.wNormal); // [-1, 1]
                value = saturate(value); // value < 0 , value = 0, value > 1, value = 1
                value = 1 - value;
                // _PowerValue可以在编辑器调节,调得越大,暗的部分越大
                value = pow(value, _PowerValue); 
                return col * value;
            }
            ENDCG
        }
    }
}

Voodoo风格游戏水面

我们用一个Plane来当水面的模型,并创建一个材质和shader,shader关联到材质上,材质关联到模型上。然后步骤如下:

  1. 给水面添加一个本来的颜色
  2. 给水面远处添加一个边际颜色,美术需要用一个颜色分量图做渐变,渐变色是通过alpha混合盖到水面上的
  3. 给水面添加一个水波纹,水波纹是一张黑白的图片,叠加在水面之上,黑色的部分会显示出原本水的颜色,白色的就是波纹。
  4. 给水波纹添加动画,UV动画不断改变UV坐标,让贴图在表面运动起来
Shader "Bycw/Water"
{
    Properties
    {
		// 渐变百分比,渐变的远处颜色,近处颜色
        _GradTex ("GradTex", 2D) = "white" {}
		_GradNearColor("GradNearColor", Color) = (1.0, 1.0, 1.0, 1.0) 
		_GradFarColor("GradFarColor", Color) = (1.0, 1.0, 1.0, 1.0) 

		// 波纹的贴图和本色(0,80,95,255)
		_WaveTex("WaveTex", 2D) = "white" {}
		_WaveBaseColor("WaveBaseColor", Color) = (1.0, 1.0, 1.0, 1.0)
        // 水的本色(20,170,255,255)
		_BaseColor("BaseColor", Color) = (1.0 , 1.0, 1.0, 1.0)
        // 波纹移动速度
		_Speed("Speed", Float) = 0.03
    }
    SubShader
    {
        
		Tags { "RenderType"="Opaque" }
		LOD 100


        Pass
        {
			
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            uniform sampler2D _GradTex; 
            uniform sampler2D _WaveTex; 
			float4 _BaseColor; 
			float4 _GradNearColor;
			float4 _GradFarColor;
			float4 _WaveBaseColor;
			float _Speed;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                // o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.uv = v.uv;

                return o;
            }
            // 这个函数用来获取图片的渐变值
			float4 gradColor(float2 uv) {
				float gradValue = tex2D(_GradTex, uv).r; // [0, ~1.0]
				gradValue = clamp(gradValue, 0, 1);
                // 插值,gradValue为0-1映射到_GradNearColor-_GradFarColor
				float4 gColor = lerp(_GradNearColor, _GradFarColor, gradValue);
				return gColor;
			}
			// 获取水波颜色
			float4 waveColor(float2 uv) {
                // 让贴图横竖平铺50次(相当于给贴图缩小50倍,会自动平铺)
				float tilling = 50;
                // 移动距离,_Time.y是Unity加载游戏开始过去的时间
				float2 offset = float2(1, 0) * _Speed * _Time.y;
                // 从不同的坐标上向右移动
				float4 wColorLhs = tex2D(_WaveTex, uv * tilling + offset);
				float4 wColorRhs = tex2D(_WaveTex, -uv * tilling + offset);
                // 取较暗的(水)
				float4 wColor = min(wColorLhs, wColorRhs);

				float stren = 2;
                // 强度*纹理贴图*本色
				return stren * wColor * _WaveBaseColor;
			}

            fixed4 frag (v2f i) : SV_Target
            {
                float4 col = _BaseColor;

				float4 gColor = gradColor(i.uv);
                // 颜色根据alpha混合
				col = gColor * gColor.a + _BaseColor * (1 - gColor.a);
                // 计算水波纹
				float4 wColor = waveColor(i.uv);
                // 颜色+波纹
                return col + wColor;
            }
            ENDCG
        }
    }
}

氮气加速

首先我们需要一个子弹头的模型,把整个车覆盖,子弹头的贴图是外围发光的贴图,贴图是一种光的渐变,我们不断改变子弹头顶点的纹理坐标,从而形成光的动画效果。

Shader "Bycw/UVShader"
{
	// 属性,就可以被绑定到材质;
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
        // 定义一张遮罩图片,用来切割掉原来纹理多余的部分
		_MaskTex ("Mask", 2D) = "white" {}

		_TintColor("TintColor", Color) = (0.5, 0.5, 0.5, 0.5)
	}

	SubShader
	{
		Tags { "RenderType"="Transparent" "Queue"="Transparent" }
		Blend SrcAlpha One
		cull off
		ZWrite off
		// Unity 开发语言, Cg ---> OpenGL + Dx;
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag	
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION; // 管道默认顶点坐标;
				float2 uv : TEXCOORD0; // 传递是纹理坐标
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				float2 uv_mask: TEXCOORD1;
			};

			sampler2D _MainTex; 
			float4 _MainTex_ST;  

			sampler2D _MaskTex;
			float4 _MaskTex_ST; 

			fixed4 _TintColor;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.uv_mask = TRANSFORM_TEX(v.uv, _MaskTex);

				return o;
			}
			
			// 着色Shader
			fixed4 frag (v2f i) : SV_Target
			{
                // 改变纹理坐标,从而实现动画效果
				i.uv.y -= (_Time.y * 5);
				fixed4 mask = tex2D(_MaskTex, i.uv_mask);
				fixed4 col = tex2D(_MainTex, i.uv);
                // 乘上光的颜色
				col *= _TintColor;
                // 乘上遮罩的透明度
				col.a *= mask.a;
                // 加亮度
				col *= 2;
				return col;
			}

			ENDCG // Cg代码结束

		} // end 
	} // end SubShader
}

UV动画和脚本传递数据给shader

原理:不断改变顶点UV的纹理坐标,物体表面的贴图就会移动。例如上面案例的水面和氮气加速.
注意:纹理图片的Wrap Mode要是repeat模式,纹理坐标超过0到1会进行平铺取值

Shader "Bycw/UVShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _UVAnimTex("Texture", 2D) = "white" {}


    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            sampler2D _UVAnimTex; 
            // 代码可以访问这个变量,可以不用在材质球中定义
            uniform float _UVOffset;

            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                UNITY_TRANSFER_FOG(o,o.vertex);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                // sample the texture
                // float4 _Time(t/20, t, t*2, t * 3)
                // float2 offset = float2(0, 0);
                // offset.u = 0.25 * _Time.y;

                // fixed4 uvColor = tex2D(_UVAnimTex, i.uv + 0.25 * _Time.y);
                // C#代码中,this.mat.SetFloat("",0.0f);就可以设置对应的材质参数
                fixed4 uvColor = tex2D(_UVAnimTex, i.uv + _UVOffset);
                fixed4 col = tex2D(_MainTex, i.uv);
                // 两张纹理进行叠加
                col = uvColor + col;

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

C#代码传递参数:

public class UVAnimCtrl:MonoBehaviour{
    public Material mat;
    private float _UVOffset;
    void Start(){
        _UVOffset = 0;
        mat.SetFloat("_UVOffset",0.0f);
    }
    void Update(){
        float v = 0.25f;
        _UVOffset += v * Time.deltaTime;
        this.mat.SetFloat("_UVOffset",_UVOffset);
    }

}

屏幕后期处理

概念:对摄像机显示的画面进行一些特殊的处理,例如:人物死亡游戏场景置为灰色,模糊处理

原理:截获后期处理屏幕的时机,然后编写处理图片的shader。Unity会自动检查摄像机上挂载的脚本有没有实现一个固定的接口OnRenderImage,Unity会给这个接口传递两个参数,第一个是原画面,第二个是目标画面,原画面经过处理后,我们返回目标画面,完成后期处理

Tips:不想使用后期处理的时候,把摄像机脚本禁用掉即可

摄像机脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GrayPostProcessing : MonoBehaviour
{
    public Shader grayPostProcessingShader = null;
    private Material grayMat;

    private float grayPercent = 0;

    void Start() {
        if (this.grayPostProcessingShader != null) {
            this.grayMat = new Material(this.grayPostProcessingShader);
        }
    }

    private void OnEnable() {
        this.grayPercent = 0;
    }

    // 注意,只有放到摄像机节点下才有效果;
    // source: 就是我们当前摄像机绘制好的画面;
    // destination: 你处理完后要把数据传送的目标画面;
    // 获取到了后期处理的时机;
    // 可以通过控制这个脚本enable来确定是否有后期处理;

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (this.grayMat != null) {
            // Blit函数会把source,作为一个_MainTex传递给shader
            // 这个函数有多个重载,可以指定传递的Pass
            Graphics.Blit(source, destination, this.grayMat);
        }
        else {
            Graphics.Blit(source, destination);
        }
        
    }

    private void OnDisable() {
        this.grayPercent = 0;
    }

    private void Update()
    {
        if (this.grayPercent >= 1.0f) {
            return;
        }
        // 灰度渐变,传参数给GPU
        this.grayPercent = Mathf.MoveTowards(this.grayPercent, 1.0f, 0.5f * Time.deltaTime);
        this.grayMat.SetFloat("_grayPercent", this.grayPercent);
    }
} 

shader:

Shader "Hidden/GrayPostProcessing"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            float _grayPercent;

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                // rgb转灰度公式,计算的是亮度
                fixed grayValue = 0.299 * col.r + 0.587 * col.g + 0.114 * col.b;
                fixed4 grayColor = fixed4(grayValue, grayValue, grayValue, 1.0);
                // CPU控制转灰度的比例
                col = lerp(col, grayColor, _grayPercent); // [0.0, 0.5, 1.0]
                // col = 1 - col;
                return col;
            }
            ENDCG
        }
    }
}

卡通渲染着色器

卡通渲染的特征:

  1. 边缘有明显的线条,这可以采用模型描边的方法,用纯色把模型往外后侧扩一点
  2. 有高光亮部,用镜面反射原理解决,并通过smoothstep函数渐变
  3. 有明显的明暗分界线,通过一个渐变贴图来实现,贴图的右边是白色,左边渐变成黑灰色。我们可以点乘片元法线与光源方向来计算渲染的亮度,当我们片元的法线与光源方向越一致,颜色采用贴图越右边的,如果相反则采用越左边的。这个贴图是阶梯式的突变,所以有明显的交界线。

Tips:SmoothStep函数的妙用:https://zhuanlan.zhihu.com/p/157758600,可以从0进行平滑过渡到1,通过相减操作可以平滑过渡出一片区域

Shader "Custom/Toony"
{
	Properties{
		_Color("Color Tint", Color) = (1, 1, 1, 1) // 整体色调
		_MainTex("Main Tex", 2D) = "white" {}
		_Ramp("Ramp Texture", 2D) = "white" {}    //渐变纹理,右边比左边亮,右边白左边黑
		_Outline("Outline", Range(0, 2)) = 0.1    //控制轮廓线宽度
		_OutlineColor("Outline Color", Color) = (0, 0, 0, 1)    //轮廓线颜色
		_Specular("Specular", Color) = (1, 1, 1, 1)    //高光颜色
		_SpecularScale("Specular Scale", Range(0, 0.1)) = 0.01    //控制计算高光反射时的阈值
	}
		SubShader{
			Tags { "RenderType" = "Opaque" "Queue" = "Geometry"}
            // 这个Pass用于模型描边
			Pass {
				NAME "OUTLINE"
				Cull Front  // 只渲染背面

				CGPROGRAM

				#pragma vertex vert
				#pragma fragment frag

				#include "UnityCG.cginc"

				float _Outline;
				fixed4 _OutlineColor;

				struct a2v {
					float4 vertex : POSITION;
					float3 normal : NORMAL;
				};

				struct v2f {
					float4 pos : SV_POSITION;
				};

				v2f vert(a2v v) {
					v2f o;

					float4 pos = float4(UnityObjectToViewPos(v.vertex).xyz, 1);
                    // 法线从模型空间转到3D视角空间
					float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
					// float3 normal = UnityObjectToViewNormal(v.normal);
                    // 往后扩,法线统一朝向后方
					normal.z = -0.5;
                    // 顶点沿着法线外扩
					pos = pos + float4(normalize(normal), 0) * _Outline;
                    // 乘以透视投影转到2D空间
					o.pos = mul(UNITY_MATRIX_P, pos);

					return o;
				}

				float4 frag(v2f i) : SV_Target {
                    // 纯色的着色
					return float4(_OutlineColor.rgb, 1);
				}

				ENDCG
			}

            // 本来颜色的着色
			Pass {
				Tags { "LightMode" = "ForwardBase" }

				Cull Back

				CGPROGRAM

				#pragma vertex vert
				#pragma fragment frag

				#pragma multi_compile_fwdbase

				#include "UnityCG.cginc"
				#include "Lighting.cginc"
				#include "AutoLight.cginc"
				#include "UnityShaderVariables.cginc"

				fixed4 _Color;
				sampler2D _MainTex;
				float4 _MainTex_ST;
                // 包含三种阶级黑白颜色的图片,这是一种渐变纹理
				sampler2D _Ramp;
                // 高光相关
				fixed4 _Specular;
				fixed _SpecularScale;

				struct a2v {
					float4 vertex : POSITION;
					float3 normal : NORMAL;
					float4 texcoord : TEXCOORD0;
					float4 tangent : TANGENT;
				};

				struct v2f {
					float4 pos : POSITION;
					float2 uv : TEXCOORD0;
					float3 worldNormal : TEXCOORD1;
					float3 worldPos : TEXCOORD2;
                    // 阴影相关,相当于使用了TEXCOORD3
					SHADOW_COORDS(3)
				};

				v2f vert(a2v v) {
					v2f o;

					o.pos = UnityObjectToClipPos(v.vertex);
					o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
					o.worldNormal = UnityObjectToWorldNormal(v.normal);
					o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                    // 计算阴影相关的数据
					TRANSFER_SHADOW(o);

					return o;
				}

				float4 frag(v2f i) : SV_Target {
                    // 法线
					fixed3 worldNormal = normalize(i.worldNormal);
                    // 光源方向
					fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                    // 摄像机方向
					fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                    // 半角向量
					fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);

					fixed4 c = tex2D(_MainTex, i.uv);
                    // 本色*色调
					fixed3 albedo = c.rgb * _Color.rgb;
                    // 颜色*环境光
					fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                    // 计算光照衰减,atten是返回的强度,1代表无衰减,0是完全衰减
					UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                    // 半兰伯特计算光照强度,这里还叠加上了衰减
					fixed diff = dot(worldNormal, worldLightDir);
					diff = (diff * 0.5 + 0.5) * atten;
                    // 光的颜色*本色*色调*阶梯变化颜色,如果用diff直接代替tex2D(_Ramp, float2(diff, diff)).rgb就是没有阶梯突变的柔和阴影
					fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;

                    // 高光
					fixed spec = dot(worldNormal, worldHalfDir);
                    // fwidth可以求出相邻片元的近似导数(x和y方向上变化的绝对值之和),值大于0,2.0是高光的范围
					fixed w = fwidth(spec) * 2.0;
                    // 高光颜色 * 强度(平滑过渡)
                    // 平滑过渡函数smoothstep(t1,t2,x): x = clamp((x-t1)/(t2-t1),0,1) return x*x*(3-2*x)
                    // step函数:如果小于0.0001则为0否则为1
					fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);

					return fixed4(ambient + diffuse + specular, 1.0);
				}

				ENDCG
			}
		}
			FallBack "Diffuse"
}

顶点索引和GPU Instancing

概念:每个模型的顶点都对应唯一的索引,可以在Shader中的盒子拿到这个索引

对于每一帧动画来说,CPU都需要到动画文件采集对应的顶点数据给GPU,非常消耗性能,我们可以在编辑器模式下事先把每个顶点位置计算处理,存放到一个纹理中,Shader根据顶点的索引来查表,动态改变顶点位置,即可用空间换时间。

这里参考《千人战斗场景优化》中的动画采样

GPU Instancing可以在一次渲染调用中(DrawCall)绘制多个相同网格,减少CPU和GPU之间数据传输的开销。

Shader "BYCW/AnimMapShader"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
        // 动画采样组件生成的图片
		_AnimMap ("AnimMap", 2D) ="white" {}
        // 动画时间长度
		_AnimLen("Anim Length", Float) = 0
	}
		SubShader
		{
			Tags { "RenderType" = "Opaque" }
			LOD 100
			Cull off

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			//开启gpu instancing
			#pragma multi_compile_instancing

			
			#include "UnityCG.cginc"

			struct appdata
			{
				float2 uv : TEXCOORD0;
                // 模型顶点索引
				// uint vid : SV_VertexID;
                // 用于设置当前渲染的实例ID
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
                // 调用宏
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;

			sampler2D _AnimMap;
			// xxx_TexelSize是unity为我们提供的访问xxx纹理对应的每个像素大小, 例如一张512X512大小的纹理,该值大约为0.001953(1/512, 1/512)
			float4 _AnimMap_TexelSize;// x == 1/width

			float _AnimLen;

			// 额外加参数,不用放到appdata中
			v2f vert (appdata v, uint vid : SV_VertexID)
			{
                // 调用宏
				UNITY_SETUP_INSTANCE_ID(v);
				float f = _Time.y / _AnimLen; // 百分比,动画进度的,超过1纹理会循环寻址

				// fmod(f, 1.0);

				float animMap_x = (vid + 0.5) * _AnimMap_TexelSize.x; // 索引对应的百分比,_AnimMap_TexelSize.x是每个像素在x方向上占用整张图的百分比,0.5指的是像素中心点位置
				float animMap_y = f; 
                // 根据百分比采样,得到模型坐标
				float4 pos = tex2Dlod(_AnimMap, float4(animMap_x, animMap_y, 0, 0));

				v2f o;
				o.uv = v.uv;
				o.vertex = UnityObjectToClipPos(pos);

				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				
				fixed4 col = tex2D(_MainTex, i.uv);
				return col;
			}
			ENDCG
		}
	}
}

几何shader详解

曲面细分着色器可以更加曲面化模型表面
几何着色器可以修改几何形状,插入更多的面,让渲染的面变成线,点,改变图源

Shader "Bycw/GTYShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            // 几何shader
            #pragma geometry gmty
            #pragma fragment frag
            

            #include "UnityCG.cginc"

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

            struct g2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2g vert (appdata v)
            {
                v2g o;
                // o.vertex = UnityObjectToClipPos(v.vertex);
                o.vertex = v.vertex;
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                
                return o;
            }
            // 表示输出最多的顶点数目
            [maxvertexcount(3)]
            /*void gmty(triangle v2g tri[3], inout TriangleStream<g2f> outTris) {

                g2f o;
                o.uv = tri[0].uv;
                o.vertex = UnityObjectToClipPos(tri[0].vertex);
                outTris.Append(o);

                o.uv = tri[1].uv;
                o.vertex = UnityObjectToClipPos(tri[1].vertex);
                outTris.Append(o);

                o.uv = tri[2].uv;
                o.vertex = UnityObjectToClipPos(tri[2].vertex);
                outTris.Append(o);

                outTris.RestartStrip(); 

            }*/
            // 3个顶点,该函数没有返回值,通过图源输出流,LineStream绘制的是线
            void gmty(triangle v2g tri[3], inout LineStream<g2f> outTris) {

                g2f o;

                // 一个图源
                o.uv = tri[0].uv;
                o.vertex = UnityObjectToClipPos(tri[0].vertex);
                outTris.Append(o);

                o.uv = tri[1].uv;
                o.vertex = UnityObjectToClipPos(tri[1].vertex);
                outTris.Append(o);

                o.uv = tri[2].uv;
                o.vertex = UnityObjectToClipPos(tri[2].vertex);
                outTris.Append(o);
                // 结束,进入下一个图源
                outTris.RestartStrip(); // 

            }

            fixed4 frag (g2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                
                return col;
            }
            ENDCG
        }
    }
}

噪声纹理之溶解特效

原理:溶解现象是给物体的一些地方着色,一些地方不着色,随着溶解现象的发生,不着色的地方越来越多,最后整个物体消失。这时候我们需要引入噪声纹理,这是一张不规则的灰度图(可以根据效果调整),如果某个片元的颜色分量小于噪声纹理和某个阈值(可以自己调节),那么这个片元不着色,随着这个阈值的增大,物体消失的部分越多。

Shader "Bycw/DissolveShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        // 噪声纹理
        _DissolveTex("DissolveTex", 2D) = "white" {}
        // 溶解阈值
        _DissolveThreshold("DissolveThreshold", Range(0, 1)) = 0
        // 灼烧边缘
        _DissolveEdge("DissolveEdge", Range(0, 0.01)) = 0.01


    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;  // CPU--->GPU
            float4 _MainTex_ST;


            sampler2D _DissolveTex; 
            float _DissolveThreshold; 
            uniform float _DissolveEdge; 

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 dissolveCol = tex2D(_DissolveTex, i.uv);
                // 关键行,判断r通道是否大于阈值
                float value = dissolveCol.r - (_DissolveThreshold - _DissolveEdge);
                if (value <= 0) {
                    discard;   // 不渲染
                }

                if (dissolveCol.r < _DissolveThreshold) { 
                    return fixed4(1.0, 0, 0, 1.0);   // 溶解边缘加上红色颜色表示灼烧
                }

                return col;
            }
            ENDCG
        }
    }
}

C#代码控制:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DissolveCtrl : MonoBehaviour
{
    public Material mat;

    private float totalTime = 3.0f; // 
    private float curTime = 0;
    private bool isRunning = false;

    // Start is called before the first frame update
    void Start()
    {
        this.isRunning = false;

        this.StartCtrlDissolve(); // test;
    }

    public void StartCtrlDissolve() {
        this.isRunning = true;
        this.curTime = 0;

        this.mat.SetFloat("_DissolveThreshold", 0);
        this.mat.SetFloat("_DissolveEdge", 0.01f);
    }

    // Update is called once per frame
    void Update()
    {
        if (this.isRunning == false) {
            return;
        }

        this.curTime += Time.deltaTime;
        float per = this.curTime / this.totalTime;
        per = (per > 1) ? 1 : per;

        if (this.mat) {
            Debug.Log(per);
            this.mat.SetFloat("_DissolveThreshold", per);
        }

        if (this.curTime >= this.totalTime - 0.1f) {
            this.mat.SetFloat("_DissolveEdge", 0);
        }

        if (this.curTime >= this.totalTime) {
            this.isRunning = false;
        }
    }
}

边缘发光

Shader "Bycw/light_shader"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100
        // 绘制模型本体
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
				
			#include "UnityCG.cginc"
			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal: NORMAL;

			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;

				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = tex2D(_MainTex, i.uv);	
				return col;
			}
			ENDCG
		}
        // 绘制发光边缘
		Pass
		{
			cull front
            // 设置混合模式
			blend SrcAlpha One

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
				
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal: NORMAL;

			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				float4 w_vertex: TEXTURE2;
				float3 w_normal: TEXTURE1;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;
				float4 new_vert = v.vertex + float4(v.normal, 1) * float4(0.1, 0.1, 0.1, 1);
				o.vertex = UnityObjectToClipPos(new_vert);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				
				o.w_normal = UnityObjectToWorldNormal(v.normal);
				o.w_vertex = mul(unity_ObjectToWorld, v.vertex); // 20%
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				float3 view_dir = i.w_vertex - _WorldSpaceCameraPos;
				float3 w_normal = i.w_normal;

				view_dir = normalize(view_dir);
				w_normal = normalize(w_normal);

				float value = dot(view_dir, w_normal);
                // 最低只能是0
				value = (value < 0) ? 0 : value;
                // 5可以调节渐变的快慢,为了使得边缘明显一点,用15调节强度
				value = pow(value, 5) * 15;
				fixed4 col = fixed4(1, 1, 0, value);	
				return col;
			}
			ENDCG
		}
	}

URP渲染管线

可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。

URP渲染管线概念:URP是一种轻量级渲染管线,适用于移动设备、虚拟现实(VR)、增强现实(AR)以及高性能需求的平台。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。它支持基于物理的渲染(PBR),包括光照、阴影、反射等效果。URP还支持后处理效果、2D渲染、粒子系统、Shader图形编程、自定义渲染管线等功能。不同的渲染管线,shader是不能共用的。

Unity内置标准渲染管线:每个重要光源都需要绘制一次物体,着色。场景中存在多个重要光源则需要绘制物体多次并叠加效果,消耗性能,但是可以支持任意多光源
URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。对于手机游戏来说,URP更加适合。

创建项目的时候,我们需要选择3D Sample Scene(URP)。项目的Package就会安排好URP的编程环境。

与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。Scenes中会有Sample Scene Lighting Setting文件,对场景的烘焙进行设置。Settings文件夹中就是关于渲染管线的设置了。包括:

  1. ForwardRenderer设置,URP实现的向前渲染,延时渲染
  2. SampleSceneProfile:示例场景后期处理的配置文件
  3. URP渲染质量的配置文件

URP的变化:

  1. 摄像机:多摄像机机制,base+overlay
  2. 光源:主光源,附加光源
  3. 后期处理
  4. 反射探头(Reflection Probes)
  5. 光照探头组(Light Probe Group)

摄像机

URP摄像机分为主摄像机,主摄像机会有一个列表管理若干个Overlay摄像机

主摄像机物体下包含Camera组件和Universal Additional Camera Data组件

Camera组件:
    render type:渲染类型,分为base和overlay
    Projection:
        Projection:分为透视投影和正交投影
        FOV Axis:FOV轴,垂直还是水平
        Field of View:视场角
        Physical Camera
        Clipping Planes:近端和远端距离
    Rendering:
        Renderer:向前渲染和延迟渲染
        Post Processing:是否后处理
        Anti-aliasing:抗锯齿算法设置
        Dithering:防抖
        Render Shadow:是否绘制阴影
        priority:优先级,越高的绘制在低的上面
        Opaque Texture:纹理设置
        Depth Texture:深度贴图设置
        Culling Mask:绘制哪些物体
        Occlusion Culling:
    Environment:
        Background type:背景,可以选择天空盒和纯色
        HDR:高清
        MSAA:抗锯齿
        Viewport Rect:显示范围
        Allow Dynamic Resolution:动态分辨率
        Target Display:显示到哪个显示器
        Target Eye:
    Stack:Overlay摄像机的栈,Overlay摄像机放到里面才能显示

    Output:
        OutPut Texture:把摄像机的画面输出到的纹理对象

上面所有的属性都可以在Universal Additional Camera Data脚本中找到

光照

全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。对于手机游戏来说,URP更加适合。

Light:
    Type:光源类型,平行光,点光源,聚光灯,区域光
    Color:颜色
    Mode: Realtime(没有间接光照),Baked(静态计算,有间接光照),mixed(实时和静态烘焙)
    Intensity:强度
    indirect Multiplier:控制间接光照的强度(反射过来的光照)
    Shadow Type:Soft Shadows,Hard Shadows,No Shadows
    Render Mode:
    Culling Mask:

默认情况下太阳光是主光源,其他是附加光源。

在Settings渲染质量的配置文件 中可以配置光源数目等信息。

Shader模板

这个模板基于Unlit Shader,最为灵活,可以开发出任意的shader
同样是ShaderLab,但是URP和Unity内置渲染管线不同,它不是内嵌的CG,内嵌的是HLSL

Shader "Universal Render Pipeline/Unlit"
{
    Properties
    {
        [MainTexture] _BaseMap("Texture", 2D) = "white" {}
        [MainColor]   _BaseColor("Color", Color) = (1, 1, 1, 1)
        _Cutoff("AlphaCutout", Range(0.0, 1.0)) = 0.5

        // BlendMode
        [HideInInspector] _Surface("__surface", Float) = 0.0
        [HideInInspector] _Blend("__blend", Float) = 0.0
        [HideInInspector] _AlphaClip("__clip", Float) = 0.0
        [HideInInspector] _SrcBlend("Src", Float) = 1.0
        [HideInInspector] _DstBlend("Dst", Float) = 0.0
        [HideInInspector] _ZWrite("ZWrite", Float) = 1.0
        [HideInInspector] _Cull("__cull", Float) = 2.0

        // Editmode props
        [HideInInspector] _QueueOffset("Queue offset", Float) = 0.0

        // ObsoleteProperties
        [HideInInspector] _MainTex("BaseMap", 2D) = "white" {}
        [HideInInspector] _Color("Base Color", Color) = (0.5, 0.5, 0.5, 1)
        [HideInInspector] _SampleGI("SampleGI", float) = 0.0 // needed from bakedlit
    }
    SubShader
    {
        Tags {"RenderType" = "Opaque" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" "ShaderModel"="4.5"}
        LOD 100

        Blend [_SrcBlend][_DstBlend]
        ZWrite [_ZWrite]
        Cull [_Cull]

        Pass
        {
            Name "Unlit"

            HLSLPROGRAM
            #pragma exclude_renderers gles gles3 glcore
            #pragma target 4.5

            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature_local_fragment _ALPHATEST_ON
            #pragma shader_feature_local_fragment _ALPHAPREMULTIPLY_ON

            // -------------------------------------
            // Unity defined keywords
            #pragma multi_compile_fog
            #pragma multi_compile_instancing
            #pragma multi_compile _ DOTS_INSTANCING_ON
            // CPU传过来的数据
            #include "UnlitInput.hlsl"
            // 渲染管线传过来的
            struct Attributes
            {
                float4 positionOS       : POSITION;
                float2 uv               : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            // 顶点shader传递给片元shader的数据
            struct Varyings
            {
                float2 uv        : TEXCOORD0;
                float fogCoord  : TEXCOORD1;
                float4 vertex : SV_POSITION;

                UNITY_VERTEX_INPUT_INSTANCE_ID
                UNITY_VERTEX_OUTPUT_STEREO
            };
            // 顶点shader
            Varyings vert(Attributes input)
            {
                Varyings output = (Varyings)0;

                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

                VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
                output.vertex = vertexInput.positionCS;
                output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
                output.fogCoord = ComputeFogFactor(vertexInput.positionCS.z);

                return output;
            }
            //片元着色shader
            half4 frag(Varyings input) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

                half2 uv = input.uv;
                half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);
                half3 color = texColor.rgb * _BaseColor.rgb;
                half alpha = texColor.a * _BaseColor.a;
                AlphaDiscard(alpha, _Cutoff);

#ifdef _ALPHAPREMULTIPLY_ON
                color *= alpha;
#endif

                color = MixFog(color, input.fogCoord);

                return half4(color, alpha);
            }
            ENDHLSL
        }
        // 只渲染深度信息
        Pass
        {
            Name "DepthOnly"
            Tags{"LightMode" = "DepthOnly"}

            ZWrite On
            ColorMask 0

            HLSLPROGRAM
            #pragma exclude_renderers gles gles3 glcore
            #pragma target 4.5

            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment

            // -------------------------------------
            // Material Keywords
            #pragma shader_feature_local_fragment _ALPHATEST_ON

            //--------------------------------------
            // GPU Instancing
            #pragma multi_compile_instancing
            #pragma multi_compile _ DOTS_INSTANCING_ON

            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
            ENDHLSL
        }

        // This pass it not used during regular rendering, only for lightmap baking.
        Pass
        {
            Name "Meta"
            Tags{"LightMode" = "Meta"}

            Cull Off

            HLSLPROGRAM
            #pragma exclude_renderers gles gles3 glcore
            #pragma target 4.5

            #pragma vertex UniversalVertexMeta
            #pragma fragment UniversalFragmentMetaUnlit

            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitMetaPass.hlsl"

            ENDHLSL
        }
    }

    SubShader
    {
        Tags {"RenderType" = "Opaque" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" "ShaderModel"="2.0"}
        LOD 100

        Blend [_SrcBlend][_DstBlend]
        ZWrite [_ZWrite]
        Cull [_Cull]

        Pass
        {
            Name "Unlit"
            HLSLPROGRAM
            #pragma only_renderers gles gles3 glcore d3d11
            #pragma target 2.0

            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature_local_fragment _ALPHATEST_ON
            #pragma shader_feature_local_fragment _ALPHAPREMULTIPLY_ON

            // -------------------------------------
            // Unity defined keywords
            #pragma multi_compile_fog
            #pragma multi_compile_instancing

            #include "UnlitInput.hlsl"

            struct Attributes
            {
                float4 positionOS       : POSITION;
                float2 uv               : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct Varyings
            {
                float2 uv        : TEXCOORD0;
                float fogCoord  : TEXCOORD1;
                float4 vertex : SV_POSITION;

                UNITY_VERTEX_INPUT_INSTANCE_ID
                UNITY_VERTEX_OUTPUT_STEREO
            };

            Varyings vert(Attributes input)
            {
                Varyings output = (Varyings)0;

                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

                VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
                output.vertex = vertexInput.positionCS;
                output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
                output.fogCoord = ComputeFogFactor(vertexInput.positionCS.z);

                return output;
            }

            half4 frag(Varyings input) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

                half2 uv = input.uv;
                half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);
                half3 color = texColor.rgb * _BaseColor.rgb;
                half alpha = texColor.a * _BaseColor.a;
                AlphaDiscard(alpha, _Cutoff);

#ifdef _ALPHAPREMULTIPLY_ON
                color *= alpha;
#endif

                color = MixFog(color, input.fogCoord);
                alpha = OutputAlpha(alpha, _Surface);

                return half4(color, alpha);
            }
            ENDHLSL
        }
        Pass
        {
            Name "DepthOnly"
            Tags{"LightMode" = "DepthOnly"}

            ZWrite On
            ColorMask 0

            HLSLPROGRAM
            #pragma only_renderers gles gles3 glcore d3d11
            #pragma target 2.0

            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment

            // -------------------------------------
            // Material Keywords
            #pragma shader_feature_local_fragment _ALPHATEST_ON

            //--------------------------------------
            // GPU Instancing
            #pragma multi_compile_instancing

            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
            ENDHLSL
        }

        // This pass it not used during regular rendering, only for lightmap baking.
        Pass
        {
            Name "Meta"
            Tags{"LightMode" = "Meta"}

            Cull Off

            HLSLPROGRAM
            #pragma only_renderers gles gles3 glcore d3d11
            #pragma target 2.0

            #pragma vertex UniversalVertexMeta
            #pragma fragment UniversalFragmentMetaUnlit

            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitMetaPass.hlsl"

            ENDHLSL
        }
    }
    FallBack "Hidden/Universal Render Pipeline/FallbackError"
    CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.UnlitShader"
}

基于PBR的Lit参数

美术工作流:金属度与粗糙度、反射度与光泽度
Surface Type:渲染队列
Render face:渲染前面还是后面
alpha clipping:alpha小于某个值就不着色
Receive Shadow:是否接收阴影

Base Map:本色贴图
Metallic Map:金属度贴图,可以调节光滑度
Normal Map:法线贴图
Height Map:高度贴图
Occlusion Map:遮挡贴图
Tilling、Offset:和Unity自带的渲染管线一致

具体功能我们可以根据Lit shader脚本进行改造,具体信息查看LitForwardPass.hlsl文件,建议通读相关引用脚本的源码。

问题

UNITY_MATRIX_IT_MV为什么能把法线从模型空间转换成观察空间?


文章作者: 微笑紫瞳星
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 微笑紫瞳星 !
  目录