February 19, 2023

Unity Shader入门精要 读书笔记

Unity Shader入门精要 读书笔记

注意事项

这篇笔记是从�?2章开始的,笔记中的代码都是URP管线下的HLSL

书目

1
MonoBehaviour.OnRenderImage(RenderTexture src, RednerTexture dest)

Unity会将当前渲染的图像存储在RenderTexture src,然后函数处理后在输出到RednerTexture dest

通才利用Graphics.Blit函数来处理纹理,函数如下�?/p>

1
2
3
4
5
public static void Blit(Texture src,RenderTexture dest)

public static void Blit(Texture src,RenderTexture dest,Material mat,int pass = -1)

public static void Blit(Texture src,Material mat,int pass = -1))

Blit函数调用逻辑�?/p>

  • src 是源纹理
  • dest 是目标渲染纹�?如果为null则直接显示结�?/li>
  • mat 是使用的材质,src会被传递给_MainTex
  • pass 默认�?1,依次调用pass
    OnRenderImage函数调用
  • OnRenderImage函数会在所有不透明和透明的Pass执行完毕后被调用
  • 如果不想对透明产生应影�?可以�?strong>OnRenderImage前添�?strong>ImageEffectOpaque
    Unity实现屏幕后处理的流程
  • 相机添加后处理脚�?ul>
  • OnRenderImage
  • Graphics.Blit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 检查条件脚�?/span>
using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
public class PostEffectsBase : MonoBehaviour {

// Called when start
protected void CheckResources() {
bool isSupported = CheckSupport();

if (isSupported == false) {
NotSupported();
}
}

// Called in CheckResources to check support on this platform
protected bool CheckSupport() {
if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false) {
Debug.LogWarning("This platform does not support image effects or render textures.");
return false;
}

return true;
}

// Called when the platform doesn't support this effect
protected void NotSupported() {
enabled = false;
}

protected void Start() {
CheckResources();
}

