深入URP之Shader篇3: Unlit Shader分析[下]

news/2024/10/30 19:32:06/

Unlit shader

上篇中我们分析了Unlit shader的Properties在ShaderGUI中的处理,接下来看Sub Shader。

SubShader

unlit shader以及其他URP shader包含两个SubShader,分别是针对ShaderModel4.5和2.0。由于unlit shader本身很简单,这两个SubShader几乎一样,唯一的差别是ShaderModel 4.5的SubShader会定义#pragma multi_compile _ DOTS_INSTANCING_ON,这个可参考dots-instancing-shaders,本文中先忽略它。总之这儿的要点是URP会针对比较强大的设备使用一个SubShader,而比较弱的设备使用另一个。但这并不是说URP只是把设备分成两类这么简单,这儿只说Shader。由于硬件总是要发展,我们关注的重点是ShaderModel4.5,因此就只看这第一个SubShader了。

Pass

我们看到unlit shader的SubShader中包含了3个Pass,分别是Unlit,DepthOnlyMeta。我们知道URP是单Pass渲染,这只是说URP是在一个Pass中完成物体的大部分计算,包含各种光照的计算等等。而Sub Shader中仍然是会包含多个pass,这些Pass是在渲染管线中特定的时候被执行。例如这儿的DepthOnlypass,是在渲染所有不透明物体之前预先生成一张depth texture的pass;而Metapass则只在烘焙光照贴图时使用。而决定这些pass被使用的并不是他们的名字,而是上面说过的LightModetag。但是Unlitpass并没有指定这个tag,所以它使用的就是默认的SRPDefaultUnlit

unlit pass

首先分析unlit pass

  • keyword定义:
            #pragma shader_feature_local_fragment _ALPHATEST_ON#pragma shader_feature_local_fragment _ALPHAPREMULTIPLY_ON// -------------------------------------// Unity defined keywords#pragma multi_compile_fog#pragma multi_compile_instancing#pragma multi_compile _ DOTS_INSTANCING_ON

shader feature支持alpha test和 alpha premultiply,muti compile则包含fog, instacing和 dots instancing。这些都是一些宏定义开关,shader使用了不同的开关,会编译成不同的变体,变体才是最终使用的shader。对于shader feature是可以定义在材质上的,上面处理Properties的时候看到会根据不同情况设置不同的关键字,就是设置使用这儿的关键字,所以要首先包含这些关键字。而multi compile是指无论如何都要包含这些关键字(和材质是否开启没关系)。

  • uniform定义:
    uniform定义被包含在文件 UnlitInput.hlsl 中。这个hlsl文件定义了unlit shader使用的uniform,根据SRP的规则,使用的是CBuffer。
CBUFFER_START(UnityPerMaterial)float4 _BaseMap_ST;half4 _BaseColor;half _Cutoff;half _Surface;
CBUFFER_END

CBUFFER_STARTCBUFFER_END是定义在SRP Core的Shader Library中的宏,并且根据不同的API有不同的定义,在GLES2中由于不支持constant uniform buffer,因此这两个宏被定义为空。

  • Attributes 和 Varyings
    这两个分别是vertex shader的输入,以及fragment shader的输入,这是URP的命名习惯,和内置流水线不一样。
            struct Attributes{float4 positionOS       : POSITION;float2 uv               : TEXCOORD0;UNITY_VERTEX_INPUT_INSTANCE_ID};struct Varyings{float2 uv        : TEXCOORD0;float fogCoord  : TEXCOORD1;float4 vertex : SV_POSITION;UNITY_VERTEX_INPUT_INSTANCE_IDUNITY_VERTEX_OUTPUT_STEREO};

