当前位置: 首页 > news >正文

Custom SRP - LOD and Reflections

1 LOD Groups

场景中对象越多,场景就越丰富,但是过多的对象,也会增加 CPU 和 GPU 的负担.同时如果对象最终渲染在屏幕上后覆盖的像素太少,就会产生模糊不清的像素点/噪点.如果能够不渲染这些过小的对象,就能解决噪点问题,同时释放 CPU GPU,去处理更重要的对象.

裁剪掉这些对象,可能会导致对象突然消失/出现的问题,因此,可以基于对象在屏幕上的大小,定义一系列子对象,根据对象到摄像机的距离,选择一个子对象进行渲染

这些逻辑,都可以有 LOD Group 组件来实现.

1.1 LOD Group Component

组件创建后,默认有4个LOD级别, LOD 0,1,2 和culled(裁剪掉,不渲染).

组件上的百分数,代表对象在屏幕上,渲染的高度与屏幕高度的比值(一般都是这样),叫做对象的屏占比.如上图,它表示如果屏占比大于60%则用 LOD 0 渲染,以此类推,知道屏占比小于10% 时就会被裁剪掉

在 Quality Settings 中,可以配置 LOD Bias,默认是 2, 会将对象屏占比乘以2.在上面的配置中,意味着屏占比大于30%时渲染 LOD 0.

对于一个 LOD 对象,我们一般会创建一个对象,添加 LOD Group 组件,然后为其创建子对象,这些子对象被LOD驱动,渲染当前 LOD 级别配置的 Renderer.

选择一个 LOD 级别,点击 + 号,创建一个 Renderer 项, 然后就可以将 LOD 子对象拖动到 Renderers 列表中,表示该LOD会渲染它.

一个对象可以配置到多个 LOD 中.比如有A,B,C三个子对象,LOD0时,ABC都渲染,LOD1时渲染AB,LOD2时渲染A.这样随着距离增加,按照重要程度(ABC),三个子对象会一次消失.

1.2 LOD Transition

LOD之间的切换可能会过于突兀, 在LOD Group 的 Fade Mode 选项中,选择 cross fade,则切换时旧的 LOD 会有淡出过程,新的也会有淡入.但是目前该选项不会带来任何效果,因为这需要我们的 shader 的支持.

切换时两个LOD 的 Renderer 都会渲染, shader 中需要对他们进行混合.

Unity 用 LOD_FADE_CROSSFADE 来定义支持混合的 shader 变体.需要在 CustomLit 和 ShadowCaster 两个 pass 中定义 shader keyword:

#pragma multi_compile _ LOD_FADE_CROSSFADE

Fade 控制参数由 per draw buffer 中的 unity_LODFade 提供.其 x 时淡出参数.该参数在淡出和淡入时的取值是不同的:

  • 淡出时,1 ~ 0
  • 淡入时,-1 ~ 0 (一定是负的,但是是不是 0 ~ -1 ?)

后面实现 fade 效果时,会针对性处理.

1.3 Dithering fade 效果

我们通过 clip 来实现淡入淡出效果.

定义 ClipLOD 方法,并在像素着色器开始时调用

// 执行 lod fade 裁剪
void ClipLOD(float2 positionCS, float fade)
{
#if defined(LOD_FADE_CROSSFADE)//// 在垂直方向上划分条纹的效果//float dither = (positionCS.y % 32)/32;// unity dither 生成函数float dither = InterleavedGradientNoise(positionCS.xy, 0);// 淡入时,fade 时负的,因此需要 + ditherclip(fade + (fade < 0 ? dither : -dither));
#endif
}float4 LitPassFragment(Varyings input) : SV_TARGET
{UNITY_SETUP_INSTANCE_ID(input);ClipLOD(input.positionCS, unity_LODFade.x);...
}

可以看到我们自定义的 dither 的 fade 效果

lightmap 过度时,我们用到了 unity 提供的 dither 生成函数 InterleavedGradientNoise,这里可以换成同样的函数看效果

1.4 Animated cross-fading

开启后, cross fade 会在一定时间内完成,默认时 0.5 秒.这个值可以在代码中,修改 LODGroup.crossFadeAnimationDuration 的值来改变.注意这个值是静态变量,会影响所有 LOD Group

