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

posted @   被迫吃冰淇淋的小学生  阅读(103)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示