什么是渲染路径

程序员指定渲染路径,是想配置光照属性与渲染流程。这是程序员与Unity的一个暗号。

渲染路径中主要有两种,一是前向渲染路径(Forward Rendering Path),一是延迟渲染路径(Differred Rendering Path)。

前向渲染路径

前向渲染需要计算两个缓冲区的信息,一是颜色缓冲区,二是深度缓冲区。利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区。

Unity含有内置的光照变量和函数,指定了前向渲染路径时,Unity会进行相关计算并填充一些变量如_LightColor0。

如何指定前向渲染路径

大多数情况下,一个项目只使用一种渲染路径,可以在菜单Edit里设置。

要是想使用多种渲染路径,也可以对不同的摄像机进行不同的设置。

完成了上面的设置,就可以在Pass中使用标签“LightMode”来指定该Path使用的的渲染路径,比如,“LightMode”=“ForwardBase”告诉Unity,当前Pass使用的是前向渲染路径中的“ForwadBase”路径,在shader中访问到Unity填好的光照属性。对于前向渲染,Unity Shader通常会定义一个Base Pass和一个Add Path。通常在Base Path中执行平行光照的逐像素计算,在Add Path中计算其他逐像素光照,最后Blend这些值。实际上,由于Shader中如何利用内置属性进行光照计算,完全取决于程序员,所以也可以在Base Path中进行逐顶点光照计算。

延迟渲染路径

前向渲染会多出很多重复的操作,延迟渲染可以解决。延迟渲染不仅要计算深度缓冲、颜色缓冲,还需要计算G缓冲(Geometry Buffer,G-Buffer)。G-Buffer中存储表面信息,包括表面的法线、位置、材质属性等。

延迟渲染的流程主要包括两个Pass,第一个Pass利用深度缓冲计算片元的可见性,如果一个片元可见就把其相关信息放到G-Buffer中。在第二个Pass中利用G-Buffer中的表面信息计算真正的着色。

在阅读《Unity Shader入门精要》时,跟着此书实现了案例shader,今天把第十一章介绍的纹理动画复现了一遍。纹理动画一个很有趣的效果,用一些简单的技巧实现动画。记录一下。感谢乐乐女神~

书中介绍两种实现简单动画的技巧,一种是利用纹理坐标的偏移,一种是利用模型顶点坐标的偏移。还介绍了一种广告牌效果,作者也归于动画的范畴:)

纹理动画

实际上就是对Texture的采样坐标进行偏移,随着时间的流动,采样不断变化,以达到动画的效果。
书中介绍了两个例子,一个是火焰动画,一个是无限移动的背景板。

火焰动画关键的代码块是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fixed4 frag(v2f i) :SV_Target{

float time = floor(_Time.y*_Speed);//_Time.y 表示自场景加载到当前经过的时间
float row=floor(time/_HorizonAmount);//行索引
float column = time - _HorizonAmount * row;//列索引

float2 uv;
uv.x =i.uv.x + column;//加在uv上
uv.y =i.uv.y - row;//因为纹理列坐标 上大下小 所以是减法 变小
uv.x /= _HorizonAmount;//映射到 局部的小片段纹理中的坐标
uv.y /= _VerticleAmount;
fixed4 c=tex2D(_MainTex,uv);
c.rgb *= _Color.rgb;
return c;

}

主要就是给一个含有N*N个局部小片段纹理的动画纹理,在fragment中对采样坐标进行从左向右,从上倒下的偏移,因为时间变化是连续的,即采样坐标是连续的,所以只要动画纹理中的局部片段是连续的,则可以得到连续的动画。随着时间_Time.y的变化。其中在uv.x/=_HorizonAmount把坐标映射到局部的小片段纹理中,需要仔细体会。

移动的背景墙就简单一点,关键的代码块为:

1
2
3
4
5
6
7
8
9
10
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _FarTex)+frac(float2(_Time.y * _FarMoveSpeed,0.0));
o.uv.zw = TRANSFORM_TEX(v.uv, _NearTex)+frac(float2(_Time.y*_NearMoveSpeed,0.0));
//对采样坐标进行偏移

return o;
}

因为是横向移动的背景墙,所以只在uv.x上叠加时间的流动
其中frac函数是取值的小数位。

顶点动画

顶点动画,就是对顶点的位置进行变化。

利用Asin(Bx+C)对顶点进行偏转。其中只对X方向进行位移,对应的mesh也是在xoz平面上分布如下图所示(如果在yoz平面上,对x偏移就使得mesh像飘动的丝带)。

关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
v2f vert (appdata v)
{
v2f o;
float4 offset;
offset.yzw = float3(0.0,0.0,0.0);
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnet;

o.vertex = UnityObjectToClipPos(v.vertex+offset);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.uv += float2(0.0, _Time.y * _Speed);
return o;
}

对正弦函数调参就能得到各种波浪效果了

另外,在纹理采样时,也需要对纹理进行偏移达到水流的效果。y偏转为0时,水流不动。对y值进行偏转的话,结合纹理(如下图所示)的特点,会使得采样点迅速更新向偏转位置采样,则造成水流的效果。

广告牌效果

(挖个坑,下面这个是什么意思?)

1
2
3
float3 centerOffs = v.vertex.xyz - center;
float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;

参考文献

Unity Shader 入门精要

在阅读《Unity Shader入门精要》时,跟着此书实现了案例shader,今天把第十章介绍的GlassRefractionShader复现了一遍。这是一个很有趣的效果,表现玻璃的纹理折射。记录一下。感谢乐乐女神~

