高级 12-16 一轮整理:2021年6月25日09:52:09
二轮整理:2021年11月4日17:26:23
作者:聪头
笔记主题和书本主题并非一一对应
Ch12.屏幕后处理效果 12.1 后处理脚本系统 重要接口 OnRenderImage 要实现屏幕后处理效果的基础在于得到渲染后的屏幕图像,即抓取屏幕,而Unity提供了这样一个方便的接口:OnRenderImage函数。声明如下:
MonoBehaviour.OnRenderImage(RenderTexture src, RenderTexture dest)
当我们在脚本中声明此函数后,Unity会把当前渲染得到的图像存储在第一个参数对应的源渲染纹理中,通过函数中的一系列操作后,再渲染到目标纹理,即第二个参数对应的渲染纹理显示到屏幕上。在OnRenderImage函数中,我们通常是利用Graphics.Blit函数 来完成对渲染纹理的处理。它有3种函数声明:
![image-20211102145004679](高级 12-16.assets/image-20211102145004679.png)
参数解析:
src:源纹理,在屏幕后处理技术中,这个参数通常就是当前屏幕的渲染纹理或上一步处理后得到的渲染纹理
dest:目标渲染纹理,如果它的值为null,就会直接将结果显示在屏幕上
mat:使用的材质,这个材质使用Unity Shader将会进行各种屏幕后处理操作,而src纹理将会被传递给Shader中名为_MainTex的纹理属性
pass:默认为-1,表示将会一次调用Shader内所有Pass。否则,只会调用给定索引的Pass(第一个Pass下标从0开始)
调用时机
OnRenderImage函数会在所有不透明和透明的Pass执行完毕后被调用,以便对场景中所有游戏对象都产生影响
若我们希望在不透明的Pass执行完毕后立即调用OnRenderImage函数,可以在OnRenderImage函数前添加ImageEffectOpaque属性来实现这样的目的
处理过程
需要在摄像机中添加一个用于屏幕后处理的脚本。在这个脚本中,我们会实现OnRenderImage函数来获取当前屏幕的渲染纹理
检查一系列条件是否满足,如平台是否支持渲染纹理和屏幕特效(Unity2019必支持),是否支持当前使用的Unity Shader等
调用Graphics.Blit函数使用特定的Unity Shader 来对当前图像进行处理,再把返回的渲染纹理显示到屏幕上
对于一些复杂的屏幕特效,我们可能需要多次重复3
脚本:后处理系统基类
截止2019.4版本,许多书中的判断已经过时,下面给出自己修改后的最新代码
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 using System.Collections;using System.Collections.Generic;using UnityEngine;[ExecuteInEditMode ] [RequireComponent(typeof(Camera)) ] public class CTPostEffectsBase : MonoBehaviour { 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); if (material) return material; else return null ; } } }
Shader:共同点 Pass设置 1 2 3 4 5 6 7 8 Pass { ZTest Always Cull Off Zwrite Off ...... }
屏幕后处理实际上是在场景中绘制了一个与屏幕同宽高的四边形面片
顶点着色器
屏幕特效使用的顶点着色器代码通常比较简单,只需要进行必须的顶点变换。重要是传递正确纹理坐标
1 2 3 4 5 6 v2f vert (appdata_img v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; return o; }
实战12-1:亮度、饱和度和对比度 脚本 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 using System.Collections;using System.Collections.Generic;using UnityEngine;public class CTBrightnessSaturationAndContrast : CTPostEffectsBase { 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); } } }
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 Shader "Unity Shaders Book/Chapter 12/CT_BrightnessSaturationAndContrast" { Properties { _MainTex ("Texture" , 2 D) = "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; float2 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 }
lerp的本质!!! 参考:https://zhuanlan.zhihu.com/p/73487722
等价于 float3 lerp(float3 a, float3 b, float w) { return a + w*(b-a); }
最终效果
实战12-2:边缘检测 原理:使用卷积核(通常包含两个,分别为水平和竖直),对每个像素分别进行一次卷积计算,得到两个方向上的梯度值Gx和Gy,从而得到整体梯度来判断是否为边缘(梯度越大,越是边缘)
卷积参考:https://www.bilibili.com/video/BV1VV411478E?from=search&seid=11813963409647903137&spm_id_from=333.337.0.0
使用场景:一个系统,输入不稳定,输出稳定,用卷积求系统存量
(一维)卷积公式:
应用:二维图像卷积
G函数旋转180度后才是卷积核,卷积核是能够直接扣在图像上的 ,即图像坐标的(-1,-1)对应卷积核的(-1,-1)位置的值
脚本 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 using System.Collections;using System.Collections.Generic;using UnityEngine;public class MyEdgeDetection : MyPostEffectsBase { 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); } } }
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 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 Shader "Unity Shaders Book/Chapter 12/CT_EdgeDetection" { Properties { _MainTex ("Texture" , 2 D) = "white" {} _EdgeOnly ("Edge Only" , Float) = 1 _EdgeColor ("Edge 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; fixed _EdgeOnly; fixed4 _EdgeColor; fixed4 _BackgroundColor; 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; 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; } 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 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 frag (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 } } }
最终效果
实战12-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 using System.Collections;using System.Collections.Generic;using UnityEngine;public class CTGaussianBlur : CTPostEffectsBase { public Shader gaussianBlur; private Material gaussianBlurMaterial = null ; public Material material { get { gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlur, 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 ; 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; 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); } 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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 Shader "Unity Shaders Book/Chapter 12/CT_GaussianBlur" { Properties { _MainTex ("Texture" , 2 D) = "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" }
CGINCLUDE用法!!!
类似C++中头文件的功能,上面例子下可以避免我们编写两个完全一样的frag函数
1 2 3 4 5 6 SubShader { CGINCLUDE ... ENDCG ... }
最终效果
实战12-4:bloom效果 实现思路:首先根据一个阈值提取出图像中的较亮区域,把它们存储在一张渲染纹理中,再利用高斯模糊对这张渲染纹理进行模糊处理,模拟光线扩散效果,最后再将其和原图像进行混合,得到最终效果
脚本 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 System.Collections;using System.Collections.Generic;using UnityEngine;public class MyBloom : MyPostEffectsBase { public Shader bloomShader; private Material bloomMaterial; 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; 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); } 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 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 Shader "Unity Shaders Book/Chapter 12/CT_Bloom" { Properties { _MainTex ("Base (RGB)" , 2 D) = "white" {} _Bloom ("Bloom (RGB)" , 2 D) = "black" {} _LuminanceThreshold ("Luminance Threshold" , 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; }; fixed luminance (fixed4 color) { return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; } v2f vertExtractBright (appdata_img v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; return o; } fixed4 fragExtractBright (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 vertExtractBright #pragma fragment fragExtractBright ENDCG } UsePass "Learning/Sampler12_4/GAUSSIAN_BLUR_VERTICAL" UsePass "Learning/Sampler12_4/GAUSSIAN_BLUR_HORIZONTAL" Pass { CGPROGRAM #pragma vertex vertBloom #pragma fragment fragBloom ENDCG } } FallBack Off }
最终效果
实战12-5:运动模糊 两种主要实现方式:
方式1:利用一块累积缓存 (accumulation buffer)来混合多张连续图像
当物体快速移动产生多张图像后,我们取它们之间的平均值作为最后的运动模糊图像
这种暴力的方法对性能消耗很大,因为获取多张帧图像往往意味着我们需要在同一帧里渲染多次场景,或者将前几帧图像保存起来
方式2(推荐):创建和使用速度缓存 (velocity buffer),这个缓存中存储了各个像素当前的运动速度,然后利用该值来决定模糊的方向和大小
本节使用第一种方法实现运动模糊
脚本 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 using System.Collections;using System.Collections.Generic;using UnityEngine;public class CTMotionBlur : CTPostEffectsBase { 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; void OnDisable () { if (accumulationTexture != null ) DestroyImmediate(accumulationTexture); } void OnRenderImage (RenderTexture src, RenderTexture dest ) { if (material != null ) { if (accumulationTexture == null || accumulationTexture.width != src.width || accumulationTexture.height != src.height) { DestroyImmediate(accumulationTexture); accumulationTexture = new RenderTexture(src.width, src.height, 0 ); Graphics.Blit(src, accumulationTexture); } accumulationTexture.MarkRestoreExpected(); material.SetFloat("_BlurAmount" , 1.0f - blurAmount); Graphics.Blit(src, accumulationTexture, material); Graphics.Blit(accumulationTexture, dest); } else { Graphics.Blit(src, dest); } } }
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 72 Shader "Unity Shaders Book/Chapter 12/CT_MotionBlur" { Properties { _MainTex ("Base (RGB)" , 2 D) = "white" {} _BlurAmount ("Blur Amount" , Float) = 1.0 } SubShader { CGINCLUDE #include "UnityCG.cginc" sampler2D _MainTex; fixed _BlurAmount; 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 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 }
最终效果
Ch13.使用深度和法线纹理 13.1 深度和法线纹理 1.规定:Unity中观察空间使用右手坐标系,NDC的z分量∈[-1,1]
2.获取深度和法线纹理的两种途径:
来自于真正的缓存,一般是使用延迟渲染时,通过G-buffer获取
单独的Pass渲染而得。会使用着色器替换 (Shader Replacement)技术选择那些渲染类型为Opaque 的物体,判断它们的渲染队列是否小于等于2500 (内置的Background、Geometry和AlphaTest渲染队列均在此范围内),如果满足条件,就把它们渲染到深度和法线纹理中
RenderType:让物体能够出现在深度和法线纹理中
单独的Pass:想让物体出现在深度纹理中,需要投射阴影时使用的Pass(即LightMode被设置为ShadowCaster的Pass)
3.精度:
深度纹理:精度通常是24位或16位,这取决于使用的深度缓存的精度
深度+法线纹理:和屏幕分辨率相同,精度为32位(每个通道8位)的纹理,其中观察空间 下的法线信息会被编码进纹理的R和G通道,而深度信息会被编码进B和A通道(16位)
4.获取:
1 2 3 4 5 camera.depthTextureMode = DepthTextureMode.Depth; camera.depthTextureMode |= DepthTextureMode.Depth; camera.depthTextureMode |= DepthTextureMode.DepthNormals;
5.采样
通常情况,直接使用tex2D函数
特殊情况(如PS3和PS2),使用统一宏SAMPLE_DEPTH_TEXTURE
对深度纹理采样
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
i.uv是一个float2类型变量,对应了当前像素的纹理坐标
类似的宏还有(了解):SAMPLE_DEPTH_TEXTURE_PROJ
和SAMPLE_DEPTH_TEXTURE_LOD
6.深度纹理的处理
7.深度+法线纹理的处理
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 Shader "Unity Shaders Book/Chapter 13/CT_MotionBlurWithDepthTexture" { Properties { _MainTex ("Base (RGB)" , 2 D) = "white" {} _BlurSize ("Blur Size" , Float) = 1.0 } SubShader { CGINCLUDE #include "UnityCG.cginc" sampler2D _MainTex; half4 _MainTex_TexelSize; sampler2D _CameraDepthTexture; float4x4 _CurrentViewProjectionInverseMatrix; float4x4 _PreviousViewProjectionMatrix; half _BlurSize; 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 ndcPos = float4(i.uv.x * 2 - 1 , i.uv.y * 2 - 1 , d * 2 - 1 , 1 ); float4 worldPos = mul(_CurrentViewProjectionInverseMatrix, ndcPos); worldPos = worldPos / worldPos.w; float4 previousNdcPos = mul(_PreviousViewProjectionMatrix, worldPos); previousNdcPos /= previousNdcPos.w; float2 velocity = (ndcPos.xy - previousNdcPos.xy) / 2.0f ; float2 uv = i.uv; float4 c = tex2D(_MainTex, uv); uv += velocity * _BlurSize; for (int it = 0 ; it < 2 ; it++, uv += velocity * _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 全局雾效* 关键:根据深度纹理来重建每个像素在世界空间下的位置,本节采用另一种更省性能的方法
对图像空间下的视锥体射线(从摄像机出发,指向图像上的某点的射线)进行插值,这条射线存储了该像素在世界空间下到摄像机的方向信息(重点就是求射线)
把该射线和线性化后的视角空间下的深度值相乘,再加上摄像机的世界位置,就可以得到该像素在世界空间下的位置,进而模拟全局雾效
float4 worldPos = _WorldSpaceCameraPos + linearDepth * interpolatedRay;
关键步骤推导 1.摄像机位置可以直接获取
2.像素对应时间空间的深度信息(linearDepth)可以通过采样深度纹理(SAMPLE_DEPTH_TEXTURE)和调用Unity内置函数(LinearEyeDepth)获取
3.难点在于interpolatedRay的获取,下面作简要分析:
理解interpolatedRay
的核心(书本原话):
interpolatedRay是由顶点着色器输出并插值 后得到的射线,它包含了该像素到摄像机的方向 ,也包含了距离信息
首先求出近平面的toTop和toRight两个向量,分别指向相机的向上方向和向右方向,模长分别为高、宽的一半
通过顶点着色器输出并插值,可以得到该像素位置的射线(包含距离信息,不可归一化),此时得到的向量为interpolatedRay,即插值的射线向量
根据摄像机位置和深度,最终可以求得该像素对应的世界空间坐标
脚本 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 using System.Collections;using System.Collections.Generic;using UnityEngine;public class CTFogWithDepthTexture : CTPostEffectsBase { 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; } void OnRenderImage (RenderTexture src, RenderTexture dest ) { if (material != null ) { Matrix4x4 frustumCorners = Matrix4x4.identity; float fov = camera.fieldOfView; float near = camera.nearClipPlane; 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 scale = topLeft.magnitude / near; topLeft.Normalize(); topLeft *= scale; Vector3 topRight = cameraTransform.forward * near + toRight + toTop; topRight.Normalize(); topRight *= scale; Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight; bottomLeft.Normalize(); bottomLeft *= scale; Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop; bottomRight.Normalize(); bottomRight *= scale; frustumCorners.SetRow(0 , bottomLeft); frustumCorners.SetRow(1 , bottomRight); frustumCorners.SetRow(2 , topRight); frustumCorners.SetRow(3 , topLeft); material.SetMatrix("_FrustumCornersRay" , frustumCorners); material.SetFloat("_FogDensity" , fogDensity); material.SetColor("_FogColor" , fogColor); material.SetFloat("_FogStart" , fogStart); material.SetFloat("_FogEnd" , fogEnd); Graphics.Blit(src, dest, material); } else { Graphics.Blit(src, dest); } } }
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 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 Shader "Unity Shaders Book/Chapter 13/CT_FogWithDepthTexture" { Properties { _MainTex ("Base (RGB)" , 2 D) = "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" float4x4 _FrustumCornersRay; sampler2D _MainTex; half4 _MainTex_TexelSize; sampler2D _CameraDepthTexture; half _FogDensity; fixed4 _FogColor; float _FogStart; float _FogEnd; 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 = _FrustumCornersRay[index]; return o; } fixed4 frag (v2f i) : SV_Target { float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth)); float3 worldPos = _WorldSpaceCameraPos + linearDepth * normalize(i.interpolatedRay.xyz); float fogDensity = saturate((_FogEnd - worldPos.y) / (_FogEnd - _FogStart)); fogDensity = saturate(fogDensity * _FogDensity); fixed4 finalColor = tex2D(_MainTex, i.uv); finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity); return finalColor; } ENDCG Pass { ZTest Always Cull Off ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag ENDCG } } FallBack Off }
最终效果
插值结果验证 remap原理:https://zhuanlan.zhihu.com/p/158039963
修改Shader文件
主要就是修改了片元着色器代码,将各分量映射到0~1,其他不变
1 2 3 4 5 6 7 8 9 10 11 half remap (half x, half old1, half old2, half new1, half new2) { return (x - old1) / (old2 - old1) * (new2 - new1) + new1; } fixed4 frag (v2f i) : SV_Target { i.interpolatedRay.x = remap(i.interpolatedRay.x, _FrustumCornersRay[0 ].x, _FrustumCornersRay[1 ].x, 0 , 1 ); i.interpolatedRay.y = remap(i.interpolatedRay.y, _FrustumCornersRay[0 ].y, _FrustumCornersRay[2 ].y, 0 , 1 ); return half4(i.interpolatedRay.xy, 0 , 1 ); }
不难发现,确实插值得很完美
13.4 再谈边缘检测 原理:取对角方向的深度和法线值,比较它们的差值,如果超过某个阈值,就认为它们之间存在一条边
好处:在深度和法线纹理上进行边缘检测,这些图像不会受纹理和光照的影响,而仅仅保存了当前渲染物体的模型信息,通过这样的方式检测出来的边缘更加可靠
脚本 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 using System.Collections;using System.Collections.Generic;using UnityEngine;public class CTEdgeDetectNormalsAndDepth : CTPostEffectsBase { 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 src, RenderTexture dest ) { if (material != null ) { material.SetFloat("_EdgeOnly" , edgesOnly); material.SetColor("_EdgeColor" , edgeColor); material.SetColor("_BackgroundColor" , backgroundColor); material.SetFloat("_SampleDistance" , sampleDistance); material.SetVector("_Sensitivity" , new Vector4(sensitivityNormals, sensitivityDepth, 0.0f , 0.0f )); Graphics.Blit(src, dest, material); } else { Graphics.Blit(src, dest); } } }
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 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 Shader "Unity Shaders Book/Chapter 13/CT_EdgeDetectNormalsAndDepth" { Properties { _MainTex ("Base (RGB)" , 2 D) = "white" {} _EdgeOnly ("Edge Only" , Float) = 1.0 _EdgeColor ("Edge Color" , Color) = (0 , 0 , 0 , 1 ) _BackgroundColor ("Background Color" , 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; 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 centerNormal = center.xy; float centerDepth = DecodeFloatRG(center.zw); half2 sampleNormal = sample.xy; float sampleDepth = DecodeFloatRG(sample.zw); half2 diffNormal = abs (centerNormal - sampleNormal) * _Sensitivity.x; int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1 ; float diffDepth = abs (centerDepth - sampleDepth) * _Sensitivity.y; int isSameDepth = diffDepth < 0.1 * centerDepth; return isSameNormal * 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 }
最终效果
Ch14.非真实感渲染 NRP:Non-Photorealistic Rendering
14.1 卡通风格的渲染 1.渲染轮廓线
本节使用过程式几何轮廓线渲染(方法二),简要介绍实现思路
第一个Pass:描边。我们会使用轮廓线颜色渲染整个背面的面片,渲染时会在视角空间下把模型顶点沿着法线方向向外扩张一段距离,以此来让背部轮廓线可见
第二个Pass:渲染模型
2.添加高光
float spec = dot(worldNormal, worldHalfDir);
spec = lerp(0, 1, smoothstep(-w, w, spec - threshold));
这样的效果是,我们可以在[-w, w]区间内,即高光区域的边界处,得到一个从0到1平滑变化的spec值,从而实现抗锯齿目的。本例中,我们使用邻域像素之间的近似导数值,这可以通过CG的 **fwidth函数 **来得到
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 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 Shader "Unity Shaders Book/Chapter 14/CT_ToonShading" { Properties { _MainTex ("Texture" , 2 D) = "white" {} _Color ("Color Tint" , Color) = (1 ,1 ,1 ,1 ) _Ramp ("RampTexture" , 2 D) = "white" {} _Outline ("Outline" , Range(0 ,1 )) = 0.1 _OutlineColor ("Outline Color" , Color) = (0 ,0 ,0 ,1 ) _Specular ("Specular" , Color) = (1 ,1 ,1 ,1 ) _SpecularScale ("Specular Scale" , Range(0 , 0.1 )) = 0.01 } SubShader { Tags { "RenderType" ="Opaque" "Queue" ="Geometry" } Pass { 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(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" } Cull Back CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" #include "UnityShaderVariables.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _Ramp; fixed4 _Specular; fixed _SpecularScale; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 tangent : TANGENT; }; struct v2f { float4 pos : POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; SHADOW_COORDS(3 ) }; v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex); TRANSFER_SHADOW(o); return o; } float4 frag (v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir); fixed4 c = tex2D(_MainTex, i.uv); fixed3 albedo = c * _Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT; UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); fixed diff = dot(worldNormal, worldLightDir); diff = (diff * 0.5 + 0.5 ) * atten; fixed3 diffuse = _LightColor0 * albedo * tex2D(_Ramp, float2(diff, diff)).rgb; fixed spec = dot(worldNormal, worldHalfDir); fixed w = fwidth(spec) * 2.0 ; fixed3 specular = _Specular * 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 素描风格的渲染 步骤:
首先,在顶点着色器阶段计算逐顶点光照,根据光照来决定6张纹理的混合权重,并传递给片元着色器
然后,在片元着色器中根据这些权重来混合6张纹理的采样结果
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 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 Shader "Unity Shaders Book/Chapter 14/CT_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" , 2 D) = "white" {} _Hatch1 ("Hatch 1" , 2 D) = "white" {} _Hatch2 ("Hatch 2" , 2 D) = "white" {} _Hatch3 ("Hatch 3" , 2 D) = "white" {} _Hatch4 ("Hatch 4" , 2 D) = "white" {} _Hatch5 ("Hatch 5" , 2 D) = "white" {} } SubShader { Tags { "RenderType" ="Opaque" "Queue" ="Geometry" } UsePass "Unity Shaders Book/Chapter 14/CT_ToonShading/OUTLINE" Pass { Tags { "LightMode" ="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" #include "UnityShaderVariables.cginc" fixed4 _Color; float _TileFactor; sampler2D _Hatch0; sampler2D _Hatch1; sampler2D _Hatch2; sampler2D _Hatch3; sampler2D _Hatch4; sampler2D _Hatch5; struct a2v { float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; fixed3 hatchWeights0 : TEXCOORD1; fixed3 hatchWeights1 : TEXCOORD2; float3 worldPos : TEXCOORD3; SHADOW_COORDS(4 ) }; v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord.xy * _TileFactor; fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex)); fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); fixed diff = max(0 , dot(worldLightDir, worldNormal)); 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.0 - 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" }
最终效果
Ch15.使用噪声 15.1 消融效果 原理:概括来说就是噪声纹理+透明度测试
我们使用对噪声纹理的采样结果和某个控制消融程度的阈值比较,如果小于阈值,就使用clip函数把它对应的像素裁减掉,这些部分就对应了图中被“烧毁”的区域
而镂空区域边缘的烧焦效果则将两种颜色混合,再用pow函数处理后,与原纹理颜色混合后的结果
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 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 Shader "Unity Shaders Book/Chapter 15/CT_Dissolve" { Properties { _BurnAmount ("Burn Amount" , Range(0.0 , 1.0 )) = 0.0 _LineWidth("Burn Line Width" , Range(0.0 , 0.2 )) = 0.1 _MainTex ("Base (RGB)" , 2 D) = "white" {} _BumpMap ("Normal Map" , 2 D) = "bump" {} _BurnFirstColor("Burn First Color" , Color) = (1 , 0 , 0 , 1 ) _BurnSecondColor("Burn Second Color" , Color) = (1 , 0 , 0 , 1 ) _BurnMap("Burn Map" , 2 D) = "white" {} } SubShader { Tags { "RenderType" ="Opaque" "Queue" ="Geometry" } Pass { Tags { "LightMode" ="ForwardBase" } Cull Off CGPROGRAM #include "Lighting.cginc" #include "AutoLight.cginc" #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag fixed _BurnAmount; fixed _LineWidth; sampler2D _MainTex; sampler2D _BumpMap; fixed4 _BurnFirstColor; fixed4 _BurnSecondColor; sampler2D _BurnMap; float4 _MainTex_ST; float4 _BumpMap_ST; float4 _BurnMap_ST; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uvMainTex : TEXCOORD0; float2 uvBumpMap : TEXCOORD1; float2 uvBurnMap : TEXCOORD2; float3 lightDir : TEXCOORD3; float3 worldPos : TEXCOORD4; SHADOW_COORDS(5 ) }; v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex); o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap); o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _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; clip(burn.r - _BurnAmount); float3 tangentLightDir = normalize(i.lightDir); fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap)); fixed3 albedo = tex2D(_MainTex, i.uvMainTex).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); fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001 , _BurnAmount)); return fixed4(finalColor, 1 ); } ENDCG } 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 水波效果 原理:
我们使用一张立方体纹理(Cubemap)作为环境映射纹理,模拟反射
为了模拟折射效果,我们使用GrabPass来获取当前屏幕的渲染纹理,并使用切线空间下的法线方向(x,y)对像素的屏幕坐标进行 偏移 ,再使用该坐标对渲染纹理进行屏幕采样,从而模拟近似的折射效果
本节与中级篇第10章玻璃效果的不同之处在于,水波的法线纹理是由一张噪声纹理生成而得,而且会随着时间变换不断平移,模拟波光粼粼的效果。除此之外,我们没有使用一个定值来混合反射和折射颜色,而是使用之前提到的菲涅尔系数来动态决定混合系数
菲涅尔系数计算公式:fresnel=pow(1-max(0, v·n), 4)
其中,v和n分别对应了视角方向和法线方向,它们之间夹角越小,fresnel值越小,反射越弱,折射越强
菲涅尔系数还经常会用于边缘光的计算中
折射:使用切线空间的法线,偏扰屏幕uv坐标,进而采样屏幕纹理即可
反射:使用反射向量采样立方体贴图
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 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 Shader "Unity Shaders Book/Chapter 15/CT_WaterWave" { Properties { _Color ("Main Color" , Color) = (0 , 0.15 , 0.115 , 1 ) _MainTex ("Base (RGB)" , 2 D) = "white" {} _WaveMap ("Wave Map" , 2 D) = "bump" {} _Cubemap ("Environment Cubemap" , Cube) = "_Skybox" {} _WaveXSpeed ("Wave Horizontal Speed" , Range(-0.1 , 0.1 )) = 0.01 _WaveYSpeed ("Wave Vertical Speed" , Range(-0.1 , 0.1 )) = 0.01 _Distortion ("Distortion" , Range(0 , 100 )) = 10 } SubShader { Tags { "Queue" ="Transparent" "RenderType" ="Opaque" } GrabPass { "_RefractionTex" } Pass { Tags { "LightMode" ="ForwardBase" } CGPROGRAM #include "UnityCG.cginc" #include "Lighting.cginc" #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _WaveMap; float4 _WaveMap_ST; samplerCUBE _Cubemap; fixed _WaveXSpeed; fixed _WaveYSpeed; float _Distortion; sampler2D _RefractionTex; float4 _RefractionTex_TexelSize; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float4 scrPos : TEXCOORD0; float4 uv : TEXCOORD1; float4 TtoW0 : TEXCOORD2; float4 TtoW1 : TEXCOORD3; float4 TtoW2 : TEXCOORD4; }; v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.scrPos = ComputeGrabScreenPos(o.pos); o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap); float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x); o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y); o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z); return o; } fixed4 frag (v2f i) : SV_Target { float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w); fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos)); float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed); fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb; fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb; fixed3 bump = normalize(bump1 + bump2); float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy; i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy; fixed3 refrCol = tex2D( _RefractionTex, i.scrPos.xy/i.scrPos.w).rgb; bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump))); fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed); fixed3 reflDir = reflect(-viewDir, bump); fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb * _Color.rgb; fixed fresnel = pow (1 - saturate(dot(viewDir, bump)), 4 ); fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel); return fixed4(finalColor, 1 ); } ENDCG } } FallBack Off }
最终效果 ![GIF 2021-11-4 17-41-51](高级 12-16.assets/GIF%202021-11-4%2017-41-51.gif)
15.3 动态雾效 回顾全局雾效:
我们由深度纹理重建每个像素在世界空间下的位置,再使用一个基于高度的公式来计算雾效的混合系数,最终使用该系数来混合雾的颜色和屏幕颜色
本节要求:
之前是基于高度的均匀雾效,即在同一个高度上,雾的浓度是相同的。这节我们要使用一张噪声纹理,让雾不断飘动,看起来更加缥缈
实现思路:
本节实现非常简单,绝大多数代码和[全局雾效](#13.3 全局雾效*)中的完全一样,只是添加噪声相关的参数和属性,并在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 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 using System.Collections;using System.Collections.Generic;using UnityEngine;public class CTFogWithNoise : CTPostEffectsBase { 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.1f, 3.0f) ] public float fogDensity = 1.0f ; public Color fogColor = Color.white; public float fogStart = 0.0f ; public float fogEnd = 2.0f ; public Texture noiseTexture; [Range(-0.5f, 0.5f) ] public float fogXSpeed = 0.1f ; [Range(-0.5f, 0.5f) ] public float fogYSpeed = 0.1f ; [Range(0.0f, 3.0f) ] public float noiseAmount = 1.0f ; void OnEnable () { GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth; } void OnRenderImage (RenderTexture src, RenderTexture dest ) { if (material != null ) { Matrix4x4 frustumCorners = Matrix4x4.identity; float fov = camera.fieldOfView; float near = camera.nearClipPlane; 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 scale = topLeft.magnitude / near; topLeft.Normalize(); topLeft *= scale; Vector3 topRight = cameraTransform.forward * near + toRight + toTop; topRight.Normalize(); topRight *= scale; Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight; bottomLeft.Normalize(); bottomLeft *= scale; Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop; bottomRight.Normalize(); bottomRight *= scale; frustumCorners.SetRow(0 , bottomLeft); frustumCorners.SetRow(1 , bottomRight); frustumCorners.SetRow(2 , topRight); frustumCorners.SetRow(3 , topLeft); material.SetMatrix("_FrustumCornersRay" , frustumCorners); material.SetFloat("_FogDensity" , fogDensity); material.SetColor("_FogColor" , fogColor); material.SetFloat("_FogStart" , fogStart); material.SetFloat("_FogEnd" , fogEnd); material.SetTexture("_NoiseTex" , noiseTexture); material.SetFloat("_FogXSpeed" , fogXSpeed); material.SetFloat("_FogYSpeed" , fogYSpeed); material.SetFloat("_NoiseAmount" , noiseAmount); Graphics.Blit(src, dest, material); } else { Graphics.Blit(src, dest); } } }
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 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 Shader "Unity Shaders Book/Chapter 15/CT_FogWithNoise" { Properties { _MainTex ("Base (RGB)" , 2 D) = "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 _NoiseTex ("Noise Texture" , 2 D) = "white" {} _FogXSpeed ("Fog Horizontal Speed" , Float) = 0.1 _FogYSpeed ("Fog Vertical Speed" , Float) = 0.1 _NoiseAmount ("Noise Amount" , Float) = 1 } SubShader { CGINCLUDE #include "UnityCG.cginc" float4x4 _FrustumCornersRay; sampler2D _MainTex; half4 _MainTex_TexelSize; sampler2D _CameraDepthTexture; half _FogDensity; fixed4 _FogColor; float _FogStart; float _FogEnd; sampler2D _NoiseTex; half _FogXSpeed; half _FogYSpeed; half _NoiseAmount; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float2 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 = _FrustumCornersRay[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; float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed); float noise = (tex2D(_NoiseTex, i.uv + speed).r - 0.5 ) * _NoiseAmount; float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); fogDensity = saturate(fogDensity * _FogDensity * (1 + noise)); fixed4 finalColor = tex2D(_MainTex, i.uv); finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity); return finalColor; } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag ENDCG } } FallBack Off }
最终效果 ![GIF 2021-11-4 17-40-40](高级 12-16.assets/GIF%202021-11-4%2017-40-40.gif)
Ch16.Unity中的渲染优化技术* 本章涉及的优化技术:
CPU优化
GPU优化
节省内存带宽
16.1 Unity中的渲染分析工具 渲染统计窗口
Rendering Statistics Window
![image-20211104154855347](高级 12-16.assets/image-20211104154855347.png)
性能分析器
Profiler
打开(2019.4):Window->Analysis->Profiler
帧调试器
Frame Debugger
打开:Window->Analysis->Frame Debugger
16.2 减少DrawCall数目 使用批处理(batching)技术,前提是共享 同一个材质且材质属性相同,Unity中支持两种:
动态批处理:Unity自动完成 ,优点是仍可移动;缺点是限制多,容易破坏这种机制
静态批处理:自由度很高,限制很少;缺点是可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了(包括脚本也无法改变其位置)
这节总结起来就是两句话:
模型之间能共享材质就共享
将模型设为Batching Static,可以用空间换时间
动态批处理 原理:每一帧把可以进行批处理的模型网格进行合并,再把合并后模型数据传递给GPU,然后使用同一个材质对其渲染
主要的条件限制(基于Unity5.2.1):
静态批处理 原理:只在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网格结构中,这意味着这些模型不可以在运行时刻被移动。但由于它只需要进行一次合并操作,因此,比动态批处理更加高效
缺点及产生原因:需要占用更多的内存来存储合并后的几何结构。如果在静态批处理前一些物体共享了相同的网格,那么内存中每个物体都会对应一个该网格的复制品,即一个网格会变成多个网格再发给CPU(程序上,体现在VBO total的变化,VBO数目更大了)
共享材质 无论是静态批处理还是动态批处理,都要求模型之间需要共享同一材质且属性相同
两个材质纹理不同,可以把这些纹理合并到一张更大的纹理中,再使用不同的采样坐标对纹理采样即可
两个材质参数不同,可以使用网格的顶点数据(最常见的是顶点颜色)来存储这些参数
注意点:
在脚本中访问共享材质,应该使用Renderer.sharedMaterial来保证修改的是和其他物体共享的材质。若使用Renderer.material来修改材质,Unity会创建一个该材质的复制品,从而破坏批处理在该物体上的应用
小结之小建议
尽可能选择静态批处理,但时刻小心内存的消耗,牢记经过静态批处理的物体不可以再被移动
如果无法进行静态批处理,而要使用动态批处理的话,那么请小心上面提到的各种条件限制
尽可能让这样的物体少并且包含少量顶点属性和顶点数目
对于游戏中的小道具,例如可以捡拾的金币等,可以使用动态批处理
对于包含动画的这类物体,我们无法全部使用静态批处理,但其中如果有不动的部分,可以把这部分标识成“Static”
DisableBatching标签的使用。批处理需要把多个模型变换到世界空间下再合并它们,因此,如果shader中存在基于模型空间下的坐标运算,往往会得到错误的结果。需要使用DisableBatching标签 强制shader的材质不会被批处理
Unity5.2中渲染摄像机的深度纹理等部分没有实现批处理
16.3 减少需要处理的顶点数目 3个常用顶点优化策略
优化几何体:建模时,移除不必要的硬边以及纹理衔接,避免边界平滑和纹理分离
模型的LOD技术
原理:物体根据离摄像机远近,动态改变模型上面片的数量,从而提高性能
Unity中使用LOD Gropu
组件来为一个物体构建一个LOD,我们需要为同一个对象准备多个包含不同细节程度的模型,然后赋值给不同等级
遮挡剔除技术
16.4 减少需要处理的片元数目 这一部分优化的中点在于减少overdraw,即减少同一个像素被绘制了多次
控制渲染顺序。由于深度测试的存在,如果我们可以保证物体都是从前往后绘制的,那么就可以很大程度上减少overdraw。理由显而易见
在Unity中,渲染队列小于2500(如”Background”,”Geometry”和”AlphaTest”)的对象都被认为是不透明(opaque)的物体,这些物体总体上是从前往后绘制的。所以尽量把物体的渲染队列设为不透明 的
时刻警惕透明物体。由于它们没有开启深度写入,因此,如果要得到正确的渲染效果,就必须从后往前渲染。这意味着半透明物体会从后往前渲染,如果不注意,在一些机器会严重影响性能
GUI通常是半透明的,我们可以尽量减少窗口中GUI所占面积。或者,将GUI的绘制和三维场景的绘制交给不同的摄像机,二者尽量不要重叠(不实用)
减少实时光照和阴影
16.5 节省带宽 大量使用未经压缩的纹理以及使用过大的分辨率都会造成由于带宽而引发的性能瓶颈
节省纹理大小
长宽值最好为2的整数幂
尽可能使用多级渐远纹理技术和纹理压缩
利用分辨率缩放
16.6 减少计算复杂度 两个方面减少计算复杂度:
Shader的LOD技术
原理:只有Shader的LOD值小于 某个设定的值,这个Shader才会被使用,而使用了那些超过设定值的Shader的物体将不会被渲染
1 2 3 4 5 SubShader { Tags {"RenderType" ="Opaque" } LOD 200 ...... }
我们可以在Untiy Shader的导入面板上看到该Shader使用的LOD值。默认情况下,允许的LOD等级是无限大,意味着任何被当前显卡支持的Shader都可以被使用。若想去掉一些复杂计算的Shader渲染,可以使用Shader.maximumLOD或Shader.globalMaximumLOD来设置允许的最大LOD值
Unity内置的Shader实验了不同的LOD值,例如Diffuse的LOD为200,而Bumped Specular的LOD为400
代码方面的优化 实现游戏效果时,我们可以选择在哪里进行某些特定的运算。通常来讲,游戏需要计算的对象、顶点、像素的数目排序为:对象数 < 顶点数 < 像素数
我们应尽可能把计算放在每个对象和顶点上
尽可能使用低精度的浮点值进行运算
上图参考CSDN:https://www.cnblogs.com/hiker-online/p/14228555.html
顶点着色器输出给下一阶段时,我们应该使用尽可能少的插值变量
通常,如果需要对两个纹理坐标进行插值,我们会打包在同一个float4变量中(VR平台除外)
尽可能不要使用全屏的屏幕后处理效果
代码优化规则
尽可能不要使用分支和循环语句
尽可能避免使用类似sin、tan、pow、log等较为复杂的数学运算。可以使用查找表来替代
尽可能不要使用discard,因为这会影响硬件的某些优化