没啥用的主页
学习杂记
UnityShader入门精要笔记
Dec 05 2022

光照

基础光照

标准光照模型

在现实中我们能看见物体是因为物体反射了光线,这种反射也被称之为漫反射。他的计算方法是
_diffuse = I * cosθ_;
其中的 I 是入射的光强,而cosθ 则是入射方向与物体的法线的夹角。在代码中我们先是获得了每一个顶点的法线与位置,我们将坐标转化到裁剪空间内。然后再计算世界中的环境光,可以通过内置变量来获得,随后归一化世界法线和灯光法线,随后调用公式
LightColor.rgb * Diffuse.rgb * max(0,dot(worldNormal,worldLight));

Pass
    {
        Tags { "LightMode" = "ForwardBase"}
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
#include "Lighting.cginc"
    fixed4 _Diffuse;
struct a2v {
    float4 vertex :POSITION;
    float3 normal :NORMAL;
};
struct v2f {
    float4 pos:SV_POSITION;
    fixed3 color : COLOR;
};
        v2f vert(a2v v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
            fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));

            o.color = diffuse + ambient;
            return o;
        }

        fixed4 frag(v2f o) : SV_Target
        {
            return fixed4(o.color,1.0);
        }
        ENDCG
    }

以上的代码为逐顶点光照,至于逐像素光照我们只需要把计算部分移动到片元着色器,不用插值版本即可

半兰伯特光照

在光照中完全照不到的地方会全黑,这会感觉很不好,因此我们可以简单的给他加一个全局亮光,这样就能保证了不会全黑。公式很简单,放弃了 max来判断强度大小。转为缩放,顺便加上一个大小。
LightColor.rgb * Diffuse.rgb * (0.5 * ( dot(worldNormal,worldLight)) +0.5f);

Shader "Unlit/03_DiffusePixel"
{
Properties
{
    _Diffuse("Diffuse",Color) = (1,1,1,1)
    _Scale("scale",Range(0,1)) = 0.5
    _Add("ADD",Range(0,1)) = 0.5
}
SubShader
{
    Pass
    {
        Tags { "LightMode" = "ForwardBase"}
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
#include "Lighting.cginc"
        struct a2v {
        float4 vertex :POSITION;
        float3 normal:NORMAL;
        };
        struct v2f {
            float4 pos:SV_POSITION;
            fixed3 worldNormal : TEXCOORD0;
        };
        fixed4 _Diffuse;
        float _Scale;
        float _Add;
        v2f vert (a2v a)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(a.vertex);
            o.worldNormal = mul(a.normal, (float3x3) unity_WorldToObject);
            return o;
        }
        fixed4 frag(v2f v) : SV_Target
        {
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
            fixed3 worldNormal = normalize(v.worldNormal);
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (_Scale* dot(worldNormal,worldLight)+_Add);
            return fixed4(diffuse, 1);
        }
        ENDCG
        }
    }
}

高光光照

Phong光照模型

在现实生活中,不只是存在漫反射,在光滑的镜面上会存在高光,他是由平面反射而形成一种现象,具体的公式为,这个公式也被称之为 Phong光照模型,他没有一点物理依据,只是为了视觉效果而体现的。
c1 = (c2 * m) * pow(max(0,v*r),Mg);
其中 c1 是我们的高光颜色,c2 为灯光颜色,m 高光系数,v 为视角方向以及反射方向r。Mg 则是控制高光点大小。其中的 r 可以通过表面法线 n 和灯光方向 l 来获得。 r = l -2(n * l)*n 。而在unity中可以直接通过函数 reflect 来获得,只需要提供入射方向和法线方向。

fixed3 reflectDir = normalize(reflect(-worldLight,i.worldNormal));

此处的worldLight需要加一个负号是因为在 Cg 语言中reflect函数的入射方向要求光照指向交点处,因此我们需要加一个负号,
其他的参数获得也与标准光照类似
{

// 以下版本为逐顶点高光
Shader "Unlit/04_SpecularVertex"
{
Properties
{
    _Diffuse("diffuse",Color) = (1,1,1,1)
    _Specular("specular",Color) = (1,1,1,1)
    _Gloss("gloss",Range(8,256)) = 20
}
    SubShader
{
    Pass
    {
        Tags{"LightMode" = "ForwardBase"}
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "Lighting.cginc"
        fixed4 _Diffuse;
        fixed4 _Specular;
        float _Gloss;
        struct a2v {
            float4 vertex :POSITION;
            float3 normal:NORMAL;
        };
        struct v2f {
            float4 pos:SV_POSITION;
            fixed3 color : Color;
        };
        v2f vert(a2v a)
        {
            v2f v;
            v.pos = UnityObjectToClipPos(a.vertex);
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            fixed3 worldNormal = normalize(mul(a.normal, (float3x3)unity_WorldToObject));
            fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, (dot(worldNormal, worldLight)));
            fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
            //计算出一条由物体指向摄像机的射线
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld,a.vertex).xyz);
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(reflectDir, viewDir)), _Gloss);
            v.color = diffuse + specular + ambient;
            return v;
        }
        fixed4 frag(v2f i) : SV_Target
        {
            return fixed4(i.color,1);
        }
        ENDCG
    }
}
}

}
{

//以下为逐像素高光
Shader "Unlit/05_SpecularPixel"
{
Properties
{
    _Diffuse("diffuse",Color) = (1,1,1,1)
    _Specular("specular",Color) = (1,1,1,1)
    _Gloss("gloss",Range(8,256)) = 20
}
SubShader
{
    Pass
    {
        Tags{"LightMode" = "ForwardBase"}
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        fixed4 _Diffuse;
        fixed4 _Specular;
        float _Gloss;
        #include "Lighting.cginc"
        struct a2v {
            float4 vertex : POSITION;
            float3 normal :NORMAL;
        };
        struct v2f
        {
            float4 pos :SV_POSITION;
            fixed3 worldNormal : TEXCOORD0;
            fixed3 objectVertex : TEXCOORD1;
        };
        v2f vert (a2v v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
            o.objectVertex = mul(unity_ObjectToWorld, v.vertex).xyz;
            return o;
        }
        fixed4 frag(v2f i) : SV_Target
        {
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(i.worldNormal, worldLight));
            fixed3 reflectDir = normalize(reflect(-worldLight,i.worldNormal));
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.objectVertex);
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(reflectDir, viewDir)), _Gloss);
            fixed3 color = diffuse + specular + ambient;
            return fixed4(color, 1);
        }
        ENDCG
    }
}
}

}

Blinn-Phong光照模型

