时间:2021年11月30日09:12:27

本笔记仅作为《Shader入门精要》的部分要点回顾和补充,重点放在表面着色器和ASE的使用上

Ch7.透明效果

P125 模板测试

  • 不管通过还是未通过测试,在测试结束之后都可以对缓存中的模板值做操作

语法:

  1. Stencil
  2. {
  3. Ref referenceValue
  4. ReadMask readMask
  5. WriteMask writeMask
  6. Comp comparisonFunction
  7. Pass stencilOperation
  8. Fail stencilOperation
  9. ZFail stencilOperation
  10. }

Ch8.表面着色器的基础概念

P134 组织结构

表面着色器编译指令的语法结构为:

#pragma surface surfaceFunction lightModel [optionalparams]

1)surface:声明所使用的Shader是表面着色器

2)surfaceFunction:声明表面着色器的函数名称,被称为表面函数,一般使用surf作为表面函数的名称

3)lightModel:声明所使用的的光照模型。Unity内置四种光照模型,分别为:

  • 非物理光照模型:Lambert和BlinnPhong
  • 物理光照模型:Standard和StandardSpecular

4)[optionalparams]:其他可选参数

P134 编译指令中的可选参数

自行查询书籍了解

P137 表面函数的语法结构

  1. void surf(Input IN, inout SurfaceOutput o)
  2. {
  3. //表面函数代码
  4. }

1.表面函数输入结构体

使用者可以通过下表中的变量直接获取到相关数据,然后传入表面函数中进行计算

变量 说明
float2 uv_texName、float2 uv2_texName uv关键词后接纹理名称,获取模型某套uv坐标
float3 viewDir 摄像机视角方向(世界空间),没有被标准化
使用COLOR语义定义的float4变量 插值后的逐顶点颜色
float4 screenPos 屏幕空间坐标,可用于反射或屏幕空间特效,但不适用于GrabPass,需要使用ComputeGrabScreenPos函数单独计算uv
float3 worldPos 世界空间坐标
float3 worldRefl 世界空间反射向量,前提是没有修改表面法线 o.Normal
float3 worldNormal 世界空间法线线路,前提是没有修改表面法线 o.Normal
float3 worldRefl; INTERNAL_DATA 如果表面法线 o.Normal 进行了修改,在表面函数中通过WorldReflectionVector(IN, o.Normal)得到基于法线贴图的世界空间反射向量
float3 worldNormal; INTERNAL_DATA 如果表面法线 o.Normal 进行了修改,在表面函数中通过 WorldNormalVector(IN, o.Normal)得到基于法线贴图的世界空间法线向量

2.表面函数输出结构体

Lambert和BlinnPhong使用SurfaceOutput结构体输出

  1. struct SurfaceOutput
  2. {
  3. fixed3 Albedo; //漫反射颜色
  4. fixed3 Normal; //切线空间法线
  5. fixed3 Emission; //自发光
  6. half Specular; //镜面反射指数,范围0~1
  7. fixed Gloss; //镜面反射强度
  8. fixed Alpha; //透明通道
  9. };

金属工作流输出结构体

  1. struct SurfaceOutputStandard
  2. {
  3. fixed3 Albedo; //漫反射颜色
  4. fixed3 Normal; //切线空间法线
  5. fixed3 Emission; //自发光
  6. half Metallic; //0表示非金属,1表示金属
  7. half Smoothness; //0表示非常粗糙,1表示非常光滑
  8. half Occlusion; //环境光遮蔽,默认为1
  9. fixed Alpha; //透明通道
  10. };

高光工作流输出结构体

  1. struct SurfaceOutputStandard
  2. {
  3. fixed3 Albedo; //漫反射颜色
  4. fixed3 Normal; //切线空间法线
  5. fixed3 Emission; //自发光
  6. //half Metallic; //0表示非金属,1表示金属
  7. half Smoothness; //0表示非常粗糙,1表示非常光滑
  8. half Occlusion; //环境光遮蔽,默认为1
  9. fixed Alpha; //透明通道
  10. };

Ch9.编写表面着色器

P141 使用法线贴图

  1. Shader "Surface Shader/Normal Map"
  2. {
  3. Properties
  4. {
  5. _MainTex ("MainTex", 2D) = "white" {}
  6. _Color ("Color", Color) = (1,1,1,1)
  7. _Normal ("Normal Map", 2D) = (1,1,1,1)
  8. _Bumpiness ("Bumpiness", Range(0, 1)) = 0
  9. }
  10. SubShader
  11. {
  12. CGPROGRAM
  13. #pragma surface surf Lambert
  14. struct Input
  15. {
  16. float2 uv_MainTex;
  17. float2 uv_Normal;
  18. };
  19. sampler2D _MainTex;
  20. fixed4 _Color;
  21. sampler2D _Normal;
  22. fixed _Bumpiness;
  23. void surf (Input IN, inout SurfaceOutput o)
  24. {
  25. fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
  26. o.Albedo = c.rgb;
  27. //采样法线贴图并解包
  28. fixed3 n = UnpackNormal(tex2D(_Normal, IN.uv_Normal));
  29. n *= float3(_Bumpiness, _Bumpiness, 1);
  30. o.Normal = n;
  31. }
  32. ENDCG
  33. }
  34. FallBack "Diffuse"
  35. }

P146 使用顶点修改函数

  1. Shader "Surface Shader/Vertex Modify"
  2. {
  3. Properties
  4. {
  5. _MainTex ("MainTex", 2D) = "white" {}
  6. _Expansion ("Expansion", Range(0, 0.1)) = 0
  7. }
  8. SubShader
  9. {
  10. CGPROGRAM
  11. #pragma surface surf Lambert vertex:vert
  12. struct Input
  13. {
  14. float2 uv_MainTex;
  15. };
  16. sampler2D _MainTex;
  17. fixed _Expansion;
  18. //顶点修改函数,输入/输出 appdata_full 结构体
  19. void vert (inout appdata_full v)
  20. {
  21. v.vertex.xyz += v.normal * _Expansion;
  22. }
  23. void surf (Input IN, inout SurfaceOutput o)
  24. {
  25. fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
  26. o.Albedo = c.rgb;
  27. }
  28. ENDCG
  29. }
  30. FallBack "Diffuse"
  31. }

P148 自定义光照函数

Shader "Surface Shader/Custom Lambert"
{
  Properties
  {
    _MainTex ("MainTex", 2D) = "white" {}
  }
  SubShader
  {
    CGPROGRAM
    #pragma surface surf CustomLambert

    struct Input
    {
      float2 uv_MainTex;
    };

    sampler2D _MainTex;

    void surf (Input IN, inout SurfaceOutput o)
    {
      fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
      o.Albedo = c.rgb;
    }

    //自定义光照函数
    half4 LightingCustomLambert(SurfaceOutput s, half3 lightDir, half atten)
    {
      fixed NdotL = saturate(dot(s.Normal, lightDir));
      half4 c;
      c.rgb = s.Albedo * _LightColor0 * NdotL * atten;
      c.a = s.Alpha;
      return c;
    }

    ENDCG
  }
  FallBack "Diffuse"
}

