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 中,应用该强度
// 间接 BRDF 光
float3 IndirectBRDF(Surface surface, BRDF brdf, float3 giDiffuse, float3 giSpecular)
{//return gi.diffuse * brdf.diffuse;//float3 reflection = giSpecular * brdf.specular;// 计算菲涅尔强度 float fresnelStrength = surface.fresnelStrength * Pow4(1.0 - saturate(dot(surface.normal, surface.viewDirection)));// 反射是根据菲尼尔强度,在高光反射和菲涅尔反射之间插值,在与 giSpecular(来自环境图) 相乘float3 reflection = giSpecular * lerp(brdf.specular, brdf.fresnel, fresnelStrength);// 粗糙表面会散射光线,因此除以粗糙度的平方+1reflection /= (brdf.roughness * brdf.roughness + 1.0);// 计算间接 diffusefloat3 diffuse = brdf.diffuse * giDiffuse;// 累加 diffuse 和 reflectionreturn diffuse + 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.pswp.cn/news/921028.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921028.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux篇章】互联网身份密码:解密 Session 与 Cookie 的隐藏玩法和致命漏洞!

本篇摘要 本篇将承接上篇HTTP讲解&#xff08; 戳我查看 &#xff09;遗留的关于Cookie与Session的介绍&#xff0c;在本篇&#xff0c;将会介绍Cookie的由来&#xff0c;作用&#xff0c;以及缺点等&#xff0c;进而引出Session&#xff0c;最后介绍一下它们的性质等&#xf…

Postman接口测试工具:高效管理测试用例与环境变量,支持断言验证及团队协作同步

之前跟你们聊过能搭知识网络的 Obsidian&#xff0c;今天换个偏向接口测试的方向 —— 给你们安利一个 Github 上的「Postman」&#xff0c;它是个接口测试工具&#xff0c;官网能直接下载&#xff08;Postman: The Worlds Leading API Platform | Sign Up for Free&#xff09…

可可图片编辑 HarmonyOS 上架应用分享

可可图片编辑 HarmonyOS 上架应用分享 介绍 可可图片编辑 原名 图片编辑大师&#xff0c;因为上架审核的时候 &#xff0c;提示与一些已有应用重名&#xff0c;为了避免冲突&#xff0c;需要改名字&#xff0c;所以苦心思考了一分钟&#xff0c;就调整成 可可图片编辑。 应用…

Notepad++近期版本避雷

近期Notepad若干版本存在投毒事件&#xff0c;虽然也欢迎大家使用替代软件&#xff0c;但是Notepad作为一款开源软件&#xff0c;如有需要也可以继续白嫖使用&#xff0c;但是请务必避开若干埋雷版本&#xff01; 经检查&#xff0c;部分版本在帮助菜单中加入了有关tw的部分个人…

【lucene核心】impacts的由来

在 Lucene 的 Impact 概念&#xff08;出现在 ImpactsEnum / Impact 对象里&#xff09;中&#xff1a;字段 含义 freq 当前 term 在该文档中出现了多少次&#xff08;即词频 term frequency&#xff09;。 norm 当前 文档在该字段中的长度因子&#xff08;即之前 norms 里保存…

基于Echarts+HTML5可视化数据大屏展示-惠民服务平台

效果展示代码结构&#xff1a;主要代码实现 index.html布局 <!doctype html> <html><head><meta charset"utf-8"><title>双数智慧公卫-传染病督导平台</title><meta http-equiv"refresh" content"60;urlhttps…

【Flink】DataStream API:执行环境、执行模式、触发程序执行

目录执行环境getExecutionEnvironmentcreateLocalEnvironmentcreateRemoteEnvironment执行模式流执行模式&#xff08;Streaming&#xff09;批执行模式&#xff08;Batch&#xff09;自动模式&#xff08;AutoMatic&#xff09;触发程序执行DataStream API是Flink的核心层API&…

CentOS7.6

腾讯云服务器 腾讯云 产业智变云启未来 - 腾讯 服务器在控制台显示 点击进入面板&#xff0c;显示所有信息 现在来安装桌面的远程控制软件 宝塔SSH终端:一款同时支持SSH和SFTP客户端的免费软件! 点击立即下载 在云服务器的实例列表复制公网ip 密码就是服务器的密码&#xff…

前端架构知识体系:常见图片格式详解与最佳实践

前端开发必备&#xff1a; 在前端开发中&#xff0c;合理选择图片格式直接影响网页加载性能、用户体验和带宽成本。本文将系统梳理常见图片格式&#xff0c;分析它们的优缺点、压缩原理、兼容性和推荐使用场景&#xff0c;并提供前端优化实战建议。1. JPEG / JPG 全称&#xff…

ARM的编程模型

ARM的编程模型 ARM 的编程模型指的是从程序员&#xff08;特别是汇编程序员和编译器设计者&#xff09;视角所看到的 ARM 处理器架构。它定义了程序员可以使用的资源、数据操作方式以及规则&#xff0c;主要包括&#xff1a;寄存器组、数据类型、内存访问方式、执行状态和异常处…

最大熵强化学习相比传统强化学习,有什么缺点?

要理解最大熵强化学习&#xff08;MaxEnt RL&#xff09;相比传统强化学习&#xff08;如DQN、PPO、DDPG等&#xff09;的缺点&#xff0c;首先需要明确两者的核心差异&#xff1a;传统RL的目标是“最大化累积奖励”&#xff0c;而MaxEnt RL在该目标基础上额外增加了“最大化策…

python生成器与协程深度剖析

目录 生成器 传统列表 vs 生成器对比 yield机制深度解析 生成器的高级用法 协程的演进:从yield到async/await 基于yield的协程 现代async/await语法 协程的错误处理和超时控制 异步生成器与异步迭代器 异步生成器 异步迭代器实现 实战案例:异步爬虫框架设计 生成器…

论文解读:基于 77 GHz FMCW 毫米波雷达的舱内占位检测

毫米波 (mm-Wave) 雷达是汽车应用&#xff08;例如高级驾驶辅助系统 (ADAS)&#xff09;的一种解决方案。本研究探索了商用毫米波雷达技术在车内应用领域的应用。本文提出了一种基于 77 GHz 毫米波雷达的车辆占用检测器框架。本研究采用了德州仪器 (Texas Instruments) 的多输入…

进程优先级(Process Priority)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录进…

OpenCV的轮廓检测

1. 轮廓检测的基本概念轮廓是图像中连续的、闭合的曲线段&#xff0c;代表物体的边界&#xff08;如圆形的轮廓是一条闭合曲线&#xff09;。OpenCV 的轮廓检测通过 cv2.findContours() 实现&#xff0c;可用于形状识别、物体计数、图像分割等场景。2. 核心函数与参数&#xff…

亚信安全亮相鸿蒙生态大会2025 携手鸿蒙生态绘就万物智联新蓝图

8 月30 日&#xff0c;以 “新场景・新体验” 为主题的鸿蒙生态大会 2025 在深圳福田会展中心隆重开幕。本次大会由全球智慧物联网联盟&#xff08;GIIC&#xff09;主办、鸿蒙生态服务&#xff08;深圳&#xff09;有限公司承办&#xff0c;旨在搭建全球鸿蒙生态伙伴的高层次交…

Linux内核进程管理子系统有什么第四十回 —— 进程主结构详解(36)

接前一篇文章&#xff1a;Linux内核进程管理子系统有什么第三十九回 —— 进程主结构详解&#xff08;35&#xff09; 本文内容参考&#xff1a; Linux内核进程管理专题报告_linux rseq-CSDN博客 《趣谈Linux操作系统 核心原理篇&#xff1a;第三部分 进程管理》—— 刘超 《…

面试问题:进程和线程,编译步骤,const,map和unordered_map,深入理解unordered_map

目录 进程和线程的区别 const修饰指针(左边内容&#xff0c;右边指向) 1. const 修饰指针指向的内容&#xff08;指向常量&#xff09; 2. const 修饰指针本身&#xff08;常量指针&#xff09; 3. const 同时修饰指针本身和指向的内容&#xff08;指向常量的常量指针&…

利用棒棒糖图探索Office (US)的IMDB评分

利用棒棒糖图探索Office (US)的IMDB评分 import numpy as np import pandas as pd import matplotlib.colors as mc import matplotlib.image as image import matplotlib.pyplot as pltfrom matplotlib.cm import ScalarMappable from matplotlib.lines import Line2D from m…

Zephyr如何注册设备实例

设备树 → 编译期生成 → 运行时访问 流程图&#xff1a;Zephyr dev->config 工作流程设备树 (.dts) ───────────────────────────── anx745139 {compatible "analogix,anx7451";reg <0x39>;reset-gpios <&gpio1 5 …