这里我们来介绍另外一种光照模型的实现,他没有用到反射的方向,而是引入了一个新的向量 h,他是通过对视角方向 v 与光照方向 l 相加然后再归一化所得。即
h = (v+l ) / | (v+l) |
而计算高光反射的自然也有所不同 , 具体为
cs = (cl * m) * pow( max(0,n * h) , Mg )
其中参数与上文差不多。也就不多做解释了。
{

//以下为逐顶点版本
Shader "Unlit/06_BlinnVertex"
{
Properties
{
    _Diffuse("diffuse",Color) = (1,1,1,1)
    _Specular("specular",Color) = (1,1,1,1)
    _Gloss("gloss",Range(8,256)) = 20
}
SubShader
{
    Pass
    {
        Tags {"LightMode" = "ForwardBase"}
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "Lighting.cginc"
        fixed4 _Diffuse;
        fixed4 _Specular;
        float _Gloss;
        struct a2v {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
        };
        struct v2f {
            float4 pos : SV_POSITION;
            float3 color :COLOR;
        };
        v2f vert (a2v v)
        {
            v2f o;
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            o.pos = UnityObjectToClipPos(v.vertex);
            fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
            fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex));
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, (dot(worldNormal, worldLight)));
            fixed3 halfDir = normalize(viewDir + worldLight);
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
            o.color = specular + diffuse + ambient;
            return o;
        }
        fixed4 frag(v2f i) : SV_Target
        {
            return fixed4(i.color,1);
        }
        ENDCG
    }
}
}

}
{

//以下为逐像素版本
Shader "Unlit/07_BlinnPixel"
{
Properties
{
    _Diffuse("diffuse",Color) = (1,1,1,1)
    _Specular("specular",Color) = (1,1,1,1)
    _Gloss("gloss",Range(8,256)) = 20
}
SubShader
{
    Pass
    {
        Tags{"LightMode" = "ForwardBase"}
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
        #include "Lighting.cginc"
        fixed4 _Diffuse;
        fixed4 _Specular;
        float _Gloss;
        struct a2v {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
        };
        struct v2f
        {
            float4 pos : SV_POSITION;
            fixed3 worldNormal : TEXCOORD0;
            fixed3 worldVertex : TEXCOORD1;
        };
        v2f vert (a2v v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
            o.worldVertex = normalize(mul(unity_ObjectToWorld, v.vertex).xyz);
            return o;
        }
        fixed4 frag (v2f i) : SV_Target
        {
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldVertex);
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, (dot(i.worldNormal, worldLight)));
            fixed3 halfDir = normalize(viewDir + worldLight);
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(i.worldNormal, halfDir)), _Gloss);
            fixed3 color = specular + diffuse + ambient;
            return fixed4(color, 1);
        }
        ENDCG
    }
}
}

}

高级光照

在前面的例子中我们的场景中都是只有一个默认的平行光光源,而在后面的例子中我们会学习多光源的光影和阴影处理。

前向渲染

在此之前我们需要介绍一下渲染路径,他决定了我们底层是如何处理这些光照信息的。在默认情况下我们是Forward(前向渲染)。也只有在声明了正确的路径后,我们才能获得正确的光照信息。如_LightColor0。
在前向渲染中,我们对于光源的处理也是有三种:逐顶点处理,逐像素处理,球谐处理(SH)。在渲染一个物体之前,Unity会根据场景中的光源的优先性(比如举例物体的远近,光源强度等)进行一个排序,他会将一定数量的光源作为逐像素的处理,其中每个顶点最多计算4个逐顶点光照(原文:然后最多有4个光源按逐顶点的方式处理 ),剩下的以SH的方式来处理,另外假设在场景中有N个逐像素光源,那么我们一个物体就会执行N次Pass,然后将他们的片元颜色混合得到最终的颜色,因此消耗是比较大的。

  1. 场景中最亮的平行光一定是逐像素的
  2. 渲染模式为 No Important 的光源会按照逐顶点或者SH来处理,
  3. 设置为Important的会按照逐像素的来处理,
    前向渲染中也有两种Pass:Base Pass,Additional Pass。

Base Pass: 他需要在代码中的设置为,负责计算一个逐像素的平行光,以及剩下的所有逐顶点和SH的光源,在计算完毕以后,他们会将颜色混合起来作为最终输出的颜色,前提是需要在第二个Pass中开启混合

Blend One One

Tags {"LightMode" = "ForwardBase"}
#pragma multi_compile_fwbase

可以实现的效果有

  1. 光照纹理
  2. 环境光
  3. 自发光
  4. 平行光的阴影
    他使用了编译命令后,才能获得更多一些的变量值和更多的效果
    Additional Pass:计算剩下的逐像素光源,且每个光源执行一次
Tags{"LightMode" ="ForwardAdd"}
Blend One One 
#pragma multi_Compile_fwdadd

只有在使用了编译命令后,他才支持阴影,默认下是不支持的。

前向渲染可以使用的内置光照函数

函数名 描述
float3 WorldSpaceLightDir(float4 v) 仅用于前向渲染,输入模型空间的顶点位置,返回世界空间从该点到光照的方向
float3 UnityWorldSpaceLightDir(flaot4 v) 输入世界空间顶点位置,返回世界空间中从该点到光源的光照方向
float3 ObjSpaceLightDir(float4 v) 输入模型空间顶点位置,返回模型空间下该点到光源的方向

多光源混合物体

一般来说我们的shader会定义两个Pass,一个BasePass,一个Additional Pass。
在下面的代码中我们会实现一个简单的多光源光照,在第一个Pass中计算了最亮的平行光,在第二个Pass中计算了剩余的逐像素光源,

需要注意的点:因为平行光与点光源他们的性质不同,因此在计算的时候我们需要区分开来,如我们需要自己计算点光源的方向。在后面的代码中我们使用了宏命令来区分

#ifdef USING_DIRECTIONAL_LIGHT
#if define(POINT)

//

Shader "Unlit/15_ForwardRendering"
{
Properties
{
    _Diffuse("Diffuse",Color) = (1,1,1,1)
    _Gloss("Gloss",Range(2,256)) = 8
    _Specular("Specular",Color) = (1,1,1,1)
}
SubShader
{
    Tags {"RenderType" = "Opaque"}
    Pass{
        Tags {"LightMode" = "ForwardBase"}
        CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
        fixed4 _Diffuse;
        fixed4 _Specular;
        float _Gloss;
        struct a2v {
            float4 vertex:POSITION;
            float3 normal:NORMAL;
        };
        struct v2f {
            float4 pos :SV_POSITION;
            float3 worldNormal :TEXCOORD0;
            float3 worldPos:TEXCOORD1;
        };
        v2f vert(a2v v) {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            o.worldPos = mul(unity_ObjectToWorld, v.vertex);
            return o;
        }
        fixed4 frag(v2f i) :SV_Target{
            fixed3 worldNormal = normalize(i.worldNormal);
            fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
            fixed3 halfDir = normalize(worldLightDir + viewDir);
            fixed3 specular = _LightColor0.xyz * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
            fixed atten = 1.0;
            return fixed4(ambient +(diffuse + specular) * atten, 1.0);
        }
            ENDCG
}

    Pass{
            Tags{"LightMode" = "ForwardAdd"}
            Blend One One
            CGPROGRAM
#pragma multi_compile_fwdadd
#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"
#include "AutoLight.cginc"
        fixed4 _Diffuse;
        fixed4 _Specular;
        float _Gloss;

        struct a2v {
            float4 vertex:POSITION;
            float3 normal:NORMAL;
        };
        struct v2f {
            float4 pos:SV_POSITION;
            float3 worldPos:TEXCOORD0;
            float3 worldNormal:TEXCOORD1;
        };

        v2f vert(a2v v) {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
            return o;
        }
        fixed4 frag(v2f i) :SV_Target{
            fixed3 worldNormal = normalize(i.worldNormal);
#ifdef USING_DIRECTIONAL_LIGHT
        fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
        fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
        fixed3 diffuse = _LightColor0.xyz * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
        fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
        fixed3 halfDir = normalize(worldLightDir + viewDir);
        fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal, halfDir)), _Gloss);
#ifdef USING_DIRECTIONAL_LIGHT
        float atten = 1.0;
#else
#if defined(POINT)
        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos,1.0)).xyz;
        fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined(SPOT)
        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1.0)).xyz;
        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
        return fixed4((diffuse + specular) * atten, 1.0);
        }
        ENDCG
    }
}
}

