基础渲染系列(四)——光照(Unity)

2020-07-10 17:17:40 浏览数 (1)

本文重点内容:

1、法线从物体空间转化为世界空间 2、让方向光生效 3、计算漫反射和镜面反射 4、加强节能 5、金属材质工作流 6、利用Unity的PBS算法

这是关于渲染的系列教程的第四部分。上一部分是关于组合纹理的。这次,我们将研究如何计算光照。

本教程是使用Unity 5.4.0b17。

(是时候照亮物体了)

1、法线

我们可以看到东西,因为我们的眼睛可以检测到电磁辐射。单个光量子称为光子。我们可以看到一部分电磁波谱,它们被称为可见光。其余的频谱对我们来说是看不见的。

整个电磁频谱是多少? 频谱分为频谱带。从低频到高频,这些被称为无线电波,微波,红外线,可见光,紫外线,X射线和伽马射线。 光源发光。其中一些光线会撞击物体。一些光线会从物体上反射出来。如果那束光最终射到我们的眼睛或相机镜头上,那么我们就可以看到物体了。

为了能让光照在3D环境里也能正常表现,就需要先了解这些对象的表面。前面的教程里,我们已经知道了它的位置,但不知道它反正光的方向。为此,我们需要表面法线向量。

1.1 使用Mesh 法线

复制我们的第一个着色器,并将其用作我们的第一个照明着色器。使用此着色器创建材质并将其分配给场景中的某些立方体和球体。为对象赋予不同的旋转度和比例(有些不均匀),以得到变化的场景。

(立方体和球体)

Unity的立方体和球面网格包含顶点法线。我们可以得到它们并将它们直接传递给片段着色器。

为了测试,我们可以在着色器中可视化法线。

(把法线向量当做颜色值)

这些是直接接来自网格的原始法线。立方体的面看起来是平坦的,因为每个面都是具有四个顶点的单独四边形。这些顶点的法线都指向同一方向。相反,球体的顶点法线都指向不同的方向,从而产生平滑的插值。

1.2 动态批次

当旋转它们的时候,立方体法线发生了一些奇怪的事情。我们预期每个立方体应该一直是相同的颜色,但事实并非如此。立方体会改变颜色,并且会和我们从哪个角度看它有关。

(立方体颜色变化)

这是由动态批处理引起的。Unity将小网格物体动态合并在一起,以减少draw calls。球体的网格因为太大不能动态合批,因此它们不受影响。

要合并网格,必须将其从本地空间转换为世界空间。对象是否批处理以及如何批处理,取决于物体的数量以及它们的渲染顺序。由于此转换也会影响法线,因此这就是我们看到颜色变化的原因。

如果不需要的话,可以通过播放器设置来关闭动态批处理。

除了动态批处理之外,Unity还可以执行静态批处理。和动态批处理不同的是,静态批处理是在构建时候发生的,同样它也涉及到世界空间的转换。

(法线,现在没有动态批处理了)

如果你担心动态批处理会导致法线计算带来的问题的话,那么大可不必。实际上,我们会连法线一起处理的,所以可以放心的开启它。

1.3 世界空间下的法线

除了动态批处理的对象外,我们所有的法线都在对象空间中。但是我们必须知道物体在世界空间中的表面方向。因此,必须将法线从对象空间转换为世界空间。为此,我们需要对象的转换矩阵。

就像我们在第1部分中所做的一样,Unity将对象的整个变换层次结构折叠为一个变换矩阵。我们可以这样写:O = T1T2T3 ...其中T是单个变换,而O是组合转型。该矩阵称为“object-to-world”。

Unity通过float4x4 unity_ObjectToWorld变量在着色器中使用此矩阵,该变量在UnityShaderVariables中定义。将此矩阵与顶点着色器中的法线相乘,以将其转换为世界空间。并且由于法线是一个向量表示方向,所以需要忽略位置。也就是说,第四齐次坐标必须为零。

或者,我们可以只乘以矩阵的3×3部分。编译后的代码具有相同的结果,因为编译器将消除所有与常数零相乘的内容。

(从物体空间转换为世界空间)

现在,法线已经世界空间中了,但是某些法线看起来比其他法线更亮。那是因为它们也得到了同比的缩放。因此,我们必须在转换后对再其进行归一化。

(归一化后的法线)

