☰
Unity URP PBR Shader从原理到实战:BRDF、金属度工作流与调参指南
2026/9/30 4:59:31 网站建设 项目流程

1. 从零理解PBR:为什么它成了现代渲染的标配

第一次接触PBR(Physically Based Rendering,基于物理的渲染)是在做一个室内场景项目的时候。当时用传统的光照模型调材质,金属看起来像塑料,塑料看起来像纸,怎么调参数都不对。后来被一位前辈点醒:“你试试PBR,金属度和粗糙度两个参数就能把材质质感拉开。”换到PBR工作流之后,同一个场景的质感提升了一个档次,而且不管换什么光照环境,材质表现都稳定得多。

这就是PBR的核心价值:用物理规律约束渲染方程,让材质在不同光照条件下都能保持视觉一致性。传统渲染模型(比如Blinn-Phong)是经验模型,参数调出来好看就行,但换一个光照环境就得重新调。PBR不一样,它基于能量守恒和微表面理论,材质的金属度、粗糙度、基础色这些参数是有物理含义的,一旦设定好,在晴天、阴天、室内灯光下都能得到合理的结果。

这篇笔记面向的是已经在Unity里写过基础Shader、想系统搞懂PBR在URP管线里怎么落地的朋友。如果你还在用Standard管线或者内置管线,思路是通的,但URP的Shader写法有区别,我会在关键位置标注出来。整篇内容会从PBR的数学原理讲起,然后一步步拆解URP里怎么实现一个完整的PBR Shader,最后分享我在实际项目中踩过的坑和调参经验。

2. PBR的数学底座:BRDF到底在算什么

2.1 渲染方程与BRDF的定位

PBR的理论根基是渲染方程,用一句话概括就是:一个着色点向观察方向发出的辐射亮度,等于该点自身发出的光加上所有入射光经过BRDF作用后的反射总和。写成公式是:

L_o(p, ω_o) = L_e(p, ω_o) + ∫_Ω f_r(p, ω_i, ω_o) L_i(p, ω_i) (n · ω_i) dω_i

这个积分在实际渲染里不可能实时算,所以PBR做的第一件事就是把积分拆解成可以直接计算的形式。拆解的关键就是BRDF(Bidirectional Reflectance Distribution Function,双向反射分布函数),它描述了“从某个入射方向来的光,有多少被反射到某个出射方向”。

BRDF有两个必须遵守的物理约束:能量守恒(反射出去的能量不能超过入射能量)和亥姆霍兹互易性(交换入射和出射方向,BRDF值不变)。这两个约束是PBR材质看起来“对”的根本原因。

2.2 微表面理论:把表面拆成无数小镜子

BRDF的经典实现基于微表面理论(Microfacet Theory)。这个理论的核心假设是:任何表面在微观尺度上都是由无数微小的完美镜面组成的,每个微面有自己的法线方向。宏观上看到的粗糙表面,其实是这些微面法线分布比较散;宏观上看到的光滑表面,微面法线比较集中。

基于这个假设,BRDF被拆成三项:

  • D项(法线分布函数,Normal Distribution Function):描述微面法线朝向某个方向的概率密度。粗糙度越低,法线越集中,高光越锐利。
  • G项(几何遮蔽函数,Geometry Function):描述微面之间互相遮挡的比例。掠射角下遮挡严重,高光会衰减。
  • F项(菲涅尔项,Fresnel Term):描述反射率随观察角度变化的关系。垂直看表面反射弱,掠射看反射强。

最终的BRDF是这三项的乘积再除以一个归一化因子:

f_r = (D · G · F) / (4 · (n·ω_i) · (n·ω_o))

分母里的4和两个点积是归一化用的,保证能量守恒。这个公式就是Cook-Torrance BRDF,也是URP里Lit Shader的核心。

2.3 为什么选GGX而不是Beckmann

D项有很多种实现,常见的有Beckmann分布和GGX(Trowbridge-Reitz)分布。URP默认用的是GGX,原因很实际:GGX的尾部衰减更慢,高光边缘更柔和,看起来更接近真实材质。Beckmann分布的高光衰减太快,边缘会有明显的截断感,金属材质尤其明显。