阴影

物体提供阴影

阴影的产生很简单,即物体遮挡了光线的传播,而在游戏中我们也运用了类似的原理,我们将摄像机放在光源处,然后深度检测,在后面的片元即是被遮挡住的阴影部分。这种技术也被称之为 Shadow Map
也叫做阴影映射纹理,它的本质是一张深度图。记录了从光源位置出发,能看到的场景中举例它最近的深度信息(表面位置)。至于怎么来更新这张图,有两个办法。1.在BasePass和AdditionalPass中来更新他,但是会比较浪费性能,因为我们只用算一次深度信息,第二种便是新开一个Pass来计算.
总结:一个物体如果想要提供阴影,就需要把这个物体加入到光源的阴影映射纹理的计算中,在本文中我们可以利用Fallback的原理,来使用unity内置的Shader实现这个功能,即

Fallback "Specular"

虽然Specular也不提供这个功能,但是再下一层的Fallback——VertexLit提供了这个功能。详细的可以自己去看源码。另外,平面由于有剔除的现象,所以我们在墙外照进来会没有阴影,我们可以在设置里面开启Two Sided来开启阴影。

物体接收阴影

物体接收阴影和提供阴影是两个不同的事情,而想让物体可以接收阴影很简单,我们只需要添加三个宏即可。第一个添加在v2f中

struct v2f{
float4 pos :SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos :TEXCOORD1;
SHADOW_COORDS(2);
}

其中的SHADOW_COORDS 是为了让我们获得阴影纹理信息,由unity计算,其中的数字为下一个可用的寄存器下标。即TECOORD后面的数字,因为0和1 都用了,所以填写2.
在顶点着色器中需要来计算值,我们需要保证a2v的参数必须为 v,且里面的顶点坐标的变量名必须为vertex。 且v2f中顶点位置的变量名必须为pos。

v2f vert(a2v v){
    v2f o;
    //xxxxxx
    TRANSFER_SHADOW(o);
    return o;
}

其中的 TRANSFER_SHADOW 便是帮我们计算阴影纹理坐标,即前面声明的内容,
在片元着色器中计算阴影值,这是在BasePass 中的片元着色器

fixed shadow = SHADOW_ATTENUATION(i);
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);

这个会计算出当前片元着色器的阴影值。由于原理所限,当我们接受物体不能产生阴影的时候,也就无法接收阴影。

Shader "Unlit/16_Shadow"
{
Properties
{
    _Diffuse("Diffuse",Color) = (1,1,1,1)
    _Gloss("Gloss",Range(2,256)) = 8
    _Specular("Specular",Color) = (1,1,1,1)
}
SubShader
{
    Tags { "RenderType" = "Opaque" }
    Pass {
        // Pass for ambient light & first pixel light (directional light)
        Tags { "LightMode" = "ForwardBase" }
        CGPROGRAM
    // Apparently need to add this declaration 
    #pragma multi_compile_fwdbase	
    #pragma vertex vert
    #pragma fragment frag
    #include "Lighting.cginc"
    #include "AutoLight.cginc"
    fixed4 _Diffuse;
    fixed4 _Specular;
    float _Gloss;
    struct a2v {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
    };
    struct v2f {
        float4 pos : SV_POSITION;
        float3 worldNormal : TEXCOORD0;
        float3 worldPos : TEXCOORD1;
        SHADOW_COORDS(2)
    };
    v2f vert(a2v v) {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.worldNormal = UnityObjectToWorldNormal(v.normal);
        o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
        // Pass shadow coordinates to pixel shader
        TRANSFER_SHADOW(o);
        return o;
    }
    fixed4 frag(v2f i) : SV_Target {
        fixed3 worldNormal = normalize(i.worldNormal);
        fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
        fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
        fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
        fixed3 halfDir = normalize(worldLightDir + viewDir);
        fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
        fixed atten = 1.0;
        fixed shadow = SHADOW_ATTENUATION(i);
        return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
    }
    ENDCG
}
Pass {
        // Pass for other pixel lights
        Tags { "LightMode" = "ForwardAdd" }
        Blend One One
        CGPROGRAM
        // Apparently need to add this declaration
        #pragma multi_compile_fwdadd
        // 使用下面这行可以为点光源和聚光灯添加阴影
        //#pragma multi_compile_fwdadd_fullshadows
        #pragma vertex vert
        #pragma fragment frag
        #include "Lighting.cginc"
        #include "AutoLight.cginc"
        fixed4 _Diffuse;
        fixed4 _Specular;
        float _Gloss;
        struct a2v {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
        };
    
        struct v2f {
            float4 position : SV_POSITION;
            float3 worldNormal : TEXCOORD0;
            float3 worldPos : TEXCOORD1;
        };
        v2f vert(a2v v) {
            v2f o;
            o.position = UnityObjectToClipPos(v.vertex);
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
            return o;
        }
        fixed4 frag(v2f i) : SV_Target {
            fixed3 worldNormal = normalize(i.worldNormal);
            #ifdef USING_DIRECTIONAL_LIGHT
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
            #else
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
            #endif
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
            fixed3 halfDir = normalize(worldLightDir + viewDir);
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
            #ifdef USING_DIRECTIONAL_LIGHT
                fixed atten = 1.0;
            #else
                float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
                fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
            #endif
            return fixed4((diffuse + specular) * atten, 1.0);
        }
        ENDCG
    }
}
FallBack "Specular"
}

本章节主要讲述了阴影的基本原理(实际上还是不懂),有很多的不足之处。比如我们是在BasePass中写下的阴影计算,因此多个像素光源下并不正确。我们在接下来的代码里面来写下常用的写法。
前面的两个宏照常使用,只有第三个有所变化,这三个宏定义都在 AutoLIght.cginc 中。

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4((diffuse + specular) *atten,1));

这里我们在最后的部分来声明即可,其中的 atten 在前面我们并没有声明但仍然可以使用,这是因为宏内以及帮我们定义了好了。当用了这个宏以后,我们也不再需要自己去判断光源类型了,保证了代码的简洁。第一个参数为光源衰减系数,第二个为阴影纹理贴图(也就是参数 i ,第三个为世界坐标)。这段代码需要在BasePass,AdditionalPass中都需要写,才能正确计算出所有的光照和阴影。

