URP常用的方法和结构及其对应的位置
前言
不知道你们是否和我一样有着这样的经历。明明知道某些东西可以实现个人想要的功能,但是就是记不起函数。或是说对函数名字有些模糊。如果我们使用类似Visual
Studio这样的工具,那么它或许会给我们一些提示。但这些大多数是没有做Unity
Shader的适配。即使有也是需要花钱的。虽然我个人是使用VSCode来写Unity
Shader,但是我也没有找到一个好的插件。
我个人其实大多数时候不会去记这些函数,且我自身写Shader不多(主要是项目没需求,闲暇时间我也不知道怎么去学这方面的知识)。所以我只好写一篇文章来记录一下,我常用的信息。这里我只做了Unity
URP管线下的东西,毕竟内置管线还是有着不错的插件和一些文章总结。
URP版本
Universal RP 14.0.8
URP文件位置
我们打开任意一个Unity项目所在的文件位置。我们可以看到其Assets文件。在Assets文件的同目录下,我们可以找到Library\PackageCache\
。在PackageCache文件夹下,我们可以找到com.unity.render-pipelines.universal@14.0.8
和com.unity.render-pipelines.core@14.0.8
(这里的14.0.8表示的是我当前URP的版本号 )。URP对应的文件就被存储在这里。
各种坐标转变函数
所有的坐标系转变函数(包括了点和向量)都在下面显示的文件中
1 com.unity.render-pipelines.core@14.0.8\ShaderLibrary\SpaceTransforms.hlsl
这里我列举几个,我认为常用的坐标系转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 float3 TransformObjectToWorld(float3 positionOS) { #if defined(SHADER_STAGE_RAY_TRACING) return mul(ObjectToWorld3x4(), float4(positionOS, 1.0)).xyz; #else return mul(GetObjectToWorldMatrix(), float4(positionOS, 1.0)).xyz; #endif } // Transforms position from object space to homogenous space float4 TransformObjectToHClip(float3 positionOS) { // More efficient than computing M*VP matrix product return mul(GetWorldToHClipMatrix(), mul(GetObjectToWorldMatrix(), float4(positionOS, 1.0))); } // Normalize to support uniform scaling float3 TransformObjectToWorldDir(float3 dirOS, bool doNormalize = true) { #ifndef SHADER_STAGE_RAY_TRACING float3 dirWS = mul((float3x3)GetObjectToWorldMatrix(), dirOS); #else float3 dirWS = mul((float3x3)ObjectToWorld3x4(), dirOS); #endif if (doNormalize) return SafeNormalize(dirWS); return dirWS; } // Transforms normal from object to world space float3 TransformObjectToWorldNormal(float3 normalOS, bool doNormalize = true) { #ifdef UNITY_ASSUME_UNIFORM_SCALING return TransformObjectToWorldDir(normalOS, doNormalize); #else // Normal need to be multiply by inverse transpose float3 normalWS = mul(normalOS, (float3x3)GetWorldToObjectMatrix()); if (doNormalize) return SafeNormalize(normalWS); return normalWS; #endif }
如果你看过网络上一些关于URP的文章,你会发现有些作者会使用一些函数如:GetVertexPositionInputs、GetVertexNormalInputs,去获取一些点位和法线相关的信息。这些函数都在下的显示的路径下:
1 com.unity.render-pipelines.universal@14.0.8\ShaderLibrary\ShaderVariablesFunctions.hlsl
示例中的具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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; } VertexNormalInputs GetVertexNormalInputs(float3 normalOS) { VertexNormalInputs tbn; tbn.tangentWS = real3(1.0, 0.0, 0.0); tbn.bitangentWS = real3(0.0, 1.0, 0.0); tbn.normalWS = TransformObjectToWorldNormal(normalOS); return tbn; }
于光相关的函数
光是我们写Shader经常要用到的东西。在URP中于光相关的操作一般而言我们都需要引用下面这段代码:
1 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
往往我们引用这段代码后就可以使用GetMainLight
方法去获取。但是如果你翻阅过其中的代码,你就会发现Lighting.hlsl中并没有GetMainLight
的实现,且其返回的Light
数据也不在其中声明。它们的声明都在Lighting.hlsl同目录下的RealtimeLights.hlsl中。其代码如下:
1 2 3 4 5 6 7 8 9 struct Light { half3 direction; half3 color; // full-float precision required on some platforms float distanceAttenuation; half shadowAttenuation; uint layerMask; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Light GetMainLight() { Light light; light.direction = half3(_MainLightPosition.xyz); #if USE_FORWARD_PLUS #if defined(LIGHTMAP_ON) && defined(LIGHTMAP_SHADOW_MIXING) light.distanceAttenuation = _MainLightColor.a; #else light.distanceAttenuation = 1.0; #endif #else // unity_LightData.z is 1 when not culled by the culling mask, otherwise 0. light.distanceAttenuation = unity_LightData.z; #endif light.shadowAttenuation = 1.0; light.color = _MainLightColor.rgb; light.layerMask = _MainLightLayerMask; return light; }
GetMainLight
还有很多带参数的重载。如果你感兴趣可以翻阅一下RealtimeLights.hlsl中的源码,这里由于篇幅有限我就不在写明了。除了获取主光源的函数外,你也可以在RealtimeLights.hlsl看到获取其他光源的函数————GetAdditionalLight
。
这里我在说明一个获取光的坑点。如果你选用的渲染类型是Forward+,那么我们使用GetMainLight
是获取不到正确的信息。在Lighting.hlsl的UniversalFragmentBlinnPhong
函数中有这样一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #if USE_FORWARD_PLUS for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++) { FORWARD_PLUS_SUBTRACTIVE_LIGHT_CHECK Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor); #ifdef _LIGHT_LAYERS if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers)) #endif { lightingData.additionalLightsColor += CalculateBlinnPhong(light, inputData, surfaceData); } } #endif
这里它通过这样的方式才得到了正确的光信息。如果你只有一个光源的话,那直接使用GetAdditionalLight(0)
得到的效果是一样的。
阴影
在Light结构中,你可以找到名为shadowAttenuation
的变量。这个值就可以表示是否有阴影。如果你想得到正确的shadowAttenuation
值,那么你就要将着色点的世界坐标转到阴影坐标系(ShadowCoord)中。具体代码如下:
1 Light mainLight = GetMainLight(TransformWorldToShadowCoord(i.worldPos));
在写上面这段代码前,我们还需要一些着色器变体来保证得到正确的数据,声明如下:
1 2 #pragma multi_compile _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE #pragma multi_compile_fragment _SHADOWS_SOFT
有关着色器变体的信息可以查看官方文档 。
TransformWorldToShadowCoord
看似是一个不同坐标系之间坐标转换的函数。但是在SpaceTransforms.hlsl(请查看本文中各种坐标转变函数 )中并不存在这个函数。它被定义在Shadows.hlsl(于RealtimeLights.hlsl在同一文件下)中。具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 float4 TransformWorldToShadowCoord(float3 positionWS) { #ifdef _MAIN_LIGHT_SHADOWS_CASCADE half cascadeIndex = ComputeCascadeIndex(positionWS); #else half cascadeIndex = half(0.0); #endif float4 shadowCoord = mul(_MainLightWorldToShadow[cascadeIndex], float4(positionWS, 1.0)); return float4(shadowCoord.xyz, 0); }
Shadows.hlsl中定义了URP关于阴影的操作。比如计算级联阴影的函数ComputeCascadeIndex
,对阴影贴图的采样SampleShadowmap
等。除此之外,Shadows.hlsl中也包含了一些阴影变量的定义,如_ScreenSpaceShadowmapTexture
,_MainLightShadowmapTexture
等。
如果你只是想要一个简单的阴影实现可以去看一下,我写的这篇文章Unity
URP下接受阴影 。
实例化
在URP中实现实例化只需要像下面这样进行:
1 2 3 4 5 6 7 8 9 10 #include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DOTS.hlsl" #ifdef UNITY_DOTS_INSTANCING_ENABLED UNITY_DOTS_INSTANCING_START(MaterialPropertyMetadata) UNITY_DOTS_INSTANCED_PROP(float4, _Color) UNITY_DOTS_INSTANCED_PROP(half , _MainScale) UNITY_DOTS_INSTANCING_END(MaterialPropertyMetadata) #define _Color UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT(float4 , _Color) #define _MainScale UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT(half , _MainScale) #endif
更加具体的例子,大家可以去看一下LitInput.hlsl中对应的实例化声明。
1 com.unity.render-pipelines.universal@14.0.8\Shaders\LitInput.hlsl
获取屏幕坐标
获取屏幕坐标的函数如下:
1 2 3 4 5 6 7 float4 ComputeScreenPos(float4 positionCS) { float4 o = positionCS * 0.5f; o.xy = float2(o.x, o.y * _ProjectionParams.x) + o.w; o.zw = positionCS.zw; return o; }
其在Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.deprecated.hlsl
下,但是我们不需要额外的进行引用。因为
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
已经帮我们引用好了。
屏幕贴图的采样
在某些情况下,我们需要对当前渲染出来的信息进行采样。比如屏幕扭曲的效果
屏幕采样的函数,大家只要引入下面这段代码:
1 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl"
其中的函数如下:
1 2 3 4 5 6 7 8 9 float3 SampleSceneColor(float2 uv) { return SAMPLE_TEXTURE2D_X(_CameraOpaqueTexture, sampler_CameraOpaqueTexture, UnityStereoTransformScreenSpaceTex(uv)).rgb; } float3 LoadSceneColor(uint2 uv) { return LOAD_TEXTURE2D_X(_CameraOpaqueTexture, uv).rgb; }
函数中uv一般是我们的屏幕坐标的xy。
深度信息
获取物体的深度也是我们时常需要用到的功能。在URP中,我们只需引入下面这段代码:
1 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl"
其中的函数如下:
1 2 3 4 5 6 7 8 9 float SampleSceneDepth(float2 uv) { return SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, UnityStereoTransformScreenSpaceTex(uv)).r; } float LoadSceneDepth(uint2 uv) { return LOAD_TEXTURE2D_X(_CameraDepthTexture, uv).r; }
函数中uv一般是我们的屏幕坐标的xy。
LinearEyeDepth
LinearEyeDepth
也是一个常用的函数。虽然它很常用,但是我到现在也没有搞懂它。这个函数需要两个参数,一个是深度值,另一个是_ZBufferParams
(有关_ZBufferParams
的信息,Unity官方文档 上有说明)。
其函数声明在:
1 PackageCache\com.unity.render-pipelines.core@14.0.8\ShaderLibrary\Common.hlsl
函数实现:
1 2 3 4 float LinearEyeDepth(float depth, float4 zBufferParam) { return 1.0 / (zBufferParam.z * depth + zBufferParam.w); }
这里特别说明一下,这个函数只有在透视投影的时候才能计算出正确的值。如果是在正交投影下,请使用LinearDepthToEyeDepth
已获取正确的深度值。
其函数声明在:
1 PackageCache\com.unity.render-pipelines.universal@14.0.8\ShaderLibrary\ShaderVariablesFunctions.hlsl
函数实现:
1 2 3 4 5 6 7 8 9 // Linear depth buffer value between [0, 1] or [1, 0] to eye depth value between [near, far] half LinearDepthToEyeDepth(half rawDepth) { #if UNITY_REVERSED_Z return half(_ProjectionParams.z - (_ProjectionParams.z - _ProjectionParams.y) * rawDepth); #else return half(_ProjectionParams.y + (_ProjectionParams.z - _ProjectionParams.y) * rawDepth); #endif }
如果你嫌这个麻烦,你可以直接使用GetLinearEyeDepth
。其声明在
1 PackageCache\com.unity.render-pipelines.universal@14.0.8\ShaderLibrary\SSAO.hlsl
函数实现:
1 2 3 4 5 6 7 8 float GetLinearEyeDepth(float rawDepth) { #if defined(_ORTHOGRAPHIC) return LinearDepthToEyeDepth(rawDepth); #else return LinearEyeDepth(rawDepth, _ZBufferParams); #endif }
碎碎念
这篇文章的时间跨度也有几个月了。我原本想记录的东西也差不多忘了。上述这些是现在的我觉得需要的。如果后面我发现还有一些函数十分常用,那我再来丰富这篇文章。