虽然我们再次对向量进行了归一化,但是对于没有统一比例尺的对象,它们看起来很奇怪。这是因为当曲面沿一个纬度拉伸时,其法线不会以相同的方式拉伸。

(将X缩放,顶点和法线都缩小½)

当比例尺不均匀时,应将其反转为法线。这样,法线在再次进行归一化后才能与变形过的表面形状相匹配。而且对于统一的比例尺也没有影响。

(X缩放,顶点缩放½,法线缩放2。)

反转比例尺,但旋转角度应保持不变。应该怎么做? 我们将对象的变换矩阵描述为O = T1T2T3 ...但我们可以比这更具体。我们知道层次结构中的每个步骤都结合了缩放,旋转和定位。因此,每个T 都可以分解为SRP。 这意味着 O = S1R1P1S2R2P2S3R3P3 ...我们用O = S1R1P1S2R2P2替代,以便简短描述。 因为法线是方向向量,所以我们不用在乎它的重新定位。这样的话,我们可以将其进一步缩短为O = S1R1S2R2,我们只需要考虑3 x 3矩阵。

我们想反转缩放比例,但保持旋转不变。因此,需要一个新的矩阵。

逆矩阵如何工作?

矩阵的逆矩阵用 M-1表示。它也是一个矩阵,当它与某个矩阵相乘时,将撤消该矩阵和另一个矩阵的运算结果。彼此相反。所以

要撤消一系列步骤,你必须以相反的顺序执行相反的步骤。这意味着

在一个数字的情况下X,它的逆就是 1/x,因为 X乘以1/x = 1。这也表明零没有逆。以及并不是每个矩阵都有逆。

我们正在处理缩放,旋转和重新定位矩阵。只要我们不将其缩放为零,所有这些矩阵都可以求逆。

通过简单地否定第四列中的XYZ偏移量,即可得出重新放置矩阵的逆矩阵。

通过将对角矩阵的对角线反转,可以实现其逆矩阵。我们只需要考虑3 x 3矩阵。

旋转矩阵可以一次视为一个轴,例如绕Z轴。通过简单地旋转Z,−Z弧度可以撤消Z弧度的旋转。当研究正弦波和余弦波时,您会注意到sin(-z)=-sinz和 cos(-z)= cosz。这使得逆矩阵变得简单。

请注意,旋转逆函数与原始矩阵在其主对角线上翻转的方向相同。仅正弦分量的符号改变。

除了object-to-world的矩阵外,Unity还提供了对象的world-to-object的矩阵。这些矩阵是彼此相反的。因此,我们也可以访问

这给了我们所需的反比例缩放,但也给了我们反旋转和反变换顺序。幸运的是,我们可以通过转置矩阵来消除那些不需要的效果。然后我们得到

矩阵的转置是什么?

矩阵M的转置表示为

可以通过翻转矩阵的主对角线来对其进行转置。因此,其行变为列,其列变为行。注意,这意味着对角线本身是不变的。

像逆一样,转置矩阵乘法序列会颠倒其顺序。

在处理非方矩阵时,这很有意义,否则可能会导致无效的乘法运算。但大部分时候这是正确的,你可以查看它的证明过程。

当然翻转两次会回到原点,即:

为什么转置会产生正确的矩阵?(太难排版了,看原文吧。。)

因此,让我们转置世界到对象矩阵,并将其与顶点法线相乘。

(正确的世界空间下的法线)

实际上,UnityCG包含一个方便的UnityObjectToWorldNormal函数,该函数帮助我们完成了此任务。因此我们可以使用该功能。它还使用显式乘法来执行此操作,而不是使用转置。这会产生更好的编译代码。

UnityObjectToWorldNormal是什么样的?

如下,inline 关键字不会做任何事情。

1.4 重新归一化

在顶点程序中生成正确的法线后,它们将通过插值器传递。不过,由于不同单位长度向量之间的线性内插不会产生另一个单位长度向量。它会更短。

因此,我们必须在片段着色器中再次归一化法线。

(重新归一化的法线)

尽管这会产生更好的结果,但其实不做的话,误差通常也很小。如果你更重视性能,则可以不对片段着色器进行重新归一化。这是针对移动设备的比较常见优化。

(放大误差)

2 漫反射

我们能看到普通物体,并不是它本会发光,而是它们可以反射光。发生这种反射的方式有多种。这里先考虑漫反射。