2 Reflections

目前我们的材质还不支持高光反射,导致我们的 metallic 材质是黑色的.

首先向 baked scene 中,创建几个球,配置其材质为 metallic > 0.8,可以发现是黑色的

2.1 Indirect BRDF

首先增加 IndirectBRDF 函数

// BRDF.hlsl
float3 IndirectBRDF(Surface surface, BRDF brdf, float3 diffuse, float3 specular)
{float3 reflection = brdf.specular * specular;// 粗糙表面会散射光线,因此除以粗糙度的平方+1reflection /= (brdf.roughness * brdf.roughness + 1.0);float3 diff = brdf.diffuse * diffuse;return diff + reflection;
}

粗糙度除了降低反射强度,还会让反射变得模糊.我们可以通过采样 cubemap 的低级别 mipmap 来实现这样的效果.Unity 在 Core RP Library 中的 ImageBasedLighting.hlsl 中定义了基于 perceptual roughness(感知粗糙度)来获取mipmap level,因此需要在 BRDF 结构体中定义该数据

// BRDF.hlsl
struct BRDF
{float3 specular;float3 diffuse;float roughness;float perceptualRoughness;
};
...BRDF GetBRDF(Surface surface, bool premultiplyAlpha)
{BRDF brdf;float oneMinusReflectivity = OneMinusReflectivity(surface.metallic);oneMinusReflectivity = 1.0 - surface.metallic;brdf.diffuse = surface.color * oneMinusReflectivity;if (premultiplyAlpha)brdf.diffuse *= surface.alpha;brdf.specular = lerp(MIN_REFLECTIVITY, surface.color, surface.metallic);brdf.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(surface.smoothness);brdf.roughness = PerceptualRoughnessToRoughness(brdf.perceptualRoughness);return brdf;
}

unity 通过 unity_SpecCube0 提供环境贴图,在GI.hlsl 中声明相应的贴图和采样器,然后定义采样函数.在GI结构体中增加 specular 用来传递反射

// GI.hlsl#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/EntityLighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/ImageBasedLighting.hlsl"
...
TEXTURECUBE(unity_SpecCube0);
SAMPLER(samplerunity_SpecCube0);
...
struct GI
{float3 diffuse;ShadowMask shadowMask;float3 specular;
};
...
// 采样环境贴图
float3 SampleEnvironment(Surface surfaceWS, BRDF brdf)
{// 采样环境贴图需要一个方向,由表面法线和表面的观察方向计算反射方向.float3 uvw = reflect(-surfaceWS.viewDirection, surfaceWS.normal);// 基于“感知粗糙度”,计算采样时的 mipmap levelfloat mip = PerceptualRoughnessToMipmapLevel(brdf.perceptualRoughness);float4 environment = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, uvw, 0.0);return environment.rgb;
}
..
GI GetGI (float2 lightMapUV, Surface surfaceWS) 
{GI gi;gi.diffuse = SampleLightMap(lightMapUV) + SampleLightProbe(surfaceWS);// 采样环境图gi.specular = SampleEnvironment(surfaceWS);...return gi;
}

修改Lighting 中的 GetLighting 函数,调用 IndirectBRDF

float3 GetLighting(Surface surfaceWS, BRDF brdf, GI gi)
{ShadowData shadowData = GetShadowData(surfaceWS);shadowData.shadowMask = gi.shadowMask;// 临时返回以查看数据//return gi.shadowMask.shadows.rgb;//float3 color = gi.diffuse * brdf.diffuse;float3 color = IndirectBRDF(surfaceWS, brdf, gi.diffuse, 1.0);for(int i = 0; i < GetDirectionalLightCount(); ++i){Light light = GetDirectionalLight(i, surfaceWS, shadowData);color += GetLighting(surfaceWS, brdf, light);}return color;
}

如下图,反射了天空盒

2.2 Fresnel Reflection

当视线掠过表面(观察方向与表面法线接近90度)时,表面更像镜子,会反射更多,这种现象就是 菲涅尔反射.模拟这种现象非常复杂,我们参考 URP 中采用的 Schlick approximation(石里克近似) 法.