Shader "Unlit/17"
{
Properties
{
    _Diffuse("Diffuse",Color) = (1,1,1,1)
    _Gloss("Gloss",Range(2,256)) = 8
    _Specular("Specular",Color) = (1,1,1,1)
}
    SubShader
{
    Tags { "RenderType" = "Opaque" }
    Pass
    {
        Tags{"LightMode" = "ForwardBase"}
        CGPROGRAM
    #pragma multi_compile_fwdbase
        #pragma vertex vert
        #pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
    fixed3 _Diffuse;
    fixed4 _Specular;
    float _Gloss;
    struct a2v {
        float4 vertex:POSITION;
        float3 normal:NORMAL;
    };
    struct v2f {
        float4 pos :SV_POSITION;
        float3 worldNormal :TEXCOORD0;
        float3 worldPos:TEXCOORD1;
        SHADOW_COORDS(2)
    };
        v2f vert (a2v v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
            TRANSFER_SHADOW(o);
            return o;
        }
        fixed4 frag (v2f i) : SV_Target
        {
            fixed3 worldNormal = normalize(i.worldNormal);
        fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
        fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
        fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
        fixed3 halfDir = normalize(worldLightDir + viewDir);
        fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
        UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
        return fixed4(ambient + (diffuse + specular) * atten, 1);
        }
        ENDCG
    }
    Pass{
            Tags{"LightMode" = "ForwardAdd"}
            Blend One One
            CGPROGRAM
            #pragma multi_compile_fwdadd
            #pragma vertex vert
        #pragma fragment frag
        #include "Lighting.cginc"
        #include "AutoLight.cginc"
        fixed4 _Diffuse;
        fixed4 _Specular;
        float _Gloss;
        struct a2v {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
        };
        struct v2f {
            float4 pos : SV_POSITION;
            float3 worldNormal : TEXCOORD0;
            float3 worldPos : TEXCOORD1;
            SHADOW_COORDS(2)
        };
        v2f vert(a2v v) {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;    
            TRANSFER_SHADOW(o);
            return o;
        }
        fixed4 frag(v2f i) :SV_Target{
            fixed3 worldNormal = normalize(i.worldNormal);
        fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
        fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
        fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
        fixed3 halfDir = normalize(worldLightDir + viewDir);
        fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
        UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
        return fixed4((diffuse + specular) * atten, 1);
        }
            ENDCG
}
}
        Fallback "Specular"
}

纹理

基础纹理

在unity中实现一个简单的贴图shader

在使用中,我们需要在变量中声明一个存储贴图的属性,

_MainTex("MainTex",2D) = "white"{}

要注意的点有 贴图的类型为 2D,而不是Texture2D,后面的默认颜色值也是全小写的。而想要使用相关的数据时,在声明的时候还要额外声明一个float4类型的变量,名字为 贴图名_ST,即

float4 _MainTex_ST;

其中的ST为scale 与 translation的缩写。在本章节中我们会介绍一个新的语义 TEXCOORD ,他会将我们顶点的纹理坐标存储进去,可以为float,float2,float3,float4等,它可以理解为是顶点所存储的一组数据,当我们只有一套贴图的时候,我们可以通过TEXCOORD0来获得相关数据,而剩下的1——5则可以用于存储一些我们自己的数据。
因为在片元着色器中我们需要获得每个像素点的颜色,因此我们将纹理信息传递给片元着色器。

struct v2f{
    float4 pos :SV_POSITION;
    float3 worldNormal :TEXCOORD0;
    float3 worldPos:TEXCOORD1;
    float2 uv:TEXCOORD2;
}

其中的 uv的赋值为以下其中一个。不写这一句的话我们依然可以在片元着色器中使用,但是无法使用缩放和位移等

o.uv = v.texcoord.xy *_MainTex_ST.xy +_MainTex_ST.zw;
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex) 

然后在片元着色器中使用即可

fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

{

Shader "Unlit/08_SingleTexture"
{
Properties
{
    _Color("Color",Color) = (1,1,1,1)
    _MainTex("MainTex",2D) = "white" {}
    _Specular("Specular",Color) = (1,1,1,1)
    _Gloss("Gloss",Range(2,256)) = 8
}
SubShader
{
    Pass
    {
        Tags{"LightMode" = "ForwardBase"}
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "Lighting.cginc"
        #include "UnityCG.cginc"

        fixed4 _Color;
        sampler2D _MainTex;
        float4 _MainTex_ST;
        fixed4 _Specular;
        float _Gloss;
        struct a2v
        {
            float4 vertex:POSITION;
            float3 normal:NORMAL;
            float4 texcoord:TEXCOORD;
        };
        struct v2f {
            float4 pos:SV_POSITION;
            float3 worldNormal:TEXCOORD0;
            float3 worldPos:TEXCOORD1;
            float2 uv:TEXCOORD2;
        };
        v2f vert (a2v v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
            o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
            //此处也可以直接调用内置管线的方法
            //o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
            return o;

        }

        fixed4 frag(v2f i) : SV_Target
        {
            fixed3 worldNormal = normalize(i.worldNormal);
            fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
            fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
            fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
            fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
            fixed3 halfDir = normalize(worldLightDir + viewDir);
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
            return fixed4(ambient + diffuse + specular, 1);
        }
        ENDCG
}
}
}

}

扩展知识

由于我们的贴图不是每次都能与物体一一对应的,因此我们必定会面临贴图拉伸和缩放的问题

拉伸

在unity里面可以设置两种模式,他们可以在贴图中设置
Warp Mode:Repeat 会让贴图纹理不断重复下去, Clamp则会在超过边界的部分一直保持为边界颜色。需要注意的是想要达到效果需要在shader中进行变化,也即上一节的代码。
Filter Mode :他告知了我们在拉伸了贴图以后,会采用那种滤波模式。分为了三种,Point,Bilinear,Trilinear,他们可以得到的图片效果以此提升,但是所耗费的性能也依次增大。滤波会影响放大或者缩小纹理的时候的图片质量。

缩小

我们常用的解决方法是多级渐远纹理,原理大致为我们会提前获得很多较小的纹理,然后在摄像机远离的时候采用较小的纹理

凹凸映射

在游戏中,有很多物体我们不可能给他加太多的凹凸,因为这会导致模型的面数增加。但是不添加这些我们就会导致物体也无法实现更多的细节,让物体看上去是一个套着一层皮的球,因此我们可以引入另一个贴图来告知我们这个物体所包含的一些细节,这个贴图也被称之为法线贴图 _(还有一种贴图是包含了物体的高度信息,然后自己计算出法线等细节,也被称之为高度纹理)_,
法线贴图:即图中每一个像素点存储的都是表面的法线方向,我们物体顶点的法线信息都是在模型空间下,如果我们直接提取出来映射到一张图上,那么这个图就是模型空间下的法线贴图,但是实际的生产中我们则会采用另一种贴图方式,他存储的是每一个顶点在自己的切线空间下的法线信息,这种法线贴图看起来是一种蓝紫色的样子。
总结:模型空间下的法线贴图更加方便我们的工作。也方便我们的制作,但是缺点也十分的明显,他换一个模型以后就无法正常工作。切线空间下的法线贴图则更加的自由与可扩展。
首先声明的变量会多出来

_BumpTex("normal texure",2D) ="white"{}
_BumpScale("bumpScale",float) = 1;

在a2v中增加了

float4 tangent:TANGENT

因为我们的相关计算都是在切线空间下计算的。相关的更改还有

struct v2f{
    float4 pos :SV_POSITION;
    float4 uv :TEXCOORD0;
    float3 lightDir:TEXCOORD1;
    float3 viewDir:TEXCOORD2;
}

其中的uv变成了float4类型的,因为我们还需要后面两个zw分量来存储法线贴图的信息,在顶点着色器中,我们需要计算出来切线空间下的光照方向和视角方向,具体的推导可以见P150页,这里直接使用了内置宏来转化,当写下 TANGENT_SPACE_ROTATION 后,我们便可以使用rotation矩阵来直接转化。需要记住的是宏后面加分号

TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;

在片元着色器中,需要注意的就是关于贴图的处理,如果我们的贴图导入的格式不是纹理贴图,那么就需要在这里手动处理一下,见P151。然后我们需要将法线贴图重新给映射回来,可以直接调用函数来获得