P149 最终颜色修改函数

Shader "Surface Shader/Final Color Modify"
{
  Properties
  {
    _MainTex ("MainTex", 2D) = "white" {}
    _ColorTint ("Color Tint", Color) = (1,1,1,1)
  }
  SubShader
  {
    CGPROGRAM
    //声明最终颜色修改函数为 ModifyColor
    #pragma surface surf Lambert finalcolor:ModifyColor

    struct Input
    {
      float2 uv_MainTex;
    };

    sampler2D _MainTex;
    fixed4 _ColorTint;

    void surf (Input IN, inout SurfaceOutput o)
    {
      fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
      o.Albedo = c.rgb;
    }

        //最终颜色修改函数
    void ModifyColor (Input IN, SurfaceOutput o, inout fixed4 color)
    {
      color *= _ColorTint;
    }

    ENDCG
  }
  FallBack "Diffuse"
}

P151 使用曲面细分函数

曲线细分语法:

添加编译指令:tessellate:FunctionName

float4 FunctionName()
{
  return tess; //曲面细分等级
}

固定数量的曲面细分

#pragma surface surf Lambert tesellate:tessellation vertex:height addshadow

half _Tessellation; //参考值 1 ~ 32

//曲面细分函数
float4 tessellation()
{
  return _Tessellation;
}

基于边长的曲面细分

会根据不同长度的边分别对应不同的曲面细分等级,边越长细分等级越高,从而使模型的边长在屏幕上看起来一致

#pragma surface surf Lambert tesellate:tessellateEdge vertex:height addshadow

half _EdgeLength; //边的平均长度,越小细分程度越高 参考值 1 ~ 32

//曲面细分函数
float4 tessellateEdge(appdata_full v0, appdata_full v1, appdata_full v2)
{
  //调用基于边长的曲面细分函数
  return UnityEdgeLengthBasedTess(v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}

视锥剔除曲面细分

在基于边长的基础上,额外判断顶点是否在摄像机的视锥体内,超出视锥体范围的顶点将会被剔除

#pragma surface surf Lambert tesellate:tessellateCull vertex:height addshadow

half _EdgeLength; //边的平均长度,越小细分程度越高 参考值 1 ~ 32
float _MaxHeight; //参考值 0 ~ 0.5

//曲面细分函数
float4 tessellateCull(appdata_full v0, appdata_full v1, appdata_full v2)
{
  //调用基于边长的曲面细分函数
  return UnityEdgeLengthBasedTessCull(v0.vertex, v1.vertex, v2.vertex, _EdgeLength, _MaxHeight);
}

基于距离的曲面细分

  • d < minDist,细分等级为tess
  • minDist < d < maxDist:逐渐降低
  • d > maxDist:细分等级保持为1
#pragma surface surf Lambert tesellate:tessellateDistance vertex:height addshadow

half _MinDistance;
half _MaxDistance;
half _Tessellation; //参考值 1 ~ 32

//曲面细分函数
float4 tessellateDistance(appdata_full v0, appdata_full v1, appdata_full v2)
{
  //基于距离的曲线细分
  return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, _MinDistance, _MaxDistance, _Tessellation);
}

Phong 曲面细分

Phong细分会使新生成的顶点沿着原始顶点的平均法线方向偏移一段距离,使模型表面变得更加光滑

  • Phong曲面细分单独使用是没有效果的,需要结合曲面细分函数才会生效
#pragma surface surf Lambert tesellate:tessellation tessphong:_Phong

half _Tessellation; //参考值 1 ~ 32
fixed _Phong; //不加会报错,值越大,越光滑 0~1

//曲面细分函数
float4 tessellation()
{
  return _Tessellation;
}

P164 透明度混合

Shader "Surface Shader/Transparent"
{
  Properties
  {
    _MainTex ("MainTex", 2D) = "white" {}
        _Color ("Color(RGB-A)", Color) = (1,1,1,1)
  }
  SubShader
  {
    Tags {"Queue" = "Transparent"}

    CGPROGRAM
    //使用alpha指令开启透明效果
    #pragma surface surf Lambert alpha

    struct Input
    {
      float2 uv_MainTex;
    };

    sampler2D _MainTex;
        fixed4 Color;

    void surf (Input IN, inout SurfaceOutput o)
    {
      fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
      o.Albedo = c.rgb;
      o.Alpha = c.a * _Color.a;
    }
    ENDCG
  }
  FallBack "Diffuse"
  //个人感觉 Fallback "Transparent/VertexLit" 更合适
}

P166 透明度测试

Shader "Surface Shader/Transparent"
{
  Properties
  {
    _MainTex ("MainTex", 2D) = "white" {}
        _AlphaTest ("Alpha Test", Range(0,1)) = 0
  }
  SubShader
  {
    Tags {"Queue" = "AlphaTest"}

    CGPROGRAM
    //添加alphatest指令开启透明测试
    //添加addshadow 指令开启
    #pragma surface surf Lambert alphatest:_AlphaTest addshadow

    struct Input
    {
      float2 uv_MainTex;
    };

    sampler2D _MainTex;
    //不用再次声明_AlphaTest照样没问题(和Phong曲面细分不同)

    void surf (Input IN, inout SurfaceOutput o)
    {
      fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
      o.Albedo = c.rgb;
      o.Alpha = c.a * _Color.a;
    }
    ENDCG
  }
  FallBack "Diffuse"
  //个人感觉 Fallback "Transparent/Cutout/VertexLit" 更合适
}

Ch10.Image Effect

P168 GrabPass

GrabPass可以获取模型对应的屏幕纹理

Shader "Custom/GrabPass"
{
  Properties
  {
    _GrayScale ("Gray Scale", Range(0, 1)) = 0
  }
  SubShader
  {
    Tags {"Queue" = "Transparent"}

    //调用GrabPass,并定义抓取图像的名称
    GrabPass {"_ScreenTex"}

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

      struct v2f
      {
        float4 pos : SV_POSITION;
        float4 grabPos : TEXCOORD0;
      };

      v2f vert(float4 vertex : POSITION)
      {
        v2f o;
        o.pos = UnityObjectToClipPos(vertex);
        //计算抓取图像在屏幕上的位置
        o.grabPos = ComputeGrabScreenPos(o.pos);

        return o;
      }

      fixed _GrayScale;

      //声明抓取图像
      sampler2D _ScreenTex;

      half4 frag (v2f i) : SV_TARGET
      {
        //采样抓取图像
        //tex2Dproj相当于将纹理的xy分量除以w分量之后再对纹理贴图进行采样
        half4 src = tex2Dproj(_ScreenTex, i.grabPos);
        half grayscale = Luminance(src.rgb);
        half4 dst = half4(grayscale, grayscale, grayscale, 1);

        return lerp(src, dst, _GrayScale);
      }
    }
  }
}

