Zack.Zhang Game Developer

基于URP的PBR角色特写小记

2022-05-26
zack.zhang

项目上需要做一个展示角色的场景,角色基于PBR渲染,需要展现出模型的细节。特写场景有很多实用的情景,比如选人界面、对话半身像、换装系统等。本文总结了一下在URP里做角色特写展示场景需要注意的一些问题。

做PBR的角色展示场景,不同于以往的非PBR做法,无限制地写Shader堆砌、Hack各种效果。PBR最大的优势就是定义了一套标准化的理论体系,开发人员在这套理论体系的指引下,去使用它就可以了。因此我们没有必要特别对Shader做什么订制,URP自带的“Universal Render Pipline/Lit”就够了。

PBR Shader方面不再赘述,有很多的文章去讲述。这里要总结的是如何设置场景环境参数,使渲染效果尽量正确、精细、美观,尽量接近Substance Painter的效果。下图为Substance Painter里面的效果。

substance-painter-rendering.png

首先将角色拖入到Unity中,换上基于URP Graph实现的Shader。

urp-shader.png

渲染效果如下,可见还是相去甚远。

Unity-rendering1.png

接下来一步一步设置环境参数来调整渲染效果。

校正Metallic、Roughness和AO

从Shader上来看,控制金属度(Metallic)、粗糙度(Roughness)和环境光遮蔽(AO)是靠一张控制贴图来实现的,这张贴图的R通道控制的是金属度,G通道控制的是粗糙度,B通道控制的是环境光遮蔽。

Unity的贴图导入到引擎后默认是选中sRGB选项的,sRGB这个东西是为了模拟人眼的感知而做的一种颜色空间的Gamma校正。然而控制贴图是为了模拟现实世界的物理属性,和人眼的感知无关,R值、G值和B值都需要是线性的数值,所以这里需要将sRGB去掉。

sRGB.png

如果使用sRGB的控制贴图,从前面的渲染效果来看,感觉上会“油腻”一些。下图改为线性空间贴图后,可见金属度、粗糙度都正确了许多。

Unity-rendering2.png

精细化阴影

从上图看,角色的阴影还是很粗糙的,有很明显的“马赛克”。究其原因,是因为我们在大世界里布置照明的时候,通常会使用一盏平行光作为主光源来模拟太阳光。从Shadowmap的原理来看,这种平行光源想要只用一张深度图,精细地保存灯光所照射的区域的每一个深度值,是几乎不可能的,因为平行光的照射范围是无限大的。在Unity中通常使用级联阴影贴图(CSM)来尽可能使阴影精细化,然而尽管参数调节得再如何精巧,这种做法依然会让人看到如上图所示的明显的锯齿走样。

既然目标是做特写,而不是在大世界中实时渲染,那么我们可以选择一种“照射区域有限”的灯光来对角色打光照明,这样就可以用有限大小的贴图来精细地存储照射到的每一个像素的深度值了。

综上所诉,探照灯在角色特写场景是不二的选择。

在URP里,主光源(Main Light)的定义是RenderSettings.sun或者场景里的最后一盏平行光,除了主光源的其他可见光源都称作附加光源(Additional Lights)。因此需要在URP的设置里,将Additional Lights的Cast Shadows选项勾上,且设置合适的Shadowmap分辨率。

additional-lights-settings.png

同时也需要设置灯光的阴影参数,需要投射阴影的灯光就打开阴影,不需要投射阴影的灯光就关闭阴影,如下图。

spto-light-settings.png

只要Shadowmap的分辨率足够,那么投射出来的阴影就会是精细的。

Unity-rendering3.png

调节暗部

从上图来看,阴影虽然很精细了,但是太过于黑了。如果需要将暗部颜色进行调整要怎么办呢?

从Shader源码上看,环境光源码主要是写在Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl里的GlobalIllumination函数里面。

从函数看如果需要调节暗部,那么有几种途径:

  • 调整控制图的B通道(控制环境光遮蔽)

  • 调整环境光颜色(菜单栏“Window/Rendering/Lighting Settings”里面的“Environment Lighting”相关的参数)

  • 调整光照探头、光照图等

这里简单调整环境光颜色来调整暗部颜色和亮度,效果如下。

Unity-rendering4.png

调节亮部

从上图来看,虽然暗部不是那么死黑了,但是衣袖部分亮度太高了,以至于掩藏住了衣服细节。

因为PBR是在线性空间里模拟的物理,那么就很容易数值超过屏幕能够承受的区间,所以就显得曝光过度了。为了能够将HDR映射到LDR,我们就需要用到Tonemapping(色调映射)技术。通常我们会选用ACES Tonemapping(Academy Color Encoding System)来高效地重新编码颜色到另一个颜色空间。

ACES_curve.png

要在URP里应用上ACES Tonemapping,就需要添加Post-Processing。

urp-aces.png

通过ACES Tonemapping就可以很好地在显示器上显示高曝光部分了。

Unity-rendering5.png

小技巧:如果觉得Post-Processing切Render Target的消耗接受不了,可以考虑将ACES Tonemapping的映射算法写在Pixel Shader里。如果觉得Post-Processing切Render Target的消耗可以接受,推荐将Bloom也打开,HDR配合Bloom会有令人惊喜的效果。

环境光反射

根据渲染方程的描述,我们可以知道基于物理的BRDF其实就是要模拟着色表面对环境半球上光线的反射效果的积分。在渲染上这部分主要是靠IBL(Image Based Lighting)来实现的,也就是说我们需要用到一个Cubemap来模拟半球上光线的反射效果。

