Unity Shader入门精要 读书笔记
简介
《Unity Shader入门精要》的作者是冯乐乐,本书是一本在游戏业内广为流传的shader基础入门书籍。
注意事项
这篇笔记是从第12章开始的,笔记中的代码都是URP管线下的HLSL
书目
- 第十二章 屏幕后处理效果
- 第十三章 使用深度和法线纹理
- 第十四章 非真实渲染
- 第十五章 使用噪声
- 第十六章 Unity中的渲染优化技术
- 第十七章 Unity的表面着色器探秘
- 第十八章 基于物理的渲染
- 第十九章 Unity5 更新了什么
- 第二十章 还有更多内容么
第十二章 屏幕后处理效果
简介
- 屏幕后处理效果(screen post-processing effects)是游戏中实现屏幕特效的常见方法
12.1 建立一个基础的屏幕后处理脚本系统
屏幕后处理:渲染完整个场景得到屏幕图像后,对其进行一系列操作的技术,如:景深,运动模糊,各种故障效果。
后处理的基础基础是获取渲染后的屏幕图像,即抓取屏幕,unity提供了OnRenderImage
1
MonoBehaviour.OnRenderImage(RenderTexture src, RednerTexture dest)
Unity会将当前渲染的图像存储在RenderTexture src,然后函数处理后在输出到RednerTexture dest
通才利用Graphics.Blit函数来处理纹理,函数如下:
1
2
3
4
5public 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函数调用逻辑:
- src 是源纹理
- dest 是目标渲染纹理,如果为null则直接显示结果
- mat 是使用的材质,src会被传递给_MainTex
- pass 默认为-1,依次调用pass
OnRenderImage函数调用 - OnRenderImage函数会在所有不透明和透明的Pass执行完毕后被调用
- 如果不想对透明产生应影响 可以在OnRenderImage前添加ImageEffectOpaque
Unity实现屏幕后处理的流程 - 相机添加后处理脚本
- 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// 检查条件脚本
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
39using 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
66Shader "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
// make fog work
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用于修改输入图像的亮度,饱和度和对比度
- 其中关于饱和度就是一个灰度值加权公式具体请看Grayscale-Wikipedia
12.3 边缘检测
简介
- 边缘检测是描边效果的一种实现方法
- 边缘检测的原理:
- 利用边缘检测算子对图像进行卷积(Convolution)操作
12.3.1 什么是卷积
卷积操作指使用一个卷积核(kernel)对一张图像的每个像素进进行一系列操作
卷积核通常是一个四方形网格结构,该区域每个方格都有一个权重值
12.3.2 常见的边缘检测算子
相邻像素之间存在差别明显的颜色,亮度,纹理等属性,这个状态下他们之间就应该有一条边界。
相邻像素之间的差值可以用梯度(gradient)来表示。
12.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
36using 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
94Shader "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
// make fog work
struct v2f
{
// UV 定义
float2 uv[9] : TEXCOORD0;
float4 pos : SV_POSITION;
};
sampler2D _MainTex;
half4 _MainTex_TexelSize;
// 用于访问纹理的每个纹素大小
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 高斯模糊
简介
高斯模糊 通过高斯函数模糊图像的方法叫高斯模糊[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 当前位置到卷积核中心的整数距离
- 为了图像不变暗,需要高斯核的权重归一化,即每个权重除于所有权重的和。故$e$之前的系数不会对结果造成影响
高斯采样如果使用一个NxN的高斯核,需要N * N * W * H次采样
如果使用2个一维函数惊醒采样,只需要 2 * N * W * H 次采样
12.4.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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62using 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);
// 释放和缓存
}
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
90Shader "Unlit/GaussianBlur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BlurSize("Blur Size",Float) = 1.0
}
SubShader
{
//为了避免重复定义
CGINCLUDE
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
ENDCG
}
Pass {
NAME "GAUSSIAN_BLUR_HORIZONTAL"
CGPROGRAM
ENDCG
}
}
FallBack "Diffuse"
}
12.5 Bloom
简介
Bloom 辉光,模拟真实相机的图像效果,让画面中比较亮的区域“扩散”到周围区域,造成一种朦胧感- Bloom实现原理
- 根据阈值提取图像中比较亮的区域,存在一个RT中
- 再使用高斯模糊处理这张RT
- 与原图像混合
后处理脚本的实现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
65using 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);
// 释放和缓存
}
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
88Shader "Unlit/Bloom"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Bloom ("Bloom",2D) = "black" {}
_LuminanceThreshold("LuminanceThreshold",Float) = 0.5
_BlurSize("Blur Size",Float) = 1.0
}
SubShader
{
CGINCLUDE
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(_MainTex_TexelSize.y<0.0)
o.uv.w = 1.0-o.uv.w;
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
ENDCG
}
UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"
UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"
//调用指定Shader里的指定pass
//这个路径是sahder的路径,不是文件夹夹结构
Pass {
CGPROGRAM
ENDCG
}
}
FallBack off
}- Bloom实现原理
12.6 运动模糊
- 简介
- 运动模糊是真实世界的相机的一个效果,当相机曝光时,拍摄场景发生变化,就会发生模糊的画面
- 运动模糊实现的方法
- 利用累加缓存(accumulation buffer)来混合多张连续的图像,最后取平均值
- 创建和使用速度缓存(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
49using 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);
//隐藏并且不保存
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
59Shader "Unlit/MotionBlur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BlurAmount("Blur Amount",Float) = 1.0
}
SubShader
{
CGINCLUDE
sampler2D _MainTex;
fixed _BlurAmount;
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
ENDCG
}
Pass
{
Blend One Zero
ColorMask A
CGPROGRAM
ENDCG
}
}
FallBack off
}
- 简介
12.7 拓展阅读
- Unity Image Effect
- GPU Games - GPU Games 3 - 27章
总结
本章共学习了- 亮度,饱和度,对比度
- 边缘检测,后处理描边
- 高斯模糊
- 辉光
- 运动模糊
第十三章 使用深度和法线纹理
13.1 获取深度和法线纹理
13.1.1 背后的原理
- 深度纹理
- 深度纹理是一张渲染纹理,存储的是高精度的深度值
- 深度值范围是[0,1],但通常是非线性分布的。
- 通过MVP获得
- Unity如何获取深度纹理
- 延迟渲染,再G-buffer中
- 使用着色器替换技术选择渲染类型为Qpaque的物体,若渲染队列小于等于2500,就渲染到深度和法线中
- 存储位置
- 观察空间下,法线存储在R,G
- 观察空间下,深度存储在B,A
- 深度纹理
13.1.2 如何获取
获取深度纹理的方法
- 在脚本中设置摄像机的depthTextureMode
1
camera.depthTextureMode = DepthTextureMode.Depth;
- 在脚本中设置摄像机的depthTextureMode
- Shader中使用“_CameraDepthTexture”来访问它
获取深度和法线纹理
- 在脚本中设置摄像机的depthTextureMode
1
camera.depthTextureMode = DepthTextureMode.DepthNormals;
- 在脚本中设置摄像机的depthTextureMode
- Shader中使用“_CameraDepthNoramlsTexture”来访问它
如何采样该纹理
- 可以使用 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_{clip}$对视角空间的顶点变换,裁剪空间下顶点的$z$和$w$如下:
- $Z_{clip}=-Z_{view}\frac{Far+Near}{Far-Near}-\frac{2 \cdot Far \cdot Near}{Far-Near}$
- $w_{clip} = -Z_{view}$
- $Far$远裁剪平面距离
- $Near$近裁剪平面距离
- 通过齐次除法获得NDC的$z$分量
- $z_{ndc} = \frac {z_{clip}}{w_{clip}} = \frac{Far+Near}{Far-Near}+\frac{2Near \cdot Far}{(Far-Near) \cdot z_{view}}$
- 深度值计算
- $d = 0.5z_{ndc}+0.5$
- 通过如上公式获得
- $z_{view} = \frac{1}{\frac{Far-Near}{Near \cdot Far}d+\frac{1}{Near}}$
- Unity时间空间中摄像机正向为-z,因此结果取反
- $z_{view} = \frac{1}{\frac{Near-Far}{Near \cdot Far}d+\frac{1}{Near}}$
- 当0表示该点与摄像机同一位置,1表示该点位于视锥体的远裁剪平面
- $z_{01} = \frac{1}{\frac{Near-Far}{Near}d+\frac{Far}{Near}}$
- 裁剪矩阵$P_{clip}$对视角空间的顶点变换,裁剪空间下顶点的$z$和$w$如下:
- Unity 提供的转换函数
- LinearEyeDepth:将深度纹理的采样结果转换到视角空间下的深度值
- Linear01EyeDepth:返回一个0到1的线性深度值
- 内置变量 _ZBufferParams:获得远近裁剪平面的距离
- 推导过程
如何获得深度+法线纹理
- tex2D 采样_CameraDepthNoramlsTexture
- 信息解码函数如下
1
2
3
4inline void DecodeDepthNoraml(float4 enc,out float depth , ouyt float3 normal){
depth = DecodeFloatRG(enc.zw);
noraml = DecodeViewNoramlStereo(enc);
}- 获得深度值是0到1的线性深度值
- 法线是视角空间下的法线
13.1.3 查看深度和法线纹理
- 如何查看生成的深度和法线纹理
- Frame Debugger
- RenderDoc
- 输出线性空间或者转码后的法线和深度信息
1
2
3float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
floar linearDepth = Linear01Depth(depth);
return fixed4(linearDepth,linearDepth,linearDepth,1.0);1
2float3 noraml = DecodeViewNoramlStereo(tex2D(_CameraNoramlsTexture,i.uv).xy);
return fixed4(noraml*0.5+0.5,1.0);
- 如何查看生成的深度和法线纹理
13.2 再谈运动模糊
Gpu Gems3中提到的方案
实现逻辑
- 利用深度纹理再片元着色器中计算像素的世界空间位置
- 使用前一帧的视角*投影矩阵把变换获得的前一帧的NDC左边
- 计算前一帧和当前帧的位置差,生成速度
优点
可以再一个后处理中完成整个模拟
缺点
片元进行了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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58using 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
71Shader "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 全局雾效
简介
- 雾效(Fog)是游戏中常见的一种效果
- 基于屏幕后处理的全局雾效的核心,根据深度纹理重建每个像素在世界空间下的位置
- 首先对图像空间下的视锥体射线进行插值,射线存储的是该像素在世界空间下到摄像机的方向信息
- 把该射线和线性化后的视角空间下的深度值相乘,加上摄像机的世界位置,获得该像素的在世界空间的位置
13.3.1 重建世界坐标
如何从深度纹理中重建世界坐标
思想:坐标系中的顶点位置可以通过它相对于另一个顶点坐标的偏移量获得。
1
2
3float4 worldPos = _WorldSpaceCameraPos+linearDepth*interpolatedRay;
// _WorldSpaceCameraPos 摄像机世界空间位置
// linearDepth*interpolatedRay 像素对于相机的偏移量interpolatedRay 的获得方法
interpolatedRay是近裁剪平面4个角的某个特定向量的插值,这4个向量包含了它们到摄像机的距离和方向信息
可以利用摄像机近裁剪平面距离,Fov,纵横比计算获得
toTop 和 toRight 计算公式如下,他们是起点位于近裁剪平面中心,分别指向摄像机正上方和正右方的向量
- $halfHeight = Near \times \tan(\frac{FOV}{2})$
- $toTop = camera.up \times halfHeight$
- $toRight = camera.right \times halfHeight \cdot aspect$
- Near 是近裁剪平面距离
- FOV 是竖直方向视角范围
- camera.up 摄像机正上方
- camera.right 摄像机的正右方
四个角相对于摄像机的位置的计算公式
- $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|}$
- 所以距离如下
- $dist = \frac{|TL|}{Near} \times depth$
- 根据相似三角形 TL所在的射线上,像素的深度值和它到摄像机的实际距离比等于近裁剪平面的距离和TL向量的摸的比
由于4个点对称,因此模长相同,所以我们用单一的因子进行计算
- $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$
相关的示意图如下
13.3.2 雾的计算
- 首先需要一个雾效系数,作为混合系数
1
float3 afterFog = f*fogColor +(1-f)*origColor;
- 内置的雾效计算方式
- 线性(Linear)
- $f = \frac{d_{max}-|z|}{d_{max}-d_{min}}$
- $d_{max}$雾的最大影响范围
- $d_{min}$雾的最小影响范围
- $f = \frac{d_{max}-|z|}{d_{max}-d_{min}}$
- 指数(Exponential)
- $f=e^{-d\cdot|z|}$
- d 是是控制雾浓度的值
- $f=e^{-d\cdot|z|}$
- 指数的平方(Exponential Squared)
- $f = e^{-(d-|z|)^2}$
- d 是是控制雾浓度的值
- $f = e^{-(d-|z|)^2}$
- 线性(Linear)
- 内置的雾效计算方式
- 类似线性雾的计算方式,基于高度的雾效
- 给定一个点的世界空间下的高度y
- 公式
- $f = \frac{H_{end}-y}{H_{end}-H_{start}}$
- $H_{end}$ 雾影响的终止高度
- $H_{start}$ 雾影响的起始高度
- $f = \frac{H_{end}-y}{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
103using 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);
//将向量存储到矩阵中
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
88Shader "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 再谈边缘检测
简介
使用深度纹理进行边缘检测更可靠,对比效果如下
本章使用 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
48using 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
90Shader "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;
// 用于访问纹理的每个纹素大小
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)
总结
- 本章学习了
- 基于深度的运动模糊
- 全局雾效
- 基于深度的描边
第十四章 非真实渲染
- 本章学习了
简介
- 非真实渲染(Non-Photorealistic Rendering,NPR)使用一些方法让画面达到某种特殊绘画风格类似的效果
14.1 卡通风格的渲染
简介
- 卡通渲染的特点:
- 描边
- 分明的变换
- 技术简介:
- 基于色调的作色技术(tone-based shading),1998年Gooch提出的
- 卡通渲染的特点:
14.1.1 渲染轮廓线
渲染轮廓线的方案
- 基于观察角度和表面法线的轮廓线渲染
- 过程式几何轮廓线渲染
- 基于图像处理的轮廓线渲染
- 基于轮廓边检测的轮廓线渲染
- 混合以上的方案
本章的方案是 过程式几何轮廓线渲染
双Pass,第一个pass渲染背面,然后在视角空间下沿法线方向扩张,另一个pass渲染正面
1
viewPos = viewPos +viewNormal *_Outline;
为了处理一些内凹的模型,背面面片遮挡正面面片的情况,需要处理法线的Z分量
1
2
3viewNormal.z = -0.5;
viewNormal = normalize(viewNormal);
viewPos = viewPos + viewNormal * _Outline;
14.1.2 添加高光
和Blinn-Phong 不同,需要进行阈值比较,小于阈值,反射系数为0,否则返回1.
1
2float spec = dot(worldNoraml,worldHalfDir);
spec = step(threshold,spec);抗锯齿优化
1
2float spec = dot(worldNoraml,worldHalfDir);
spec = lerp(0,1,smppthstep(-w,w,spec-threshold));拓展参考
- 风格化高光参考:2003年 ANJYO
- https://blog.csdn.net/candycat1992/article/details/47284289
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 素描风格的渲染
简介
- 2001年,微软研究院的Praum等人在SIGGRAPH提出
- 色调艺术映射(Tonal Art Map,TAM)
实现思路
- 使用6张素描纹理进行渲染
- 根据光照结果来决定6张纹理的混合权重
- 在片元中根据权重混合采样结果
实现
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 上有许多关于非真实感渲染的论文
- 《艺术化绘制的图形学原理与方法》 是一本不错的参考书
- Unity 资源包
- Toon Shader Free是一个免费卡通资源包
- Toon Styles Shader Pack 付费
- Hnad-Drawn Shader Pack 付费
第十五章 使用噪声
15.1 消融效果
简介
- 消融(dissolve)效果常见于游戏中的角色死亡,地图烧毁效果
实现
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 水波效果
实现思路
- 使用噪声纹理作为高度图,改变水面的法相方向
- 使用时间相关的变量对噪波采样
使用的计算菲涅耳系数的公式:
$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),利用计算机算法生成
- 常用的噪声
- Perlin 噪声
- http://f1afla2.github.io/2014/08/09/perlinnoise.html
- Worley 噪声
- http://scrawkblog.com/category/procedural-noise/
## 第十六章 Unity中的渲染优化技术
- 16.1 移动平台的特点
- 特点
- 1. GPU架构专注于尽可能使用更小的带宽和功能
- 造成的优化手段
- 1. 使用基于瓦片的延迟渲染(Tiled-based-Deferred Rendering,TBDR)架构,为了较少OverDraw
- 2. Early-Z或者相似技术进行一个低精度的深度检测,来剔除那些不需要的片元,Adreno和Mali芯片
- IOS硬件指南http://docs.unity3d.com/Manual/iphone-Hardware.htrnl
- 16.2 影响性能的因素
- 性能瓶颈的主要原因
- CPU
- 过多的Draw Call
- 复杂的脚本或者物理模拟
- GPU
- 顶点处理
- 过多的顶点
- 过多的逐顶点计算
- 片元
- 过多的片元(分辨率或者overdraw)
- 过多的逐片元计算
- 带宽
- 使用了储存很大且未压缩的纹理
- 分辨率过高的帧缓存
- 优化手段
- cpu优化
- 使用批处理减少draw call数目
- gpu优化
- 减少需要处理的顶点数目
- 优化几何体
- 使用lod
- 使用遮挡剔除(Occlus ion Culling)技术
- 减少需要处理的片元数目
- 控制绘制顺序
- 警惕透明物体
- 减少实时光照
- 减少计算复杂度
- 使用shader的lod
- 代码优化
- 带宽优化
- 减少纹理大小
- 利用分辨率缩放
- 16.3 Unity中的渲染分析工具
- 简介
- 渲染统计窗口(Rendering Statistics Window)
- 性能分析器(Profiler)
- 帧调试器(Frame Debugger)
- 16.3.1 认识Unity5的渲染统计窗口
- 位置:Game视窗右上方的菜单(Stats)
- 音频(Audio)
- 图像(Graphics)
- 网络(Network)
- 重要信息
- FPS 处理和渲染一帧的时间和FPS(Frames Per Second)
- Batches 一帧需要进行的批处理数目
- saved by batching 合并的批处理数目,表明节省了多少draw call
- Tris和Verts 需要绘制的三角面片和顶点数目
- screen 屏幕大小以及占用内存大小
- SetPass pass数目
- Visible Skinned Meshes 渲染的蒙皮网格的数目
- Animations 动画数目
- Shadow Casters 可以投射阴影的物体数量
- 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试图查看该事件绘制结果
- 16.3.4 其他性能分析工具
- Android
- 高通的Adreno分析工具
- 英伟达 NvPerfHUD工具
- ios
- PowerVRAM的PVRUniSCo shader 大致性能评估
- Xcode 的 OpenGL ES Driver Instruments 宏观性能信息,设备利用率和渲染器利用率
- 补充
- 现在(2021)流行使用RenderDoc进行分析
- 16.4 减少draw call数目
- 批处理(batching)
- 目的 为了较少每帧的draw call数目
- 原理 :
- 每次调用draw call 尽可能处理多个物体
- 使用条件:
- 使用相同材质的物体
- 顶点不同可以合并顶点数据
- 方式:
- 动态批处理
- 自动完成
- 限制很多
- 静态批处理
- 自由度搞,限制少
- 占用内存多
- 处理后无法被移动
- 16.4.1 动态批处理
- 优点
- 实现方便
- 经过批处理的物体仍然可以移动(因为处理每帧时unity都会重新合并一次网格)
- 条件:
- 能够进行动态批处理的网格的顶点属性规模要小于900
- 保证指向光纹理中的位置相同,lightmap
- 多pass的shader会中断批处理
- 16.4.2 静态批处理
- 适用范围:
- 任何大小的几何模型
- 问题:
- 内存增大
- 不可移动
- 16.4.3 共享材质
- 注意:是同一个材质,而非同一种
- 纹理不同可以合并纹理
- 数据不同,部分可以移动到顶点数据里
- 16.4.4 批处理的注意事项
- 建议
- 尽可能选择静态批处理,小心内存
- 如果要使用动态批处理
- 对于游戏中的小道具可以使用动态批处理
- 对于包含动画这类物体,我们无法全部使用静态批处理,如果不动的的部分可以标为“Static”
- 16.5 减少需要处理的顶点数目
- 16.5.1 优化几何体
- 移除不必要的硬边以及纹理衔接,避免边界平滑和纹理分离
- 16.5.2 模型的LOD技术
- 使用lodGroup
- 模型准备多个不同细节程度的模型
- 16.5.3 遮挡剔除技术
- 遮挡剔除技术(Occlusion culling)
- 用来消除那些在其他物件后面看不到的物体
- 与视锥体剔除(Frustum culling)不同
- 优化了什么
- 减少需要处理的顶点数目
- 减少overdraw·
- 16.6 减少需要处理的片元数目
- 16.6.1 控制绘制顺序
- 因为深度测试的存在,可以保证物体都是从前往后绘制的,能很大程度减少Overdraw(红棉绘制的物体无法通过深度测试,不会进行后面的渲染处理)
- 16.6.2 时刻警惕透明物体
- 原因,半透明对象没开启深度写入,必须从后往前渲染,几乎一定会造成Overdaw。
- 16.6.3 减少实时光照和阴影
- 原因,对于逐像素的光源来说,被这些光源照亮的物体需要再被渲染一次,而且无法批处理,会中断批处理。
- 解决办法,使用烘焙技术或者使用God Ray。
- LUT
- 16.7 节省带宽
- 16.7.1 减少纹理大小
- 长宽最好是二次幂
- mipmapping 多级渐远纹理技术
- 纹理压缩,不同平台压缩格式不同。
- 16.7.2 利用分辨率缩放
- 对于特定机器进行分辨率的缩放,
- 16.8 减少计算复杂度
- 计算复杂度会影响游戏的渲染性能
- 16.8.1 Shader的LOD技术
- 当Shader的LOD值小于某个设定的值时,该Shader才会被使用
```C
SubShader{
Tags { "RenderType"="Qpaque"}
LOD 200
}- 16.8.2 代码方面的优化
对象<顶点<像素- 尽可能使用低精度的浮点值运算,float 存储(顶点坐标),half适合标量纹理坐标,fixed 适合颜色变量和归一化的方向矢量,避免使用swizzle计算
- 减少插值变量
- 尽可能不要使用全屏的屏幕后处理效果
- 尽可能把多个特效合并到一个Shader中
- 尽可能不要使用分支语句和循环语句
- 尽可能避免使用sin,tan,pow,log等复杂的数学计算
- 尽可能不要使用discard操作,会影响硬件的某些优化
- 16.8.3 根据硬件条件进行缩放
scaiing 放缩思路
- 16.8.2 代码方面的优化
16.9 拓展阅读
- 官方手册 移动平台永华实践指南
第十七章 Unity的表面着色器探秘
表面着色器(Surface Shader)是顶点/片元着色器的抽象。
17.1 表面着色器的一个例子
17.2 编译命令
编译命令的作用指明该表面着色器使用的表面函数和光照函数,并设置一些可选参数。1
2- 17.2.1 表面函数
名称surf - 17.2.2 光照函数
自由度低,不好优化,不如用顶点片元
第十八章 基于物理的渲染
基于物理的渲染技术 (Physlcally Based Shading,PBS)
- 17.2.1 表面函数
18.1 PBS的理论和数学基础
- 18.1.1 光是什么?
电磁波,光纤具有吸收和散射的特性。 影响光传播的重要特质是材质的折射率。
光被折射到非金属物体内部,被散射出去的是次表面散射光 - 18.1.2 双向反射分布函数(BRDF)
- 辐射率radiance 是单位面积,单位方向上光源的辐射通量,通常用L表述
BRDF的理解 给定入射角度后,BRDF可以给出所有出射方向上的反射和散射光线的相对分布情况
当给定观察方向(出射角度),BRDF可以给出所有入射方向到该出射方向的光线分布。第十九章 Unity5 更新了什么
第二十章 还有更多内容么
- 辐射率radiance 是单位面积,单位方向上光源的辐射通量,通常用L表述
- 18.1.1 光是什么?
参考
关于本文
本文作者 Master Gong Sheng, 许可由 CC BY-NC 4.0.