P187 自定义后处理堆栈

Shader

Shader "Hidden/BSC - HLSL"
{
  HLSLINCLUDE
  #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
  //属性声明
  TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
  half _Brightness;
  half _Saturation;
  half _Contrast;

  float4 Frag(VaryingsDefault i) : SV_TARGET
  {
    //采样 RenderTexture
    float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
    //亮度
    color.rgb *= _Brightness;
    //饱和度
    float luminance = dot(color.rgb, float3(0.2126729, 0.7151522, 0.0721750));
    color.rgb = lerp(luminance, color.rgb, _Saturation);
    //对比度
    half3 grayColor = half3(0.5, 0.5, 0.5);
    color.rgb = lerp(grayColor, color.rgb, _Contrast);

    return color;
  }
  ENDHLSL

  SubShader
  {
    Cull Off ZWrite Off ZTest Always
    Pass
    {
      HLSLPROGRAM
      #pragma vertex VertDefault
      #pragma fragment Frag
      ENDHLSL
    }
  }
}

C#脚本

using System;
using UnityEngine;
using UnityEngine.Rendering.PostProcessing;

//参数模块:继承PostProcessEffectSettings
//参数需序列化
[Serializable]
//参数①:关联脚本的渲染模块
//参数②:后期处理操作应用的对象类型或所处时间阶段
// - BeforeTransparent:后处理效果只会应用于不透明物体,会在透明物体渲染之前执行
// - BeforeStack:后处理效果会在内置的堆栈之前执行,如抗锯齿、景深、色调映射等前面
// - AfterStack:后处理效果会在内置的堆栈之后执行,如果有抗锯齿效果,会在其之前执行
//参数③:后期处理效果在下拉列表中的名称,使用"/"可以创建子目录
[PostProcess(typeof(BSCRenderer), PostProcessEvent.AfterStack, "Custom/BSC")]
public sealed class BSC : PostProcessEffectSettings
{
  //开放属性
  [Range(0f, 2f), Tooltip("Brightness effect intensity.")]
  public FloatParameter Brightness = new FloatParameter { value = 1f };

  [Range(0f, 2f), Tooltip("Saturation effect intensity.")]
  public FloatParameter Saturation = new FloatParameter { value = 1f };

  [Range(0f, 2f), Tooltip("Contrast effect intensity.")]
  public FloatParameter Contrast = new FloatParameter { value = 1f };
}

//渲染模块:继承PostProcessEffectRenderer<T>
public sealed class BSCRenderer : PostProcessEffectRenderer<BSC>
{
  public override void Render(PostProcessRenderContext context)
  {
    // 查找Shader文件
    var sheet = context.propertySheets.Get(Shader.Find("Hidden/BSC-HLSL"));

    //传递属性到Shader
    sheet.properties.SetFloat("_Brightness", settings.Brightness);
    sheet.properties.SetFloat("_Saturation", settings.Saturation);
    sheet.properties.SetFloat("_Contrast", settings.Contrast);

    context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
  }
}

Setting中常用变量类型

类型 描述
FloatParameter 浮点型
BoolParameter 布尔型
ColorParameter 颜色
Vector2Parameter 二维向量
Vector3Parameter 三维向量
Vector4Parameter 四维向量
TextureParameter 纹理贴图,常用的默认值有:None、Black、White、Transparent

最终效果

《ShaderLab新手宝典》重点笔记 - 图1

Ch11.自定义材质面板

P197 Toggle

默认关键词

  • 例句:[Toggle] _Invert color?", Float) = 0
    • 以上例句会被Unity默认设置为_INVERT_ON

自定义关键词

  • 例句:[Toggle(ENABLE_FANCY)] _Fancy ("Fancy?", Float) = 0
    • 关键词:ENABLE_FANCY

P197 Enum

内置枚举类型

  • 例句:[Enum(UnityEngine.Rendering.BlendMode)] _Blend ("Blend mode", Float) = 1