// Called when need to create the material used by this effect
protected Material CheckShaderAndCreateMaterial(Shader shader, Material material) {
if (shader == null) {
return null;
}

if (shader.isSupported && material && material.shader == shader)
return material;

if (!shader.isSupported) {
return null;
}
else {
material = new Material(shader);
material.hideFlags = HideFlags.DontSave;
if (material)
return material;
else
return null;
}
}
}

  • 12.2 调整屏幕亮度,饱和度和对比度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class BrightnessSaturationAndContrast : PostEffectsBase
    {
    public Shader briSatConShader;
    private Material briSatConMaterial;
    public Material material
    {
    get
    {
    briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
    return briSatConMaterial;
    }
    }
    [Range(0.0f, 3.0f)]
    public float brightness = 1.0f;
    [Range(0.0f, 3.0f)]
    public float saturation = 1.0f;
    [Range(0.0f, 3.0f)]
    public float contrast = 1.0f;

    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
    if (material != null)
    {
    material.SetFloat("_Brightness", brightness);
    material.SetFloat("_Saturation", saturation);
    material.SetFloat("_Contrast", contrast);
    Graphics.Blit(src, dest, material);
    }
    else
    {
    Graphics.Blit(src, dest);
    }
    }
    }

    上边的脚本用于控制和修改后处理的参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    Shader "Unlit/BrightnessSaturationAndContrast"
    {
    Properties
    {
    _MainTex ("Base(RGB)", 2D) = "white" {}
    _Brightness("Brightness",Float) = 1
    _Saturation("Saturation",Float) = 1
    _Contrast("Contrast",Float) = 1

    }
    SubShader
    {
    Pass
    {
    ZTest Always Cull Off ZWrite Off
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    // make fog work
    #pragma multi_compile_fog

    #include "UnityCG.cginc"


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

    sampler2D _MainTex;
    half _Brightness;
    half _Saturation;
    half _Contrast;

    v2f vert (appdata_img v)
    {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    return o;
    }

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

    fixed3 finalColor = col.rgb*_Brightness;
    // 饱和度,灰度像素的值是RGB的加权总和
    // Y = 0.2125 R + 0.7154 G + 0.0721 B
    // https://en.wikipedia.org/wiki/Grayscale
    fixed luminance = 0.2125*col.r+0.7154*col.g+0.0721*col.b;
    fixed3 luminanceColor = fixed3(luminance,luminance,luminance);
    finalColor = lerp(luminanceColor,finalColor,_Saturation);

    fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
    finalColor = lerp(avgColor,finalColor,_Contrast);

    return fixed4(finalColor,col.a);
    }
    ENDCG
    }
    }
    Fallback Off
    }

    该sahder用于修改输入图像的亮度,饱和度和对比�?/p>

    • 其中关于饱和度就是一个灰度值加权公式具体请�?a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Grayscale">Grayscale-Wikipedia
  • 12.3 边缘检�?/p>

    • 简�?/p>

      • 边缘检测是描边效果的一种实现方�?/li>
      • 边缘检测的原理�?ul>
      • 利用边缘检测算子对图像进行卷积(Convolution)操�?/li>
  • 12.3.1 什么是卷积

    • 卷积操作指使用一个卷积核(kernel)对一张图像的每个像素进进行一系列操作

    • 卷积核通常是一个四方形网格结构,该区域每个方格都有一个权重�?/p>

      卷积.png

  • 12.3.2 常见的边缘检测算�?/p>

    • 相邻像素之间存在差别明显的颜色,亮度,纹理等属性,这个状态下他们之间就应该有一条边界�?/p>

    • 相邻像素之间的差值可以用梯度(gradient)来表示�?/p>

      边缘检测算�?png

  • 12.3.3 实现
    脚本的实�?/p>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    using UnityEngine;

    public class EdgeDetection : PostEffectsBase
    {
    public Shader edgeDetectShader;
    private Material edgeDetectMaterial = null;
    public Material material
    {
    get
    {
    edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
    return edgeDetectMaterial;
    }
    }
    [Range(0.0f, 1.0f)]
    public float edgesOnly = 0.0f;

    public Color edgeColor = Color.black;

    public Color backgroundColor = Color.white;

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
    if (material != null)
    {
    material.SetFloat("_EdgeOnly", edgesOnly);
    material.SetColor("_EdgeColor", edgeColor);
    material.SetColor("_BackgroundColor", backgroundColor);
    Graphics.Blit(source, destination, material);
    }
    else
    {
    Graphics.Blit(source, destination);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    Shader "Unlit/BrightnessSaturationAndContrast"
    {
    Properties
    {
    _MainTex ("Base(RGB)", 2D) = "white" {}
    _EdgesOnly("EdgesOnly",Float) = 1
    _EdgeColor("EdgeColor",Color) = (0,0,0,1)
    _BackgroundColor("BackgroundColor",Color) = (1,1,1,1)

    }
    SubShader
    {
    Pass
    {
    ZTest Always Cull Off ZWrite Off
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment fragSobel
    // make fog work
    #pragma multi_compile_fog

    #include "UnityCG.cginc"


    struct v2f
    {
    // UV 定义
    float2 uv[9] : TEXCOORD0;
    float4 pos : SV_POSITION;
    };

    sampler2D _MainTex;
    half4 _MainTex_TexelSize;
    // 用于访问纹理的每个纹素大�?/span>
    fixed _EdgeOnly;
    fixed4 _EdgeColor;
    fixed4 _BackgroundColor;

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

    half2 uv = v.texcoord;

    o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
    o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
    o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
    o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
    o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
    o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
    o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
    o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
    o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);

    return o;
    }
    fixed luminance(fixed4 color) {
    return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
    }

    // 使用了Sobel算子
    half Sobel(v2f i){
    const half Gx[9] = {-1, 0, 1,
    -2, 0, 2,
    -1, 0, 1};
    const half Gy[9] = {-1, -2, -1,
    0, 0, 0,
    1, 2, 1};
    half texColor;
    half edgeX = 0;
    half edgeY = 0;
    for(int it=0;it<9;it++){
    texColor = luminance(tex2D(_MainTex, i.uv[it]));
    edgeX+=texColor*Gx[it];
    edgeY+=texColor*Gy[it];
    }
    half edge = 1-abs(edgeX)-abs(edgeY);
    return edge;
    }
    fixed4 fragSobel(v2f i) : SV_Target {
    half edge = Sobel(i);

    fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);
    fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
    return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
    }


    ENDCG
    }
    }
    Fallback Off
    }

  • 12.4 高斯模糊

    • 简�?br>高斯模糊 通过高斯函数模糊图像的方法叫高斯模糊[1]

    • 12.4.1
      高斯函数�?$G(x,y) = \frac{1}{2\pi\sigma^2}e^-{\frac{x^2+y^2}{2\sigma^2}}$

      • $\sigma$ 标准方差
      • x和y 当前位置到卷积核中心的整数距�?/li>
      • 为了图像不变暗,需要高斯核的权重归一化,即每个权重除于所有权重的和。故$e$之前的系数不会对结果造成影响

      高斯采样如果使用一个NxN的高斯核,需要N * N * W * H次采�?/p>

      如果使用2个一维函数惊醒采样,只需�?2 * N * W * H 次采�?/p>

    • 12.4.2 实现
      脚本的实�?/p>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;

      public class GaussianBlur : PostEffectsBase
      {
      public Shader gaussianBlurShader;
      private Material gaussianBlurMaterial = null;
      public Material material
      {
      get
      {
      gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
      return gaussianBlurMaterial;
      }
      }
      [Range(0, 4)]
      public int iterations = 3;

      [Range(0.2f, 3.0f)]
      public float blurSpread = 0.6f;
      //
      [Range(1, 8)]
      public int downSample = 2;
      // downSample越大需要处理的像素数量越少
      private void OnRenderImage(RenderTexture source, RenderTexture destination)
      {
      if (material != null)
      {
      //降采样优�?过大会像素化
      int rtW = source.width/downSample;
      int rtH = source.height/ downSample;
      RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
      buffer0.filterMode = FilterMode.Bilinear;
      // 创建一个buffer
      Graphics.Blit(source, buffer0);
      for (int i = 0; i < iterations; i++)
      {
      material.SetFloat("_BlurSize", 1.0f+i*blurSpread);
      RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
      Graphics.Blit(buffer0, buffer1, material, 0);

      RenderTexture.ReleaseTemporary(buffer0);
      buffer0 = buffer1;
      buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
      Graphics.Blit(buffer0, buffer1, material, 1);
      RenderTexture.ReleaseTemporary(buffer0);
      buffer0 = buffer1;
      }
      Graphics.Blit(buffer0, destination);
      RenderTexture.ReleaseTemporary(buffer0);
      // 释放和缓�?/span>

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

      }

      Shader的实�?/p>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      Shader "Unlit/GaussianBlur"
      {
      Properties
      {
      _MainTex ("Texture", 2D) = "white" {}
      _BlurSize("Blur Size",Float) = 1.0
      }
      SubShader
      {
      //为了避免重复定义
      CGINCLUDE
      #include "UnityCG.cginc"
      sampler2D _MainTex;
      half4 _MainTex_TexelSize;
      float _BlurSize;

      struct v2f {
      float4 pos : SV_POSITION;
      half2 uv[5]: TEXCOORD0;
      };

      v2f vertBlurVertical(appdata_img v) {
      v2f o;
      o.pos = UnityObjectToClipPos(v.vertex);

      half2 uv = v.texcoord;

      o.uv[0] = uv;
      o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
      o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
      o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
      o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;

      return o;
      }
      v2f vertBlurHorizontal(appdata_img v) {
      v2f o;
      o.pos = UnityObjectToClipPos(v.vertex);

      half2 uv = v.texcoord;

      o.uv[0] = uv;
      o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
      o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
      o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
      o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;

      return o;
      }
      fixed4 fragBlur(v2f i) : SV_Target {
      float weight[3] = {0.4026, 0.2442, 0.0545};

      fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];

      for (int it = 1; it < 3; it++) {
      sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
      sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
      }

      return fixed4(sum, 1.0);
      }


      ENDCG

      ZTest Always Cull Off ZWrite Off
      Pass {
      NAME "GAUSSIAN_BLUR_VERTICAL"

      CGPROGRAM

      #pragma vertex vertBlurVertical
      #pragma fragment fragBlur

      ENDCG
      }


      Pass {
      NAME "GAUSSIAN_BLUR_HORIZONTAL"
      CGPROGRAM
      #pragma vertex vertBlurHorizontal
      #pragma fragment fragBlur

      ENDCG
      }
      }
      FallBack "Diffuse"
      }

  • 12.5 Bloom

    • 简�?br>Bloom 辉光,模拟真实相机的图像效果,让画面中比较亮的区域“扩散”到周围区域,造成一种朦胧感

      • Bloom实现原理
        • 根据阈值提取图像中比较亮的区域,存在一个RT�?/li>
        • 再使用高斯模糊处理这张RT
        • 与原图像混合
          后处理脚本的实现
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          using UnityEngine;

          public class Bloom : PostEffectsBase
          {
          public Shader bloomShader;
          private Material bloomMaterial = null;
          public Material material
          {
          get
          {
          bloomMaterial = CheckShaderAndCreateMaterial(bloomShader, bloomMaterial);
          return bloomMaterial;
          }
          }
          [Range(0, 4)]
          public int iterations = 3;

          [Range(0.2f, 3.0f)]
          public float blurSpread = 0.6f;

          [Range(1, 8)]
          public int downSample = 2;

          [Range(0.0f, 4.0f)]
          public float luminanceThreshold = 0.6f;

          private void OnRenderImage(RenderTexture source, RenderTexture destination)
          {
          if (material != null)
          {
          material.SetFloat("_LuminanceThreshold", luminanceThreshold);
          //降采样优�?过大会像素化
          int rtW = source.width / downSample;
          int rtH = source.height / downSample;
          RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
          buffer0.filterMode = FilterMode.Bilinear;
          // 创建一个buffer
          Graphics.Blit(source, buffer0, material,0);
          for (int i = 0; i < iterations; i++)
          {
          material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
          RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
          Graphics.Blit(buffer0, buffer1, material, 1);

          RenderTexture.ReleaseTemporary(buffer0);
          buffer0 = buffer1;
          buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
          Graphics.Blit(buffer0, buffer1, material, 2);
          RenderTexture.ReleaseTemporary(buffer0);
          buffer0 = buffer1;
          }
          material.SetTexture("_Bloom", buffer0);
          Graphics.Blit(source, destination, material,3);
          RenderTexture.ReleaseTemporary(buffer0);
          // 释放和缓�?/span>

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

          }

          shader的实�?/li>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      Shader "Unlit/Bloom"
      {
      Properties
      {
      _MainTex ("Texture", 2D) = "white" {}
      _Bloom ("Bloom",2D) = "black" {}
      _LuminanceThreshold("LuminanceThreshold",Float) = 0.5
      _BlurSize("Blur Size",Float) = 1.0
      }
      SubShader
      {
      CGINCLUDE
      #include "UnityCG.cginc"
      sampler2D _MainTex;
      half4 _MainTex_TexelSize;
      sampler2D _Bloom;
      float _LuminanceThreshold;
      float _BlurSize;

      struct v2f {
      float4 pos : SV_POSITION;
      half2 uv: TEXCOORD0;
      };
      v2f vertExtractBrught(appdata_img v){
      v2f o;
      o.pos = UnityObjectToClipPos(v.vertex);
      o.uv = v.texcoord;
      return o;
      }
      fixed luminance(fixed4 color) {
      return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
      }
      fixed4 fragExtractBrught(v2f i) : SV_Target{
      fixed4 c = tex2D(_MainTex,i.uv);
      fixed val = clamp(luminance(c)-_LuminanceThreshold,0.0,1.0);
      return c*val;
      }

      struct v2fBloom{
      float4 pos :SV_POSITION;
      half4 uv: TEXCOORD0;
      };
      v2fBloom vertBloom(appdata_img v){
      v2fBloom o;
      o.pos = UnityObjectToClipPos(v.vertex);
      o.uv.xy = v.texcoord;
      o.uv.zw = v.texcoord;

      #if UNITY_UV_STARTS_AT_TOP
      if(_MainTex_TexelSize.y<0.0)
      o.uv.w = 1.0-o.uv.w;
      #endif

      return o;
      }
      fixed4 fragBloom(v2fBloom i) :SV_Target{
      return tex2D(_MainTex,i.uv.xy)+tex2D(_Bloom,i.uv.zw);
      }


      ENDCG

      ZTest Always Cull Off ZWrite Off
      Pass {

      CGPROGRAM

      #pragma vertex vertExtractBrught
      #pragma fragment fragExtractBrught

      ENDCG
      }
      UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"
      UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"
      //调用指定Shader里的指定pass
      //这个路径是sahder的路径,不是文件夹夹结构

      Pass {
      CGPROGRAM
      #pragma vertex vertBloom
      #pragma fragment fragBloom

      ENDCG
      }
      }
      FallBack off
      }

  • 12.6 运动模糊

    • 简�?ul>
    • 运动模糊是真实世界的相机的一个效果,当相机曝光时,拍摄场景发生变化,就会发生模糊的画�?/li>
    • 运动模糊实现的方�?ul>
      1. 利用累加缓存(accumulation buffer)来混合多张连续的图像,最后取平均�?/li>
      1. 创建和使用速度缓存(velocity buffer),存储像素的当前运动速度,利用该值来计算模糊的方向和大小�?/li>
  • 实现,以下时对于第一种方案的实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class MotionBlur : PostEffectsBase
    {
    public Shader motionBlurShader;
    private Material motionBlurMaterial = null;
    public Material material
    {
    get
    {
    motionBlurMaterial = CheckShaderAndCreateMaterial(motionBlurShader, motionBlurMaterial);
    return motionBlurMaterial;
    }
    }
    [Range(0.0f, 0.9f)]
    public float blurAmount = 0.5f;
    private RenderTexture accumulationTexture;

    private void OnDisable()
    {
    DestroyImmediate(accumulationTexture);
    }
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
    if (material != null)
    {
    if(accumulationTexture==null|| accumulationTexture.width!= source.width|| accumulationTexture.height != source.height)
    {
    DestroyImmediate(accumulationTexture);
    accumulationTexture = new RenderTexture(source.width, source.height, 0);
    //隐藏并且不保�?/span>
    accumulationTexture.hideFlags = HideFlags.HideAndDontSave;
    Graphics.Blit(source, accumulationTexture);
    }
    //恢复操作
    accumulationTexture.MarkRestoreExpected();
    material.SetFloat("_BlurAmount", 1.0f-blurAmount);
    Graphics.Blit(source, accumulationTexture,material);
    Graphics.Blit(accumulationTexture, destination);
    }
    else
    {
    Graphics.Blit(source, destination);
    }
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    Shader "Unlit/MotionBlur"
    {
    Properties
    {
    _MainTex ("Texture", 2D) = "white" {}
    _BlurAmount("Blur Amount",Float) = 1.0
    }
    SubShader
    {

    CGINCLUDE
    sampler2D _MainTex;
    fixed _BlurAmount;
    #include "UnityCG.cginc"
    struct v2f
    {
    float2 uv : TEXCOORD0;
    float4 pos : SV_POSITION;
    };
    v2f vert(appdata_img v)
    {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    return o;
    }
    fixed4 fragRGB(v2f i) :SV_Target{
    return fixed4(tex2D(_MainTex,i.uv).rgb,_BlurAmount);
    }

    half4 fragA(v2f i):SV_Target{
    return tex2D(_MainTex,i.uv);
    }
    ENDCG
    ZTest Always Cull Off ZWrite Off
    Pass
    {
    Blend SrcAlpha OneMinusSrcAlpha
    ColorMask RGB
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment fragRGB

    ENDCG
    }
    Pass
    {
    Blend One Zero
    ColorMask A
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment fragA

    ENDCG
    }
    }
    FallBack off
    }

  • 12.7 拓展阅读

    • Unity Image Effect
    • GPU Games - GPU Games 3 - 27�?/li>
  • 总结
    本章共学习了

    • 亮度,饱和度,对比度
    • 边缘检测,后处理描�?/li>
    • 高斯模糊
    • 辉光
    • 运动模糊

    • 使用着色器替换技术选择渲染类型为Qpaque的物体,若渲染队列小于等�?500,就渲染到深度和法线�?/li>
  • 存储位置
    • 观察空间下,法线存储在R,G
    • 观察空间下,深度存储在B,A
  • 13.1.2 如何获取

    • 获取深度纹理的方�?/p>

        1. 在脚本中设置摄像机的depthTextureMode
          1
          camera.depthTextureMode = DepthTextureMode.Depth;
        1. Shader中使用“_CameraDepthTexture”来访问�?/li>
    • 获取深度和法线纹�?

        1. 在脚本中设置摄像机的depthTextureMode
          1
          camera.depthTextureMode = DepthTextureMode.DepthNormals;
        1. Shader中使用“_CameraDepthNoramlsTexture”来访问�?/li>
    • 如何采样该纹�?/p>

      • 可以使用 tex2D
      • 也可以用�?SAMPLE_DEPTH_TEXTURE
    • �?SAMPLE_DEPTH_TEXTURE_PROJ

      1
      2
      // i.srcPos 屏幕坐标 ComputeScreenPos(o.pos)
      float result = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture,UNITY_PROJ_COORD(i.srcPos));
      • 更多的信息可以在HLSLSupport.cginc 看到
    • 如何将深度纹理中的深度信息计算得到视角空间下的深度�?/p>

      • 推导过程
        • 裁剪矩阵$P_{clip}$对视角空间的顶点变换,裁剪空间下顶点�?z$�?w$如下�?ul>
        • $Z_{clip}=-Z_{view}\frac{Far+Near}{Far-Near}-\frac{2 \cdot Far \cdot Near}{Far-Near}$
        • $w_{clip} = -Z_{view}$
          • $Far$远裁剪平面距�?/li>
          • $Near$近裁剪平面距�?/li>
      • 通过齐次除法获得NDC�?z$分量
        • $z_{ndc} = \frac {z_{clip}}{w_{clip}} = \frac{Far+Near}{Far-Near}+\frac{2Near \cdot Far}{(Far-Near) \cdot z_{view}}$
      • 深度值计�?ul>
      • $d = 0.5z_{ndc}+0.5$
    • 通过如上公式获得
      • $z_{view} = \frac{1}{\frac{Far-Near}{Near \cdot Far}d+\frac{1}{Near}}$
    • Unity时间空间中摄像机正向�?z,因此结果取�?ul>
    • $z_{view} = \frac{1}{\frac{Near-Far}{Near \cdot Far}d+\frac{1}{Near}}$
  • �?表示该点与摄像机同一位置�?表示该点位于视锥体的远裁剪平�?ul>
  • $z_{01} = \frac{1}{\frac{Near-Far}{Near}d+\frac{Far}{Near}}$
  • Unity 提供的转换函�?ul>
  • LinearEyeDepth:将深度纹理的采样结果转换到视角空间下的深度�?/li>
  • Linear01EyeDepth:返回一�?�?的线性深度�?ul>
  • 内置变量 _ZBufferParams:获得远近裁剪平面的距离
  • 如何获得深度+法线纹理

    • tex2D 采样_CameraDepthNoramlsTexture
    • 信息解码函数如下
      1
      2
      3
      4
      inline void DecodeDepthNoraml(float4 enc,out float depth , ouyt float3 normal){
      depth = DecodeFloatRG(enc.zw);
      noraml = DecodeViewNoramlStereo(enc);
      }
      • 获得深度值是0�?的线性深度�?/li>
      • 法线是视角空间下的法�?/li>
  • 13.1.3 查看深度和法线纹�?/p>

    • 如何查看生成的深度和法线纹理
      • Frame Debugger
      • RenderDoc
    • 输出线性空间或者转码后的法线和深度信息
      1
      2
      3
      float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
      floar linearDepth = Linear01Depth(depth);
      return fixed4(linearDepth,linearDepth,linearDepth,1.0);
      1
      2
      float3 noraml = DecodeViewNoramlStereo(tex2D(_CameraNoramlsTexture,i.uv).xy);
      return fixed4(noraml*0.5+0.5,1.0);
  • 13.2 再谈运动模糊

    • Gpu Gems3中提到的方案

      • 实现逻辑

        • 利用深度纹理再片元着色器中计算像素的世界空间位置
        • 使用前一帧的视角*投影矩阵把变换获得的前一帧的NDC左边
        • 计算前一帧和当前帧的位置差,生成速度
      • 优点

        可以再一个后处理中完成整个模�?/p>

      • 缺点

        片元进行�?次矩阵,性能有所影响

    • 实现
      后处理脚本实�?/p>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;

      public class MotionBlurWithDepthTexture : PostEffectsBase
      {
      public Shader motionBlurShader;
      private Material motionBlurMaterial = null;
      public Material material
      {
      get
      {
      motionBlurMaterial = CheckShaderAndCreateMaterial(motionBlurShader, motionBlurMaterial);
      return motionBlurMaterial;
      }
      }
      [Range(0.0f, 1.0f)]
      public float blurSize = 0.5f;
      private Camera myCamera;
      public Camera camera
      {
      get
      {
      if (myCamera == null)
      {
      myCamera = GetComponent<Camera>();
      }
      return myCamera;
      }
      }
      private Matrix4x4 previousViewProjectionMatrix;

      void OnEnable()
      {
      camera.depthTextureMode |= DepthTextureMode.Depth;
      }

      void OnRenderImage(RenderTexture source, RenderTexture destination)
      {
      if (material != null)
      {
      material.SetFloat("_BlurSize", blurSize);
      material.SetMatrix("_previousViewProjectionMatrix", previousViewProjectionMatrix);
      Matrix4x4 currentViewProjectionMatrix = camera.projectionMatrix * camera.worldToCameraMatrix;
      // worldToCameraMatrix 摄像机的视角矩阵
      // projectionMatrix 摄像机的投影矩阵
      Matrix4x4 currentViewProjectionInverseMatrix = currentViewProjectionMatrix.inverse;
      material.SetMatrix("_CurrentViewProjectionInverseMatrix", currentViewProjectionInverseMatrix);
      previousViewProjectionMatrix = currentViewProjectionMatrix;
      Graphics.Blit(source, destination, material);
      }
      else
      {
      Graphics.Blit(source, destination);
      }
      }
      }

      Shader实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      Shader "Unlit/MotionBlurWithDepthTexture"
      {
      Properties
      {
      _MainTex ("Texture", 2D) = "white" {}
      _BlurSize ("Blur Size",Float) = 1.0
      }
      SubShader
      {
      CGINCLUDE
      #include "UnityCG.cginc"
      sampler2D _MainTex;
      half4 _MainTex_TexelSize;
      half _BlurSize;
      sampler2D _CameraDepthTexture;
      float4x4 _PreviousViewProjectionMatrix;
      float4x4 _CurrentViewProjectionInverseMatrix;

      struct v2f{
      float4 pos :SV_POSITION;
      half2 uv:TEXCOORD0;
      half2 uv_depth:TEXCOORD1;
      };
      v2f vert(appdata_img v){
      v2f o;
      o.pos = UnityObjectToClipPos(v.vertex);
      o.uv = v.texcoord;
      o.uv_depth = v.texcoord;
      #if UNITY_UV_STARTS_AT_TOP
      if(_MainTex_TexelSize.y<0)
      o.uv_depth.y = 1-o.uv_depth.y;
      #endif
      //处理兼容问题
      return o;
      }
      fixed4 frag(v2f i):SV_Target{
      float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth);
      float4 H = float4(i.uv.x*2-1,i.uv.y*2-1,d*2-1,1);
      float4 D = mul(_CurrentViewProjectionInverseMatrix,H);
      float4 worldPos = D/D.w;
      float4 currentPos = H;
      float4 previousPos = mul(_PreviousViewProjectionMatrix,worldPos);
      previousPos/=previousPos.w;
      float2 veloctiy = (currentPos.xy-previousPos.xy)/2.0f;

      float2 uv = i.uv;
      float4 c = tex2D(_MainTex,uv);
      uv+=veloctiy*_BlurSize;
      for(int it=1;it<3;it++,uv+=veloctiy*_BlurSize){

      float4 currentColor = tex2D(_MainTex,uv);
      c+=currentColor;
      }
      c/=3;
      return fixed4(c.rgb,1.0);
      }
      ENDCG
      Pass
      {
      ZTest Always Cull Off ZWrite Off
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag


      ENDCG
      }
      }
      Fallback Off
      }

  • 13.3 全局雾效

    • 简�?/p>

      • 雾效(Fog)是游戏中常见的一种效�?/li>
      • 基于屏幕后处理的全局雾效的核心,根据深度纹理重建每个像素在世界空间下的位�?ul>
      • 首先对图像空间下的视锥体射线进行插值,射线存储的是该像素在世界空间下到摄像机的方向信息
      • 把该射线和线性化后的视角空间下的深度值相乘,加上摄像机的世界位置,获得该像素的在世界空间的位�?/li>
  • 13.3.1 重建世界坐标

    • 如何从深度纹理中重建世界坐标

      • 思想:坐标系中的顶点位置可以通过它相对于另一个顶点坐标的偏移量获得�?/p>

        1
        2
        3
        float4 worldPos = _WorldSpaceCameraPos+linearDepth*interpolatedRay;
        // _WorldSpaceCameraPos 摄像机世界空间位�?/span>
        // linearDepth*interpolatedRay 像素对于相机的偏移量
        • interpolatedRay 的获得方�?/p>

          • interpolatedRay是近裁剪平面4个角的某个特定向量的插值,�?个向量包含了它们到摄像机的距离和方向信息

          • 可以利用摄像机近裁剪平面距离,Fov,纵横比计算获�?/p>

          • toTop �?toRight 计算公式如下,他们是起点位于近裁剪平面中心,分别指向摄像机正上方和正右方的向�?/p>

            • $halfHeight = Near \times \tan(\frac{FOV}{2})$
            • $toTop = camera.up \times halfHeight$
            • $toRight = camera.right \times halfHeight \cdot aspect$
              • Near 是近裁剪平面距离
              • FOV 是竖直方向视角范�?/li>
              • camera.up 摄像机正上方
              • camera.right 摄像机的正右�?/li>
          • 四个角相对于摄像机的位置的计算公�?/p>

            • $TL=camera.forward \cdot Near +toTop-toRight$
            • $TR=camera.forward \cdot Near +toTop+toRight$
            • $BL=camera.forward \cdot Near -toTop-toRight$
            • $BR=camera.forward \cdot Near -toTop+toRight$
          • 以上的数据为Z方向上的距离,将深度转为摄像机的欧式距离如下

            • 根据相似三角�?TL所在的射线上,像素的深度值和它到摄像机的实际距离比等于近裁剪平面的距离和TL向量的摸的比
              • $\frac{depth}{dist} = \frac{Near}{|TL|}$
            • 所以距离如�?ul>
            • $dist = \frac{|TL|}{Near} \times depth$
        • 由于4个点对称,因此模长相同,所以我们用单一的因子进行计�?/p>

          • $scale = \frac{|TL|}{|Near|}$
          • $Ray_{TL}= \frac{TL}{|TL|} \times scale$
          • $Ray_{TR}= \frac{TR}{|TR|} \times scale$
          • $Ray_{BL}= \frac{BL}{|BL|} \times scale$
          • $Ray_{BR}= \frac{BR}{|BR|} \times scale$
        • 相关的示意图如下

          interpolatedRay.png

  • 13.3.2 雾的计算

    • 首先需要一个雾效系数,作为混合系数
      1
      float3 afterFog = f*fogColor +(1-f)*origColor;
      • 内置的雾效计算方�?ul>
      • 线性(Linear�?ul>
      • $f = \frac{d_{max}-|z|}{d_{max}-d_{min}}$
        • $d_{max}$雾的最大影响范�?/li>
        • $d_{min}$雾的最小影响范�?/li>
    • 指数(Exponential�?ul>
    • $f=e^{-d\cdot|z|}$
      • d 是是控制雾浓度的�?/li>
  • 指数的平方(Exponential Squared�?ul>
  • $f = e^{-(d-|z|)^2}$
    • d 是是控制雾浓度的�?/li>
  • 类似线性雾的计算方式,基于高度的雾�?ul>
  • 给定一个点的世界空间下的高度y
  • 公式
    • $f = \frac{H_{end}-y}{H_{end}-H_{start}}$
      • $H_{end}$ 雾影响的终止高度
      • $H_{start}$ 雾影响的起始高度
  • 13.3.3 实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    using UnityEngine;

    public class FogWithDepthTexture : PostEffectsBase
    {
    // Start is called before the first frame update
    public Shader fogShader;
    private Material fogMaterial = null;
    public Material material
    {
    get
    {
    fogMaterial = CheckShaderAndCreateMaterial(fogShader, fogMaterial);
    return fogMaterial;
    }
    }
    private Camera myCamera;
    public Camera camera
    {
    get
    {
    if (myCamera == null)
    {
    myCamera = GetComponent<Camera>();
    }
    return myCamera;
    }
    }
    private Transform myCameraTransform;
    public Transform cameraTransform
    {
    get
    {
    if (myCameraTransform == null)
    {
    myCameraTransform = camera.transform;
    }
    return myCameraTransform;
    }
    }

    [Range(0.0f, 3.0f)]
    public float fogDensity = 1.0f;

    public Color fogColor = Color.white;

    public float fogStart = 0.0f;
    public float fogEnd = 2.0f;

    void OnEnable()
    {
    camera.depthTextureMode |= DepthTextureMode.Depth;
    }
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
    if (material != null)
    {
    Matrix4x4 frustumCormers = Matrix4x4.identity;

    float fov = camera.fieldOfView;
    float near = camera.nearClipPlane;
    float far = camera.farClipPlane;
    float aspect = camera.aspect;

    float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
    Vector3 toRight = cameraTransform.right * halfHeight * aspect;
    Vector3 toTop = cameraTransform.up * halfHeight;
    Vector3 topLeft = cameraTransform.forward * near + toTop - toRight;
    float sacle = topLeft.magnitude / near;

    topLeft.Normalize();
    topLeft *= sacle;

    Vector3 topRight = cameraTransform.forward * near + toTop + toRight;
    topRight.Normalize();
    topRight *= sacle;
    Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;
    bottomLeft.Normalize();
    bottomLeft *= sacle;
    Vector3 bottomRight = cameraTransform.forward * near - toTop + toRight;
    bottomRight.Normalize();
    bottomRight *= sacle;

    frustumCormers.SetRow(0, bottomLeft);
    frustumCormers.SetRow(1, bottomRight);
    frustumCormers.SetRow(2, topRight);
    frustumCormers.SetRow(3, topLeft);
    //将向量存储到矩阵�?/span>
    material.SetMatrix("_FrustumCormers", frustumCormers);
    material.SetMatrix("_ViewProjectionInverseMatrix", (camera.projectionMatrix * camera.worldToCameraMatrix).inverse);
    material.SetFloat("_FogDensity", fogDensity);
    material.SetColor("_FogColor", fogColor);
    material.SetFloat("_FogStart", fogStart);
    material.SetFloat("_FogEnd", fogEnd);
    Graphics.Blit(source, destination,material);

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    Shader "Unlit/FogWithDepthTexture"
    {
    Properties
    {
    _MainTex ("Texture", 2D) = "white" {}
    _FogDensity("Fog Density",Float) = 1.0
    _FogColor("Fog Color",Color) = (1,1,1,1)
    _FogStart("Fog Start",Float) = 0.0
    _FogEnd("Fog End",Float) = 1.0

    }
    SubShader
    {
    CGINCLUDE
    #include "UnityCG.cginc"
    sampler2D _MainTex;
    half4 _MainTex_TexelSize;
    sampler2D _CameraDepthTexture;
    half _FogDensity;
    fixed4 _FogColor;
    float _FogStart;
    float _FogEnd;
    float4x4 _FrustumCormers;
    struct v2f{
    float4 pos :SV_POSITION;
    half2 uv :TEXCOORD0;
    half2 uv_depth :TEXCOORD1;
    float4 interpolatedRay :TEXCOORD2;
    };

    v2f vert(appdata_img v){
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    o.uv_depth = v.texcoord;
    // 平台兼容
    #if UNITY_UV_STARTS_AT_TOP
    if(_MainTex_TexelSize.y<0)
    o.uv_depth.y=1-o.uv_depth.y;
    #endif
    //坐标索引判断
    int index = 0;
    if(v.texcoord.x<0.5 && v.texcoord.y<0.5){
    index=0;
    }
    else if(v.texcoord.x>0.5 && v.texcoord.y<0.5){
    index=1;
    }
    else if(v.texcoord.x>0.5 && v.texcoord.y>0.5){
    index=2;
    }
    else{
    index=3;
    }
    // 平台兼容
    #if UNITY_UV_STARTS_AT_TOP
    if(_MainTex_TexelSize.y<0)
    index = 3- index;
    #endif
    o.interpolatedRay = _FrustumCormers[index];
    return o;
    }
    fixed4 frag(v2f i) :SV_Target{
    float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth));
    float3 worldPos = _WorldSpaceCameraPos+linearDepth*i.interpolatedRay.xyz;
    float fogDensity = (_FogEnd-worldPos.y)/(_FogEnd-_FogStart);
    fogDensity=saturate(fogDensity*_FogDensity);
    fixed4 finalColor = tex2D(_MainTex,i.uv);
    finalColor.rgb = lerp(finalColor.rgb,_FogColor,fogDensity);

    return finalColor;

    }

    ENDCG

    Pass
    {
    ZTest Always Cull Off ZWrite Off
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    ENDCG
    }
    }
    Fallback Off
    }

  • 13.4 再谈边缘检�?/p>

    • 简�?/p>

      • 使用深度纹理进行边缘检测更可靠,对比效果如�?/p>

        再谈边缘检�?png

      • 本章使用 Roberts 算子

        • Roberts本质是计算左上角和右下角的插值,乘于右上角和左下角的插�?
    • 实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      using UnityEngine;

      public class EdgeDetectNoramlsAndDepth : PostEffectsBase
      {
      public Shader edgeDetectShader;
      private Material edgeDetectMaterial = null;
      public Material material
      {
      get
      {
      edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
      return edgeDetectMaterial;
      }
      }
      [Range(0.0f, 1.0f)]
      public float edgesOnly = 0.0f;

      public Color edgeColor = Color.black;

      public Color backgroundColor = Color.white;
      public float sampleDistance = 1.0f;
      // 采样距离,值越大,描边越宽
      public float sensitivityDepth = 1.0f;
      public float sensitivityNormals = 1.0f;
      //权重
      void OnEnable()
      {
      GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;
      }
      [ImageEffectOpaque]//定义仅影响不透明物体
      void OnRenderImage(RenderTexture source, RenderTexture destination)
      {
      if (material != null)
      {
      material.SetFloat("_EdgeOnly", edgesOnly);
      material.SetColor("_EdgeColor", edgeColor);
      material.SetColor("_BackgroundColor", backgroundColor);
      material.SetFloat("_SampleDistance", sampleDistance);
      material.SetVector("_Sensitivity", new Vector4(sensitivityDepth, sensitivityNormals,0.0f,0.0f));
      Graphics.Blit(source, destination, material);
      }
      else
      {
      Graphics.Blit(source, destination);
      }
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      Shader "Unlit/EdgeDetectNoramlsAndDepth"
      {
      Properties
      {
      _MainTex ("Base(RGB)", 2D) = "white" {}
      _EdgeOnly("EdgeOnly",Float) = 1
      _EdgeColor("EdgeColor",Color) = (0,0,0,1)
      _BackgroundColor("BackgroundColor",Color) = (1,1,1,1)
      _SampleDistance("Sample Distance",Float) = 1.0
      _Sensitivity("Sensitivity",Vector) = (1,1,1,1)
      }
      SubShader
      {
      CGINCLUDE
      #include "UnityCG.cginc"
      sampler2D _MainTex;
      half4 _MainTex_TexelSize;
      // 用于访问纹理的每个纹素大�?/span>
      fixed _EdgeOnly;
      fixed4 _EdgeColor;
      fixed4 _BackgroundColor;
      float _SampleDistance;
      half4 _Sensitivity;
      sampler2D _CameraDepthNormalsTexture;
      struct v2f{
      float4 pos:SV_POSITION;
      half2 uv[5] :TEXCOORD0;
      };
      v2f vert(appdata_img v){
      v2f o;
      o.pos = UnityObjectToClipPos(v.vertex);
      half2 uv = v.texcoord;
      o.uv[0]=uv;
      //平台兼容
      #if UNITY_UV_STARTS_AT_TOP
      if(_MainTex_TexelSize.y<0)
      uv.y=1-uv.y;
      #endif

      o.uv[1] = uv+_MainTex_TexelSize.xy*half2(1,1)*_SampleDistance;
      o.uv[2] = uv+_MainTex_TexelSize.xy*half2(-1,-1)*_SampleDistance;
      o.uv[3] = uv+_MainTex_TexelSize.xy*half2(-1,1)*_SampleDistance;
      o.uv[4] = uv+_MainTex_TexelSize.xy*half2(1,-1)*_SampleDistance;
      // 纹理坐标数据
      return o;
      }
      half CheckSame(half4 center,half4 sample){
      half2 centerNoraml = center.xy;
      float centerDepth = DecodeFloatRG(center.zw);
      half2 sampleNoraml = sample.xy;
      float sampleDepth = DecodeFloatRG(sample.zw);

      half2 diffNoraml = abs(centerNoraml-sampleNoraml)*_Sensitivity.x;
      int isSameNoraml = (diffNoraml.x+diffNoraml.y)<0.1;
      float diffDepth = abs(centerDepth-sampleDepth)*_Sensitivity.y;
      int isSameDepth = diffDepth<0.1*centerDepth;

      return isSameNoraml*isSameDepth ? 1.0:0.0;
      }


      fixed4 fragRobertsCrossDepthAndNormal(v2f i) :SV_Target{
      half4 sample1 = tex2D(_CameraDepthNormalsTexture,i.uv[1]);
      half4 sample2 = tex2D(_CameraDepthNormalsTexture,i.uv[2]);
      half4 sample3 = tex2D(_CameraDepthNormalsTexture,i.uv[3]);
      half4 sample4 = tex2D(_CameraDepthNormalsTexture,i.uv[4]);

      half edge = 1.0;

      edge*=CheckSame(sample1,sample2);
      edge*=CheckSame(sample3,sample4);
      fixed4 withEdgeColor = lerp(_EdgeColor,tex2D(_MainTex,i.uv[0]),edge);
      fixed4 onlyEdgeColor = lerp(_EdgeColor,_BackgroundColor,edge);

      return lerp(withEdgeColor,onlyEdgeColor,_EdgeOnly);
      }
      ENDCG

      Pass
      {
      ZTest Always Cull Off ZWrite Off
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment fragRobertsCrossDepthAndNormal
      ENDCG
      }
      }
      Fallback Off
      }

    • 这是全局的后处理描边

    • 特定物体描边 需要使�?Graphics.DrawMesh或者Graphics.DrawMeshNow

  • 13.5 拓展阅读

    • SIGGRAPH 2011 Unity 有一篇关于利用深度纹理制作各种特效的演讲
    • Unity 的Image Effect 中包含了许多相关的特效,例如屏幕空间环境遮蔽(SSAO�?/li>
  • 总结

    • 本章学习�?ul>
    • 基于深度的运动模�?/li>
    • 全局雾效
    • 基于深度的描�?h2>
      1
      viewPos = viewPos +viewNormal *_Outline;
    • 为了处理一些内凹的模型,背面面片遮挡正面面片的情况,需要处理法线的Z分量

      1
      2
      3
      viewNormal.z = -0.5;
      viewNormal = normalize(viewNormal);
      viewPos = viewPos + viewNormal * _Outline;
  • 14.1.2 添加高光

    • 和Blinn-Phong 不同,需要进行阈值比较,小于阈值,反射系数�?,否则返�?.

      1
      2
      float spec = dot(worldNoraml,worldHalfDir);
      spec = step(threshold,spec);
    • 抗锯齿优�?/p>

      1
      2
      float spec = dot(worldNoraml,worldHalfDir);
      spec = lerp(0,1,smppthstep(-w,w,spec-threshold));
    • 拓展参�?/p>

  • 14.1.3 实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

    Shader "Unlit/Toon"
    {
    Properties
    {
    _Color ("Color Tint",Color) = (1,1,1,1)
    _MainTex ("Texture", 2D) = "white" {}
    _Ramp ("Ramp Texture",2D) = "white" {}
    _Outline ("Outline",Range(0,1)) = 0.1
    _OutlineColor ("Outline Color",Color) = (0,0,0,1)
    _Specular ("Spacular",Color) = (1,1,1,1)
    _SpecularScale ("Spacular Scale",Range(0,0.1)) = 0.01
    }
    SubShader
    {
    Tags { "RenderType"="Opaque" "Queue"="Geometry"}


    pass{
    NAME "OUTLINE"//定义名称为了以后调用
    Cull Front
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    #include "UnityCG.cginc"

    float _Outline;
    fixed4 _OutlineColor;
    struct a2v
    {
    float4 vertex: POSITION;
    float3 normal :NORMAL;
    };

    struct v2f
    {
    float4 pos : SV_POSITION;
    };

    v2f vert (a2v v) {
    v2f o;

    float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
    float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
    normal.z = -0.5;
    pos = pos + float4(normalize(normal), 0) * _Outline;
    o.pos = mul(UNITY_MATRIX_P, pos);

    return o;
    }
    float4 frag(v2f i) : SV_Target{
    return float4(_OutlineColor.rgb,1);
    }
    ENDCG


    }


    Pass
    {
    Tags{"LightMode"="ForwardBase"}
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    // make fog work
    #pragma multi_compile_fwdbase

    #include "UnityCG.cginc"
    #include "Lighting.cginc"
    #include "AutoLight.cginc"
    #include "UnityShaderVariables.cginc"
    fixed4 _Color;
    sampler2D _MainTex;
    float4 _MainTex_ST;
    sampler2D _Ramp;
    fixed4 _Specular;
    fixed _SpecularScale;

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

    struct v2f{
    float4 pos:POSITION;
    float2 uv:TEXCOORD0;
    float3 worldNormal :TEXCOORD1;
    float3 worldPos :TEXCOORD2;
    SHADOW_COORDS(3)
    };



    v2f vert (a2v v)
    {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    o.worldNormal = mul(v.normal,(float3x3)unity_ObjectToWorld);
    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(UnityWorldSpaceLightDir(i.worldPos));
    fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
    fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);

    fixed4 c = tex2D(_MainTex,i.uv);
    fixed3 albedo = c.rgb*_Color.rgb;

    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;

    UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

    fixed diff = dot(worldNormal,worldLightDir);
    diff=(diff*0.5+0.5)*atten;

    fixed3 diffuse = _LightColor0.rgb * albedo*tex2D(_Ramp,float2(diff,diff)).rgb;

    fixed spec = dot(worldNormal,worldHalfDir);
    fixed w = fwidth(spec)*2.0;

    fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);

    return fixed4(ambient+diffuse+specular,1.0);
    }
    ENDCG
    }
    }
    FallBack "Diffuse"
    }

  • 14.2 素描风格的渲�?/p>

    • 简�?

      • 2001年,微软研究院的Praum等人在SIGGRAPH提出
      • 色调艺术映射(Tonal Art Map,TAM�?/li>
    • 实现思路

      • 使用6张素描纹理进行渲�?/li>
      • 根据光照结果来决�?张纹理的混合权重
      • 在片元中根据权重混合采样结果
    • 实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

      Shader "Unlit/Hatching"
      {
      Properties
      {
      _Color ("Color Tint",Color) = (1,1,1,1)
      _TileFactor ("Tile Factor",Float) = 1
      _Outline("Outline",Range(0,1)) = 0.1
      _Hatch0 ("Hatch 0",2D)= "white" {}
      _Hatch1 ("Hatch 1",2D)= "white" {}
      _Hatch2 ("Hatch 2",2D)= "white" {}
      _Hatch3 ("Hatch 3",2D)= "white" {}
      _Hatch4 ("Hatch 4",2D)= "white" {}
      _Hatch5 ("Hatch 5",2D)= "white" {}

      }
      SubShader
      {
      Tags { "RenderType"="Opaque" "Opaque" ="Geometry"}
      UsePass "Unlit/Toon/OUTLINE"

      Pass
      {
      Tags{"LightMode"="ForwardBase"}
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag
      // make fog work
      #pragma multi_compile_fwdbase

      #include "UnityCG.cginc"
      #include "Lighting.cginc"
      #include "AutoLight.cginc"
      #include "UnityShaderVariables.cginc"
      struct appdata
      {
      float4 vertex : POSITION;
      //float2 uv : TEXCOORD0;
      float3 normal : NORMAL;
      float4 texcoord : TEXCOORD0;
      };

      struct v2f
      {
      float4 pos : SV_POSITION;
      float2 uv : TEXCOORD0;
      fixed3 hatchWeights0 :TEXCOORD1;
      fixed3 hatchWeights1 :TEXCOORD2;
      float3 worldPos :TEXCOORD3;
      SHADOW_COORDS(4)

      };

      fixed4 _Color;
      fixed _TileFactor;
      fixed _Outline;
      sampler2D _Hatch0;
      sampler2D _Hatch1;
      sampler2D _Hatch2;
      sampler2D _Hatch3;
      sampler2D _Hatch4;
      sampler2D _Hatch5;

      v2f vert (appdata v)
      {
      v2f o;
      o.pos = UnityObjectToClipPos(v.vertex);
      o.uv = v.texcoord.xy*_TileFactor;
      fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
      fixed3 worldNoraml = UnityObjectToWorldNormal(v.normal);
      fixed diff = max(0,dot(worldLightDir,worldNoraml));
      o.hatchWeights0 = fixed3(0,0,0);
      o.hatchWeights1 = fixed3(0,0,0);
      float hatchFactor = diff*7.0;
      if(hatchFactor>6.0){
      //
      }else if(hatchFactor>5.0){
      o.hatchWeights0.x = hatchFactor-5.0;
      }else if(hatchFactor>4.0){
      o.hatchWeights0.x = hatchFactor-4.0;
      o.hatchWeights0.y = 1- o.hatchWeights0.x;
      }else if(hatchFactor>3.0){
      o.hatchWeights0.y = hatchFactor-3.0;
      o.hatchWeights0.z = 1.0- o.hatchWeights0.y;
      }else if(hatchFactor>2.0){
      o.hatchWeights0.z = hatchFactor-2.0;
      o.hatchWeights1.x = 1.0- o.hatchWeights0.z;
      }else if(hatchFactor>1.0){
      o.hatchWeights1.x = hatchFactor-1.0;
      o.hatchWeights1.y = 1.0 - o.hatchWeights1.x;
      }else {
      o.hatchWeights1.y = hatchFactor;
      o.hatchWeights1.z = 1.0 - o.hatchWeights1.y;
      }
      o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
      TRANSFER_SHADOW (o);

      return o;
      }

      fixed4 frag (v2f i) : SV_Target
      {

      fixed4 hatchTex0 = tex2D(_Hatch0,i.uv)*i.hatchWeights0.x;
      fixed4 hatchTex1 = tex2D(_Hatch1,i.uv)*i.hatchWeights0.y;
      fixed4 hatchTex2 = tex2D(_Hatch2,i.uv)*i.hatchWeights0.z;
      fixed4 hatchTex3 = tex2D(_Hatch3,i.uv)*i.hatchWeights1.x;
      fixed4 hatchTex4 = tex2D(_Hatch4,i.uv)*i.hatchWeights1.y;
      fixed4 hatchTex5 = tex2D(_Hatch5,i.uv)*i.hatchWeights1.z;
      fixed4 whiteColor = fixed4(1,1,1,1)*(1-i.hatchWeights0.x-i.hatchWeights0.y-i.hatchWeights0.z-i.hatchWeights1.x-i.hatchWeights1.y-i.hatchWeights1.z);
      fixed4 hatchColor = hatchTex0+hatchTex1+hatchTex2+hatchTex3+hatchTex4+hatchTex5+whiteColor;
      UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

      return fixed4(hatchColor.rgb*_Color.rgb*atten,1.0);
      }
      ENDCG
      }
      }
      FallBack "Diffuse"
      }
  • 14.3 扩招阅读

    • 国际讨论�?NPAR( Non-Photorealistic Animation and Rendering 上有许多关于非真实感渲染的论�?/li>
    • 《艺术化绘制的图形学原理与方法�?是一本不错的参考书
    • Unity 资源�?ul>
    • Toon Shader Free是一个免费卡通资源包
    • Toon Styles Shader Pack 付费
    • Hnad-Drawn Shader Pack 付费
  • 第十五章 使用噪声

    • 15.1 消融效果

      • 简�?/p>

        • 消融(dissolve)效果常见于游戏中的角色死亡,地图烧毁效�?/li>
      • 实现

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        123
        124
        125
        126
        127
        128
        129
        130
        131
        132
        133
        134
        135
        136
        137
        138
        139
        140
        141
        142
        143
        144
        145
        146
        147
        148
        149
        // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

        Shader "Unlit/Dissolve"
        {
        Properties
        {
        _BurnAmount ("Burn Amount",Range(0.0,1.0)) = 0.0
        _LineWidth("Burn Line Width",Range(0.0,0.2)) = 0.1
        _MainTex ("Texture", 2D) = "white" {}
        _BumpMap("Noraml Map",2D) = "bump" {}
        _BurnFirstColor ("Burn First Color",Color) = (1,0,0,1)
        _BurnSecondColor ("Burn Second Color",Color) = (1,0,0,1)
        _BurnMap ("Burn Map",2D) = "white" {}
        }
        SubShader
        {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}

        Pass
        {
        Tags{"LightMode"="ForwardBase"}
        Cull Off
        CGPROGRAM

        #include "Lighting.cginc"
        #include "AutoLight.cginc"
        #pragma vertex vert
        #pragma fragment frag
        // make fog work
        #pragma multi_compile_fwdbase

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

        struct v2f
        {
        float4 pos : SV_POSITION;
        float2 uv : TEXCOORD0;
        float2 uvBumpMap : TEXCOORD1;
        float2 uvBurnMap : TEXCOORD2;
        float3 lightDir : TEXCOORD3;
        float3 worldPos : TEXCOORD4;
        SHADOW_COORDS(5)
        };


        sampler2D _MainTex;
        float4 _MainTex_ST;
        float _BurnAmount;
        float _LineWidth;
        sampler2D _BumpMap;
        float4 _BumpMap_ST;
        float4 _BurnFirstColor;
        float4 _BurnSecondColor;
        sampler2D _BurnMap;
        float4 _BurnMap_ST;

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

        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        o.uvBumpMap = TRANSFORM_TEX(v.uv, _BumpMap);
        o.uvBurnMap = TRANSFORM_TEX(v.uv, _BurnMap);

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

        o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

        TRANSFER_SHADOW(o);

        return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
        fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
        //fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
        clip(burn.r-_BurnAmount);
        float3 tangentLightDir = normalize(i.lightDir);
        fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap));

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

        fixed t = 1-smoothstep(0.0,_LineWidth,burn.r-_BurnAmount);
        fixed3 burnColor = lerp(_BurnFirstColor,_BurnSecondColor,t);
        burnColor=pow(burnColor,5);

        UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
        float3 result = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount));

        return fixed4(result,1);
        }
        ENDCG
        }
        // Pass to render object as a shadow caster
        Pass {
        Tags { "LightMode" = "ShadowCaster" }

        CGPROGRAM

        #pragma vertex vert
        #pragma fragment frag

        #pragma multi_compile_shadowcaster

        #include "UnityCG.cginc"

        fixed _BurnAmount;
        sampler2D _BurnMap;
        float4 _BurnMap_ST;

        struct v2f {
        V2F_SHADOW_CASTER;
        float2 uvBurnMap : TEXCOORD1;
        };

        v2f vert(appdata_base v) {
        v2f o;

        TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)

        o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);

        return o;
        }

        fixed4 frag(v2f i) : SV_Target {
        fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;

        clip(burn.r - _BurnAmount);

        SHADOW_CASTER_FRAGMENT(i)
        }
        ENDCG
        }
        }
        FallBack "Diffuse"
        }

    • 15.2 水波效果

      • 实现思路

        • 使用噪声纹理作为高度图,改变水面的法相方�?/li>
        • 使用时间相关的变量对噪波采样
      • 使用的计算菲涅耳系数的公式�?br>$frensnel = pow(1-max(0,v \cdot n),4)$

      • 实现

        1
        2
        3
        4
        5
            ```
        - 15.3 再谈全局雾效
        - 使用之前的高度雾增加噪声贴图
        - 实现

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        123
        124
        125
        126
        127
        128
        129
        130
        131
        132
        133
        134
        135
        136
        137
        138
        139
        140
        141
        142
        143
        144
        145
        146
        147
        148
        149
        150
        151
        152
        153
        154
        155
        156
        157
        158
        159
        160
        161
        162
        163
        164
        165
        166
        - 15.4 拓展阅读
        - 噪声纹理是一种程序纹理(Procedure Texture�?利用计算机算法生�?/span>
        - 常用的噪�?/span>
        - Perlin 噪声
        - http://f1afla2.github.io/2014/08/09/perlinnoise.html
        - Worley 噪声
        - http://scrawkblog.com/category/procedural-noise/
        ## 第十六章 Unity中的渲染优化技�?/span>
        - 16.1 移动平台的特�?/span>
        - 特点
        - 1. GPU架构专注于尽可能使用更小的带宽和功能
        - 造成的优化手�?/span>
        - 1. 使用基于瓦片的延迟渲染(Tiled-based-Deferred Rendering,TBDR)架构,为了较少OverDraw
        - 2. Early-Z或者相似技术进行一个低精度的深度检测,来剔除那些不需要的片元,Adreno和Mali芯片
        - IOS硬件指南http://docs.unity3d.com/Manual/iphone-Hardware.htrnl
        - 16.2 影响性能的因�?/span>
        - 性能瓶颈的主要原�?/span>
        - CPU
        - 过多的Draw Call
        - 复杂的脚本或者物理模�?/span>
        - GPU
        - 顶点处理
        - 过多的顶�?/span>
        - 过多的逐顶点计�?/span>
        - 片元
        - 过多的片元(分辨率或者overdraw�?/span>
        - 过多的逐片元计�?/span>
        - 带宽
        - 使用了储存很大且未压缩的纹理
        - 分辨率过高的帧缓�?/span>
        - 优化手段
        - cpu优化
        - 使用批处理减少draw call数目
        - gpu优化
        - 减少需要处理的顶点数目
        - 优化几何�?/span>
        - 使用lod
        - 使用遮挡剔除(Occlus ion Culling)技�?/span>
        - 减少需要处理的片元数目
        - 控制绘制顺序
        - 警惕透明物体
        - 减少实时光照
        - 减少计算复杂�?/span>
        - 使用shader的lod
        - 代码优化
        - 带宽优化
        - 减少纹理大小
        - 利用分辨率缩�?/span>
        - 16.3 Unity中的渲染分析工具
        - 简�?/span>
        - 渲染统计窗口(Rendering Statistics Window�?/span>
        - 性能分析器(Profiler�?/span>
        - 帧调试器(Frame Debugger�?/span>
        - 16.3.1 认识Unity5的渲染统计窗�?/span>
        - 位置:Game视窗右上方的菜单(Stats�?/span>
        - 音频(Audio�?/span>
        - 图像(Graphics�?/span>
        - 网络(Network�?/span>
        - 重要信息
        - FPS 处理和渲染一帧的时间和FPS(Frames Per Second�?/span>
        - Batches 一帧需要进行的批处理数�?/span>
        - saved by batching 合并的批处理数目,表明节省了多少draw call
        - Tris和Verts 需要绘制的三角面片和顶点数�?/span>
        - screen 屏幕大小以及占用内存大小
        - SetPass pass数目
        - Visible Skinned Meshes 渲染的蒙皮网格的数目
        - Animations 动画数目
        - Shadow Casters 可以投射阴影的物体数�?/span>
        - 16.3.2 性能分析器的渲染区域
        - 位置 Window-Analysis-Profiler(Unity 2019)
        - Rendering Area

        ![RenderingArea.png](RenderingArea.png)

        - 注意:Draw call数目和批处理数目,pass数目并不相等,并且大于估算数目,因为Unity背后有很多工作,例如初始化各个缓存,为阴影更新深度纹理和阴影映射纹理
        - 16.3.3 再谈帧调试器
        - 位置 Window-Analysis-Frame Debugger(Unity 2019)
        - 作用 显示渲染这一帧所需要的所有的渲染事件,单机面板每个时间,可以在Game试图查看该事件绘制结�?/span>
        - 16.3.4 其他性能分析工具
        - Android
        - 高通的Adreno分析工具
        - 英伟�?NvPerfHUD工具
        - ios
        - PowerVRAM的PVRUniSCo shader 大致性能评估
        - Xcode �?OpenGL ES Driver Instruments 宏观性能信息,设备利用率和渲染器利用�?/span>
        - 补充
        - 现在�?021)流行使用RenderDoc进行分析
        - 16.4 减少draw call数目
        - 批处理(batching�?/span>
        - 目的 为了较少每帧的draw call数目
        - 原理 �?/span>
        - 每次调用draw call 尽可能处理多个物�?/span>
        - 使用条件�?/span>
        - 使用相同材质的物�?/span>
        - 顶点不同可以合并顶点数据
        - 方式�?/span>
        - 动态批处理
        - 自动完成
        - 限制很多
        - 静态批处理
        - 自由度搞,限制少
        - 占用内存�?/span>
        - 处理后无法被移动
        - 16.4.1 动态批处理
        - 优点
        - 实现方便
        - 经过批处理的物体仍然可以移动(因为处理每帧时unity都会重新合并一次网格)
        - 条件�?/span>
        - 能够进行动态批处理的网格的顶点属性规模要小于900
        - 保证指向光纹理中的位置相同,lightmap
        - 多pass的shader会中断批处理
        - 16.4.2 静态批处理
        - 适用范围�?/span>
        - 任何大小的几何模�?/span>
        - 问题�?/span>
        - 内存增大
        - 不可移动
        - 16.4.3 共享材质
        - 注意:是同一个材质,而非同一�?/span>
        - 纹理不同可以合并纹理
        - 数据不同,部分可以移动到顶点数据�?/span>
        - 16.4.4 批处理的注意事项
        - 建议
        - 尽可能选择静态批处理,小心内�?/span>
        - 如果要使用动态批处理
        - 对于游戏中的小道具可以使用动态批处理
        - 对于包含动画这类物体,我们无法全部使用静态批处理,如果不动的的部分可以标为“Static�?/span>
        - 16.5 减少需要处理的顶点数目
        - 16.5.1 优化几何�?/span>
        - 移除不必要的硬边以及纹理衔接,避免边界平滑和纹理分离
        - 16.5.2 模型的LOD技�?/span>
        - 使用lodGroup
        - 模型准备多个不同细节程度的模�?/span>
        - 16.5.3 遮挡剔除技�?/span>
        - 遮挡剔除技术(Occlusion culling�?/span>
        - 用来消除那些在其他物件后面看不到的物�?/span>
        - 与视锥体剔除(Frustum culling)不�?/span>
        - 优化了什�?/span>
        - 减少需要处理的顶点数目
        - 减少overdraw·
        - 16.6 减少需要处理的片元数目
        - 16.6.1 控制绘制顺序
        - 因为深度测试的存在,可以保证物体都是从前往后绘制的,能很大程度减少Overdraw(红棉绘制的物体无法通过深度测试,不会进行后面的渲染处理�?/span>
        - 16.6.2 时刻警惕透明物体
        - 原因,半透明对象没开启深度写入,必须从后往前渲染,几乎一定会造成Overdaw�?/span>
        - 16.6.3 减少实时光照和阴�?/span>
        - 原因,对于逐像素的光源来说,被这些光源照亮的物体需要再被渲染一次,而且无法批处理,会中断批处理�?/span>
        - 解决办法,使用烘焙技术或者使用God Ray�?/span>
        - LUT
        - 16.7 节省带宽
        - 16.7.1 减少纹理大小
        - 长宽最好是二次�?/span>
        - mipmapping 多级渐远纹理技�?/span>
        - 纹理压缩,不同平台压缩格式不同�?/span>
        - 16.7.2 利用分辨率缩�?/span>
        - 对于特定机器进行分辨率的缩放�?/span>
        - 16.8 减少计算复杂�?/span>
        - 计算复杂度会影响游戏的渲染性能
        - 16.8.1 Shader的LOD技�?/span>
        - 当Shader的LOD值小于某个设定的值时,该Shader才会被使�?/span>

        ```C
        SubShader{
        Tags { "RenderType"="Qpaque"}
        LOD 200
        }
        • 16.8.2 代码方面的优�?br>对象<顶点<像素
          1. 尽可能使用低精度的浮点值运算,float 存储(顶点坐标),half适合标量纹理坐标,fixed 适合颜色变量和归一化的方向矢量,避免使用swizzle计算
          2. 减少插值变�?/li>
          3. 尽可能不要使用全屏的屏幕后处理效�?/li>
          4. 尽可能把多个特效合并到一个Shader�?/li>
          5. 尽可能不要使用分支语句和循环语句
          6. 尽可能避免使用sin,tan,pow,log等复杂的数学计算
          7. 尽可能不要使用discard操作,会影响硬件的某些优�?/li>
        • 16.8.3 根据硬件条件进行缩放
          scaiing 放缩思路
      • 16.9 拓展阅读

        • 官方手册 移动平台永华实践指南
        • 第十七章 Unity的表面着色器探秘

      表面着色器(Surface Shader)是顶点/片元着色器的抽象�?/p>

    • 17.1 表面着色器的一个例�?/p>

    • 17.2 编译命令
      编译命令的作用指明该表面着色器使用的表面函数和光照函数,并设置一些可选参数�?/p>

      1
      2
      #pragma surface surfaceFunction lightModel [optionalarams]

      • 17.2.1 表面函数
        名称surf
      • 17.2.2 光照函数

      自由度低,不好优化,不如用顶点片�?/p>

      第十九章 Unity5 更新了什�?/span>

      参�?/span>

      [1] Gaussian blur - wikipedia

      [2] Bloom (shader effect) - wikipedia

      [3] Cel shading - wikipedia

    关于本文

    本文作�?Master Gong Sheng, 许可�?CC BY-NC 4.0.