我们将菲涅尔反射定义为白色,而粗糙表面会降低菲涅尔效应,因此我们基于粗糙度计算菲涅尔颜色(本质上灰度).然后基于观察方向和表面法线计算菲涅尔强度,并在菲涅尔颜色和反射之间插值.

// BRDF.hlslstruct BRDF
{float3 specular;          // 高光反射float3 diffuse;          // 漫反射float roughness;          // 粗糙度float perceptualRoughness; // 感知粗糙度float fresnel; // 菲涅尔颜色,因为是灰度,rgb都相等,因此只需要一个浮点数
};...BRDF GetBRDF(Surface surface, bool premultiplyAlpha)
{BRDF brdf;float oneMinusReflectivity = OneMinusReflectivity(surface.metallic);oneMinusReflectivity = 1.0 - surface.metallic;brdf.diffuse = surface.color * oneMinusReflectivity;if (premultiplyAlpha)brdf.diffuse *= surface.alpha;brdf.specular = lerp(MIN_REFLECTIVITY, surface.color, surface.metallic);brdf.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(surface.smoothness);brdf.roughness = PerceptualRoughnessToRoughness(brdf.perceptualRoughness);// 计算菲涅尔灰度颜色brdf.fresnel = saturate(surface.smoothness + 1.0 - oneMinusReflectivity);return brdf;
}float3 IndirectBRDF(Surface surface, BRDF brdf, float3 diffuse, float3 specular)
{// 计算菲涅尔强度float fresnelStrength = Pow4(1.0 - saturate(dot(surface.normal, surface.viewDirection)));// 反射是根据菲尼尔强度,在高光反射和菲涅尔反射之间插值float3 reflection = specular + lerp(brdf.specular, brdf.fresnel, fresnelStrength);// 粗糙表面会散射光线,因此除以粗糙度的平方+1reflection /= (brdf.roughness * brdf.roughness + 1.0);float3 diff = brdf.diffuse * diffuse;return diff + reflection;
}

2.3 Fresnel Slider

菲涅尔反射主要出现在几何体的边缘.当环境图与物体背后的颜色匹配时,效果很好.但是如果颜色不匹配,就会显得怪异.

降低光滑度可以降低菲涅尔反射,但会让整个表面变暗.同时,在某些情况下,近似的菲涅尔反射可能不合适,如水下.因此,需要加一个参数来控制菲涅尔强度.

// Lit.shader 中定义材质参数
_Smoothness("Smoothness", Range(0,1)) = 0.51
_Fresnel("Fresnel", Range(0,1)) = 1// LitInput.hlsl 中,定义 per material 变量,并定义查询函数
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
...
float _Smoothness;
float _Fresnel;
...
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)...float GetFresnel (float2 baseUV)
{return UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Fresnel);
}// UnlitInput.hlsl 中,定义返回0的函数
float GetFresnel (float2 baseUV)
{return 0.0;
}// Surface.hlsl 中,为Surface结构体增加 fresnelStrength
struct Surface
{...float smoothness;float fresnelStrength;...
};// LitPass.hlsl 中,为 Surface.fresnelStrength 赋值
float4 LitPassFragment(Varyings input) : SV_TARGET
{...surface.smoothness = GetSmoothness(input.uv);surface.fresnelStrength = GetFresnel(input.uv);...
}// BRDF.hlsl 中,应用该强度
float3 IndirectBRDF(Surface surface, BRDF brdf, float3 diffuse, float3 specular)
{// 计算菲涅尔强度float fresnelStrength = surface.fresnelStrength * Pow4(1.0 - saturate(dot(surface.normal, surface.viewDirection)));// 反射是根据菲尼尔强度,在高光反射和菲涅尔反射之间插值float3 reflection = specular + lerp(brdf.specular, brdf.fresnel, fresnelStrength);// 粗糙表面会散射光线,因此除以粗糙度的平方+1reflection /= (brdf.roughness * brdf.roughness + 1.0);float3 diff = brdf.diffuse * diffuse;return diff + reflection;
}

2.4 Reflection Probes