发生漫反射是不是因为光线会从表面反射回来。相反,它穿透了表面之后,反弹了一点,分裂了几次,直到再次离开表面。实际上,光子和原子之间的相互作用要复杂得多,但是我们不需要那么详细地了解现实世界的物理学。

表面漫反射的光量取决于光线照射到该物体的角度。当表面以0°角直击时,大多数光会反射。随着该角度增加,反射将减少。在90°时,不再有光照射到表面,因此会保持黑暗。漫射的光量与光方向和表面法线之间的角度的余弦值成正比。这就是兰伯特余弦定律。

(漫反射)

我们可以通过计算表面法线和光方向的点积来确定反射率。现在我们已经知道法线的方向,但还不知道光的方向。让我们从一个固定的光方向开始,直接从上方射入。

(光从上方照射,gamma空间和线性空间)

点积会产生什么?

两个向量之间的点积在几何上定义为A⋅B= || A || || B || cosθ。这意味着它是矢量之间的角度的余弦乘以它们的长度。因此,在两个单位矢量的情况下,A⋅B=cosθ。

代数上,它的定义为:

这意味着你可以通过将所有组件对相乘,并用求和来计算它。

在视觉上,此操作将一个向量直接投影到另一个向量上。仿佛在其上投下阴影。这样,你最终得到一个直角三角形,其底边的长度是点积的结果。而且,如果两个向量都是单位长度,那就是它们角度的余弦。

(点积)

2.1 Clamped 灯光

当表面指向光时,计算点积有效,但当光背离的时候,则无效。在这种情况下,表面在逻辑上会处于其自身的阴影中,并且根本不应该接收任何光。由于此时光方向与法线之间的角度肯定大于90°了,因此它的余弦值和点积变为负值。因为我们不想要负光,所以我们必须限制结果。可以为此使用标准的max函数。

但通常会用saturate 替代max函数,此标准功能限定结果在0到1之间。

这似乎是不必要的,因为我们知道点乘积永远不会产生大于1的结果。但其实在某些情况下(视硬件而定),它实际上可能会更高效。现在不必担心这种微优化。实际上,我们可以将其委托给Unity。

UnityStandardBRDF包含文件定义了方便的DotClamped函数。此函数执行点积,并确保它永远不会为负。这正是我们所需要的。它还包含许多其他光照功能,并且还包含其他有用的文件,稍后我们将需要它们。因此,现在替换使用它!

DotClamped是怎么写的?

如下。显然,他们认为在针对低性能着色器硬件以及针对PS3时,最好使用Saturate。

它使用半精度数字,但不必担心数值精度。它仅对移动设备有所不同。

因为UnityStandardBRDF已经包含UnityCG和其他文件,所以我们不必再显式包含它。虽然现在这么做是没有错,但还是可以简化一下。

(包含文件层次列表 最开始是UnityStandardBRDF)

2.2 光源

真实的光线方向应该是场景中光线的方向,而不是硬编码的光线方向。默认情况下,每个Unity场景都有代表太阳的灯光。它是定向光,这意味着它被认为是无限远的。结论就是,它所有的光线都可以认为是来自完全相同的方向。当然,在现实生活中并非如此,但是太阳距离太远了,大部分时候还是可以当做平行光。

(默认的场景光)

UnityShaderVariables定义float4 _WorldSpaceLightPos0,其中包含当前灯光的位置。或者在定向光的情况下光线来自的方向。它具有四个分量,因为它们是齐次坐标。因此,对于我们的定向光,第四个分量是0。

2.3 光模式

在此产生正确的结果之前,我们必须告诉Unity我们要使用哪些灯光数据。为此,我们向着色器通道添加了LightMode标签。

我们需要哪种灯光模式取决于我们如何渲染场景。可以使用正向或延迟渲染路径。还有两种较旧的渲染模式,但不用理会它们。你可以通过player rendering settings选择rendering path。它位于颜色空间选择的上方。我们正在使用的是正向渲染,这也是默认设置。

(选择渲染路径)

着色器需要使用ForwardBase 通道。它是使用forward rendering path渲染物体的时候,用到的第一个pass。这个通道可以可以访问场景的主要定向光,也设置了其他一些内容,但稍后再进行介绍。

(漫反射光)

2.4 灯光颜色

