URP入门
URP基础
URP由来
Unity内置渲染管线Build-in Render Pipeline
由于该管线上要适配高画质主机游戏
下要适配古早移动设备,这使它的内部有非常多的冗杂
因此
需要针对某一平台发布特定的管线,即Universal Render Pipeline
此外
Unity早起推出High Definition RP和Light Weight RP
但大家觉得后者太low,就不用,但Unity知道后者才是更优秀的,因此更名为URP
这些都是可编程渲染管线:Scriptable RP
URP的使用
1.Shader Graph
虽然是连连看,但他会很好地帮助我们写Shader
相当于一个快速写代码组件,可以验证脑海中的猜想是否可行
如果可行,就可以把Graph写成Shader了
2.最简单Shader-Unlit
写法和Build-in管线的普通Shader类似
查看代码
Shader "MyURP/Unlit" { Properties { _Color("Color", color) = (1,1,1,1) } SubShader { Tags { "RenderPipeline"="UniversalPipeline" } Pass { HLSLPROGRAM//CG->HLSL #pragma vertex vert #pragma fragment frag //UnityCG.cginc->↓ #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" //命名更规范,OS:ObjectSpace,WS:WorldSpace,CS:ClipSpace struct Attributes { float3 positionOS : POSITION; }; struct Varyings { float4 positionCS : SV_POSITION; }; //可以使用SRP的特性-常量缓冲区优化数据 //自定义的常量缓冲区会放到UnityPerMaterial缓冲区中 //Unity内部定义的常量,如旋转矩阵、渲染平台参数、相机位置、主光源方向等,会放到UnityPerDraw缓冲区中 CBUFFER_START(UnityPerMaterial) half4 _Color; CBUFFER_END Varyings vert (Attributes v) { Varyings o = (Varyings)0; float3 positionWS = TransformObjectToWorld(v.positionOS); o.positionCS = TransformWorldToHClip(positionWS); return o; } half4 frag (Varyings i) : SV_TARGET { half4 col = _Color; col.a = 1; return col; } ENDHLSL } } FallBack "Hidden/Universal Render Pipeline/FallbackError" } //最后一点可能要注意的是Unity更新了深度引动模式:Depth Priming Mode //即非透明物体都需要一个深度Pass来帮助渲染 //此处没有写是为了最简化 //解决方法一:UsePass "Universal Render Pipeline/Unlit/DepthOnly //解决方法二:在RendererData中关闭该技术
SRP Batch
介绍
可编程渲染管线合批功能,是一个很强的性能优化功能
每一个同类Shader都使用显存中同一个区域的数据
在往深处看,就是在编译时,会根据不同平台去生成一个常量缓冲区,例如 GLES2 就没有这东西
具体效果直接看官网就行了
要求和使用
1.静态网格,动态的例如蒙皮网格就不能使用
2.Shader支持SRP合批,CBUFFER,
3.
生成5个材质,用的同一个Shader
未开启SRP Batching前就是5个批次
开启后就是一个批次不过是5个DrawCall,但问题不大
回顾其他合批,分析不同点
静态合批:场景烘焙,不同网格,同一材质球(参数一样)
动态合批:顶点、属性显示,不同网格,同一材质球(参数一样)
GPU实例化:同一网格,同一材质球(参数不一样)
SRP合批:Build-in无法使用,不同网格,不同材质球,同一Shader
内存处理
Build-in渲染管线运行逻辑
1.CPU要渲染某一物体时,去内存中拿该物体的数据,分为两部分,
位置、组件、网格、材质球等,
另一是该物体身上的所有材质球的属性参数,贴图、map等
2.CPU拿到数据后,将部分渲染需要用到的数据发送给显存,在显存中生成一个个常量缓冲区
3.Shader运行时再去这一个个常量缓冲区拿数据
URP中就是把Build-in中拿的两部分数据分开了
1.在显存中开一个比较大Buffer群组,去存放物体旋转矩阵、相机位置、主光源方向等内部参数,每一个物体都会有一个Buffer
2.CPU另开一块,专门去处理材质球,当某材质球被初始化或者被修改时会调用
3.就是传到专属于该材质的Buffer中
4.Shader运行时去Buffer群组中和目标材质Buffer中拿数据
纹理与采样分离
对象声明的变化
//纹理的定义,背后是平台的区分,例如GLES2就是sampler2D _XXX,其他的是Texture2D _XXX TEXTURE2D(_Texture); //采样器的定义,背后是平台的区分,例如GLES2就是空,其他的会生成一个采样器SamplerState sampler_Texture SAMPLER(sampler_Texture);
纹理采样的变化
//GLES2和默认管线的采样操作:tex2D(_Texture, i.uv); //纹理采样,背后也是平台的区分,其他的是_Texture.Sample(sampler_Texture, i.uv); half4 tex = SAMPLE_TEXTURE2D(_Texture, sampler_Texture, i.uv);
采样器是什么
就是采样贴图的工具,但可以自定义如何去采样
分离的作用
旧的渲染管线下,定义了贴图,采样也会同时被确认,如果需要对该贴图进行其他的采样,就需要特别的写算法
因此需要由不同的采样器其采样同一张贴图,以此来得到不同的效果
使用方法
传统采样设置是在Inspector面板设置的
但这样采样就会被限制死了
因此需要采样器介入
SAMPLER(smp);
其中smp的命名就定义了采样方式
第一个下划线后的为过滤模式 如:xxx_linear就定义为线性采样
第二个下划线后面的为重复方式 如:xxx_xxx_clamp就定义为非重复模式采样
倘若为_linear_clampU_repeatV就定义为U(x)方向不重复,V(y)方向重复的采样模式
URP模板
URP手册地址taecg/ShaderReference: 针对Unity的Shader参考大全 (github.com)
Editor/Data/Resources/ScriptTemplates/94-Shader__URP-NewURPShader.shader.txt
Shader "MyURP/XXX" { Properties { _BaseColor("Base Color",color) = (1,1,1,1) _BaseMap("BaseMap", 2D) = "white" {} } SubShader { Tags { "Queue"="Geometry" "RenderType" = "Opaque" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" } LOD 100 Pass { Name "XXX" HLSLPROGRAM // Required to compile gles 2.0 with standard srp library #pragma prefer_hlslcc gles #pragma exclude_renderers d3d11_9x #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float fogCoord : TEXCOORD1; }; CBUFFER_START(UnityPerMaterial) half4 _BaseColor; float4 _BaseMap_ST; CBUFFER_END TEXTURE2D (_BaseMap);SAMPLER(sampler_BaseMap); Varyings vert(Attributes v) { Varyings o = (Varyings)0; o.positionCS = TransformObjectToHClip(v.positionOS.xyz); o.uv = TRANSFORM_TEX(v.uv, _BaseMap); o.fogCoord = ComputeFogFactor(o.positionCS.z); return o; } half4 frag(Varyings i) : SV_Target { half4 c; half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv); c = baseMap * _BaseColor; c.rgb = MixFog(c.rgb, i.fogCoord); return c; } ENDHLSL } } }
URP案例
能量罩
最终代码
Shader "MyURP/EnergyShield" { Properties { [Header(High Light)] _HighLightFade("High Light Fade", Range(1, 10)) = 10 _HighLightColor("High Light Color", color) = (1,1,1,1) [Header(Fresnel)] [PowerSlider(3)]_FresnelPow("Fresnel Pow", Range(1, 15)) = 7 _FresnelColor("Fresnel Color", color) = (1,1,1,1) [Header(Flow Distort)] _FlowTiling("Flow Tiling", float) = 6 _FlowDistort("Flow Distort", Range(0,1)) = 0.4 _FlowMap("Base Map", 2D) = "white" {} } SubShader { Tags { "Queue"="Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" } LOD 100 Blend SrcAlpha OneMinusSrcAlpha Pass { Name "EnergyShield" HLSLPROGRAM // Required to compile gles 2.0 with standard srp library #pragma prefer_hlslcc gles #pragma exclude_renderers d3d11_9x #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl" struct Attributes { float4 positionOS : POSITION; half3 normalOS : NORMAL; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float4 uv : TEXCOORD0; float fogCoord : TEXCOORD1; half positionVS_Z : TEXCOORD2; half3 normalWS : TEXCOORD3; half3 viewWs : TEXCOORD4; }; CBUFFER_START(UnityPerMaterial) half _HighLightFade; half4 _HighLightColor; half _FresnelPow; half4 _FresnelColor; half _FlowTiling; half _FlowDistort; float4 _FlowMap_ST; CBUFFER_END TEXTURE2D (_FlowMap);SAMPLER(sampler_FlowMap); TEXTURE2D (_CameraDepthTexture);SAMPLER(sampler_CameraDepthTexture); TEXTURE2D (_CameraOpaqueTexture);SAMPLER(sampler_CameraOpaqueTexture); Varyings vert(Attributes v) { Varyings o = (Varyings)0; o.uv.xy = v.uv; o.uv.zw = TRANSFORM_TEX(v.uv, _FlowMap); //由于需要获取深度值(离相机越远越大),就需要View空间下的Z值 float3 positionWS = TransformObjectToWorld(v.positionOS.xyz); float3 positionVS = TransformWorldToView(positionWS); //由于都算到这了,TransformObjectToHClip内部也要做这么些事,不如节省一下,把值拿来用 o.positionCS = TransformWViewToHClip(positionVS); o.positionVS_Z = -positionVS.z; o.normalWS = TransformObjectToWorldNormal(v.normalOS); o.viewWs = normalize(_WorldSpaceCameraPos - positionWS); return o; } half4 frag(Varyings i) : SV_Target { half4 c = 0; //---靠近物体部分高光--- //获取片段对应深度图中像素在观察空间下的深度,0~正无穷 float2 screenUV = i.positionCS.xy / _ScreenParams.xy; half4 depthMap = SAMPLE_TEXTURE2D(_CameraDepthTexture, sampler_CameraDepthTexture, screenUV); //将值限制为0~1,符合View空间 half depth = LinearEyeDepth(depthMap.r, _ZBufferParams); //计算边缘高光 half delta = (depth - i.positionVS_Z) * _HighLightFade; half highLight = saturate(1 - delta); c += highLight * _HighLightColor; //--------------------- //-----菲尼尔外发光----- //pow(max(0, dot(N,V)), Intensity) half3 N = i.normalWS; half3 V = i.viewWs; half NdotV = 1 - saturate(dot(N, V)); half fresnel = pow(abs(NdotV), _FresnelPow); c += fresnel * _FresnelColor; //--------------------- //--------扭曲流动-------- half baseMap = SAMPLE_TEXTURE2D(_FlowMap, sampler_FlowMap, i.uv.zw + float2(0, _Time.y)).r; c += baseMap * 0.05f; float2 distortUV = lerp(screenUV, baseMap, (1 - baseMap) * _FlowDistort); //抓屏 half4 opaqueTex = SAMPLE_TEXTURE2D(_CameraOpaqueTexture, sampler_CameraOpaqueTexture, distortUV); half flow = frac(i.uv.y * _FlowTiling + _Time.y); c += opaqueTex * flow; //----------------------- return c; } ENDHLSL } } }
1.近处高光
启用深度图
获取深度图,转到View空间下
片段着色器中:
此时我们获得了一个关于Cube的深度图
然后计算该顶点在View空间下的深度
顶点着色器中:
获取差距
添加细节半透明混合、约束范围并给出颜色
2.外发光
这个很简单,就是用菲涅尔
公式:pow(max(0, dot(N,V)), Intensity)
我们需要该顶点法线和该顶点指向相机的向量
顶点着色器:
然后带入公式
片段着色器:
因为我们需要的是外圈,所以需要1-NdotV,不然就是这样的
3.流动扭曲
流动的贴图
Y方向上的,被分割成几块的流动
使蜂窝贴图的缝隙偏移
1.计算偏移后的UV,使用1-baseMap是因为,黑色0需要偏移,白色1不需要偏移
2.抓屏
将抓屏结果和流动相乘,得到最终的流动扭曲结果
深度贴花
通过深度图求出像素所在的观察空间的Z值
通过当前渲染的面片求出像素在观察空间的坐标
将此坐标转换到本地空间,把XY当做UV进行采样
场景中使用Cube作为承载体,如果使用面片,在特定角度,图案会被切割
深度贴花
Shader "MyURP/DepthDecal" { Properties { _BaseColor("Base Color",color) = (1,1,1,1) _BaseMap("BaseMap", 2D) = "white" {} } SubShader { Tags { "Queue"="Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" } LOD 100 Blend One One Pass { Name "DepthDecal" HLSLPROGRAM // Required to compile gles 2.0 with standard srp library #pragma prefer_hlslcc gles #pragma exclude_renderers d3d11_9x #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl" struct Attributes { float4 positionOS : POSITION; }; struct Varyings { float4 positionCS : SV_POSITION; float fogCoord : TEXCOORD1; float3 positionVS : TEXCOORD2; }; CBUFFER_START(UnityPerMaterial) half4 _BaseColor; float4 _BaseMap_ST; CBUFFER_END TEXTURE2D (_BaseMap);SAMPLER(sampler_BaseMap); TEXTURE2D (_CameraDepthTexture);SAMPLER(sampler_CameraDepthTexture); #define smp _linear_clamp SAMPLER(smp); Varyings vert(Attributes v) { Varyings o = (Varyings)0; float3 positionWS = TransformObjectToWorld(v.positionOS.xyz); o.positionVS = TransformWorldToView(positionWS); o.positionCS = TransformWViewToHClip(o.positionVS); o.fogCoord = ComputeFogFactor(o.positionCS.z); return o; } half4 frag(Varyings i) : SV_Target { half4 c; float2 screenUV = i.positionCS.xy / _ScreenParams.xy; half depthMap = SAMPLE_TEXTURE2D(_CameraDepthTexture, sampler_CameraDepthTexture, screenUV); //通过深度图求出像素所在的观察空间的Z值 half depthZ = LinearEyeDepth(depthMap, _ZBufferParams); half4 depthVS = half4(0, 0, depthZ, 1); //通过当前渲染的面片求出像素在观察空间的XY坐标,使用的是相似三角形的比值,我们有了Z值 //depthXY:depthZ = vsXY:vsZ depthVS.xy = i.positionVS.xy * depthZ / -i.positionVS.z; //将此观察空间坐标转换到本地空间,把XY当做UV进行采样 half4 depthWS = mul(unity_CameraToWorld, depthVS); half3 depthOS = mul(unity_WorldToObject, depthWS); //采样 half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, smp, depthOS.xz + 0.5); c = baseMap * _BaseColor; //雾效根据混合模式进行了调整 c.rgb *= saturate(lerp(1, 0, i.fogCoord)); return c; } ENDHLSL } } }
定点消融效果
ASE/数组/简化ASE
案例的初心是简化ASE构造完成后的shader,但我感觉没意思,直接写吧,也不难
在世界空间下随机定几个点,让物体根据这些点产生如下图的效果
1.由C#脚本在Start时,将数组赋给shader
void Start() { int n = transform.childCount; Vector4[] vectors = new Vector4[n]; for (int i = 0; i < n; i++) { //遍历所有子物体,拿到所有子物体的pos,然后隐藏 Transform child = transform.GetChild(i); vectors[i] = child.position; child.gameObject.SetActive(false); } Material mtl = GetComponent<MeshRenderer>().sharedMaterial; mtl.SetInt("StartPosCount", n); mtl.SetVectorArray("StartPosArr", vectors); }
2.需要计算简单的Lambert光照,因此需要知道顶点法线
然后片段着色器就算一下
3.由于是在世界空间下计算的,因此顶点着色器需要计算顶点的世界坐标
然后片段着色器计算,具体解释都在代码中
GroundDisapper
Shader "GroundDisapper" { Properties { _Radius("Radius", Float) = 0 _OutlineColor("OutlineColor", Color) = (0,0,0,0) _FadeRange("FadeRange", Float) = 0 [Toggle(_DISAPPEAR_ON)]_Disappear("Disappear", Float) = 1 } SubShader { Pass { Name "FORWARD" Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 #pragma multi_compile _ _DISAPPEAR_ON #include "UnityCG.cginc" struct Input { float3 worldNormal; float3 worldPos; }; int StartPosCount; float4 StartPosArr[5]; float _Radius; float _FadeRange; float4 _OutlineColor; struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; }; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.worldNormal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 c = 0; //Lambert lighting fixed4 NdotL = dot(i.worldNormal, _WorldSpaceLightPos0.xyz); fixed4 diffuse = (NdotL * 0.5 + 0.5) * 0.4; #ifdef _DISAPPEAR_ON //获取渐变内侧终点 float fadeMin = _Radius - _FadeRange; //获取渐变外侧起点,同时也是半径 float fadeMax = _Radius; //起始假设该点在半径之外,倘若最后真的在半径之外,会被clip(0.999-1)剔除 float outline = 1; //遍历所有的圆心,只要有一个小于1,最后就能被保留 for (uint idx = 0; idx < StartPosCount; idx++) { float3 startPos = StartPosArr[idx].xyz; //saturate(该点离内侧的距离 / 外侧离内侧的距离) float fade = saturate((distance(i.worldPos, startPos) - fadeMin) / (fadeMax - fadeMin)); outline *= fade; } clip(0.999 - outline); c = outline * _OutlineColor + diffuse; #else c = diffuse; #endif return c; } ENDCG } } }
裂痕深坑
此处为渲染顺序和色彩模板的配合知识点
渲染裂痕下部分,写入深度,写入颜色值
渲染裂痕上部分,写入深度覆盖渲染裂痕下部分的深度,不写入颜色值
渲染地面,写入深度,发现部分片段不如裂痕上部分浅,取消这部分片段的渲染
场景部分:
其中up就是down模型的压缩版,Scale.y = 0
下部分Shader:
重要的就是"Queue"="Geometry-2",显示的东西就是模型,Cull Front无所谓,建模时不小心法线反了,懒得改了
down
Shader "MyURP/CrackDown" { Properties { _BaseColor("Base Color",color) = (1,1,1,1) } SubShader { Tags { "Queue"="Geometry-2" "RenderType" = "Opaque" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" } LOD 100 Pass { Name "CrackDown" Cull Front HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl" struct Attributes { float4 positionOS : POSITION; }; struct Varyings { float4 positionCS : SV_POSITION; float3 positionOS : TEXCOORD0; float fogCoord : TEXCOORD1; }; CBUFFER_START(UnityPerMaterial) half4 _BaseColor; CBUFFER_END Varyings vert(Attributes v) { Varyings o = (Varyings)0; o.positionCS = TransformObjectToHClip(v.positionOS.xyz); o.positionOS = v.positionOS.xyz; o.fogCoord = ComputeFogFactor(o.positionCS.z); return o; } half4 frag(Varyings i) : SV_Target { half4 c = 1; half mask = abs(i.positionOS.y); float t = sin(_Time.y); t = t * 0.3 + 0.7; c.rgb = lerp(0, _BaseColor * t, mask); return c; } ENDHLSL } } }
上部分Shader:
重要的部分就是"Queue"="Geometry-1"、ColorMask 0
up
Shader "MyURP/CrackUP" { SubShader { Tags { "Queue"="Geometry-1" "RenderType" = "Opaque" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" } LOD 100 Pass { Name "CrackUP" Cull Front ColorMask 0 HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; }; struct Varyings { float4 positionCS : SV_POSITION; }; Varyings vert(Attributes v) { Varyings o = (Varyings)0; o.positionCS = TransformObjectToHClip(v.positionOS.xyz); return o; } half4 frag(Varyings i) : SV_Target { return 0; } ENDHLSL } } }
最后渲染的就是一般的不透明物体,队列为Geometry,在up之后
但在渲染时发现自己太深,通不过深度测试,于是显示的就是首次渲染的down
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)