先忽略gpu instancing相关的宏。vs的输入只有位置和uv坐标,而fs的输入还多了一个fogCoord。

  • Vertex shader
            Varyings vert(Attributes input){Varyings output = (Varyings)0;UNITY_SETUP_INSTANCE_ID(input);UNITY_TRANSFER_INSTANCE_ID(input, output);UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);output.vertex = vertexInput.positionCS;output.uv = TRANSFORM_TEX(input.uv, _BaseMap);output.fogCoord = ComputeFogFactor(vertexInput.positionCS.z);return output;}

同样先忽略instacing。VS就是简单的从物体局部坐标系变换到clip space,将结果写入Varying的vertex;变换uv坐标;以及计算雾参数。
这儿要注意的是GetVertexPositionInputs方法。这个方法位于Packages\com.unity.render-pipelines.universal\ShaderLibrary\ShaderVariablesFunctions.hlsl中:

VertexPositionInputs GetVertexPositionInputs(float3 positionOS)
{VertexPositionInputs input;input.positionWS = TransformObjectToWorld(positionOS);input.positionVS = TransformWorldToView(input.positionWS);input.positionCS = TransformWorldToHClip(input.positionWS);float4 ndc = input.positionCS * 0.5f;input.positionNDC.xy = float2(ndc.x, ndc.y * _ProjectionParams.x) + ndc.w;input.positionNDC.zw = input.positionCS.zw;return input;
}

可以看到,这个方法里面计算了世界坐标,view坐标和clip space坐标,以及NDC坐标。几乎所有的URP shader都会调用这个方法。虽然说unlit shader里面只需要clip space坐标,但是为了统一就直接调用了。由于shader的函数都是内联的,不使用的变量和方法应该会被编译器优化掉(错了请告诉我),所以也不用太担心性能浪费。
关于uv坐标的变换,使用了TRANSFORM_TEX这个宏,这个宏的定义位于SRP Core的Shader Library中:
#define TRANSFORM_TEX(tex, name) ((tex.xy) * name##_ST.xy + name##_ST.zw)
就是使用_BaseMap_ST去缩放偏移了一下uv坐标。
最后,ComputeFogFactor也位于ShaderVariablesFunctions.hlsl中:

real ComputeFogFactor(float z)
{float clipZ_01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(z);#if defined(FOG_LINEAR)// factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))float fogFactor = saturate(clipZ_01 * unity_FogParams.z + unity_FogParams.w);return real(fogFactor);#elif defined(FOG_EXP) || defined(FOG_EXP2)// factor = exp(-(density*z)^2)// -density * z computed at vertexreturn real(unity_FogParams.x * clipZ_01);#elsereturn 0.0h;#endif
}

首先,real是一个宏定义,位于SRP Core的Common.hlsl中,对于支持half的平台,real就是half,否则就是float。
UNITY_Z_0_FAR_FROM_CLIPSPACE是定义在URP的core.hlsl中的一个宏,会根据是否是reverse Z做不同的计算,关于z相关的话题是很重要也比较复杂的,将会单独写一篇,这儿就不说了。

  • Fragment Shader
            half4 frag(Varyings input) : SV_Target{UNITY_SETUP_INSTANCE_ID(input);UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);half2 uv = input.uv;half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);half3 color = texColor.rgb * _BaseColor.rgb;half alpha = texColor.a * _BaseColor.a;AlphaDiscard(alpha, _Cutoff);#ifdef _ALPHAPREMULTIPLY_ONcolor *= alpha;
#endifcolor = MixFog(color, input.fogCoord);return half4(color, alpha);}

SAMPLE_TEXTURE2D是一个定义在SRP Core shader library中的一个跨平台的宏,例如:
dx11是
#define SAMPLE_TEXTURE2D(textureName, samplerName, coord2) textureName.Sample(samplerName, coord2)

GLES2是
#define SAMPLE_TEXTURE2D(textureName, samplerName, coord2) tex2D(textureName, coord2)