当然,光线并不总是白色的。每个光源都有其自己的颜色,我们可以通过在UnityLightingCommon中定义的fixed4 _LightColor0变量获得该颜色。

什么是Fixed4?

这些是低精度数字,它们以精度来换取移动设备上的速度。在台式机上,fixed只是float的别名。精度优化是以后的主题。

此变量包含灯光的颜色乘以其强度。尽管它提供所有四个通道,但我们只需要RGB分量。

(带上灯光颜色)

2.5 反照率

大多数材质都会吸收电磁频谱的一部分。这会给它们产生特定的颜色。例如,如果所有可见的红色频率都被吸收,则逸出的部分将显示为青色。

无法逃脱的光线会发生什么?

光的能量通常以热量的形式存储在对象中。这就是为什么黑色的东西往往比白色的东西要温暖的原因。

材质的漫反射率的颜色称为反照率。反照率拉丁语是白色。因此,它描述了多少红色,绿色和蓝色通道被表面反射了,而其余则被吸收。我们可以使用材质的纹理和色调来定义它。

我们还要在检查器中将主纹理的标签更改为Albedo。

(带有反照率的漫反射着色器,gamma空间和linear空间)

3 镜面着色

除了漫反射之外,还还有一种叫镜面反射。当光线撞击表面后没有发生扩散时,就会发生这种情况。取而代之的是,光线以等于其撞击表面的角度的角度从表面反弹。比如你在镜子中看到的各种反射。

与漫反射不同,查看器的位置对于镜面反射很重要。仅能看见最终直接反射到你眼睛的光。其余的去了别的地方,所以你看不到它。

因此,我们需要知道从表面到观察者的方向。这需要表面和照相机的世界空间位置。

我们可以通过对象对世界矩阵确定顶点程序中表面的世界位置,然后将其传递给片段程序。

通过在UnityShaderVariables中定义的float3 _WorldSpaceCameraPos访问摄像机的位置。我们发现视图方向从中减去表面位置并进行归一化。

Unity的着色器不对观测方向插值吗?

会差值。Unity的着色器在顶点程序中计算视觉方向并对其进行插值。归一化是在片段程序中完成的,或者在功能不强的硬件的顶点程序中完成的。两种方法都可以。

3.1 反射光

要了解反射光的去向,我们可以使用标准反射功能。它接受入射光线的方向,并根据表面法线对其进行反射。因此,我们必须取反光的方向。

(反射方向)

反射向量如何工作?

你可以通过D-2N(N·D)的公式,用法线N计算出方向D

如果有一面完美光滑的镜子,我们只会看到表面角度恰好合适的反射光。在所有其他地方,反射光都会错开我们,并且表面对我们而言将显示为黑色。但是物体并不是完全光滑。它们有很多微小的凸起,这意味着表面法线变化会很大。

因此,即使我们的视角方向与反射方向不完全匹配,也可以看到一些反射。偏离反射方向的距离越多,看到的越少。再一次,我们可以使用钳位点积来计算有多少光到达我们眼中。

(镜面反射)

3.2 光滑度

通过这种效果产生的高光的大小取决于材质的粗糙度。光滑的材质可以更好地聚焦光线,因此高光较小。我们可以通过使其成为材质属性来控制此平滑度。通常将其定义为0到1之间的值,因此让我们将其设为滑块。

我们通过将点积提高到更高的幂来缩小亮点。为此,我们使用平滑度值,但是它必须比1大得多才能具有理想的效果。因此,我们乘以100。

(完美的光滑度)

3.3 Blinn-Phong模式

我们目前正在根据Blinn反射模型计算反射。但是最常用的模型是Blinn-Phong。它在光线方向和视角方向之间使用一个向量。法线和半矢量之间的点积确定镜面反射的贡献。

(Blinn-Phong 镜面效果)

这种方法会产生较大的高光,但是可以使用较高的平滑度值来抵消。结果证明,虽然这两种方法仍然只是近似值,但在视觉上比Phong更好地匹配了现实。一个很大的限制是它可能为从后面照亮的对象产生无效的高光。

(平滑度为0.01之后,不正确的高光)

使用低平滑度值时,这些失真会变得明显。可以使用阴影将它们隐藏起来,也可以根据光角度淡出镜面来隐藏它们。Unity的旧式着色器也存在此问题。当然我们也不必担心, 因为很快将继续使用另一种照明方法。

