《Unity Shader入门精要》笔记:高级篇(1)

  • 本篇博客主要为个人学习所编写读书笔记,不用于任何商业用途,以及不允许任何人以任何形式进行转载。
  • 本篇博客会补充一些扩展内容(例如其他博客链接)。
  • 本篇博客还会提供一些边读边做的效果截图。文章内所有数学公式都由Latex在线编辑器生成。
  • 本篇博客主要提供一个“glance”,知识点的总结。如有需要请到书店购买正版。
  • 博客提及所有官方文档基于2022.2版本,博客会更新一些书中的旧的知识点到2022.2版本。
  • 个人博客网址:《Unity Shader入门精要》笔记:高级篇(1) - Sugar的博客,如文章转载中出现例如传送门失效等纰漏,建议直接访问原博客网址。

  • 如有不对之处欢迎指正。
  • 我创建了一个游戏制作交流群:637959304 进群密码:(CSGO的拆包密码)欢迎各位大佬一起学习交流,不限于任何平台(U3D、UE、COCO2dx、GamesMaker等),以及欢迎编程,美术,音乐等游戏相关的任何人员一起进群学习交流。

目录

屏幕后处理效果

调整亮度、饱和度、对比度

边缘检测

高斯模糊

运动模糊


屏幕后处理效果

  • 屏幕后处理效果(screen post-processing effects):在渲染完整个屏幕图像后,再对这个图像进行一系列操作。使用该技术可以为游戏画面添加更多的艺术效果,例如景深(Depth of Field)、运动模糊(Motion Blur)等。
  • OnRenderImage,抓取屏幕函数:MonoBehavior.OnRenderImage(RenderTexture src,RenderTexture dest) 。Unity会把当前渲染得到的图像存储在src中,然后通过函数的操作把目标渲染纹理dest显示到屏幕上。
  • Graphics.Blit:利用该函数完成对渲染纹理的处理

1

2

3

4

//三种函数声明,dest=null时会直接将结果显示在屏幕上。mat是材质,pass是调用指定编号的pass,pass=-1就会一次调用所有Pass

Blit(Texture src,RenderTexture dest);

Blit(Texture src,RenderTexture dest,Material mat,int pass = -1);

Blit(Texture src,Material mat,int pass = -1);

调整亮度、饱和度、对比度

  • 调整屏幕的亮度、饱和度、对比度:该部分需要编写一些C#代码来进行综合完成。

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

//C#脚本代码,该脚本直接挂载在摄像机下

using UnityEngine;

using System.Collections;

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;

    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

//该Shader直接传入public Shader briSatConShader;

Shader "Example/Brightness Saturation And Contrast" {

    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 

               

            #include "UnityCG.cginc" 

               

            sampler2D _MainTex; 

            half _Brightness;

            half _Saturation;

            half _Contrast;

               

            struct v2f {

                float4 pos : SV_POSITION;

                half2 uv: TEXCOORD0;

            };

               

            v2f vert(appdata_img v) {

                v2f o;

                 

                o.pos = UnityObjectToClipPos(v.vertex);

                 

                o.uv = v.texcoord;

                          

                return o;

            }

         

            fixed4 frag(v2f i) : SV_Target {

                fixed4 renderTex = tex2D(_MainTex, i.uv); 

                   

                fixed3 finalColor = renderTex.rgb * _Brightness;

                //饱和度

                fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.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, renderTex.a); 

            

               

            ENDCG

        

    }

     

    Fallback Off

}

场景窗口

游戏窗口

边缘检测

  • 边缘检测:利用边缘检测算子对图像进行卷积(convolution)操作。(俗称:开卷)如果相邻像素之间存在差别明显的颜色、亮度、纹理等属性,那么就可以判定他们之间有一条边界。
  • 什么是卷积:传送门,这个讲的很不错,还有很多例子和应用。
  • 边缘检测算子:Sobel、Prewitt、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

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

Shader "Example/Shader09"

