文章目录
环境
先声明使用的环境信息:
Unity : 2019.4.30f1
URP : 7.7.1
为何要声明环境信息?
因为我自己下了好多个 Unity 版本,发现 URP 都几乎发生了变化,这不得不说:很折腾使用 URP 的开发人员
我已经下载了 5 个 Unity 版本了,-_-!
其中我也发现了: URP学习之七–RenderFeature 他使用的 URP 版本和我的也是不一样的
如下图,是 RendererFeatures Inspector 界面区别
下面我是使用了 高版本的 Unity 2021.1.21f1 ,还好发现和我现在的 RenderFeatures 的功能差不多
XRay : Rim, Pattern, Pattern+Rim
在 Built-RP 中
如果在 Built-in RP 中,我们可能需要这么写 Shader
// jave.lin 2021/10/19 这是伪代码
Shader "YourShaderName" {
Properties {}
SubShader {
Pass {
Name "DrawBody"
}
Pass {
Name "DrawXRayPattern+Rim"
}
}
Fallback "xxx"
}
然后你打开 Unity 的 Window/Analysic/Frame Debugger…,你会发现材质直接 YourShaderName Shader 的都不能合批,合批原因就是因为使用了多 Pass
为何多 Pass 不能合批?因为每个 Pass 的 GPU Data 可能都不一样,GPU Data 可能包含:ZTest, ZWrite, Blend, Fog, ColorMask, LightMode,还有各种 uniform 可能都不一样,所以合不了 DC
在 URP 中
Shader
先给原始的绘制对象写好 shader
// jave.lin 2021/10/19
// 测试 PP RenderObj 的 原始绘制 Shader
Shader "Test/RF_DrawCharacter"
{
Properties
{
_Color("Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags
{
"RenderType" = "Opaque"
}
LOD 100
Pass
{
// 标记上 Stencil,给 XRay 效果时防止重复绘制
Stencil
{
Ref 1
Comp Always
Pass Replace
}
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
};
CBUFFER_START(UnityPerMaterial)
float4 _Color;
CBUFFER_END
float3 _LightDirection;
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex.xyz);
o.normal = TransformObjectToWorldNormal(v.normal);
return o;
}
half4 frag(v2f i) : SV_Target
{
//half3 L = _LightDirection;
//return half4(L, 1); // 有时候 _LightDirection 没有数据,xyzw == 0
//
// 建议使用下面的 GetMainLight() 的方式来获取灯光相关信息
// struct Light 需要 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
Light light = GetMainLight();
half3 L = light.direction;
//return half4(L, 1); // 这里输出颜色可以看到是有效的
half diffuse = dot(L, normalize(i.normal)) * 0.5 + 0.5;
return _Color * diffuse;
}
ENDHLSL
}
}
}
注意,我们用了 Stencil
,目的:标记上 Stencil,给 XRay 效果时防止重复绘制
给遮挡的 墙体 对象的 Shader 也写上:(普通的 half lambert 着色即可)
// jave.lin 2021/10/19
// 测试 PP RenderObj 的 原始用于遮挡 Character 的 Shader
Shader "Test/RF_DrawWall"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags
{
"RenderType" = "Opaque"
}
LOD 100
Pass
{
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
};
CBUFFER_START(UnityPerMaterial)
float4 _Color;
CBUFFER_END
float3 _LightDirection;
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex.xyz);
o.normal = TransformObjectToWorldNormal(v.normal);
return o;
}
half4 frag(v2f i) : SV_Target
{
//half3 L = _LightDirection;
//return half4(L, 1); // 有时候 _LightDirection 没有数据,xyzw == 0
//
// 建议使用下面的 GetMainLight() 的方式来获取灯光相关信息
// struct Light 需要 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
Light light = GetMainLight();
half3 L = light.direction;
//return half4(L, 1); // 这里输出颜色可以看到是有效的
half diffuse = dot(L, normalize(i.normal)) * 0.5 + 0.5;
return _Color * diffuse;
}
ENDHLSL
}
}
}
虽然说是简单的 Half Lambert,但是也遇到了一些 BUG:
留意代码中的:
...
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
...
half4 frag(v2f i) : SV_Target
{
//half3 L = _LightDirection;
//return half4(L, 1); // 有时候 _LightDirection 没有数据,xyzw == 0
//
// 建议使用下面的 GetMainLight() 的方式来获取灯光相关信息
// struct Light 需要 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
Light light = GetMainLight();
half3 L = light.direction;
//return half4(L, 1); // 这里输出颜色可以看到是有效的
half diffuse = dot(L, normalize(i.normal)) * 0.5 + 0.5;
return _Color * diffuse;
}
后来发现,原来这个是 shadows 使用的灯光方向,在 反编译 unity API ShadowsUtil 中发现的内容:
public static void SetupShadowCasterConstantBuffer(CommandBuffer cmd, ref VisibleLight shadowLight, Vector4 shadowBias)
{
Vector3 vector = -shadowLight.localToWorldMatrix.GetColumn(2);
cmd.SetGlobalVector("_ShadowBias", shadowBias);
cmd.SetGlobalVector("_LightDirection", new Vector4(vector.x, vector.y, vector.z, 0f));
}
最后是我们的主角 shader,XRay 的代码,用于 RendererFeatures 中的材质用的 shader:
// jave.lin 2021/10/19
// 测试 PP RenderObj 的 shader
// - pass 0 绘制 z buffer greater 的部分
// - pass 1 绘制 z buffer greater 的 Pattern效果
Shader "Test/RF_DrawGreaterZ"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_RimColor ("Rim Color", Color) = (1, 1, 1, 1)
_PatternTex ("PatternTex", 2D) = "white" {}
_PatternAlpha ("Pattern Alpha", Range(0, 1)) = 1
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl"
CBUFFER_START(UnityPerMaterial)
half4 _Color;
half4 _RimColor;
half4 _PatternTex_ST;
half _PatternAlpha;
CBUFFER_END
ENDHLSL
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD 100
Pass
{
// 下面的 draw state 可以在 RendererFeature 中设置 depth, stencil 的 override 来设置,不一定要在这设置
//ZTest Greater
//ZWrite Off
//Stencil
//{
// Ref 1
// Comp NotEqual
// Pass Replace
//}
Name "DrawGreaterZ_Rim"
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
float3 positionWS : TEXCOORD0;
};
v2f vert (appdata v)
{
v2f o;
o.positionWS = TransformObjectToWorld(v.vertex.xyz);
o.vertex = TransformWorldToHClip(o.positionWS);
o.normal = TransformObjectToWorldNormal(v.normal);
return o;
}
half4 frag(v2f i) : SV_Target
{
float3 N = normalize(i.normal);
float3 V = normalize(GetCameraPositionWS() - i.positionWS);
half rim = 1 - dot(N, V);
return lerp(_Color, _RimColor, rim);
}
ENDHLSL
}
Pass
{
// 下面的 draw state 可以在 RendererFeature 中设置 depth, stencil 的 override 来设置,不一定要在这设置
//ZTest Greater
//ZWrite Off
//Stencil
//{
// Ref 1
// Comp NotEqual
// Pass Replace
//}
Name "DrawGreaterZ_Pattern"
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_PatternTex);
SAMPLER(sampler_PatternTex);
v2f vert (appdata v)
{
v2f o = (v2f)0;
o.vertex = TransformObjectToHClip(v.vertex.xyz);
float4 uv = ComputeScreenPos(o.vertex);
o.uv = uv.xy / uv.w;
o.uv.x *= _ScreenParams.x / _ScreenParams.y;
o.uv = TRANSFORM_TEX(o.uv, _PatternTex);
return o;
}
half4 frag(v2f i) : SV_Target
{
half pattern = SAMPLE_TEXTURE2D(_PatternTex, sampler_PatternTex, i.uv).a;
return half4(_Color.rgb, pattern * _PatternAlpha);
}
ENDHLSL
}
Pass
{
// 下面的 draw state 可以在 RendererFeature 中设置 depth, stencil 的 override 来设置,不一定要在这设置
//ZTest Greater
//ZWrite Off
//Stencil
//{
// Ref 1
// Comp NotEqual
// Pass Replace
//}
Name "DrawGreaterZ_Pattern+Rim"
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float3 positionWS : TEXCOORD1;
};
TEXTURE2D(_PatternTex);
SAMPLER(sampler_PatternTex);
v2f vert (appdata v)
{
v2f o = (v2f)0;
o.positionWS = TransformObjectToWorld(v.vertex.xyz);
o.vertex = TransformWorldToHClip(o.positionWS);
o.normal = TransformObjectToWorldNormal(v.normal);
float4 uv = ComputeScreenPos(o.vertex);
o.uv = uv.xy / uv.w;
// _ScreenSize 在 FrameDebug 中查看到,xyzw 都是 0,BUG?
//o.uv.x *= _ScreenSize.y / _ScreenSize.x;
//o.uv.x *= _ScreenParams.y / _ScreenParams.x;
o.uv.x *= _ScreenParams.x / _ScreenParams.y;
o.uv = TRANSFORM_TEX(o.uv, _PatternTex);
return o;
}
half4 frag(v2f i) : SV_Target
{
// _ScreenSize 在 FrameDebug 中查看到,xyzw 都是 0,BUG?
//return half4((float(_ScreenSize.y) / _ScreenSize.x).xxx, 1.0);
// _ScreenParams 才有值
//return half4((_ScreenParams.y / _ScreenParams.x).xxx, 1.0);
float3 N = normalize(i.normal);
float3 V = normalize(GetCameraPositionWS() - i.positionWS);
half rim = 1 - dot(N, V);
half pattern = SAMPLE_TEXTURE2D(_PatternTex, sampler_PatternTex, i.uv).a;
return half4(lerp(_Color.rgb, _RimColor.rgb, rim), pattern * _PatternAlpha);
}
ENDHLSL
}
}
}
可以看到有三个 Pass,本来一个就够的,只不过,我们可以演示,可以动态的激活其中我们需要的一个 或是 多个 RendererFeatures
然后我们可以新建一个材质,应用一下此 shader
再给 Renderer 添加 RendererFeatures
添加 RendererFeature 的时候,需要设置好:
- Name - 名字,便于理解、维护用
- Event - 决定在什么时候开始绘制,下图我们设置的是:AfterRenderingTransparents 就是在渲染透明内容后再渲染这个 RendererFeature 的内容
- Filters
- Queue - 渲染所属队列ID,Opaque 是不透明
- Layer Mask - 要筛选的 layer 渲染层级
- LightModeTags - 筛选tags 中 LightMode 包含 一个或多个 当中的其一
- Overrides
- Material - 此 RendererFeature 渲染时使用的材质
- Pass Index - 使用 Material 中对应的那个 Pass 索引,从 0 开始
- Depth - 是否覆盖设置 Depth 状态,如果没有勾,就使用 材质中、ShaderLab 中的设置,深度相关可参考:Unity Shader - ShaderLab: Culling & Depth Testing 剔除与深度测试
- Write Depth - 是否写入深度,这里不用写入,所以不勾
- Depth Test - 深度测试的比较条件,这是因为需要 XRay 效果,只需要绘制被遮挡部分所以设置为:Greater
- Stencil - 是否覆盖设置 Stencil 状态,如果没有勾,就使用 材质中、ShaderLab 中的设置,模板相关可参考:Unity Shader - ShaderLab: Stencil 模板(缓存)
- Value - Stencil 的参考值
- Compare Function - 与 Value 参考值的比较方式
- Pass - 如果比较条件通过,如何操作缓存值,这里使用 Replace 就是使用 Value 参考值替代缓存值的意思
- Fail - 如果比较条件失败,如何操作缓存值,这里使用 Keep 就是保持缓存值不变的意思
- Z Fail - 如果在深度比较失败,如何操作缓存值,这里使用 Keep 同上
- Camera - 相机相关设置,这里暂时用不上,不用管
- Material - 此 RendererFeature 渲染时使用的材质
脚本
最后我们可以添加一些脚本,在运行时动态的切换不同的 RendererFeature
// jave.lin 2021/10/19
// 测试 URP RendererFeature 的功能脚本
using UnityEngine;
using UnityEngine.Rendering.Universal;
public class TestRendererFeatureScript : MonoBehaviour
{
public ScriptableRendererData data;
public void OnRim()
{
EnabledRendererFeatureNew(0);
}
public void OnPattern()
{
EnabledRendererFeatureNew(1);
}
public void OnPatternRim()
{
EnabledRendererFeatureNew(2);
}
private void EnabledRendererFeatureNew(int idx)
{
if (data == null)
{
Debug.LogError($"{nameof(TestRendererFeatureScript)}.EnabledRendererFeatureNew data == null.");
return;
}
var features = data.rendererFeatures;
for (int i = 0; i < features.Count; i++)
{
features[i].SetActive(i == idx);
}
}
}
效果
Pattern
可以使用纹理,也可以使用算法代码生成 Shader Graph 中,我记得有类似的节点
在我很久之前翻译过的一篇文章:Unity Shader - Shader semantics 着色器语义 - 搜索文本:Screen space pixel position: VPOS
fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target
{
// screenPos.xy 将包含像素的整数坐标值。
// 用它们实现棋盘团来,替代渲染4x4的像素块(4x4纹理)
// 棋盘图案每个格将使用4x4的像素块
screenPos.xy = floor(screenPos.xy * 0.25) * 0.5;
float checker = -frac(screenPos.r + screenPos.g);
// 如果checker为负数,则使用HLSL 内置函数 clip 来停止绘制该像素
clip(checker);
// 保持绘制的像素将采样纹理值并输出
fixed4 c = tex2D (_MainTex, i.uv);
return c;
}
效果如下:
下面是我显示我再Photoshop中随便创建的一个 PatternTex,4x4 尺寸
原本使用的是 2x2 尺寸,但是 DX11 下显示不最小只能 4x4 才能使用 对应的纹理块压缩算法格式,所以我就调整为 4x4 尺寸的
然后对 PatternTex 的 Filtering 设置为 Point
Pattern 还可以通过 uv 的 scale 的实现 Pattern 的像素粗细度
如下:
下面是偶然发现 unity 2019.4.30f1 中的某个 LOD 淡入淡出的 Dither Shader 处理:
再 UnityCG.cginc
文件下
#ifdef LOD_FADE_CROSSFADE
#define UNITY_APPLY_DITHER_CROSSFADE(vpos) UnityApplyDitherCrossFade(vpos)
sampler2D unity_DitherMask;
void UnityApplyDitherCrossFade(float2 vpos)
{
vpos /= 4; // the dither mask texture is 4x4
float mask = tex2D(unity_DitherMask, vpos).a;
float sgn = unity_LODFade.x > 0 ? 1.0f : -1.0f;
clip(unity_LODFade.x - mask * sgn);
}
#else
#define UNITY_APPLY_DITHER_CROSSFADE(vpos)
#endif
URP RendererFeatures 相对 Built-in RP 多 Pass 的优势
但是 SRP Batcher 也是不能合并 DC 的,那他有什么优势?其实大家都因为了解过?这里就不厌其烦在说一次:可以减少 SetGPUData 次数,也就是 SetPassCall 的次数,可以参考:Unity Shader - shader lab 的 SRP Batcher compatible 兼容性(未使用 RenderDoc 验证 API)
URP 每个 shader 渲染实体对象,都只调用一个 Pass,为的就是提高 SRP Batcher 的可能性
而且 URP 中的灯光都在一个 Pass 支持多个,这有点像我之前写的 OpenGL 中实现多光源,在一个 Pass 中实现:LearnGL - 14 - MultiLight - 多光源,性能通常都会比多 Pass 要高
Project
TestingURP_RendererFeatures_Pattern_Rim.unitypackage 提取码: 73dz