AlphaDiscard也还是位于ShaderVariablesFunctions.hlsl中:
void AlphaDiscard(real alpha, real cutoff, real offset = 0.0h)
{
#ifdef _ALPHATEST_ON
clip(alpha - cutoff + offset);
#endif
}
很简单,根据是否定义_ALPHATEST_ON关键字,使用cutoff计算clip。
这儿还会判断关键字_ALPHAPREMULTIPLY_ON,如果定义,则会将alpha值预先乘到color上,这就是传统的Alpha预乘技术。

Depth only pass

Depth only pass用于生成场景的深度贴图。这个pass的代码在Packages\com.unity.render-pipelines.universal\Shaders\DepthOnlyPass.hlsl中。

本篇小结

本篇中我们主要分析了Unlit pass的内容,可以看到很多宏定义,函数都来自于URP和SRP Core的ShaderLibrary中。熟悉这些宏和函数方便我们写自己的自定义Shader。另外我们要注意CBuffer这个结构,后面会说一下SRP Batcher相关的内容。


http://www.ppmy.cn/news/1115.html

相关文章

【Linux】常用的Linux命令(初学者必读)

一、学习Linux的原因 开源,免费系统迭代更新系统性能稳定安全性高多任务,多用户耗资源少内核小应用领域广泛使用及入门容易 二、Linux常用的命令 我使用的Linux环境是在 腾讯云服务器上的Centos 7和 Xshell。 下面我把常用的一些命令分成了几个部分&am…

用Python预测世界杯球赛结果,还别说准确度还是蛮高的

前言 那么四年一度的世界杯即将要在卡塔尔开幕了,对于不少热爱足球运动的球迷来说,这可是十分难得的盛宴,而对于最后大力神杯的归属,相信很多人都满怀着期待,每个人心中都有不同的答案。 今天我就通过Python数据分析…

【Android App】人脸识别中使用Opencv比较两张人脸相似程度实战(附源码和演示 超详细)

需要全部代码请点赞关注收藏后评论区留言私信~~~ 一、比较两张人脸的相似程度 直方图由一排纵向的竖条或者竖线组成,横轴代表数据类型,纵轴代表数据多少。 图像直方图经常应用于特征提取、图像匹配等方面。 假设有两幅图像,它们的直方图很相…

C++ OpenCV【视频合并:多个图像拼接在一张图像】

提示:本文中视频拼接指的是将多张图像按空间合并在一张图像上,而不是将多张图像按时间顺序拼接成一个多帧片段。 文章目录 前言 一、OpenCV知识点 1.OpenCV裁剪矩形区域赋值 2.OpenCV将Mat粘贴到指定位置 二、程序样例 1.程序源码 2.运行结果 前言 C版…

高等数学(第七版)同济大学 习题10-5 个人解答

高等数学(第七版)同济大学 习题10-5 函数作图软件:Mathematica 1.求下列含参变量的积分所确定的函数的极限:\begin{aligned}&1. \ 求下列含参变量的积分所确定的函数的极限:&\end{aligned}​1. 求下列含参变量…

【机器学习】基于机器学习的反弹shell命令识别

引言 本文介绍一个基于机器学习识别反弹shell的项目。 在主机安全检测中,一般是采用基于原理的方式识别反弹shell, 通过判断socket通信相关特征,可以准确地识别到主机中的反弹shell。 但是在容器场景下,检测反弹shell 的能力,可能…

如何用蓝牙实现无线定位(四)--远程定位显示

1. 待救援定位设备 按照下面的针脚使用杜邦线将待救援定位设备的主蓝牙、从蓝牙连接到主控板上,和本地显示时的连接针脚是一样的,但是由于不需要连接OLED,因此不需要堆叠Bigfish。 参考视频 烧录程序如下(human.ino)&a…

【Flink】检查点算法实现原理之检查点分界线

一 检查点的实现算法 一种简单的想法(同步的思想) 暂停应用保存状态到检查点再重新恢复应用(Spark Streaming) Flink 的改进实现(异步的思想) 基于 Chandy-Lamport 算法的分布式快照算法将检查点的保存和数…