自定义枚举类型

  • 一个枚举最多只能自定义7个名称/数值对
  • 例句:[Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 0

P198 KeywordEnum

和Enum类似,只不过关键词枚举会有与之对应的Shader关键词

  • 例句:[KeywordEnum(None, Add, Multiply)] _Overlay ("Overlay mode", Float) = 0
    • 这三个选项对应的Shader关键词分别为:_OVERLAY_NONE_OVERLAY_ADD_OVERLAY_MULTIPLY

P198 编译指令定义关键词

定义了ToggleDrawer或KeywordEnumDrawer之后,如果想要正常使用,还需要在编译指令中声明Shader关键词

两种编译指令,不同关键词之间用空格隔开

  • shader_feature:只会为材质使用到的关键词生成变体,没有使用到的关键词不会生成变体,因此无法在运行的时候通过脚本切换效果
    • 例如,#pragma shader_feature _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY
    • 例如,#pragma shader_feature _INVERT_ON
  • multi_compile:会为所有关键词生成变体,因此可以在运行的时候通过脚本切换效果
    • 例如,#pragma multi_compile _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY

P199 PowerSlider

指数滑动条

  • 例子:[PowerSlider(3.0)] _Brightness ("Birghtness", Range(0.01, 1)) = 0.1
    • 这是一个以3为指数的滑动条

P200 IntRange

整数滑动条

  • 例子:[IntRange] _Alpha ("Alpha", Range(0, 255)) = 100

Ch13.初级案例

P228 1.流光效果

原理:通过引入_Time对uv坐标进行偏移,实现流光效果

《ShaderLab新手宝典》重点笔记 - 图2

Shader "Samples/Light Flow"
{
  Properties
  {
    _Tex("Texture", 2D) = "white" {}
    _Color("Color", Color) = (0, 1, 1, 1)

      // 关键词枚举,0为x方向,1为y方向
      [KeywordEnum(X,Y)] _DIRECTION("Flow Direction", float) = 0
      _Speed("Flow Speed", float) = 1
  }
  SubShader
  {
    Tags {"RenderType" = "Transparent" "Queue" = "Transparent"}

    Blend One One
    Cull Off

    Pass
    {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag

      // 定义枚举关键词
      #pragma shader_feature _DIRECTION_X _DIRECTION_Y

      #include "unityCG.cginc"

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

      sampler2D _Tex;
      float4 _Tex_ST;
      fixed4 _Color;
      float _Speed;

      v2f vert (appdata_base v)
      {
        v2f o;

        o.texcoord = TRANSFORM_TEX(v.texcoord, _Tex);
        o.vertex = UnityObjectToClipPos(v.vertex);

        return o;
      }

      float4 frag (v2f i) : SV_Target
      {
        float2 texcoord;

        // 判断流动方向
        #if _DIRECTION_X
        texcoord = float2(i.texcoord.x + _Time.x * _Speed,
                          i.texcoord.y);
        #elif _DIRECTION_Y
        texcoord = float2(i.texcoord.x,
                          i.texcoord.y + _Time.x * _Speed);
        #endif

        return tex2D(_Tex, texcoord) * _Color;
      }
      ENDCG
    }
  }
}

P234 2.描边效果

原理:通过两个Pass,第一个Pass关闭深度写入,绘制一遍沿法线扩张的描边色模型;第二个Pass正常绘制。由于第一个Pass关闭了深度写入,如果渲染队列为Geometry,可能会导致在模型身后的几何体将描边颜色覆盖住,进而产生错误的效果,因此将整个Shader的渲染队列延迟到Transparent

亮点亦是缺点:第二个Pass会完全覆盖第一个Pass非描边部分,这会导致描边细节丢失,而仅仅绘制模型外围一圈描边,模型内部没有任何描边

《ShaderLab新手宝典》重点笔记 - 图3

Shader "Samples/Outline"
{
  Properties
  {
    [Header(Texture Group)]
    [Space(10)]
    _Albedo ("Albedo", 2D) = "white" {}
    [NoScaleOffset]_Specular ("Specular (RGB-A)", 2D) = "black" {}
    [NoScaleOffset]_Normal ("Normal", 2D) = "bump" {}
    [NoScaleOffset]_AO ("Ambient Occlusion", 2D) = "white" {}

    [Header(Outline Properties)]
    [Space(10)]
    _OutlineColor ("Outline Color", Color) = (1,0,1,1)
    _OutlineWidth ("Outline Width", Range(0, 0.1)) = 0.01
  }

  SubShader
  {
    Tags { "RenderType"="Opaque" "Queue" = "Transparent"}

    //---------- Outline Layer ----------
    Pass
    {
      ZWrite Off

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

      struct v2f
      {
        float4 vertex : SV_POSITION;
      };

      fixed4 _OutlineColor;
      fixed _OutlineWidth;

      v2f vert(appdata_base v)
      {
        v2f o;
        v.vertex.xyz += v.normal * _OutlineWidth;
        o.vertex = UnityObjectToClipPos(v.vertex);

        return o;
      }

      fixed4 frag(v2f i) : SV_Target
      {
        return _OutlineColor;
      }

      ENDCG
    }

    //---------- Regular Layer ----------
    CGPROGRAM
    #pragma surface surf StandardSpecular fullforwardshadows

    struct Input
    {
      float2 uv_Albedo;
    };

    sampler2D _Albedo;
    sampler2D _Specular;
    sampler2D _Normal;
    sampler2D _AO;

    void surf (Input IN, inout SurfaceOutputStandardSpecular o)
    {
      fixed4 c = tex2D (_Albedo, IN.uv_Albedo);
      o.Albedo = c.rgb;

      fixed4 specular = tex2D (_Specular, IN.uv_Albedo);
      o.Specular = specular.rgb;
      o.Smoothness = specular.a;

      o.Normal = UnpackNormal(tex2D (_Normal, IN.uv_Albedo));
      o.Occlusion = tex2D (_AO, IN.uv_Albedo);
    }

    ENDCG
  }
}

P241 3.遮挡半透明

原理:两个Pass渲染,被遮挡部分深度测试设为Greater,中间半透,边缘发亮;未被遮挡部分正常绘制

  • 遮挡半透明不需要显示后面的物体,就不必更改渲染队列了

《ShaderLab新手宝典》重点笔记 - 图4

Shader "Samples/X-Ray"
{
  Properties
  {
    [Header(The Blocked Part)]
    [Space(10)]
    _Color ("X-Ray Color", Color) = (0,1,1,1)
    _Width ("X-Ray Width", Range(1, 2)) = 1
    _Brightness ("X-Ray Brightness",Range(0, 2)) = 1

    [Header(The Normal Part)]
    [Space(10)]
    _Albedo("Albedo", 2D) = "white"{}
    [NoScaleOffset]_Specular ("Specular (RGB-A)", 2D) = "black"{}
    [NoScaleOffset]_Normal ("Nromal", 2D) = "bump"{}
    [NoScaleOffset]_AO ("AO", 2D) = "white"{}
  }

  SubShader
  {
    Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}

    //---------- The Blocked Part ----------
    Pass
    {
      //深度测试设为Greater,大于则通过
      ZTest Greater
      ZWrite Off

      Blend SrcAlpha OneMinusSrcAlpha

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

      struct v2f
      {
        float4 vertexPos : SV_POSITION;
        float3 viewDir : TEXCOORD0;
        float3 worldNor : TEXCOORD1;
      };

      v2f vert(appdata_base v)
      {
        v2f o;
        o.vertexPos = UnityObjectToClipPos(v.vertex);
        o.viewDir = normalize(WorldSpaceViewDir(v.vertex));
        o.worldNor = UnityObjectToWorldNormal(v.normal);

        return o;
      }

      fixed4 _Color;
      fixed _Width;
      half _Brightness;

      float4 frag(v2f i) : SV_Target
      {
        // Fresnel算法
        half NDotV = saturate(dot(i.worldNor, i.viewDir));//中间1,边缘0
        NDotV = pow(1 - NDotV, _Width) * _Brightness;//中间0,边缘1,0的范围大

        fixed4 color;
        color.rgb = _Color.rgb;
        color.a = NDotV;
        return color;
      }
      ENDCG
    }

    //---------- The Normal Part ----------
    CGPROGRAM
    #pragma surface surf StandardSpecular
    #pragma target 3.0

    struct Input
    {
      float2 uv_Albedo;
    };

    sampler2D _Albedo;
    sampler2D _Specular;
    sampler2D _Normal;
    sampler2D _AO;

    void surf(Input IN, inout SurfaceOutputStandardSpecular o)
    {
      o.Albedo = tex2D(_Albedo, IN.uv_Albedo).rgb;

      fixed4 specular = tex2D(_Specular, IN.uv_Albedo);
      o.Specular = specular.rgb;
      o.Smoothness = specular.a;

      o.Normal = UnpackNormal(tex2D(_Normal, IN.uv_Albedo));
    }
    ENDCG
  }
}

P249 4.三平面映射

原理:使用世界空间中X、Y、Z三个方向的坐标屏幕对纹理进行采样

  • X方向上显示colorYZ
  • Y方向上显示colorXZ
  • Z方向上显示colorXY

《ShaderLab新手宝典》重点笔记 - 图5

