- 本篇博客主要为个人学习所编写读书笔记,不用于任何商业用途,以及不允许任何人以任何形式进行转载。
- 本篇博客会补充一些扩展内容(例如其他博客链接)。
- 本篇博客还会提供一些边读边做的效果截图。文章内所有数学公式都由Latex在线编辑器生成。
- 本篇博客主要提供一个“glance”,知识点的总结。如有需要请到书店购买正版。
- 博客提及所有官方文档基于2022.2版本,博客会更新一些书中的旧的知识点到2022.2版本。
-
个人博客网址:《Unity Shader入门精要》笔记:高级篇(3)以及扩展 - Sugar的博客,如文章转载中出现例如传送门失效等纰漏,建议直接访问原博客网址。
- 如有不对之处欢迎指正。
- 我创建了一个游戏制作交流群:637959304 进群密码:(CSGO的拆包密码)欢迎各位大佬一起学习交流,不限于任何平台(U3D、UE、COCO2dx、GamesMaker等),以及欢迎编程,美术,音乐等游戏相关的任何人员一起进群学习交流。
- 完结撒花~
目录
噪声
消融效果
- 原理:噪声纹理+透明度测试
//核心代码在于: //第一个Pass float4 frag (VertexOutput i) : SV_Target { fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb; //burn为噪声贴图,_BurnAmount为时间 clip(burn.r - _BurnAmount); fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount); fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t); burnColor = pow(burnColor, 5); //书里面写的是3D的,然后这里我改了改放在了2D上面 fixed4 finalColor = lerp(这里填入你原本的颜色, fixed4(burnColor,1.0), t * step(0.0001, _BurnAmount)); return finalColor; } //第二个用于裁剪的Pass fixed4 frag(v2f i) : SV_Target { { fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb; clip(burn.r - _BurnAmount); SHADOW_CASTER_FRAGMENT(i) }
- 当你试图把你的砖块贴图当成噪声放上去的时候
水波效果
- 把噪声图当做高度图使用,不断修改水面的法线方向。
把立方纹理作为环境纹理贴图模拟折射效果。 - 使用菲涅尔系数动态混合反射和折射颜色,v和n代表视角方向和法线方向。夹角越小,数值越小,反射越弱。
Shader "Example/Water Wave" { Properties { _Color ("Main Color", Color) = (0, 0.15, 0.115, 1) _MainTex ("Base (RGB)", 2D) = "white" {} _WaveMap ("Wave Map", 2D) = "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" } //抓取屏幕图像,并自定义名字传入到下方的Pass并采样 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; //得到切线空间下的3个坐标轴(切线、附切线和法线的方向)在世界空间下的表示 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); //使用反射方向对CUBE采样,把结果和主纹理颜色相乘得到反射颜色。 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 } } // Do not cast shadow FallBack Off }
Unity中的渲染优化技术
- 不同平台不同手机型号所用架构都各有差异,这就要求在移动平台上需要花更大的功夫去做优化。例如:PowerVR芯片(用于IOS和部分安卓)使用基于瓦片的延迟渲染架构(TBDR)。Adreno(高通)和Mali(ARM的芯片)使用Early-z或相似技术进行一个低精度的深度检测,来剔除那些不需要渲染的片元。Tegra(英伟达芯片)使用传统架构设计,overdraw(一个像素被绘制多次)可能会造成性能瓶颈。
影响性能的因素
- CPU:负责保证帧率。有可能的性能影响:1、过多的draw call 2、复杂的脚本或物理模拟
- GPU:负责分辨率相关的处理。有可能的性能影响
顶点处理:1、过多的顶点 2、过多的逐顶点计算
片元处理:1、过多的片元(有可能是分辨率,也有可能是overdraw)2、过多逐片元计算 - Draw Call:CPU在每次通知GPU进行渲染之前,都要提前准备好顶点数据(位置、颜色、法线、纹理坐标等),然后调用一系列API把他们放到GPU可以访问到的指定位置,最后调用绘制指令。而调用绘制命令会产生一个draw call,过多的draw call会造成CPU性能瓶颈。这是因为CPU往往需要盖面很多渲染状态的设置,这很好费时间。
- 优化技术:
CPU:使用批处理技术减少draw call数目。
GPU:1、减少需要处理的顶点数目:优化几何体,使用模型的LOD(level of detail)技术,使用遮挡剔除(Occlusion Culling)技术 2、减少需要处理的片元数目:控制绘制程序,警惕透明物体,减少实时光照 3、减少计算复杂度:使用Shader的LOD技术,代码方面的优化
节省内存带宽:1、减少纹理大小 2、利用分辨率缩放
Unity中的渲染分析工具
- 渲染统计窗口:他会统计音频、图像、网络三个方面信息。
- FPS:帧率以及处理一帧所需的时间(括号内)
- Batches:一帧需要进行的批处理数目
- Saved by batching:合并的批处理数目(表明批处理为我们节省了多少draw call)
- Tris,Verts:需要绘制的三角面片和顶点数目
- Screen:屏幕大小以及占用内存大小
- SetPass calls:渲染使用的pass数目(每个pass都需要Unity的runtime绑定新的Shader,这可能造成CPU性能瓶颈)
- shadow casters:阴影映射数目
- Visible skinned meshes:渲染的蒙皮网格数目
- Animation/Animator components playing:播放动画数目
- 性能分析器(Profiler):通过Window->Analysis->Profiler打开。
- 帧调试器(Frame Debugger):Window->Analysis->Frame Debugger打开。在窗口中可以查看每个draw call的工作和结果。
- Drawing为10代表绘制工作需要10个draw call完成,剩余的4个draw call是准备处理工作。
- 其他平台性能分析工具:
安卓:高通分析工具可以对不同测试机进行详细的性能分析。英伟达提供了NVPerfHUD工具。
IOS平台:Unity内置工具可以得到整个场景花费GPU的时间。PoweVRAM的PVRUniSCo shader也可以给出一些宏观上的性能信息。
减少draw call数目
- Unity渲染优化的4种批处理:传送门
- 动态批处理:如果场景中有一些模型共享了同一个材质并满足一些条件,Unity就可以自动把他们进行批处理,合并为一个draw call完成。
原理:每一帧把可以进行批处理的模型网络进行合并,再把合并后模型数据传递给GPU,然后使用同一个材质对其渲染。
条件限制:1、能够进行动态批处理的网格的顶点属性规模要小于900。(该规模大小为Unity5时所设定)2、所有对象最好使用同一个缩放尺度。 3、使用光照纹理的物体需要小心处理。 4、多Pass的Shader会被批处理中断。 - 静态批处理:适用于任何大小的几何模型。在Unity中只需要勾选模型的Static可选框即可变为静态批处理。
原理:在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网格结构中。但只需要进行一次合并操作,因此要比动态批处理更加高效。缺点有:模型不可以在运行时刻被移动,占用更多的内存空间来存储合并后的集合结构。
对于不同材质的物体,静态批处理可以减少draw call之间的切换,同样也可以优化性能。
- 共享材质:如果两个材质之间只有使用的纹理不同,我们可以把这些文理合并到一张更大的纹理中,这张更大的纹理被称为一张图集(atlas),再次使用时采用不同的采样坐标即可。
减少需要处理的顶点数目
- 优化几何体:Unity和三维建模软件中都有相应优化选项。Unity中显示的顶点数目一般会多于三维建模软件中的,因为Unity要进行GPU计算,会进行顶点拆分。
- 模型的LOD技术:当一个物体离摄像机很远时,模型上的细节无法被察觉到。因此LOD允许当对象原理摄像机时,减少模型上的面片数量。
- 遮挡剔除技术:消除其他物件后面看不到的物件,因此就不会计算看不到的顶点。
减少需要处理的片元数目
- 这部分的重点在于减少overdraw
- 控制绘制顺序:深度测试可以保证物体都是从前往后绘制的,可以很大程度上减少overdraw。
人为控制绘制顺序的经验:优先绘制主人公,敌人绘制顺序可以往后放,天空盒子放在渲染队列最后。 - 透明物体:移动设备尽量避免半透明物体的绘制,或者考虑开启深度写入的透明方法。
- 减少实时光照和阴影:使用逐像素点光源以及逐像素的Shader可能造成draw call数目成倍增加,同时也会增加overdraw的数量。同时同态批处理和静态批处理都无法优化逐像素点光源。
解决方法:1、提前进行光照烘焙得到光照纹理,进行光源模拟。 2、使用God Ray进行光源模拟,通过透明纹理的模拟来得到。 3、使用查找纹理,提前存储好内容,使用时根据光源方向,视角方向,法线方向等参数直接采样得到光照结果。这种方法可以得到更出色的光照模型。
减少带宽
- 减少纹理大小:
1、纹理长宽比最好是正方形,且最少是2的整数幂。
2、尽可能使用多级渐远纹理技术(mipmapping)和纹理压缩。(书中所述的Advanced的Texture Type已经没有了,在Default类型中也可以直接设置是否生成Mip Maps,如下图所示)纹理压缩如下图最下方的Format选项所示,Unity会自动根据平台不同采用不同纹理压缩技术。
- 利用分辨率缩放:过高屏幕分别率也会造成性能下降。Unity设置屏幕分辨率可以直接调用Screen.SetResolution
减少计算复杂度
- Shader的LOD技术:和模型的LOD技术类似。只有Shader的LOD值小于某个设定的值,这个Shader才会被使用,而超过了的物体将不会被渲染。(difffuse的LOD为200,Bumped Specular为400)
- 代码方面的优化:1、尽可能使用低精度的浮点值进行运算,高精度,float适用于存储顶点坐标等变量。half适用于一些标量、纹理坐标等变量,计算速度大约是float的两倍。fixed适用于绝大多数颜色变量和归一化后的方向矢量,计算速度大约是float的4倍。2、尽可能使用全屏的屏幕后处理效果。 3、尽可能不要使用分支语句和循环语句,避免三角函数、pow、log等复杂的数学运算(可以用查找表进行代替),尽可能不要使用discard操作会影响硬件某些优化。
扩展
- 书中的最后一个部分主要是一些扩展内容的讲解,这里不作过多赘述,推荐去原书中阅览一番。(另外原作者在Github中更新了最后一个章节的新内容,部分与原版书籍不同)根据书中扩展内容加之本人的理解,提到如下可以扩展的方向,也是值得有时间找专门教程仔细研究一番:
- 表面着色器
- PBS基于物理的渲染
- 光线追踪技术
- 伽马校正、HDR
- SRP、URP、HDRP
- ShaderGraph
完结撒花~