GGX的公式是:

D_GGX(n, h, α) = α² / (π · ((n·h)² · (α² - 1) + 1)²)

其中α是粗糙度的平方(有些实现直接用粗糙度,URP里做了映射)。这个公式在粗糙度接近0的时候会趋向无穷大,实际实现里会加一个极小值防止除零。

2.4 菲涅尔项的近似:Schlick公式

菲涅尔项的精确计算很复杂,Schlick给出了一个非常实用的近似:

F_Schlick(v, h, F0) = F0 + (1 - F0) · (1 - (v·h))⁵

F0是垂直入射时的反射率。对于绝缘体(非金属),F0大约是0.04;对于金属,F0就是基础色本身。这个近似在大多数情况下足够准确,而且计算量小,是实时渲染的标配。

注意:URP里金属和非金属的F0处理是分开的。非金属的F0固定为0.04乘以一个specular颜色,金属的F0直接用基础色。这个区分是PBR材质看起来“对”的关键,后面写Shader的时候会详细说。

3. URP管线下的PBR Shader结构拆解

3.1 URP Shader的基本骨架

URP的Shader和内置管线最大的区别是光照计算从Shader内部转移到了管线里。内置管线里你要自己写ForwardBase、ForwardAdd这些Pass,自己处理光照衰减。URP把这些封装成了Lighting函数,你只需要在片元着色器里调用UniversalFragmentPBR就行。

一个完整的URP PBR Shader结构大概是这样的:

Shader "Custom/PBR_Lesson" { Properties { _BaseMap ("Base Map", 2D) = "white" {} _BaseColor ("Base Color", Color) = (1,1,1,1) _Metallic ("Metallic", Range(0,1)) = 0.0 _Smoothness ("Smoothness", Range(0,1)) = 0.5 _NormalMap ("Normal Map", 2D) = "bump" {} _NormalScale ("Normal Scale", Range(0,2)) = 1.0 _OcclusionMap ("Occlusion Map", 2D) = "white" {} _OcclusionStrength ("Occlusion Strength", Range(0,1)) = 1.0 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForward" } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile _ _MAIN_LIGHT_SHADOWS #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE #pragma multi_compile _ _ADDITIONAL_LIGHTS #pragma multi_compile _ _SHADOWS_SOFT #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" // ... ENDHLSL } } }

这里有几个关键点:RenderPipeline标签必须设为UniversalPipeline,否则URP不会识别这个Shader;LightMode标签设为UniversalForward,这是URP的主光照Pass;multi_compile指令用来开启阴影、额外光源、软阴影这些功能,不写的话这些功能不会生效。

3.2 顶点着色器的输入输出结构

顶点着色器的任务是把模型空间的顶点数据转换到裁剪空间,同时把需要的光照计算数据传递到片元着色器。URP提供了Attributes和Varyings这两个结构体,但实际项目里我建议自己定义,因为默认结构体包含了很多用不到的数据,会增加带宽消耗。

struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; float2 lightmapUV : TEXCOORD1; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 positionWS : TEXCOORD1; float3 normalWS : TEXCOORD2; float4 tangentWS : TEXCOORD3; float2 lightmapUV : TEXCOORD4; float3 viewDirWS : TEXCOORD5; float4 shadowCoord : TEXCOORD6; };

positionWS、normalWS、viewDirWS这三个是世界空间的数据,PBR的光照计算都在世界空间进行。shadowCoord是阴影采样坐标,需要在顶点着色器里通过TransformWorldToShadowCoord计算。

3.3 片元着色器里的PBR核心逻辑

片元着色器是PBR真正干活的地方。核心步骤是:采样贴图、构建SurfaceData、调用UniversalFragmentPBR。

half4 frag(Varyings input) : SV_Target { // 采样基础色贴图 half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv); half3 baseColor = baseMap.rgb * _BaseColor.rgb; // 采样法线贴图并转换到世界空间 half3 normalTS = UnpackNormalScale( SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, input.uv), _NormalScale ); half3 normalWS = TransformTangentToWorld(normalTS, half3x3(input.tangentWS.xyz, cross(input.normalWS, input.tangentWS.xyz) * input.tangentWS.w, input.normalWS)); normalWS = NormalizeNormalPerPixel(normalWS); // 采样AO贴图 half occlusion = lerp(1.0, SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, input.uv).r, _OcclusionStrength); // 构建SurfaceData SurfaceData surfaceData; surfaceData.albedo = baseColor; surfaceData.metallic = _Metallic; surfaceData.smoothness = _Smoothness; surfaceData.normalTS = normalTS; surfaceData.occlusion = occlusion; surfaceData.alpha = 1.0; surfaceData.specular = half3(0,0,0); surfaceData.clearCoatMask = 0; surfaceData.clearCoatSmoothness = 0; surfaceData.emission = 0; // 构建InputData InputData inputData; inputData.positionWS = input.positionWS; inputData.normalWS = normalWS; inputData.viewDirectionWS = SafeNormalize(input.viewDirWS); inputData.shadowCoord = input.shadowCoord; inputData.fogCoord = 0; inputData.vertexLighting = 0; inputData.bakedGI = SampleSH(normalWS); inputData.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(input.positionCS); inputData.shadowMask = 1; // 调用URP的PBR光照函数 half4 color = UniversalFragmentPBR(inputData, surfaceData); return color; }

这段代码里最值得注意的是SurfaceData和InputData这两个结构体。SurfaceData描述材质本身的属性(颜色、金属度、粗糙度、法线、AO),InputData描述着色点的几何和光照环境信息(位置、法线、视线方向、阴影坐标、GI)。UniversalFragmentPBR把这两个结构体作为输入,内部完成所有BRDF计算和光照累加。

3.4 金属度工作流 vs 镜面度工作流

PBR材质有两种主流工作流:金属度工作流(Metallic Workflow)和镜面度工作流(Specular Workflow)。URP的Lit Shader默认用金属度工作流,我也推荐用这个。

金属度工作流的核心参数是基础色(Base Color)、金属度(Metallic)、粗糙度(Roughness)。金属度是一个0到1的值,0表示绝缘体,1表示金属。实际项目中大多数材质要么是0要么是1,中间值只用在过渡区域(比如生锈的金属)。

镜面度工作流直接指定镜面反射颜色,灵活度更高,但容易做出物理上不合理的材质。金属度工作流用物理约束换取了易用性和一致性,对美术来说更友好。

实操心得:如果美术同学把金属度调成0.5,大概率是想要一个“半金属”的效果,但物理上这种材质不存在。我的做法是让他明确这个材质到底是金属还是非金属,如果是金属就调到1,然后通过粗糙度和基础色来调整质感。这样出来的效果反而更可控。

4. 完整实操:从零写一个URP PBR Shader

4.1 项目准备与文件结构

先创建一个URP项目。在Package Manager里确认Universal RP已经安装,然后在Project Settings的Graphics里把Scriptable Render Pipeline Settings指向URP的Asset。如果没有这个Asset,在Assets里右键Create > Rendering > URP Asset创建一个。

Shader文件我习惯放在Assets/Shaders/PBR目录下,命名用PBR_Lesson.shader。配套的材质放在Assets/Materials下。测试场景里放一个球体、一个平面、一盏平行光,再加一个反射探针(Reflection Probe),这样能看到金属材质的反射效果。

4.2 Properties块的参数设计

Properties块决定了材质面板上能看到哪些参数。PBR的核心参数是基础色、金属度、粗糙度、法线、AO。我还会加一个Emission参数,方便做自发光材质。

Properties { [MainTexture] _BaseMap ("Base Map", 2D) = "white" {} [MainColor] _BaseColor ("Base Color", Color) = (1,1,1,1) [Space(10)] _Metallic ("Metallic", Range(0,1)) = 0.0 _Smoothness ("Smoothness", Range(0,1)) = 0.5 [Space(10)] [Normal] _NormalMap ("Normal Map", 2D) = "bump" {} _NormalScale ("Normal Scale", Range(0,2)) = 1.0 [Space(10)] _OcclusionMap ("Occlusion Map", 2D) = "white" {} _OcclusionStrength ("Occlusion Strength", Range(0,1)) = 1.0 [Space(10)] [HDR] _EmissionColor ("Emission Color", Color) = (0,0,0,1) _EmissionMap ("Emission Map", 2D) = "white" {} }

[MainTexture]和[MainColor]这两个Attribute是URP特有的,用来告诉管线哪个是主贴图和主颜色,在Shader Graph和某些工具里会用到。[Normal]标记法线贴图,Unity会自动处理sRGB和压缩格式。[HDR]让颜色可以超过1,做自发光的时候很有用。

4.3 顶点着色器的完整实现

顶点着色器要做的事情:转换位置、转换法线、计算切线空间、计算阴影坐标、传递UV。

Varyings vert(Attributes input) { Varyings output = (Varyings)0; // 位置转换 VertexPositionInputs positionInputs = GetVertexPositionInputs(input.positionOS.xyz); output.positionCS = positionInputs.positionCS; output.positionWS = positionInputs.positionWS; // 法线转换 VertexNormalInputs normalInputs = GetVertexNormalInputs(input.normalOS, input.tangentOS); output.normalWS = normalInputs.normalWS; output.tangentWS = float4(normalInputs.tangentWS, input.tangentOS.w); // UV传递 output.uv = input.uv; output.lightmapUV = input.lightmapUV * unity_LightmapST.xy + unity_LightmapST.zw; // 视线方向 output.viewDirWS = GetWorldSpaceViewDir(positionInputs.positionWS); // 阴影坐标 output.shadowCoord = GetShadowCoord(positionInputs); return output; }

GetVertexPositionInputs和GetVertexNormalInputs是URP提供的工具函数,内部处理了各种空间转换。GetWorldSpaceViewDir计算从着色点到相机的方向。GetShadowCoord根据主光源的阴影设置计算阴影采样坐标。

4.4 片元着色器的逐行解析

片元着色器的代码在3.3节已经给过完整版本,这里补充几个关键细节。

法线贴图的解码:UnpackNormalScale会根据平台的压缩格式自动选择正确的解码方式。在移动平台上法线贴图通常用ETC压缩,解码方式和桌面平台不一样,用这个函数可以避免手动处理。

切线空间的构建:TransformTangentToWorld需要三个基向量:切线、副切线、法线。副切线通过cross(normalWS, tangentWS.xyz) * tangentWS.w计算,tangentWS.w是切线的手性(handedness),用来处理UV镜像的情况。

AO的应用:AO只影响间接光,不影响直接光。URP的UniversalFragmentPBR内部会把surfaceData.occlusion应用到GI项上。如果手动写光照计算,要注意这一点。

阴影坐标的传递:shadowCoord在顶点着色器里计算,片元着色器里直接使用。如果开启级联阴影(Cascade Shadow),GetShadowCoord会自动选择合适的级联层级。

4.5 多光源与阴影的编译指令

URP的Shader通过multi_compile指令来开启不同的功能变体。不写这些指令,对应的功能就不会生效。

#pragma multi_compile _ _MAIN_LIGHT_SHADOWS #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile_fragment _ _SHADOWS_SOFT #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile _ DIRLIGHTMAP_COMBINED #pragma multi_compile_fog

_MAIN_LIGHT_SHADOWS开启主光源阴影,_MAIN_LIGHT_SHADOWS_CASCADE开启级联阴影,_ADDITIONAL_LIGHTS开启额外光源的逐像素计算,_ADDITIONAL_LIGHT_SHADOWS开启额外光源阴影,_SHADOWS_SOFT开启软阴影,LIGHTMAP_ON开启光照贴图支持。

注意:multi_compile和multi_compile_fragment的区别在于,前者在顶点和片元着色器里都会生成变体,后者只在片元着色器里生成。阴影相关的指令用multi_compile_fragment可以节省顶点着色器的变体数量。

5. 调参实战:让材质看起来“对”的经验

5.1 金属材质的参数范围

金属材质的金属度必须是1,基础色就是反射颜色。常见的金属参数:

材质基础色(sRGB)粗糙度
黄金(255, 226, 155)0.1-0.3
银(255, 255, 255)0.05-0.2
铝(245, 245, 245)0.1-0.4
铜(250, 180, 130)0.1-0.3
铁(200, 200, 200)0.3-0.6
生锈铁(120, 80, 50)0.6-0.9

金属材质对反射探针的依赖很大。没有反射探针,金属看起来就是一团黑。项目里至少放一个Reflection Probe,或者开启Screen Space Reflection。

5.2 非金属材质的参数范围

非金属材质的金属度是0,基础色是漫反射颜色。粗糙度决定了高光的锐利程度:

材质基础色粗糙度
塑料任意0.2-0.5
橡胶深色0.6-0.9
木材棕色系0.5-0.8
大理石浅色0.1-0.3
混凝土灰色0.7-0.95
皮肤肤色0.4-0.6

非金属的F0固定是0.04,所以高光颜色不会随基础色变化。如果想让高光带颜色,需要改specular参数,但URP的Lit Shader默认不暴露这个参数。

5.3 粗糙度贴图的制作要点

粗糙度贴图通常和金属度贴图打包在同一张图的不同通道里。URP的Lit Shader用的是金属度贴图的R通道存金属度,A通道存粗糙度(Smoothness)。但很多项目用的是R通道金属度,G通道粗糙度,B通道AO的打包方式,这种需要自己写Shader来采样。

制作粗糙度贴图的时候,不要用纯黑或纯白。纯黑(粗糙度0)会产生镜面反射,在实时渲染里容易出现噪点;纯白(粗糙度1)会让材质看起来像粉笔。实际材质的粗糙度通常在0.1到0.9之间。

实操心得:如果粗糙度贴图是手绘的,建议在Substance Painter里用材质球直接导出,不要手动在PS里画。手绘的粗糙度很难保证物理合理性,而且和法线贴图不匹配的时候会出现奇怪的高光。

5.4 法线贴图的强度控制

法线贴图的强度通过_NormalScale控制。URP的UnpackNormalScale会把法线贴图的RG通道乘以这个值,然后重建Z通道。强度太高会让表面看起来凹凸不平,强度太低则没有效果。

我的经验值是:细节法线用0.5到1.0,大结构法线用1.0到1.5。如果法线贴图是从高模烘焙的,强度设为1.0通常就对了。如果是手绘的法线,可能需要调到1.5才能看出效果。

法线贴图的切线空间要和模型的UV方向匹配。如果模型是镜像UV的,切线的手性会翻转,法线贴图会出现左右颠倒的问题。解决办法是在导入模型的时候勾选“Calculate MikkTSpace Tangent Space”,或者在Shader里处理手性。

6. 常见问题与排查实录

6.1 材质全黑或全白

全黑通常是因为:没有光源、光照强度为0、或者Shader编译失败。先检查场景里有没有Directional Light,然后看Console有没有报错。如果Shader编译失败,通常是include路径写错了,或者HLSL语法有问题。

全白通常是因为:基础色贴图没有赋值、或者光照强度过高。检查材质的Base Map有没有贴图,Directional Light的Intensity是不是超过了1。

6.2 金属材质没有反射

金属材质的反射来自Reflection Probe或者Screen Space Reflection。如果场景里没有Reflection Probe,金属会反射默认的Skybox。如果Skybox也是黑的,金属就是黑的。

解决办法:在场景里放一个Reflection Probe,Type设为Baked,然后Bake一下。或者开启URP的Screen Space Reflection(在URP Asset的Lighting设置里)。

6.3 阴影出现锯齿或漏光

阴影锯齿通常是因为阴影贴图分辨率不够。在URP Asset的Shadows设置里把Shadow Resolution调高,或者开启Soft Shadows。漏光通常是因为阴影偏移(Shadow Bias)设置不当,在Light组件里调整Bias和Normal Bias。

注意:Soft Shadows在移动平台上性能开销比较大,如果目标平台是手机,建议用Hard Shadows加较高的分辨率。

6.4 法线贴图效果不对

法线贴图效果不对的常见原因:贴图没有标记为Normal Map(在贴图导入设置里把Texture Type设为Normal Map)、切线空间不匹配、法线强度过高或过低。

如果法线贴图在模型上看起来是反的,可能是切线的手性不对。在Shader里把tangentWS.w取反试试。如果法线贴图在UV接缝处有裂缝,通常是MikkTSpace切线空间的问题,在模型导入设置里重新计算切线。

6.5 性能问题排查

PBR Shader的性能开销主要来自:光照计算、阴影采样、贴图采样。优化方向:

  • 减少multi_compile变体数量,只保留项目需要的功能
  • 用half精度代替float,移动平台上尤其重要
  • 合并贴图通道,把金属度、粗糙度、AO打包到一张图
  • 用LOD Group控制远处物体的Shader复杂度
  • 关闭不需要的功能,比如Screen Space Occlusion
问题现象可能原因排查方法
材质全黑无光源/Shader编译失败检查光源和Console
金属无反射无Reflection Probe添加Reflection Probe
阴影锯齿阴影分辨率低提高Shadow Resolution
法线反了切线手性错误取反tangentWS.w
性能差变体过多/精度过高减少multi_compile,用half

7. 进阶方向:从能用到好用

7.1 清漆层(Clear Coat)

清漆层用来模拟车漆、钢琴漆这类有透明涂层的材质。URP的Lit Shader在2021版本之后支持Clear Coat,在SurfaceData里设置clearCoatMask和clearCoatSmoothness就行。清漆层会在基础BRDF之上再叠加一层镜面反射,让材质看起来有“深度”。

7.2 各向异性(Anisotropy)

各向异性用来模拟拉丝金属、头发、丝绸这类有方向性高光的材质。URP的Lit Shader不直接支持各向异性,需要自己写BRDF。核心思路是把GGX的粗糙度改成和切线方向相关的值,让高光在某个方向上拉长。

7.3 次表面散射(Subsurface Scattering)

次表面散射用来模拟皮肤、玉石、蜡烛这类半透明材质。URP没有内置SSS,但可以通过修改BRDF的漫反射项来近似。常见做法是用预积分的皮肤贴图(Pre-integrated Skin)或者屏幕空间的模糊。

7.4 自定义BRDF的接入方式

如果想在URP里用自定义BRDF,有两种方式:一是直接修改UniversalFragmentPBR的源码,二是自己写光照循环,调用自定义的BRDF函数。第一种方式简单但会污染管线代码,第二种方式灵活但工作量大。

我的建议是:如果只是改D项或G项,直接改管线源码;如果要加全新的BRDF项(比如布料BRDF),自己写光照循环。自己写光照循环的时候,可以参考UniversalFragmentPBR的实现,把主光源、额外光源、GI、阴影这些部分拆开处理。

8. 我在实际项目中的几点体会

PBR不是“调参玄学”,它的每一个参数都有物理含义。金属度只有0和1,粗糙度对应微表面的法线分布,基础色对应材质的反射率或漫反射率。理解这些含义之后,调参就变成了“查表”而不是“试错”。

反射探针的质量直接决定了金属材质的上限。我见过很多项目金属看起来像塑料,就是因为反射探针没做好。要么用高质量的HDRI烘焙,要么开启Screen Space Reflection,不要用默认的Skybox凑合。

法线贴图和粗糙度贴图的匹配度比单独的质量更重要。一张高质量的法线贴图配一张不匹配的粗糙度贴图,效果还不如两张中等质量但匹配的贴图。制作的时候最好在Substance Painter里一起导出,保证一致性。

最后分享一个小技巧:在URP里调试PBR材质的时候,把Lighting Mode切到“Debug”可以看到每个光照分量的贡献。这个功能在URP的Debug窗口里,能快速定位是直接光、间接光还是反射出了问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询