shader效果:

material参数:

关键技术点

对于实现玻璃的纹理折射,主要是两块,一是对环境的反射reflection,二是对背景的折射refraction。将二者融合在一起,就是最后的效果了。

反射

反射的话,反射的是对象周围的环境,环境存在cubemap中,在shader中采样即可:

1
2
3
4
bump = normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump)));
fixed3 reflectDir = reflect(-worldViewDir, bump);
fixed4 texColor = tex2D(_MainTex,i.uv.xy);
fixed3 reflColor = texCUBE(_Cubemap,reflectDir).rgb * texColor.rgb;

主要是,反射需要采样点的法线,如果只是正方体的法线的话,效果就不有趣了,书中用的是法线纹理贴图使得cube表面凹凸不平,反射的效果就更加有趣。并且由于文章所用的法线纹理定义在切线空间,因此需要将切线空间的normal转到世界空间。

折射

折射的话,折射的是背景图片,在shader中加上GrabPass,存在一张纹理中,同样在shader中采样即可。

1
2
3
4
fixed3 bump = UnpackNormal(tex2D(_BumpTex, i.uv.zw));
float2 offset = bump.xy * _Disortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = i.scrPos.xy + offset.xy;
fixed3 refrColor = tex2D(_RefractionTex,i.scrPos.xy / i.scrPos.w).rgb;

如第二章图所示,在材质面板定义了_Disortion(emmm,我的命名应该写成distortion才对==),这个参数是用来对折射进行偏移处理的。_Disortion越小,对于背景折射的就越清晰,看起来cube表面就越光滑;反之_Disortion越大,cube表面看起来就越凹凸不平。由上面这段代码,可见变量offset中偏移的方向与数值是由采样点的法向决定的,因此,offset比重越大,对表面法向的反应就越明显。
_Disortion的有趣效果如下图所示:
_Distortion=100, _RefractionAmount=0.691:

_Distortion=1, _RefractionAmount=0.691:

完整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
Shader "chp10/GlassRefractionShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_BumpTex("Normal map",2D) = "while"{}
_Cubemap("Environment Cubemap",Cube) = "_Skybox"{}

_Disortion("Disortion",Range(0,100)) = 10
_RefractionAmount("RefractionAmount",Range(0,1)) = 0.5
}
SubShader
{
Tags {"Queue" = "Transparent" "RenderType" = "Opaque" }

GrabPass{"_RefractionTex"}

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpTex;
float4 _BumpTex_ST;
samplerCUBE _Cubemap;

float _Disortion;
fixed _RefractionAmount;

sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 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, _BumpTex);

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 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));

fixed3 bump = UnpackNormal(tex2D(_BumpTex, i.uv.zw));
float2 offset = bump.xy * _Disortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = i.scrPos.xy + offset.xy;


//折射:从grabmap读
fixed3 refrColor = tex2D(_RefractionTex,i.scrPos.xy / i.scrPos.w).rgb;

//反射:从cubemap读
bump = normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump)));
fixed3 reflectDir = reflect(-worldViewDir, bump);
fixed4 texColor = tex2D(_MainTex,i.uv.xy);
fixed3 reflColor = texCUBE(_Cubemap,reflectDir).rgb * texColor.rgb;
//混合
fixed3 finalColor = reflColor * (1 - _RefractionAmount) + refrColor * _RefractionAmount;
return fixed4(finalColor, 1);
}
ENDCG
}
}
}


参考文献

Unity Shader 入门精要

我将在后续更新unity灯光渲染demo的每一步….
之所以命名为“night”,因为想实现一个夜晚树叶在路灯下摇曳的效果。

我尽力去理清楚技术点,文中如若有误,欢迎大家批评指正。

看到YouTube里有个介绍给场景添加体积光效果的,学习一下,传送门

Volumetric Lights in HDRP with Unity 2019.3
还有一个将内置渲染管线专为HDRP的,可以用来参考,传送门
HDRP conversion tutorial

效果show

环境搭建

YouTube视频下提供了Fontainebleau的下载位置,
这是一个森林模拟器。

首先要将项目的渲染管线转成HDRP:
1.window–package manager–安装HDRP
2.资源管理器中新建HDRP资源
3.edit–project setting–graphic中选择刚刚创建的HDRP资源

此时,项目便使用HDRP了。
更多的,项目中的材质也要转为HDRP材质。参考这里—>>HDRP conversion tutorial

HDRP

HDRP,High Definition Render Pipeline是Unity可编程渲染管线的其中一种,是Unity用来适配现代兼容计算着色器平台的渲染管线。HDRP使用了基于物理的光线技术,线性空间,HDR光线,和前向光线(Forward lighting)结构,让游戏制作者用更高的图形标准去创造艺术。

使用HDRP的项目则不能使用Lightweight Render Pipeline了。每个项目必须明确使用的渲染管线。

HDRP支持光栅化,光线追踪,路径追踪等渲染技术,遵循基于物理的渲染工作流程。

总之,HDRP就是Unity集成了多种基于物理的渲染技术所设计的面向用户的渲染管线。因此使用HDRP,就非常方便的使用Unity已经集成好的强大的渲染技术了,其中包括体积光技术。

Light

Direct Light

场景中创建direct light

Spot Light

Fog

场景中创建Fog资源。调整Fog浓度

0%