fixed4 packNormal = tex2D(_BumpMap,i.uv.zw)
fixed3 tangentNormal = UnpackNormal(packNormal)
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

前者获得贴图,后者解码。因为我们法线贴图里面存储的是切线空间中的法线,而原定点的法线也就是Z轴,即(0,0,1)因此当xy为0的时候我们所获得的便是原顶点法线,而xy也不一定为是最小的,因此我们就可以通过缩放他们的值,来达到再次更改法线的目的,从而使法线更加的偏移,达到更漂亮的效果,而后者更改Z也应该是为了归一化。
然后得到的法线值乘以缩放以控制凹凸程度。剩下的也就只需要将上一节中的视线与光源方向改为切线空间下即可。

{

Shader "Unlit/09_NormalMap"
{
Properties
{
    _Color("Color",Color) = (1,1,1,1)
    _MainTex("Main Tex",2D) = "white" {}
    _BumpMap("Normal texure",2D) = "bump"{}
    _BumpScale("Bump Scale",Float) = 1
    _Specular("Specular",Color) = (1,1,1,1)
    _Gloss("Gloss",Range(2,256)) = 8
}
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "UnityCG.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpScale;
            fixed4 _Specular;
            float _Gloss;
            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
                float4 texcoord:TEXCOORD;
            };
            struct v2f {
                float4 pos:SV_POSITION;
                float3 lightDir:TEXCOORD0;
                float3 viewDir:TEXCOORD1;
                float4 uv:TEXCOORD2;
            };
            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //下面rotation的计算是为了计算出切线空间,我们也可以直接用UNITY_SPACE_ROTATION来代替rotation
                //float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
                //float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
                TANGENT_SPACE_ROTATION;
                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
                return o;

            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 tangentLightDir = normalize(i.lightDir);
                fixed3 tangentViewDir = normalize(i.viewDir);
                fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
                fixed3 tangentNormal = UnpackNormal(packedNormal);
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

                fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));

                fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
                return fixed4(ambient + diffuse + specular, 1);
            }
            ENDCG
    }
    }
}

}

渐变纹理

在最开始的时候我们采用纹理只是为了标记某个像素上面的颜色值,但是后面来发现我们可以利用纹理来存储更多的信息,如对漫反射的控制,大概的原理为我们直接通过计算出来的半兰伯特值去纹理中采样。获得所对应的值,那么我们就可以保证在某个区间内的颜色值,他们的光照颜色是相同的,而不是会有过渡的颜色。

fixed halfLambert = 0.5 * dot(worldNormal,worldLightDir) +0.5;
fixed3 diffuseColor = tex2D(_RampTex,fixed2D(halfLambert,halfLambert)).rgb *_Color.rgb;

tex2D(sampler2D tex, float2 s)函数会接收一个贴图,并返回该点对应的颜色值。

Shader "Unlit/10_RampTexture"
{
Properties
{
    _Color("Color",Color) = (1,1,1,1)
    _RampTex("Ramp Tex",2D) = "white"{}
    _Specular("高光颜色",Color) = (1,1,1,1)
    _Gloss("gloss",Range(2,256)) = 8
}
    SubShader
    {

        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
#include "Lighting.cginc"

            fixed4 _Color;
    sampler2D _RampTex;
    float4 _RampTex_ST;
    fixed4 _Specular;
    float _Gloss;
    struct a2v {
        float4 vertex :POSITION;
        float3 normal :NORMAL;
        float4 texcoord :TEXCOORD0;
    };
        
    struct v2f {
        float4 pos:SV_POSITION;
        float3 worldNormal:TEXCOORD0;
        float3 worldPos:TEXCOORD1;
        float2 uv :TEXCOORD2;
    };
        v2f vert (a2v v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
            o.uv = TRANSFORM_TEX(v.texcoord,_RampTex);
            return o;
        }

        fixed4 frag(v2f i) : SV_Target
        {
            fixed3 worldNormal = normalize(i.worldNormal);
        fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

        fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir);
        fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;
        fixed3 diffuse = _LightColor0.rgb * diffuseColor;
        fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
        fixed3 halfDir = normalize(worldLightDir + viewDir);
        fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal,halfDir)), _Gloss);
        return fixed4(specular + diffuse + ambient, 1);
        }
        ENDCG
    }
}
}

遮罩纹理

上文提到了我们可以利用贴图来存很多的信息,在本文中边使用了一个遮罩纹理来控制了某个通道颜色的影响程度

fixed3 specularMask = tex2D(_SpecularMask, i.uv).r *_SpecularScale;
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;

在这里,我们就可以控制每个像素都到高光颜色的影响程度,当该像素点的r通道为0时候,久可以保证高光的接受程度

Shader "Unlit/11_MaskTexure"
{
Properties
{
    _Color("Color",Color) =(1,1,1,1)
    _MainTex("Main Tex",2D) = "white"{}
    _BumpMap("Normal Map",2D) ="bump"{}
    _BumpScale("bumpscale",float) = 1
    _SpecularMask("Specular Mask",2D) ="white"{}
    _SpecularScale("Specular Sclae",float) = 1
    _Specular("SpecularColor",Color) = (1,1,1,1)
    _Gloss("Specular",Range(2,256)) = 20
}
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
#include "Lighting.cginc"
            fixed4 _Color;
            sampler2D _MainTex;
            sampler2D _BumpMap;
            float _BumpScale;
            sampler2D _SpecularMask;
            float _SpecularScale;
            fixed4 _Specular;
            float _Gloss;
            fixed4 _MainTex_ST;
        struct a2v {
            float4 vertex :POSITION;
            float3 normal:NORMAL;
            float4 tangent : TANGENT;
            float4 texcoord:TEXCOORD0;
        };
        struct v2f
        {
            float4 pos:SV_POSITION;
            float2 uv :TEXCOORD0;
            float3 lightDir:TEXCOORD1;
            float3 viewDir:TEXCOORD2;
        };
        v2f vert (a2v v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
            TANGENT_SPACE_ROTATION;
            o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
            o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
            return o;
        }
        fixed4 frag(v2f i) : SV_Target
        {
            fixed3 tangentLightDir = normalize(i.lightDir);
        fixed3 tangentViewDir = normalize(i.viewDir);
        fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap,i.uv));
        tangentNormal.xy *= _BumpScale;
        tangentNormal.z = sqrt(max(0, dot(tangentNormal.xy, tangentNormal.xy)));
        fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
        fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
        fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
        fixed specularMask = tex2D(_SpecularMask, i.uv).r *_SpecularScale;
        fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;
        return fixed4(specular + ambient + diffuse, 1);
        }
        ENDCG
    }
}
}

透明

透明度介绍

首先我们需要了解到渲染的工作流程,在渲染中我们会将每一个片元的颜色都写入一个缓冲区,即颜色缓冲区,也会有一个深度缓冲区,他记录该片元到摄像机的距离,当渲染非透明物体的时候,我们会先将第一个渲染的物体A写入到缓冲区,然后判断下一个物体B的该片元,如果发现他的深度值比A小,那我们就会将其覆盖掉A的值,从而在视觉上看起来是B在前面。
而对于透明物体呢,我们就需要严格控制顺序了,这样才能得到正确的视觉效果,并且我们在渲染半透明物体的时候是不能写入深度值的,因为他会覆盖掉他背后的颜色,因为没有了深度值帮我们判断渲染顺序,所以我们自己需要严格的控制渲染的顺序,假设A是一个透明物体,且在非透明物体B的前面,那我们就应该先渲染B,然后写入了深度值和颜色值,然后等到判断A的时候,他不允许写入深度值,只允许读取,他发现了深度值已经有了,因此就会取出颜色值来与自己的颜色混合,当发现没有颜色的时候,就将自己的颜色写入颜色缓冲区,这样就可以达到正确的颜色结果,而先渲染A的话,B因为发现深度缓存为空而直接覆盖掉A的颜色,从而A没有没渲染出来。总结是:越靠后的物体越先被渲染出来!
Unity则为我们提供了渲染队列,索引号越小的越早被渲染。并且内置了5个渲染队列