Shader "Samples/Tri-Planar Mapping"
{
  Properties
  {
    _Tiling ("Tiling", float) = 1
    [NoScaleOffset]_Albedo ("Albedo", 2D) = "white" {}
    [NoScaleOffset]_Normal ("Normal", 2D) = "bump" {}
    _Bumpiness ("Bumpiness", Range(0.01, 10)) = 1
  }
  SubShader
  {
    CGPROGRAM
    #pragma surface surf Lambert fullforwardshadows

    struct Input
    {
      float3 worldPos;
      float3 worldNormal;
      INTERNAL_DATA
    };

    float _Tiling;
    sampler2D _Albedo;
    sampler2D _Normal;
    half _Bumpiness;

    void surf (Input IN, inout SurfaceOutput o)
    {
      //使用世界空间坐标进行采样
      float3 texCoord = IN.worldPos * _Tiling;

      //根绝法向量与坐标方向的契合度,来计算采样权重
      // -------------------- Mask --------------------
      float3 normal = abs(WorldNormalVector(IN, o.Normal));
      //如果法向方向与(1,0,0)契合度越高,则maskX越大
      fixed maskX = saturate(dot(normal, fixed3(1, 0, 0)));
      fixed maskY = saturate(dot(normal, fixed3(0, 1, 0)));

      // -------------------- Albedo --------------------
      //用xy坐标采样(同xy,不同z的颜色会全部一致)
      fixed4 colorXY = tex2D (_Albedo, texCoord.xy);
      fixed4 colorYZ = tex2D (_Albedo, texCoord.yz);
      fixed4 colorXZ = tex2D (_Albedo, texCoord.xz);

      fixed4 c;
      //maskX越大,说明法线方向越朝向X轴,此时采样的结果就越接近colorYZ
      c = lerp(colorXY, colorYZ, maskX);
      c = lerp(c, colorXZ, maskY);

      o.Albedo = c.rgb;

      // -------------------- Normal --------------------
      fixed3 normalXY = UnpackNormal(tex2D(_Normal, texCoord.xy));
      fixed3 normalYZ = UnpackNormal(tex2D(_Normal, texCoord.yz));
      fixed3 normalXZ = UnpackNormal(tex2D(_Normal, texCoord.xz));

      fixed3 n;
      n = lerp(normalXY, normalYZ, maskX);
      n = lerp(n, normalXZ, maskY);

      o.Normal = n * half3(_Bumpiness, _Bumpiness, 1);
    }
    ENDCG
  }
  FallBack "Diffuse"
}

P258 5.MatCap效果

原理:使用法线向量对纹理进行采样

分析:模型某一点的单位化后的法线向量,投影到XY平面,用投影后的坐标进行采样。因为投影之前,所有法线向量最多构成一个单位球,因此投影后的XY坐标必不可能为(1,1)之类的点(单位圆以外的点),所以你会发现Matcap贴图长成这个样子

《ShaderLab新手宝典》重点笔记 - 图6

最终效果:

《ShaderLab新手宝典》重点笔记 - 图7

Shader "Samples/MatCap"
{
  Properties
  {
    [NoScaleOffset]_MatCap ("MatCap", 2D) = "white" {}
  }
  SubShader
  {
    Tags { "RenderType"="Opaque" "Queue" = "Geometry"}

    Pass
    {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag

      #include "UnityCG.cginc"

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

      sampler2D _MatCap;

      v2f vert (appdata_base v)
      {
        v2f o;

        // 使用UNITY_MATRIX_MV的逆转置矩阵
        // 变换非统一缩放物体的法线向量
        float4 normal = mul(UNITY_MATRIX_IT_MV, float4(v.normal, 0));
        o.texcoord = normalize(normal.xyz).xy;

        o.vertex = UnityObjectToClipPos(v.vertex);

        return o;
      }

      fixed4 frag (v2f i) : SV_Target
      {
        // 范围 [-1, 1] => [0, 1]
        float2 texcoord = i.texcoord * 0.5 + 0.5;

        return tex2D(_MatCap, texcoord);
      }
      ENDCG
    }
  }
  FallBack "Diffuse"
}

P263 6.物体切割效果

原理:利用世界坐标配合透明度测试实现物体切割效果

《ShaderLab新手宝典》重点笔记 - 图8

Shader "Samples/Object Cutting"
{
  Properties
  {
    [Header(Textures)] [Space(10)]
    [NoScaleOffset] _Albedo ("Albedo", 2D) = "white" {}
    [NoScaleOffset] _Reflection ("Specular_Smoothness", 2D) = "black" {}
    [NoScaleOffset] _Normal ("Normal", 2D) = "bump" {}
    [NoScaleOffset] _Occlusion ("Ambient Occlusion", 2D) = "white" {}

    [Header(Cutting)] [Space(10)]
    [KeywordEnum(X, Y, Z)] _Direction ("Cutting Direction", Float) = 1
    [Toggle] _Invert ("Invert Direction", Float) = 0
  }
  SubShader
  {
    Tags { "RenderType"="TransparentCutout" "Queue"="AlphaTest" }
    Cull Off

    CGPROGRAM
    #pragma surface surf StandardSpecular addshadow fullforwardshadows
    #pragma target 3.0

    #pragma multi_compile _DIRECTION_X _DIRECTION_Y _DIRECTION_Z

    sampler2D _Albedo;
    sampler2D _Reflection;
    sampler2D _Normal;
    sampler2D _Occlusion;

    //外部传入模型的Position
    float3 _Position;
    fixed _Invert;

    struct Input
    {
      float2 uv_Albedo;
      float3 worldPos;
      fixed face : VFACE; //VFACE语义,当正面朝向摄像机时返回正值,否则返回负值
    };

    void surf (Input i, inout SurfaceOutputStandardSpecular o)
    {
      fixed4 col = tex2D(_Albedo, i.uv_Albedo);
      o.Albedo =  i.face > 0 ? col.rgb : fixed3(0,0,0);//背面默认渲染成纯黑

      // 判断切割方向
      #if _DIRECTION_X
      //参数①:参考值
      //参数②:实际值
      //实际值 >= 参考值,返回1;否则返回0
      col.a = step(_Position.x, i.worldPos.x);
      #elif _DIRECTION_Y
      col.a = step(_Position.y, i.worldPos.y);
      #else 
      col.a = step(_Position.z, i.worldPos.z);
      #endif

      // 判断是否反转切割方向
      col.a = _Invert? 1 - col.a : col.a;

      clip(col.a - 0.001);

      //使用反射向量采用高光及粗糙度贴图
      fixed4 reflection = tex2D(_Reflection, i.uv_Albedo);
      o.Specular = i.face > 0 ? reflection.rgb : fixed3(0,0,0);
      o.Smoothness = i.face > 0 ? reflection.a : 0;

      o.Normal = UnpackNormal(tex2D(_Normal, i.uv_Albedo));

      o.Occlusion = tex2D(_Occlusion, i.uv_Albedo);
    }
    ENDCG
  }
}

C#脚本

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

[ExecuteInEditMode]
public class GetPosition : MonoBehaviour
{
  public GameObject CuttingPosition;

  private Material Material;
  private Vector3 Center = new Vector3(0, 0, 0);
  void Start()
  {
    //获取当前物体材质
    Material = this.GetComponent<Renderer>().sharedMaterial;
  }