{

    Properties {

        _MainTex ("Base (RGB)", 2D) = "white" {}

        _EdgesOnly("Edges Only",Float) = 1.0

        _EdgesColor("Edges Color",Color) = (0,0,0,1)

        _BackgroundColor("Background Color",Color)=(1,1,1,1)

    }

    SubShader {

        Pass { 

            ZTest Always Cull Off ZWrite Off

             

            CGPROGRAM 

            #pragma vertex vert 

            #pragma fragment frag 

               

            #include "UnityCG.cginc" 

               

            sampler2D _MainTex; 

            half4 _MainTex_TexelSize;

            fixed4 _EdgesColor;

            fixed4 _BackgroundColor;

            float _EdgesOnly;

               

            struct v2f {

                float4 pos : SV_POSITION;

                half2 uv[9]: TEXCOORD0;

            };

               

            v2f vert(appdata_img v) {

                v2f o;

                o.pos = UnityObjectToClipPos(v.vertex);

                half2 uv = v.texcoord;

                const half Gx[9] = {-1,0,1,-1,0,1,-1,0,1};

                const half Gy[9] = {-1,-1,-1,0,0,0,1,1,1};

                for(int j = 0 ; j < 9 ; j++)

                    o.uv[j] = uv + _MainTex_TexelSize.xy * half2(Gx[j],Gy[j]);

                          

                return o;

            }

            fixed luminance(fixed4 color) {

                return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;

            }

//核心卷积计算,注意不要把卷积算子抄错了,抄错一个效果就完全不一样

            half Sobel(v2f i)

            {

                const half Gx[9] = {-1,-2,-1,0,0,0,1,2,1};

                const half Gy[9] = {-1,0,1,-2,0,2,-1,0,1};

                half edgeX = 0;

                half edgeY = 0;

                half texColor = 0;

                for(int j = 0 ; j < 9 ; j++)

                {

                    texColor = luminance(tex2D(_MainTex, i.uv[j]));

                    edgeX += texColor * Gx[j];

                    edgeY += texColor * Gy[j];

                }

                return 1 - abs(edgeX) - abs(edgeY);

            }

         

            fixed4 frag(v2f i) : SV_Target

            {

                half edge = Sobel(i);

                 

                fixed4 withEdgeColor = lerp(_EdgesColor,tex2D(_MainTex,i.uv[4]),edge);

                fixed4 onlyEdgeColor = lerp(_EdgesColor,_BackgroundColor,edge);

                return lerp(withEdgeColor,onlyEdgeColor,_EdgesOnly); 

            

               

            ENDCG

        

    }

     

    Fallback Off

}

高斯模糊

  • 高斯模糊:利用高斯核进行卷积计算,每个元素的计算基于如下公式。σ是标准方差(一般取1),x和y分别对应了当前位置到卷积核中心的整数距离。为了确保滤波后的图像不会变暗,我们需要对高斯核中的权重进行归一化,让每个权重除以所有权重的和。

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

//在OnRenderImage需要进行两次Graphics.Blit,即需要用到两个Pass来实现高斯模糊

void OnRenderImage (RenderTexture src, RenderTexture dest) {

        if (material != null) {

//进行比例缩放的采样,减少处理所需像素个数,提高性能能,同时可以得到更好的模糊效果

            int rtW = src.width/downSample;

            int rtH = src.height/downSample;

            RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);

            buffer0.filterMode = FilterMode.Bilinear;

            Graphics.Blit(src, buffer0);

            for (int i = 0; i < iterations; i++) {

                material.SetFloat("_BlurSize", 1.0f + i * blurSpread);

                RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

                // Render the vertical pass

                Graphics.Blit(buffer0, buffer1, material, 0);

                RenderTexture.ReleaseTemporary(buffer0);

                buffer0 = buffer1;

                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

                // Render the horizontal pass

                Graphics.Blit(buffer0, buffer1, material, 1);

//释放缓存

                RenderTexture.ReleaseTemporary(buffer0);

                buffer0 = buffer1;

            }

            Graphics.Blit(buffer0, dest);

            RenderTexture.ReleaseTemporary(buffer0);

        }

        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

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

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 12/Gaussian Blur" {

    Properties {

        _MainTex ("Base (RGB)", 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;

        };

//片元着色器计算消耗性能会比顶点高,所以顶点着色器中计算,一次Pass进行竖直方向的计算,一次Pass进行水平方向的计算

        v2f vertBlurVertical(appdata_img v) {

            v2f o;

            o.pos = UnityObjectToClipPos(v.vertex);

             

            half2 uv = v.texcoord;

//这里的*1.0,*2.0应该是进行纹理偏移

            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 {

//5X5的高斯核化简成一个1x5和5x1的一维向量,v = {0.0545,0.2442,0.4026,0.2442,0.0545} h^T={0.0545,0.2442,0.4026,0.2442,0.0545}所以可以直接化简成如下的weight矩阵,只需要三个变量即可

            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"

}

  • Bloom扩散效果:可以将较亮的区域扩散到周围的去榆中,造成一种朦胧的效果。
  • 原理:根据设定的阈值提取较亮的区域,然后存储到一个纹理渲染中,然后进行高斯模糊处理,模拟光线扩散效果,最后再和原图像进行混合

运动模糊

  • 实现方法:1、利用一块积累缓存来混合多张连续的图像,然后取平均值输出。但消耗性能过大。
    2、利用速度缓存存储各个像素当前的运动速度,然后利用这些值来决定模糊的方向和大小。
    (在书中的下一章节会进行基于速度映射图的运动模糊,故这里就不再深究代码方面的内容)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值