Tags {"Queue" = "AlphaTest"}
ZWrite off

后面这一句则是告诉我们取消深入写入,

透明度测试

透明度测试的原理十分的粗暴。他会判断当前的片元的透明度,如果小于,则会直接放弃该片元的渲染,因此会产生十分极端的效果,要嘛完全不透明,要吗完全透明,它主要用到了clip 这个函数

_texColor = tex2D(_MainTex,i.uv);
clip(_texColor.a,_Cutoff)
//--------------------完整代码-------------------------------------
    Shader "Unlit/12_AlphaTest"
    {
    Properties
    {
    _MainTex ("Texture", 2D) = "white" {}
    _Color("Color",Color) =(1,1,1,1)
    _Cutoff("range",Range(0,1)) = 0.5
    }
    SubShader
    {
    Tags{"Queue" ="AlphaTest" "IgnoreProjector"="True" "RenderType" ="TransparentCutout"}
    Pass
    {
        Tags{"LightMode" ="ForwardBase"}
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        // make fog work
        #pragma multi_compile_fog
        #include "Lighting.cginc"
        #include "UnityCG.cginc"

        sampler2D _MainTex;
        float4 _MainTex_ST;
        fixed3 _Color;
        float _Cutoff;

        struct a2v {
            float4 vertex:POSITION;
            float3 normal :NORMAL;
            float4 texcoord:TEXCOORD0;
        };
        struct v2f {
            float4 pos : SV_POSITION;
            float3 worldNormal:TEXCOORD0;
            float3 worldPos:TEXCOORD1;
            float2 uv:TEXCOORD2;
        };
        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);
            return o;
        }

        fixed4 frag(v2f i) : SV_Target
        {
            fixed3 worldNormal = normalize(i.worldNormal);
            fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
            fixed4 texColor = tex2D(_MainTex,i.uv);
            clip(texColor.a - _Cutoff);
            fixed3 albedo = texColor.rgb * _Color.rgb;
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
            fixed3 diffuse = _LightColor0.rgb * _Color.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
            return fixed4(diffuse + ambient, 1);
        }
        ENDCG
    }
}
}

当某个片元的透明度小于_Cutoff时,它就会被抛弃。

透明度混合

透明度混合就是真正的透明,我们想要使用这个方法就需要关闭深度写入,还要使用一个混合命令

语义 描述
Blend off 关闭混合
Blend SrcFactor DstFactor 开启混合,并设置混合因子,该片元的颜色乘以系数,然后已经有的颜色乘以1-系数,然后相加存入颜色缓冲
Blend SrcFactor DstFactor,SrcFactorA DstFactorA 和上面一样,只不过使用不同的因子来混合
BlendOp BlendOperation 并非简单相加,而是使用BlendOperation对他们进行其他操作
在本书中使用了第二个,需要注意的是只有开启了混合,设置透明度才有意义,也就是说如果你在Pass块中没有开启混合,那么调整透明度也是没用的
return fixed4(ambient + diffuse+...,texColor.a * AlphaScale);
//---------完整代码-------------------------------------------------
Shader "Unlit/13_AlphaBlend"
{
Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    _Color("Color",Color) = (1,1,1,1)
    _AlphaScale("alphaScale",Range(0,1)) = 1
}
SubShader
{
    Tags{"Queue" ="Transparent" "IgnoreProjector"="True" "RenderType" = "Transparent"}
    Pass
    {
        Tags{"LightMode" = "ForwardBase"}
        ZWrite off
        Blend srcAlpha OneMinusSrcAlpha
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
#include "Lighting.cginc"
        sampler2D _MainTex;
        float4 _MainTex_ST;
        fixed4 _Color;
        float _AlphaScale;
        struct a2v {
            float4 vertex :POSITION;
            float3 normal :NORMAL;
            float4 texcoord:TEXCOORD0;
        };
        struct v2f {
            float4 pos : SV_POSITION;
            float3 worldNormal:TEXCOORD0;
            float3 worldPos:TEXCOORD1;
            float2 uv:TEXCOORD2;
        };
        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);
            return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
            fixed3 worldNormal = normalize(i.worldNormal);
            fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
            fixed4 texColor = tex2D(_MainTex, i.uv);
            fixed3 albedo = texColor.rgb * _Color.rgb;
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
            fixed3 diffuse = _LightColor0.rgb * _Color.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
            return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
        }
        ENDCG
    }
}
}

如果我们想控制一个物体的shader

混合命令

由于我们关于透明的检测,因此会出现一种透明物体自己看自己还透明的的奇怪现象,即物体内部之间的透明效果。
对此我们可以使用两个Pass块来解决问题,第一个Pass开启深度写入,但不输出颜色,他仅仅是为了将该模型的深度值写入深度缓冲,由于使用ColorMask且设置为0,因此他只会更改深度区域,而不会更改颜色缓冲区的值,对于他后面的物体而言,我们只知道他前面有一个物体,而不会更改该片元的颜色,

ColorMask 0

第二个Pass进行正常的透明度混合,
在前面我们已经通过了写入深度值来剔除了一些本模型的片元,因此在片元着色器中,我们在来对颜色进行混合,这样也能保证可以混合透明物体背后的颜色,因为我们对应的颜色缓冲区依旧是后面颜色的缓冲区.
混合的前提是开启混合命令,混合的前提是有一个混合等式,即
$Orgb = SrcFactor * Srgb +DstFactor *Drgb$
$Oa = SrcFactorA * Sa +DstFactorA *Da$

其中的因子便是我们混合的关键.在上一节透明的例子中,我们使用的最简单的版本,

Blend Srcfactor DstFactor 
Blend SrcFactor DstFactor , SrcFactorA DstFactorA

其中后面四个即为因子,他们可以选择的类型有以下几种

参数 描述
One 因子为1
Zeor 因子为0
SrcColor 因子为源颜色值
SrcAlpha 因子为源颜色值的A通道
DstColor 因子为目标颜色值
DstAlpha 因子为目标颜色值的A通道
OneMinusSrcColor 因子为(1-源颜色值)
OneMinusSrcColorAlpha 因子为(1-源颜色值的A通道)
OneMinusDstColor 因子为(1-目标颜色)
OneMinusDstColorAlpha 因子为(1-目标颜色的A通道)

在前面的例子中我们使用了

Blend srcAlpha OneMinusSrcAlpha

而在没有设置的情况下,我们默认使用的混合操作是相加,而我们还有许多的混合方式,
在代码中使用以下的代码来启用

BlendOp xxx
操作 描述
Add 相加
Sub 源颜色减去目标颜色
RevSub 目标颜色减去源颜色
Min 在俩颜色中都找到最小的
Max 俩颜色中每个通道都找到最大的

剔除