  void Update()
  {
    if (CuttingPosition)
      //获取CuttingPosition的坐标并传递给Shade
      Material.SetVector("_Position", CuttingPosition.transform.position);
    else
      Material.SetVector("_Position", Center);
  }
}

Ch14.进阶案例

P274 1.消融效果

原理:根据噪点图使用透明度测试

《ShaderLab新手宝典》重点笔记 - 图9

Shader "Samples/Dissolve"
{
  Properties
  {
    // -------------------- PBS Textures --------------------
    [Header(PBS Textures)]
    [Space(10)]
    [NoScaleOffset]_Albedo("Albedo", 2D) = "white" {}
    [NoScaleOffset]_Specular("Specular_Smoothness", 2D) = "black" {}
    [NoScaleOffset]_Normal("Normal", 2D) = "bump" {}
    [NoScaleOffset]_AO("AO", 2D) = "white" {}

    // -------------------- Dissolve Properties --------------------
    [Header(Dissolve Properties)]
    [Space(10)]
    _Noise("Dissolve Noise", 2D) = "white" {}
    _Dissolve("Dissolve", Range(0, 1)) = 0
    [NoScaleOffset]_Gradient("Edge Gradient", 2D) = "black" {}
    _Range("Edge Range", Range(2, 100)) = 6
    _Brightness("Brightness", Range(0, 10)) = 1
  }
  SubShader
  {
    Tags
    { 
      "RenderType"="TransparentCutout" "Queue" = "AlphaTest"
    }

    CGPROGRAM
    #pragma surface surf StandardSpecular addshadow fullforwardshadows

    struct Input
    {
      float2 uv_Albedo;
      float2 uv_Noise;
    };

    sampler2D _Albedo;
    sampler2D _Specular;
    sampler2D _Normal;
    sampler2D _AO;

    sampler2D _Noise;
    fixed _Dissolve;
    sampler2D _Gradient;
    float _Range;
    float _Brightness;

    void surf (Input IN, inout SurfaceOutputStandardSpecular o)
    {
      // Clip Mask
      //采样噪点图,r通道为alpha值 [0,1]
      fixed noise = tex2D(_Noise, IN.uv_Noise).r;
      //[0,1] -> [-1,1] 起始值 0:不溶解 1:完全溶解
      fixed dissolve = _Dissolve * 2 - 1;
      //dissolve为-1,则noise - dissolve >= 1 > 0.5,不溶解
      //dissolve为0,则noise - dissolve = noise ∈ [0,1],可能溶解也可能不溶解
      //dissove为1,则noise - dissolve <= 0,完全溶解
      fixed mask = saturate(noise - dissolve);
      clip(mask - 0.5);

      // Burn Effect
      //放大误差,倍率由_Range指定。这样做可以得到mask接近0.5的部分(以0~1渐变形式给出,越远值越为1)
      fixed texcoord = saturate(mask * _Range - 0.5 * _Range);
      o.Emission = tex2D(_Gradient, fixed2(texcoord, 0.5)) * _Brightness;

      fixed4 c = tex2D (_Albedo, IN.uv_Albedo);
      o.Albedo = c.rgb;

      fixed4 specular = tex2D(_Specular, IN.uv_Albedo);
      o.Specular = specular.rgb;
      o.Smoothness = specular.a;

      o.Normal = UnpackNormal(tex2D(_Normal, IN.uv_Albedo));

      o.Occlusion = tex2D(_AO, IN.uv_Albedo);
    }
    ENDCG
  }
}

P283 2.动态液体

原理:使用透明度测试,利用模型中心点,剔除上半部分。再使用sin实现波纹效果。

  • 瓶内液体的制作就是将瓶身缩小一点后即可,嵌套进瓶身即可

缺点:从瓶子底部看会穿帮

《ShaderLab新手宝典》重点笔记 - 图10

Shader "Samples/Dynamic Liquid"
{
  Properties
  {
    _Color("Color", Color) = (1, 0, 0, 1)
    _Specular("Specular_Smoothness", Color) = (0, 0, 0, 0)
    _Level("Liquid Level", float) = 0 //液体等级,控制液体高度

    // 定义关键词枚举的名称
    [KeywordEnum(X,Z)] _Direction("Ripple Direction", float) = 0

    _Speed("Ripple Speed", float) = 1 //时间倍率
    _Height("Ripple Height", float) = 1 //控制正弦曲线幅度
  }
  SubShader
  {
    Tags{"RenderType" = "Transparent" "Queue" = "Transparent"}
    Blend DstColor SrcColor
    ZWrite Off

    CGPROGRAM
    #pragma surface surf StandardSpecular noshadow

    // 定义关键词
    #pragma shader_feature _DIRECTION_X _DIRECTION_Z

    struct Input
    {
      float3 worldPos;
    };

    fixed4 _Color;
    fixed4 _Specular;
    half _Level;

    half _Speed;
    half _Height;

    void surf (Input IN, inout SurfaceOutputStandardSpecular o)
    {
      // 液面效果
      //模型中心点,模型空间 -> 世界空间
      float3 pivot = mul(unity_ObjectToWorld, float4(0, 0, 0, 1));
      //pivot.y - IN.worldPos.y表示,如果点在模型中心上方返回负值,否则返回正值
      //通过_Level进行值的修正
      float liquid = pivot.y - IN.worldPos.y + _Level * 0.01;

      // 波纹效果
      //书上代码感觉不妥,如果xz很大(∞),ripple值∈(-∞,+∞)远超过[0,1],根本无法体现波纹效果
      //float3 ripple = sin(_Time.y * _Speed) * _Height * IN.worldPos;
      //正确的波纹效果应该要和中心点做差值,这样求得的结果是离中心点越远的点波纹效果越明显
      float3 ripple = sin(_Time.y * _Speed) * _Height * (IN.worldPos - pivot);

      // 根据波纹的不同方向进行判断
      #if _DIRECTION_X
      liquid += ripple.x;
      #else
      liquid += ripple.z;
      #endif

      // 像素剔除
      liquid = step(0, liquid);
      clip(liquid - 0.001);

      o.Albedo = _Color.rgb;
      o.Specular = _Specular.rgb;
      o.Smoothness = _Specular.a;
    }
    ENDCG
  }
}

P293 3.Billboard效果

原理:重建顶点的模型坐标系。z轴方向为观察方向,x轴为向右方向,y轴由叉乘得出,通过矩阵变换将所有顶点变换至新坐标系下

分类:

  • Spherical:球体模式。能够在水平和垂直方向旋转,角度完全朝向摄像机。例如,粒子特效
  • Cylindrical:圆柱体模式。只在水平方向旋转,垂直方向固定。例如,远距离的树

本质就是z轴方向的y轴是否恒为0,恒为0就是圆柱体,否则为球体

《ShaderLab新手宝典》重点笔记 - 图11