按照通常的理解,越粗糙的表面反射的环境镜像越模糊。要模拟这种粗糙度引起的模糊效果,就需要用到Cubemap的Mip Maps,也就是越粗糙的像素采样的Mip Maps LOD等级越高。

real PerceptualRoughnessToMipmapLevel(real perceptualRoughness, uint mipMapCount)
{
    perceptualRoughness = perceptualRoughness * (1.7 - 0.7 * perceptualRoughness);

    return perceptualRoughness * mipMapCount;
}

real PerceptualRoughnessToMipmapLevel(real perceptualRoughness)
{
    return PerceptualRoughnessToMipmapLevel(perceptualRoughness, UNITY_SPECCUBE_LOD_STEPS);
}

上述代码描述了如果通过“感知粗糙度”获取Mip Maps的LOD等级。

综上所述,需要将Cubemap设置为环境反射贴图,且Cubemap本身的Mip Maps需要打开。

Unity里打开贴图Mip Maps的方法如下图所示。

mipmap.png

Unity里设置环境反射贴图的方法如下图所示。

reflection-env.png

Unity里设置环境反射贴图的方法除了上图所示,还可以使用ReflectionProbe。

Unity-rendering6.png

这个例子有没有环境光反射看不出太大的区别,我们换一个模型来展示。

robot-reflection-comp.png

选择URP管线通常是为了将其用于移动平台的,但是有些情况下会出现编辑器渲染效果和移动设备上渲染效果不一样。

SpotLight的Range在移动设备和编辑器上效果不一致

这个问题需要看源码:Packages/com.unity.render-pipelines.universal/Runtime/ForwardLights.cs的InitializeLightConstants函数。这个源码里有以下代码会导致SpotLight编辑器和移动平台表现不一致。

	float lightRangeSqr = lightData.range * lightData.range;
    float fadeStartDistanceSqr = 0.8f * 0.8f * lightRangeSqr;
    float fadeRangeSqr = (fadeStartDistanceSqr - lightRangeSqr);
    float oneOverFadeRangeSqr = 1.0f / fadeRangeSqr;
    float lightRangeSqrOverFadeRangeSqr = -lightRangeSqr / fadeRangeSqr;
    float oneOverLightRangeSqr = 1.0f / Mathf.Max(0.0001f, lightData.range * lightData.range);
	
	// On mobile and Nintendo Switch: Use the faster linear smoothing factor (SHADER_HINT_NICE_QUALITY).
	// On other devices: Use the smoothing factor that matches the GI.
	lightAttenuation.x = Application.isMobilePlatform || SystemInfo.graphicsDeviceType == GraphicsDeviceType.Switch ? oneOverFadeRangeSqr : oneOverLightRangeSqr;
	lightAttenuation.y = lightRangeSqrOverFadeRangeSqr;

如果遇到这个坑,同时非UNITY_EDITOR,则给SpotLight的Range乘以一个系数吧。

光滑度在移动设备和编辑器上效果不一致

这个问题需要看Shader源码:Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl的GlossyEnvironmentReflection函数。

half3 GlossyEnvironmentReflection(half3 reflectVector, half perceptualRoughness, half occlusion)
{
#if !defined(_ENVIRONMENTREFLECTIONS_OFF)
    half mip = PerceptualRoughnessToMipmapLevel(perceptualRoughness);
    half4 encodedIrradiance = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectVector, mip);

#if !defined(UNITY_USE_NATIVE_HDR)
    half3 irradiance = DecodeHDREnvironment(encodedIrradiance, unity_SpecCube0_HDR);
#else
    half3 irradiance = encodedIrradiance.rbg;
#endif

    return irradiance * occlusion;
#endif // GLOSSY_REFLECTIONS

    return _GlossyEnvironmentColor.rgb * occlusion;
}

其中PerceptualRoughnessToMipmapLevel的返回值有可能不准确,导致移动设备上显得“油腻”一些。如果遇到这个坑,请改为如下代码。

half3 GlossyEnvironmentReflection(half3 reflectVector, half perceptualRoughness, half occlusion)
{
#if !defined(_ENVIRONMENTREFLECTIONS_OFF)
    half mip = PerceptualRoughnessToMipmapLevel(perceptualRoughness);
#ifdef SHADER_API_MOBILE
    mip = min(mip + 1.0, UNITY_SPECCUBE_LOD_STEPS); // 这个Mip值算得不准,需要+1。
#endif
    half4 encodedIrradiance = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectVector, mip);

#if !defined(UNITY_USE_NATIVE_HDR)
    half3 irradiance = DecodeHDREnvironment(encodedIrradiance, unity_SpecCube0_HDR);
#else
    half3 irradiance = encodedIrradiance.rbg;
#endif

    return irradiance * occlusion;
#endif // GLOSSY_REFLECTIONS

    return _GlossyEnvironmentColor.rgb * occlusion;
}

总结

以上为URP管线上应用PBR的一些基本设置,应用以上的设置,仅仅是保证渲染正确,如果需要更进一步优化画面,要做的事情还很多,比如皮肤渲染需要加上SSS,衣服需要布料渲染,头发需要各向异性的光照模型,氛围烘托需要多光源或者光照探头等。

最后用类似的原理,在UE4上搭建一个最简单的环境,渲染一个模型看看效果。

UE4-rendering.png


Similar Posts

评论