在默认中我们为了减小渲染压力,我们会剔除背面的渲染,但是在某些情况下我们会需要开启背面,那么我们可以使用

Cull off //开启
Cull Front//正面
Cull Back //背面

来控制渲染

动画

纹理动画

即利用纹理来制作一些动画,在这个章节里面我们会用到一些关于时间的变量。大致如下

名称 描述
_ Time float4类型,值为(t/20,t,2t,3t)。t为自场景加载开始所经过的时间
_ SinTime 值为(t/8,t/4,t/2,t),其中的t为时间的正选值
_ CosTime 同上,只不过t为时间的余弦值
unity_DeltaTime 值为(dt,1/dt,smoothDt,1/smoothDt),dt则为时间增量
帧动画
Shader "Unlit/18_ImageSequenceAnimation"
{
Properties
{
    _Color ("Color",Color) =(1,1,1,1)
    _MainTex ("Main Tex",2D) ="white"{}
    _HorizontalAmount("Horizontal Amount",Float) = 4
    _Vertical("Vertical Amount",Float) = 4
    _Speed("Speed",Range(1,100)) = 30
}
    SubShader
    {
       Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
       Pass{
            Tags {"LightMode" = "ForwardBase"}
            ZWrite off
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
#include "UnityCG.cginc"
            fixed4 _Color;
    sampler2D _MainTex;
    float4 _MainTex_ST;
    float _HorizontalAmount;
    float _Vertical;
    float _Speed;
    struct a2v {
        float4 vertex :POSITION;
        float2 texcoord:TEXCOORD0;
    };
    struct v2f {
        float4 pos :SV_POSITION;
        float2 uv:TEXCOORD0;
    };
    v2f vert(a2v v) {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
        return o;
    }
    fixed4 frag(v2f i) :SV_Target{
        float time = floor(_Time.y * _Speed);
        float row = floor(time / _HorizontalAmount);
        float column = time - row * _HorizontalAmount;
        half2 uv = i.uv + half2(column, -row);
        uv.x /= _HorizontalAmount;
        uv.y /= _Vertical;
        fixed4 c= tex2D(_MainTex, uv);
        c.rgb *= _Color;
        return c;
    }
        ENDCG
   }
}
}
让纹理随时间运动

我们可以让我们每一个片元的采集不一样。这样便可以形成一幅动画。

o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX,0.0) * _Time.y);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X,0.0) * _Time.y);

我们在顶点着色器中采样的时候遍赋值,其中的frac函数会帮助我们返回参数的小数部份。那么这两行代码就是我们先对纹理采样,然后加上了时间 * 速度。也即是移动的部分。
然后便在片元着色器中来赋值。

fixed4 firstLayer = tex2D(_MainTex,i.uv.xy);
fixed4 secondLayer = tex2D(_DetailTex,i.uv.zw);
fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a);

其中用到了插值来混合两个图层之间的颜色。最终返回最终颜色值。

Shader "Unlit/19_ScrollingBackground"
{
Properties
{
    _MainTex("MainTex",2D) ="white"{}
    _DetailTex("DetailTex",2D) ="white"{}
    _ScrollX("Baselayer speed",Float) =1.0
    _Scroll2X("2ndLayer Speed",Float) =1.0
    _Multiplier("Layer multiplier",Float) =1
}
SubShader
{
    Tags { "RenderType"="Opaque" "Queue" = "Geometry"}
    Pass
    {
        Tags {"LightMode" ="ForwardBase"}
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
        sampler2D _MainTex;
        sampler2D _DetailTex;
        float4 _MainTex_ST;
        float4 _DetailTex_ST;
        float _ScrollX;
        float _Scroll2X;
        float _Multiplier;
        struct a2v {
            float4 vertex : POSITION;
            float4 texcoord : TEXCOORD0;
        };
        struct v2f {
            float4 pos : SV_POSITION;
            float4 uv : TEXCOORD0;
        };
        v2f vert (a2v v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX,0.0) * _Time.y);
            o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X,0.0) * _Time.y);
            return o;
        }
        fixed4 frag(v2f i) : SV_Target
        {
            fixed4 firstLayer = tex2D(_MainTex,i.uv.xy);
            fixed4 secondLayer = tex2D(_DetailTex,i.uv.zw);
            fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a);
            c.rgb *= _Multiplier;
            return c;
        }
        ENDCG
    }
}
}
顶点动画

在shader中我们可以通过修改模型空间下的坐标位置来达到运动的感觉,需要注意的是这里的顶点其实也就是Mesh中的顶点,所以我们不能使用Quad,而是需要一个单独的模型。在这一节中我们还使用了一个新的Tag——Disablebatching。他主要是会取消批处理,批处理会将相同的网格合并为一个,会导致坐标失去她原本的模型空间,所以我们需要取消和批。

float4 offset;
offset.yzw = float3(0, 0, 0);
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
o.pos = UnityObjectToClipPos(v.vertex + offset);

这时我们的物体以及可以随着时间而动了。但是纹理还没有变化,为了制造流动的感觉,我们要让纹理也动起来。

o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv += float2(0.0, _Time.y * _Speed);

这时便可以随之运动啦

Shader "Unlit/20_Water"
{
Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    _Color("Color",Color) =(1,1,1,1)
        _Magnitude("distortion Magnitude",Float) =1
        _Frequency("distortion Frequency",Float) =1
        _InvWaveLength("Distortion Inverse Wave Length",Float) =10
        _Speed("Speed",Float) =1
}
SubShader
{
        Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "DisableBatching" ="True"}
    Pass
    {
        Tags {"LightMode" = "ForwardBase"}
        ZWrite off
        Blend SrcAlpha OneMinusSrcAlpha
        Cull off
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
        sampler2D _MainTex;
        float4 _MainTex_ST;
        fixed4 _Color;
        float _Magnitude;
        float _Frequency;
        float _InvWaveLength;
        float _Speed;
        struct a2v
        {
            float4 vertex : POSITION;
            float2 texcoord : TEXCOORD0;
        };
        struct v2f
        {
            float4 pos :SV_POSITION;
            float2 uv:TEXCOORD0;
        };
        v2f vert (a2v v)
        {
            v2f o;
            float4 offset;
            offset.yzw = float3(0, 0, 0);
            //offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
            offset.x = sin(_Time.y + v.vertex.x * _InvWaveLength+v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) *_Magnitude;
            o.pos = UnityObjectToClipPos(v.vertex + offset);
            o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
            o.uv += float2(0.0, _Time.y * _Speed);
            return o;
        }
        fixed4 frag(v2f i) : SV_Target
        {
            fixed4 c = tex2D(_MainTex,i.uv);
        c.rgb *= _Color;
            return c;
        }
        ENDCG
    }
}
}
广告牌

在游戏中我们经常会有一些需求——要求某个物体一直面对摄像机,比如某些血包之类的东西。这种技术一般被称之为广告牌。在本文中我们就简单实现一个广告牌
关键代码

float3 center = float3(0, 0, 0);
float3 viewer = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1));

这里的center只是为了方便计算面朝方向。后续需要加上模型空间下的坐标。