Shader "Samples/Billboard"
{
  Properties
  {
    [NoScaleOffset] _Tex ("Texture", 2D) = "white" {}
    _Tint ("Tine", Color) = (1, 1, 1, 1)
    [KeywordEnum(Spherical, Cylindrical)] _Type ("Type", float) = 0
  }
  SubShader
  {
    Tags
    {
      "RenderType" = "Transparent"
      "Queue" = "Transparent"
      "DisableBatching" = "True"
    }

    Blend OneMinusDstColor One
      ZWrite Off

      Pass
    {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag

      // 声明枚举的关键词
      #pragma shader_feature _TYPE_SPHERICAL _TYPE_CYLINDRICAL

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

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

      sampler2D _Tex;
      fixed4 _Tint;

      v2f vert (appdata v)
      {
        v2f o;

        // 计算面片朝向摄像机的前方向量
        float3 forward = mul(unity_WorldToObject,
                             float4(_WorldSpaceCameraPos, 1)).xyz;

        // 判断Billboard的类型
        #if _TYPE_CYLINDRICAL
        forward.y = 0;
        #endif

        forward = normalize(forward);

        // 当摄像机完全在面片正上方或者正下方的时候,旋转临时的上方向量
        float3 up = abs(forward.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);

        float3 right = normalize(cross(forward, up));
        up = normalize(cross(right, forward));

        // 将顶点在新的坐标系上移动位置
        float3 vertex = v.vertex.x * right + v.vertex.y * up;

        o.vertex = UnityObjectToClipPos(vertex);
        o.texcoord = v.texcoord;
        return o;
      }

      float4 frag (v2f i) : SV_Target
      {
        return tex2D(_Tex, i.texcoord) * _Tint;
      }
      ENDCG
    }
  }
}

P302 4.序列帧动画

原理:根据时间,计算uv,采样纹理

《ShaderLab新手宝典》重点笔记 - 图12