除了反射天空,还可以创建当前场景.通过GameObject / Light / Reflection Probe创建 Reflection Probe,该组件会在其位置拍摄6方向并生成立方体贴图.属性 Box Size 用来确定影响范围,配合 Importance 重要性,该范围内的对象会使用该 Reflection Probe.

Cube map 可以离线生成,也可以实时生成.实时生成需要渲染6个画面,因此消耗比较大,不建议.

通过 Anchor Override, Renderer 可以调整 Reflection Probe 选择,即使对象本身超出了范围,但是可以指定该属性的位置在 Reflection Probe 范围内,来优化选择. 使用场景中的 Light Probe 会打断合批.同时,DrawMeshInstanced 接口渲染时,不支持指定 Reflection Probe.

Renderer 的 Reflection Probe 选项中:

  • 默认是 Blend Probes, Unity会选择两个 Reflection Probe 并进行插值,该模式与 SRP Batcher 不兼容,因此我们不能用.
  • off 表示使用天空盒的 cube map
  • Simple 表示使用最近,最重要的 Reflection Probe

Cube map 可能是 HDR 或 LDR 的,我们需要正确解码采样结果.该解码参数以 unity_SpecCube0_HDR 变量提供.

// UnityInput.hlsl 中
CBUFFER_START(UnityPerDraw)
...
float4 unity_ProbeVolumeMin;
float4 unity_SpecCube0_HDR; // 如何解码 reflection cube map
CBUFFER_END// GI.hlsl 中
// 采样环境贴图
float3 SampleEnvironment(Surface surfaceWS, BRDF brdf)
{// 采样环境贴图需要一个方向,由表面法线和表面的观察方向计算反射方向.float3 uvw = reflect(-surfaceWS.viewDirection, surfaceWS.normal);// 基于“感知粗糙度”,计算采样时的 mipmap levelfloat mip = PerceptualRoughnessToMipmapLevel(brdf.perceptualRoughness);float4 environment = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, uvw, 0.0);// unity_SpecCube0_HDR 参数可以确定 cube map 是 HDR/LDR的,通过下面的方法正确解码return DecodeHDREnvironment(environment, unity_SpecCube0_HDR);
}

http://www.dtcms.com/a/356233.html

相关文章:

  • 柳州市委常委、统战部部长,副市长潘展东率队首访深兰科技集团新总部,共探 AI 赋能制造大市与东盟合作新局
  • Claude Code 完整手册:从入门、配置到高级自动化
  • 【python】相机输出图片时保留时间戳数据
  • Linux学习——sqlite3
  • 179-183动画
  • IntelliJ IDEA2025+启动项目提示 Failed to instantiate SLF4J LoggerFactory
  • 零基础json入门教程(基于vscode的json配置文件)
  • 【贪心算法】day4
  • HTML 核心标签全解析:从文本排版到媒体嵌入
  • 联想打印机2268w安装
  • 根据并发和响应延迟,实现语音识别接口自动切换需求
  • IP v 6
  • Linux下的软件编程——数据库
  • 编程与数学 03-004 数据库系统概论 06_需求分析
  • 【Flask】测试平台开发,初始化管理第一个页面开发-第三篇
  • Charles打开后,Pc电脑端浏览器显示Not implemented或没有网络
  • Linux Shell 脚本基础002
  • 使用 Java 替换和修改 PDF 文本的方法
  • 命令行操作:逻辑运算符、重定向与管道
  • TensorFlow 深度学习 | 使用子类 API 实现 Wide Deep 模型
  • 20250829_编写10.1.11.213MySQL8.0异地备份传输脚本+在服务器上创建cron任务+测试成功
  • MySQL-索引(下)
  • Linux -- 进程间通信【命名管道】
  • 基于博客系统的自动化测试项目
  • 使用TensorFlow Lite Mirco 跑mirco_speech语音识别yes/no
  • DVWA靶场通关笔记-命令执行(Impossible级别)
  • 大数据毕业设计选题推荐:基于北京市医保药品数据分析系统,Hadoop+Spark技术详解
  • 多线程网络数据接收与处理框架设计
  • 软考-系统架构设计师 专家系统(ES)详细讲解
  • 【深度学习计算机视觉】02:微调