3.4 高光颜色

当然,镜面反射的颜色需要与光源的颜色匹配。因此,把这个也考虑在内。

但这还不是全部。反射的颜色也取决于材质。这与反照率不同。金属往往具有很少的反照率,同时具有很强的且常常是彩色的镜面反射率。相反,非金属往往具有明显的反照率,而它们的镜面反射率却较弱且不着色。

就像反射率一样,我们可以添加纹理和色调以定义镜面反射颜色。但是,先不考虑纹理的情况,只用纯色。

通过颜色属性控制镜面反射的着色和强度。

(纯色的高光反射)

我们能不能把Tint的Alpha作为平滑度?

当然可以。你还可以通过这种方式在单个纹理中存储镜面反射的颜色和平滑度。

(漫反射叠加高光,gamma和线性空间下)

4 节能

仅将漫反射和镜面反射加在一起会存在一个问题。结果可能比光源还亮。当使用全白镜面反射和低平滑度时,这一点非常明显。

(白色的高光,0.1的平滑度 太亮了)

当光线撞击表面时,其中一部分会反射为镜面反射光。它的其余部分穿透表面,或者以散射光的形式返回,或者被吸收。但是我们目前没有考虑到这一点。取而代之的是,我们的光会全反射和扩散。因此,最终可能将光的能量加倍了。

必须确保材质的漫反射和镜面反射部分的总和不超过1。这保证了我们不会在任何地方产生光。如果总数小于1最好,这仅意味着一部分光被吸收了。

当使用恒定的镜面反射色时,我们可以简单地通过将反射率乘以1减去镜面反射来调整反照率色度。但是手动进行操作不方便,特别是如果我们要使用特定的反照率色度时。因此,让我们在着色器中执行此操作。

(不再那么刺眼了)

现在,漫反射和镜面反射的贡献已链接在一起。镜面反射越强,扩散部分越暗。黑色镜面反射色会产生零反射,在这种情况下,你会看到反光镜处于完全强度。白色的镜面反射色可形成完美的镜面,因此完全消除了反照率。

(节能)

4.1 单色

当镜面反射色是灰度颜色时,此方法效果很好。但是当使用其他颜色时,会产生奇怪的结果。例如,红色的镜面反射色只会减少漫反射部分的红色分量。结果,反照率将为青色。

(红色的高光,青色的反照率)

为了防止这种着色,我们可以使用单色节能。这仅意味着我们使用镜面反射颜色的最强成分来减少反照率。

(单色节能)

4.2 辅助函数

如你所料,Unity具有实用程序功能,可以直接节能。它是EnergyConservationBetweenDiffuseAndSpecular,并在UnityStandardUtils中定义。

(文件的包含层次)

此功能将反照率和镜面反射颜色作为输入,并输出调整后的反照率。但是它还有第三个输出参数,称为一减反射率。这是减去镜面反射强度的乘积,是我们将反照率乘以的因子。这是额外的输出,因为其他照明计算也需要反射率。

EnergyConservationBetweenDiffuseAndSpecular是什么样的?

如下。它具有三种模式,无保留,单色或彩色。这些由#define语句控制。默认为单色。

4.3 金属度工作流

其实我们主要关注两种材质就好。金属和非金属。后者也称为介电材料。目前,我们可以通过使用强镜面反射色来创建金属。使用弱的单色镜面反射来创建介电材质。这是镜面反射工作流程。

如果我们可以仅在金属和非金属之间切换,那将更加简单。由于金属没有反照率,因此我们可以使用该颜色数据作为镜面反射色。而非金属也没有彩色的镜面反射,因此我们根本不需要单独的镜面反射色调。这称为金属工作流程。试试看。

哪个工作流程更好?

两种方法都很好。这就是为什么Unity每种都有一个标准着色器的原因。金属化的工作流程更为简单,因为你只有一个颜色来源和一个滑块。这足以创建逼真的材质。镜面反射工作流程可以产生相同的结果,但是由于你拥有更多的控制权,因此也可能出现不切实际的材质。

我们可以使用另一个滑块属性作为金属切换,以替换镜面反射色调。通常,应将其设置为0或1,因为某物如果不是金属。就用介于两者之间的值表示混合金属和非金属成分的材质。

(金属度滑块)

现在,我们可以从反照率和金属特性中得出镜面反射色。然后可以将反照率简单地乘以一减金属值。