float3 upDir = abs(normalDir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
float3 rightDir = normalize(cross(upDir,normalDir));
float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;
o.pos = UnityObjectToClipPos(float4(localPos,1));
o.uv = TRANSFORM_TEX(v.tex,_MainTex);

这里我们求出了竖直方向,并通过叉乘求出来另外的边。然后切换到裁切空间即可。

Shader "Unlit/21_Billboard"
{
Properties
{
    _MainTex ("Texture", 2D) = "white" {}
_Color("Color",Color) =(1,1,1,1)
    _VerticalBillboarding("Vertical",Range(0,1)) =1 
}
SubShader
{
    Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "DisableBatching" = "True"}
    Pass
    {
        ZWrite off
        Blend SrcAlpha OneMinusSrcAlpha
        Cull off
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
#include "Lighting.cginc"
        struct a2v
        {
            float4 vertex : POSITION;
            float2 tex : TEXCOORD0;
        };
        struct v2f
        {
            float4 pos : SV_POSITION;
            float2 uv :TEXCOORD0;
        };
        sampler2D _MainTex;
        float4 _MainTex_ST;
        fixed4 _Color;
        float _VerticalBillboarding;
        v2f vert (a2v v)
        {
            v2f o;
            float3 center = float3(0, 0, 0);
            float3 viewer = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1));
            float3 normalDir = viewer - center;
            normalDir.y = normalDir.y * _VerticalBillboarding;
            normalDir = normalize(normalDir);
            float3 upDir = abs(normalDir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
            float3 rightDir = normalize(cross(upDir,normalDir));
            float3 centerOffs = v.vertex.xyz - center;
            float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;
            o.pos = UnityObjectToClipPos(float4(localPos,1));
            o.uv = TRANSFORM_TEX(v.tex,_MainTex);
            return o;
        }
        
        fixed4 frag(v2f i) : SV_Target
        {
            fixed4 c = tex2D(_MainTex,i.uv);
        c.rgb *= _Color;
            return c;
        }
        ENDCG
    }
}
}

总结:在后两个例子中我们讲解了在Shader中对模型的顶点的操作,对顶点操作需要我们取消合批,这会对性能造成影响。大致的思路为,在顶点着色器中重新计算出自己想要的顶点位置,然后再把他们剪切到裁切空间。

其他

绘制遮挡物体

在很多的时候我们会需要判断一个物体是否在另一个物体之后,比如某些游戏我们可以侦测到墙面背后的敌人,这个时候我们便需要引入深度的概念,在unity中有大致两种解决方法,
我们可以通过更改深度测试来快速达到想要的效果,比如在某个Pass中开启即可,如

ZTest Greater//绘制位于现有几何体后面的几何体。不绘制位于现有几何体相同距离或前面的几何体。

这样我们便可以写两个shader,一个负责墙后,一个负责正常看到。便可以简单达到我们想要的效果。具体的选项可以去内置宏 - Unity 手册 (unity3d.com)查看

深度贴图

在unity中,我们可以在深度缓冲中来读取写入到深度缓冲中的每一个像素点的深度值,也可以获得一些透明物体的深度值。我们可以凭借深度值的信息来实现一些简单的特效,如雾气效果,扫描效果。护盾的交互边缘等。

获得深度缓冲中的深度值

既然需要去深度缓冲中,那我们就需要让摄像机来生成一个深度贴图。如

camera.depthTextureMode = DepthTextureMod.Depth;
camera.depthTextureMode |= DepthTextureMode.Depth;
camera.depthTextureMode |= DepthTextureMode.DepthNormals;

我们可以在C#脚本中让摄像机生成一个深度贴图,或者法线贴图。如果我们只需要深度贴图,那么不设置也是没有问题的。在设置完摄像机的设置了以后,我们便可以在shader中来使用下代码声明这个贴图。

sampler2D _CameraDepthTexture;

这便是我们的深度纹理了。他其中包含的每一个像素的深度值。我们可以想其他贴图一样使用tex2D来对其采样获得值。但不同的平台他们所处理的方式不太一样,因此单纯的采样可能会出现问题,这里我们建议使用Unity所提供的 SAMPLE_DEPTH_TEXTURE 来解决这个平台差异所导致的问题。使用如下

float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);

与之类似的函数还有 SAMPLE_DEPTH_TEXTURE_PROJ。我们在此处获得的深度值是非线性的。无法实现我们想要的效果,因此我们需要将其转化为线性的深度。Unity为我们提供了函数Linear01Depth和LinearEyeDepth,前者返回一个零到一的线性深度值,后者则会返回视角空间下真正的深度值。
总结,如果我们想获得深度缓冲中的像素的深度值,我们可以通过以下代码来获得。

float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv));
获得摄像机视角下物体的深度值

有的时候我们会需要物体的真实深度值,但是深度缓冲中的深度是零到一,无法靠第一种方法来获得,我们这里就需要第二种方法了。即利用 LinearEyeDepth。首先我们需要获得该片元在摄像机视角下的坐标。

v2f vert(v2f v){
    v2f o;
    //  do something
    o.pos = UnityObjectToClipPos(v.vertex);
    o.screenPos = ComputeScreenPos(o.pos);
}

ComputerScreenPos函数的参数是齐次坐标系的顶点,所以需要传递的是裁剪过后的坐标。后面我们便可以在片元着色器中来使用这个坐标以获得该点的真正深度值,需要注意的是我们此处获得的也不是透明物体的深度值,因为他没有写入深度缓冲,我们无法通过深度贴图获得。

float value = SAMPLE_DEPTH_TEXTURE_PRO(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
float depth = LinearEyeDepth(value);

其中的UNITY_PROJ_COORD是为了处理不同平台的差异使用的。这样我们变获得了该像素点所在位置且写入了深度值的真实深度,至于我们透明物体的深度值则可以直接通过

float sceenz = i.screenPos.w;

来获得,这里至于为啥是w值,

至此我们便获得同一点两个像素的深度值了,便可以利用他来实现一些效果,以下便是一个护盾效果的全部代码。

Shader "Unlit/Edge"
{
Properties
{
    _HeadColor("Color",Color) = (1,1,1,1)
    _TrailColor("Color",Color) = (1,1,1,1)
    _RadiusRange("RadiusRange",Range(0,2)) =1.5
    _Edgb("_Edgb",Range(0,6)) =0.2
    _Value("value",Range(0,1)) =0.2
}
    SubShader
{
        Tags
        {
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
        }
        ZWrite off
    cull off
    Blend SrcAlpha OneMinusSrcAlpha
    LOD 100
    Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        // make fog work
        #pragma multi_compile_fog
        #include "UnityCG.cginc"
        fixed4 _HeadColor;
fixed4 _TrailColor;
float _Value;
        float _RadiusRange;
        sampler2D _CameraDepthTexture;
        float _Edgb;
        struct appdata
        {
            float4 vertex : POSITION;
            float3 normal:NORMAL;
        };
        struct v2f
        {
            float4 pos:SV_POSITION;
            float3 viewDir:TEXCOORD0;
            float3 worldNormal:TEXCOORD1;
            float4 screenPos :TEXCOORD2;
        };
        v2f vert (appdata v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
            o.viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld,v.vertex).xyz);
            o.screenPos = ComputeScreenPos(o.pos);
            return o;
        }
        fixed4 frag(v2f i) : SV_Target
        {
            float depth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
            float sceenz = i.screenPos.w;
            float offset = max(0, _Edgb - (depth - sceenz));
            fixed c = max(offset,1-abs(dot(i.worldNormal,i.viewDir)) * _RadiusRange);
            c = min(c, 1);
            fixed4 Color = lerp(_TrailColor, _HeadColor, _Value);
            fixed4 color = Color * c;
            return color;
        }
        ENDCG
    }
}
}