Shader "Samples/Sequence Animation"
{
  Properties
  {
    [NoScaleOffset] _Tex ("Sequence Image", 2D) = "white" {}
    _Tint ("Tint", Color) = (1, 1, 1, 1)
      _Row ("Row Amount", float) = 1
      _Column ("Column Amount", float) = 1
      _Rate ("Animation Rate", float) = 1
  }
  SubShader
  {
    Tags
    {
      "RenderType" = "Transparent"
        "Queue" = "Transparent"
        "DisableBatching" = "True"
    }
    Blend OneMinusDstColor One
      ZWrite Off

      Pass
    {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag

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

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

      sampler2D _Tex;
      fixed4 _Tint;
      float _Row;
      float _Column;
      float _Rate;

      v2f vert (appdata v)
      {
        v2f o;

        // ---------- Billboard 部分 ----------
        float3 forward = mul(unity_WorldToObject,
                             float4(_WorldSpaceCameraPos, 1)).xyz;
        forward.y = 0;
        forward = normalize(forward);

        float3 up = abs(forward.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
        float3 right = normalize(cross(forward, up));
        up = normalize(cross(right, forward));

        float3 vertex = v.vertex.x * right + v.vertex.y * up;
        o.vertex = UnityObjectToClipPos(vertex);

        // ---------- 序列帧 部分 ----------

        // 计算序列帧的行索引和列索引
        float time = floor(_Time.y * _Rate);
        float row = floor(time / _Column); //商为行索引
        float column = fmod(time, _Column); //余数为列索引

        // 计算序列帧的纹理坐标
        float texcoordU = (v.texcoord.x + column) / _Column;
        //因为纹理是从左上角开始的,实际采用的V值是从0开始的
        //由于纹理设为Repeat,所求V初始值需要向负方向偏移 1 / _Row
        float texcoordV = (v.texcoord.y - 1 - row) / _Row;
        o.texcoord = float2(texcoordU, texcoordV);

        return o;
      }

      float4 frag (v2f i) : SV_Target
      {
        return tex2D(_Tex, i.texcoord) * _Tint;
      }
      ENDCG
    }
  }
}

P312 5.卡通风格效果

原理:Half Lambert采样Ramp,描边,边缘高光,光照模型

《ShaderLab新手宝典》重点笔记 - 图13

Shader "Samples/Toon"
{
  Properties
  {
    [Header(Diffuse)]
    [Space(10)] _Albedo ("Albedo", 2D) = "white" {}
    _Ramp ("Toon Ramp", 2D) = "white" {}

    [Header(Rim)]
    [Space(10)][HDR] _RimColor ("Rim Color", Color) = (0,2,2,1)
    _RimWidth ("Rim Width", Range(0,1)) = 0
    _RimFalloff ("Rim Falloff", Range(0.01,10)) = 1

    [Header(Outline)]
    [Space(10)] _OutlineColor ("Outline Color", Color) = (0,0,0,0)
    _OutlineWidth ("Outline, Width", Float) = 0.02
  }
  SubShader
  {
    Tags { "RenderType"="Opaque" "Queue"="Geometry" }

    // ---------- Outline 部分----------
    Pass
    {
      //使用先渲染背面的描边,描边细节会更丰富
      Cull Front

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

      fixed4 _OutlineColor;
      half _OutlineWidth;

      float4 vert (appdata_base v) : SV_POSITION
      {
        v.vertex.xyz += v.normal * _OutlineWidth;
        return UnityObjectToClipPos(v.vertex);
      }

      float4 frag () : SV_Target
      {
        return _OutlineColor;
      }
      ENDCG
    }

    // ---------- Surface 部分----------
    CGPROGRAM
    #pragma surface surf Toon

    sampler2D _Albedo;

    struct Input
    {
      float2 uv_Albedo;
      float3 worldNormal;
      float3 viewDir;
    };

    // 自定义的表面函数输出结构体
    struct SurfaceOutputToon
    {
      //四个必选项
      half3 Albedo;
      half3 Normal;
      half3 Emission;
      fixed Alpha;

      // 将Input结构体包含进来,为了获取worldNormal和viewDir
      Input SurfaceInput;

      // 内置的全局照明结构体,为了获取光照信息
      UnityGIInput GIdata;
    };

    //UnityGIInput的定义
    //struct UnityGIInput
    //{
    //  float3 worldPos;
    //  half3 worldViewDir;
    //  half atten;
    //  half3 ambient;
    //  float4 probeHDR[2];
    //};

    void surf (Input i, inout SurfaceOutputToon o)
    {
      o.SurfaceInput = i;
      o.Albedo = tex2D(_Albedo, i.uv_Albedo);
    }

    //GI函数:格式 "Lighting + 光照模型名称_GI"
    void LightingToon_GI (inout SurfaceOutputToon s, UnityGIInput GIdata, UnityGI gi)
    {
      s.GIdata = GIdata;
    }

    sampler2D _Ramp;
    half4 _RimColor;
    fixed _RimWidth;
    half _RimFalloff;

    //UnityGI定义
    //struct UnityGI
    //{
    //  UnityLight light;
    //  UnityIndirect indirect;
    //};
    //直接光照信息
    //struct UnityLight
    //{
    //  half3 color;
    //  half3 dir;
    //};
    //间接光照信息
    //struct UnityIndirect
    //{
    //  half3 diffuse;
    //  half3 specular;
    //};

    //自定义光照函数
    half4 LightingToon (SurfaceOutputToon s, UnityGI gi)
    {
      // 重新赋值,方便后续调用结构体内的变量
      UnityGIInput GIdata = s.GIdata;
      Input i = s.SurfaceInput;

      // 使用内置的UnityGI_Base()函数计算GI
      gi = UnityGI_Base(GIdata, GIdata.ambient, i.worldNormal);

      // 将光照转为Ramp
      fixed NdotL = dot(i.worldNormal, gi.light.dir);
      fixed2 rampTexcoord = float2(NdotL * 0.5 + 0.5, 0.5);
      fixed3 ramp = tex2D(_Ramp, rampTexcoord).rgb;

      // 计算漫反射
      half3 diffuse = s.Albedo * ramp * _LightColor0.rgb *
        (GIdata.atten + gi.indirect.diffuse);

      // 计算边缘高光
      fixed NdotV = dot(i.worldNormal , i.viewDir);
      fixed rimMask = pow((1.0 - saturate((NdotV + _RimWidth))), _RimFalloff);
      half3 rim = saturate(rimMask * NdotL) * _RimColor *
        _LightColor0.rgb * GIdata.atten;

      // 输出漫反射与边缘高光的和
      return half4(diffuse + rim, 1);
    }
    ENDCG
  }
  FallBack "Diffuse"
}

P326 6.夜视仪后期处理

原理:镜头扭曲(书上只给了公式)、调色、四周暗角效果、随机生成的噪点

《ShaderLab新手宝典》重点笔记 - 图14

Shader

Shader "Hidden/Night Vision"
{
  Properties
  {
    _MainTex ("MainTex", 2D) = "white" {}
  }
  SubShader
  {
    Cull Off ZWrite Off ZTest Always

    Pass
    {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag

      #include "UnityCG.cginc"

      struct v2f
      {
        float4 vertex : SV_POSITION;
        half2 uv : TEXCOORD0;
        half4 screenPos : TEXCOORD1;
      };

      v2f vert (appdata_img v)
      {
        v2f o;

        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = v.texcoord;

        // 通过裁切空间坐标得到屏幕空间坐标
        o.screenPos = ComputeScreenPos(o.vertex);

        return o;
      }

      sampler2D _MainTex;
      half _Distortion;
      half _Scale;

      fixed _Brightness;
      fixed _Saturation;
      fixed _Contrast;

      fixed4 _Tint;

      // 暗角属性
      half _VignetteFalloff;
      half _VignetteIntensity;

      // 噪点属性
      sampler2D _Noise;
      half _NoiseAmount;
      half _RandomValue;

      fixed4 frag (v2f i) : SV_Target
      {
        // 镜头扭曲
        fixed2 center = i.uv - 0.5;
        half radius2 = pow(center.x, 2) + pow(center.y, 2);
        half distortion = 1 + sqrt(radius2) * radius2 * _Distortion;

        half2 uvScreen = center * distortion * _Scale  + 0.5;
        fixed4 screen = tex2D(_MainTex, uvScreen);

        // 亮度、饱和度、对比度
        screen += _Brightness;

        fixed4 luminance = Luminance(screen.rgb).xxxx;
        screen = lerp(luminance, screen, _Saturation);

        fixed4 gray = fixed4(0.5,0.5,0.5,1);
        screen = lerp(gray, screen, _Contrast);

        // 着色
        screen *= _Tint;

        // 暗角
        // 得到屏幕中心到该点的距离,最终效果应该是中间黑,呈四周渐变逐渐到灰白(最大约为0.707)
        half circle = distance(i.screenPos.xy, fixed2(0.5,0.5));
        //利用pow扩大黑暗区域,利用1-结果,得到中间大面积白,四周灰
        fixed vignette = 1 - saturate(pow(circle, _VignetteFalloff));
         //整体压暗,四周值小,暗得更快。最终效果就是中间白,四周黑
        screen *= pow(vignette, _VignetteIntensity);

        // 噪点颗粒
        float2 uvNoise = i.uv * _NoiseAmount;
        //利用脚本传来的随机_RandomValue进行uv偏移
        uvNoise.x += sin(_RandomValue);
        uvNoise.y -= sin(_RandomValue + 1);

        fixed noise = tex2D(_Noise, uvNoise).r;
        screen *= noise;

        return screen;
      }
      ENDCG
    }
  }
}

C#脚本

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

[RequireComponent(typeof(Camera))]
[ExecuteInEditMode]
public class NightVision : MonoBehaviour
{
  public Shader EffectShader;

  [Header("Basic Properties")]
  [Range(-2, 2)] public float Distortion = 0.5f;
  [Range(0.01f, 1)] public float Scale = 0.5f;
  [Range(-1, 1)] public float Brightness = 0;
  [Range(0, 2)] public float Saturation = 1;
  [Range(0, 2)] public float Contrasrt = 1;

  public Color Tint = Color.black;

  [Header("Advanced Properties")]
  [Range(0, 10)] public float VignetteFalloff = 1;
  [Range(0, 100)] public float VignetteIntensity = 1;

  public Texture2D Noise;
  [Range(0, 10)] public float NoiseAmount = 1;
  private float RandomValue;

  private Material currentMaterial;

  Material EffectMaterial
  {
    get
    {
      if (currentMaterial == null)
      {
        currentMaterial = new Material(EffectShader)
        {
          hideFlags = HideFlags.HideAndDontSave
        };
      }
      return currentMaterial;
    }
  }

  private void OnRenderImage(RenderTexture source, RenderTexture destination)
  {
    if (EffectMaterial != null)
    {
      EffectMaterial.SetFloat("_Distortion", Distortion);
      EffectMaterial.SetFloat("_Scale", Scale);

      EffectMaterial.SetFloat("_Brightness", Brightness);
      EffectMaterial.SetFloat("_Saturation", Saturation);
      EffectMaterial.SetFloat("_Contrast", Contrasrt);

      EffectMaterial.SetColor("_Tint", Tint);

      EffectMaterial.SetFloat("_VignetteFalloff", VignetteFalloff);
      EffectMaterial.SetFloat("_VignetteIntensity", VignetteIntensity);

      if (Noise != null)
      {
        EffectMaterial.SetTexture("_Noise", Noise);
        EffectMaterial.SetFloat("_NoiseAmount", NoiseAmount);
        EffectMaterial.SetFloat("_RandomValue", RandomValue);
      }

      Graphics.Blit(source, destination, EffectMaterial);
    }
    else Graphics.Blit(source, destination);
  }

  private void Update()
  {
    //随机生成范围中的数值
    RandomValue = Random.Range(-3.14f, 3.14f);
  }
}