但是,这过于简单了。即使是纯介电材质,也仍然具有镜面反射。因此,镜面强度和反射值与金属滑块的值不完全匹配。而且这也受到色彩空间的影响。幸运的是,UnityStandardUtils还具有DiffuseAndSpecularFromMetallic函数,该函数为我们解决了这一问题。

(金属度工作流)

DiffuseAndSpecularFromMetallic是什么样的?

如下。请注意,它使用half4 unity_ColorSpaceDielectricSpec变量,该变量由Unity根据颜色空间设置。

一个细节是金属滑块本身应该位于伽马空间中。但是,在线性空间中渲染时,单个值不会被Unity自动伽玛校正。我们可以使用Gamma属性来告诉Unity,它也应该将gamma校正应用于金属滑块。

遗憾的是,到目前为止,对于非金属,镜面反射已经变得没有那么清晰了。为了改善这一点,我们需要一种更好的方法来计算照明。

5 基于物理的着色(PBS)

长期以来,Blinn-Phong一直是游戏行业的主力军,但如今,基于物理的阴影(称为PBS)风靡一时。这是有充分理由的,因为它更加现实和可预测。理想情况下,游戏引擎和建模工具都使用相同的着色算法。这使内容创建更加容易。业界正在慢慢地趋向于标准PBS实施。

Unity的标准着色器也使用PBS方法。Unity实际上有多种实现。它根据目标平台,硬件和API级别决定使用哪个。可通过UnityPBSLighting中定义的UNITY_BRDF_PBS宏访问该算法。BRDF代表双向反射率分布函数。

(某一部分的文件层次,从UnityPBSLighting开始)

UNITY_BRDF_PBS是什么样的?

它为Unity的BRDF函数之一定义了别名。作为平台定义,默认情况下,Unity设置UNITY_PBS_USE_BRDF为1。除非着色器目标低于3.0,否则它将选择最佳的着色器。

这里不包括实际功能,因为它们很大。你可以通过下载Unity的包含文件或在Unity安装中找到文件来查看它们。他们在UnityStandardBRDF中。

这些函数需要大量的数学运算,因此我不再赘述。它们仍然以与Blinn-Phong不同的方式来计算漫反射和镜面反射。除此之外,还有菲涅耳反射分量。这会增加你在以低角度射角度查看对象时获得的反射。一旦包含环境反射,这些将变得显而易见。

为了确保Unity选择最佳的BRDF功能,我们必须至少定位着色器级别3.0。我们用语用表述来做到这一点。

Unity的BRDF函数返回RGBA颜色,且alpha分量始终设置为1。因此,我们可以直接让我们的片段程序返回其结果。

当然,我们必须使用参数来调用它。每个功能都有八个参数。前两个是材质的漫反射和镜面反射颜色。我们已经有那些。

接下来的两个参数必须是反射率和粗糙度。这些参数必须为一减形式,这是一种优化。我们已经从DiffuseAndSpecularFromMetallic中获得了一减反射率。平滑度与粗糙度相反,因此我们可以直接使用它。

当然,还需要表面法线和视角方向。这些成为第五和第六个参数。

最后两个参数是直接光和间接光。

5.1 灯光结构

UnityLightingCommon定义了一个简单的UnityLight结构,Unity着色器使用它来传递光数据。它包含灯光的颜色,方向和ndotl值(即漫射项)。请记住,这些结构纯粹是为了我们的方便。它不会影响已编译的代码。

为什么光数据包含漫射项?

BRDF函数具有自己计算所需的全部功能,为什么我们还要提供它?之所以如此,是因为在其他情况下也使用了灯光结构。

实际上,GGX BRDF版本甚至不使用ndotl。它会自己计算,因为它会与正常情况进行比较。与往常一样,着色器编译器将摆脱所有未使用的代码。因此,你不必担心性能问题。

最后一个参数是间接照明。为此,我们必须使用UnityIndirect结构,该结构也在UnityLightingCommon中定义。它包含两种颜色,一种是漫反射色,另一种是镜面反射色。漫反射颜色代表环境光,而镜面反射颜色代表环境反射。

稍后我们将介绍间接光,因此暂时将这些颜色设置为黑色。

(非金属和金属分别在gamma和线性空间)

下一节,介绍多灯光。

0 人点赞