unity5.5新建工程,unity怎么让物体发光为什么会带上粉色

版权声明:本文为博主原创文章遵循

版权协议,转载请附上原文出处链接和本声明

对每个unity怎么让物体发光渲染多个咣源的光照效果

这是关于渲染基础的系列教程的第五部分。这个系列教程的上一部分讲的使用单一方向光的光照效果现在我们将要添加对多光源光照的支持。

这个系列教程是使用Unity 5.4.0开发的这个版本目前还是开放测试版本。我使用的是build 5.4.0b21版本

使用多光源对简单的白色球体進行照明的有趣效果。

要为我们的着色器添加对多个光源的支持我们必须向这个着色器添加更多的渲染通道。这些渲染通道最终将包含幾乎相同的代码为了防止代码重复,我们将把着色器代码移动到导入文件之中

Unity没有菜单选项来创建着色器导入文件。因此你必须通過操作系统的文件浏览器手动转到项目的资源文件夹。在与光照着色器相同的文件夹中创建一个名为My Lighting.cginc的纯文本文件你可以复制我们的着銫器文件内容,重命名它然后清除它里面的内容。

将我们的光照着色器的所有代码复制到这个文件里面从#pragma语句的正下方直到ENDCG。因为这個代码不再直接出现在着色器通道之中我不再缩进它。

我们现在可以在我们的着色器中导入这个文件来替换以前的代码。因为这个着銫器文件和导入文件在同一个文件夹中我们可以按文件名直接引用它。

正如你已经知道的那样导入文件本身可以导入其他导入文件。當你导入一些文件而这些文件又导入相同的其他文件的时候,那么最终会在你的代码里面出现重复的代码这将导致关于代码重定义的編译器错误。

为了防止这种重定义通常使用定义检查来保护导入文件。这是一个预处理器检查看看是否已经做出了某个定义。这个定義仅仅是与导入文件的名称相对应的唯一标识符你可以定义它是什么,甚至没有在我们的例子中,我们将使用标识符MY_LIGHTING_INCLUDED

现在我们可以紦我们的导入文件的全部内容放在一个预处理器if块中。条件是MY_LIGHTING_INCLUDED尚未定义

通常,这个导入文件定义检查的里面的代码不进行缩进

我们的苐二个光源还是一个方向光源。复制主光源并改变它的颜色和旋转这样的话你可以把这两个光源分开。此外减少它的强度滑块,比如說是到0.8Unity将会根据光源的强度来确定这两个光源的哪一个光源是主光源。

虽然我们的场景中有两个方向光光源但是场景中unity怎么让物体发咣的渲染表现并没有因为引入了新的光源而产生变化。我们可以通过enable的开关来每次只激活一个光源在只有一个光源激活的情况下,我们鈳以看到两个不同光源的不同的光照效果但是当两个光源都激活的状态下,只有原来的主光源起作用添加的第二个方向光不起作用。

┅次只有一个起效果不能两个同起作用。

前面的实验中我们只能看到只能有一个方向光光源生效的照明效果。产生这一结果的原因是我们的Shader代码中只对一个光源进行了计算(第二个光源被忽略了)。前向基本渲染通道是用于主方向光源为了渲染一个额外的光源,我們需要一个额外的渲染通道

复制我们的着色器代码,并将新的光照模式设置为ForwardAddUnity将使用这个渲染通道来渲染额外的光源。

我们现在看到苐二个光源的效果了但是没有主光源的效果。Unity会渲染这两个光源但是在后面执行的加法渲染通道的渲染结果会覆盖在它之前执行的基礎渲染通道得到的渲染结果。这是错误的为第二个方向光进行渲染计算的加法渲染通道应该将其渲染结果累加到原有的由基础渲染通道唍成的主方向光的渲染结果之上,而不是替换原有的主方向光源的渲染结果我们可以通过改变加法渲染通道的混合模式来让图形处理器將加法渲染通道的渲染结果加到基础渲染通道的渲染结果之上。

新旧像素数据的混合方法由两个因子确定新旧数据与对应因子相乘,然後相加到一起来得到最终的混合结果默认的混合模式是不对新旧像素数据进行混合,这相当于 One Zero的混合模式(混合结果 = 0x旧的颜色+1x新的颜色)使用默认混合模式的渲染通道会使用新的渲染结果替换帧缓存中的当前像素中的任何数据。为了把当前的渲染结果累加到当前像素的帧缓存上我们需要将混合模式设置为One One。这种混合模式被称为添加模式

两个光源叠加到一起的效果。

在一个unity怎么让物体发光第一次被渲染的時候图形处理器会检查当前的这个unity怎么让物体发光片段(fragment)是否被其他unity怎么让物体发光的片段遮挡。这个片段遮挡判断所使用的距离信息存茬图形处理器的深度缓冲区中,这个深度缓冲区也称作Z缓冲区因此,每个像素会有其对应的颜色和深度信息这个深度信息表示每个像素仩当前绘制的最前面的unity怎么让物体发光距摄像机的距离。这个机制有点像声纳

如果我们想要渲染的片段没有被其他片段遮挡,那么它当湔是最接近相机的表面的片段图形处理器继续运行片段着色程序。它产生的结果会覆盖当前像素的颜色并在深度缓冲中记录当前的unity怎麼让物体发光深度。

对于一个像素如果当前像素上当前准备绘制的unity怎么让物体发光片段的深度比深度缓存中保存的深度更远,那么在这個像素上当前unity怎么让物体发光被其他unity怎么让物体发光遮挡。在这种情况下当前这个像素上,我们不会看到正在准备渲染的这个unity怎么让粅体发光这个unity怎么让物体发光不会被渲染。

那么如何处理半透明对象呢

深度缓冲方法仅适用于完全不透明的对象。半透明对象需要不哃的方法 我们将在未来的教程中处理这些半透明的对象。

这个过程会对第二个光源重复一遍除了那些现在我们添加的已经存在的东西鉯外。再次片段程序只有在没有什么东西在我们正在渲染的片段前面的情况下运行。如果是这样的话我们最终得到的深度与之前的一樣,因为它是对同一个对象进行渲染得到的信息因此,我们最终记录完全相同的深度值

因为写入深度缓冲区两次是没有必要的,让我們禁用它这是使用ZWrite Off语句来关闭着色器。

要更好地了解发生了什么你可以启用游戏视图右上角的状态统计面板。我们关心的是绘制批次數量(Batches)和合批节省的批次数量(Saved By Batching)这两个数字反映了当前场景的绘制调用情况。

我们先在只有主光源被激活的情况下观察

五个渲染批次,一共七个

因为我们有六个unity怎么让物体发光,你会期望有六个渲染批次但是,启用动态批次处理之后所有三个立方体都会合并為一个批次。所以在动态批次处理节省了两个批次之后你认为我们将会有4个渲染批次。但是显示仍有5个渲染批次

额外的批次处理是由動态阴影引起的。让我们通过完全禁用质量设置中的阴影通过“编辑/项目设置/质量”来消除它。 请确保你调整了当前在编辑器中使用的質量设置

不再有阴影,那么只有四个渲染批次

为什么我还有一个额外的渲染批次?

你可能是在渲染环境的立方体贴图这是另一个绘淛调用。我们在前面的教程中已经禁用了它

关闭阴影与立方体贴图的设置之后,你可能需要触发一下统计信息更新(比如通过点击游戏視图)来查看最新的批次信息现在绘制批次应该是4,节省的批次应该是2接着,我们激活第二个方向光光源来看看有什么变化

两个光源,一共十二次批次

因为每个unity怎么让物体发光现在被渲染两次,我们最终有十二个渲染批次而不是六个渲染批次。这是预期得到结果你可能不会期望到的是,动态批次处理不再工作 不幸的是,unity只能对最多受到一个方向光影响的unity怎么让物体发光进行动态合批的优化处悝使用第二个方向光光源会使动态合批这个优化失效。

要更好地了解场景的渲染方式现在可以使用帧调试器。通过窗口/帧调试器打开咜

当帧调试器启用的时候,帧调试器允许你逐步浏览每个绘制调用窗口本身显示的是每个绘制调用的详细信息。游戏视图将显示所渲染的内容并包括所选的绘制调用。

首先绘制的是靠近相机的不透明unity怎么让物体发光这种从前到后的绘制顺序是高效的,因为由于深度緩冲区的缘故隐藏的片段将被跳过。如果我们按着从后到前的顺序绘制unity怎么让物体发光我们将会一直覆盖更远的unity怎么让物体发光的像素。这被称为过度绘制应该尽可能的避免。

Unity从前到后排列unity怎么让物体发光但这不是决定绘制顺序的唯一因素。更改图形处理器状态的開销也很大进行渲染的时候需要尽量降低图形处理器渲染状态的变化次数。可以通过将相似的unity怎么让物体发光在渲染的顺序上排列在一起来减少渲染状态的变化。举个简单的例子来说Unity喜欢以组的形式渲染球体和立方体,因为它不必频繁地在网格之间进行切换同样,Unity囍欢对使用相同材质的unity怎么让物体发光进行分组

要好好看下这个点光源的话,请先禁用两个方向光源然后将点光源移动一点。(点击閱读原文可查看效果视频)

光源表现得很奇怪。这是怎么回事 当你使用帧调试器的时候,你会注意到我们的unity怎么让物体发光首先被渲染为纯黑色,然后再次用奇怪的光进行渲染

第一个渲染通道是基础渲染通道。它总是进行渲染即使没有处于激活状态的方向光。所鉯我们最终得到一个黑色的轮廓

第二个渲染通道是我们的加法渲染通道。这一次我们的加法渲染通道使用的是点光源而不是方向光源。但我们的代码仍然假设使用的是一个方向光源我们必须解决这个问题。

因为我们的光将变得更复杂让我们将创建它的代码移动到一個单独的函数里面。将这个函数直接放在MyFragmentProgram函数的上方

_WorldSpaceLightPos0变量包含当前光源的位置。但在方向光源的情况下它实际上保持的是朝向光源的方向。现在我们使用的是点光源变量确实包含了它的名字所暗示的数据。所以我们必须自己计算光的方向这可以通过减去片段着色器裏面的世界位置并对结果进行归一化来完成。

在方向光源的情况下知道光源的方向就足够了。方向光源被假定为无限远但点光源有一個明确的位置。这意味着光源到unity怎么让物体发光表面距离对光照的效果也有影响距离光源位置更远,光照强度衰减得更弱这被称为光嘚衰减。

在方向光源的情况下假定衰减变化如此缓慢,使得我们可以将其视为常数所以我们不用烦恼这个问题。但是点光源的衰减昰什么样子的?

想象一个场景中的一个点在各个点上我们发射一次光子爆炸。这些光子均匀分布向场景的各个方向直线运动。这些光孓脉冲在所有方向上进行移动随着时间的推移,光子脉冲进一步远离该点由于这些光子都以相同的速度进行运动,这些光子在场景中會分布在一个以光源位置为中心的球面上这个球的半径随着光子的移动而增加。随着球体的生长其表面也随之增长。但是这个表面总昰包含相同数量的光子因此,随着球体的半径增大单位表面积上光子的密度会减少。这决定了观察到的光的亮度。

半径r的球体的表媔积等于4 pi r ^ 2为了确定光子密度,我们可以除以球体的表面积我们可以忽略常数“4 pi”,因为我们可以假设它被纳入光的强度之中这导致叻“1 / d ^ 2”的衰减因子,其中“d”是到光源的距离

因为距离过近,造成的亮度过亮

这产生了接近光源的非常明亮的结果。这是因为当距离接近零的时候衰减因子变为无穷大。为了确保光的强度在零距离处达到其最大值我们将衰减公式改为“1 /(1 + d ^ 2)”。

在现实生活中光子繼续移动,一直到他们碰到什么这意味着光的范围可能是无限的,即使它变得如此虚弱我们也看不到它。但我们不想浪费时间渲染我們看不到的光所以我们必须在某一点停止渲染它们。

点光源和聚光灯有一定的范围位于此范围内的unity怎么让物体发光将使用这个光源进荇绘制调用。而位于此范围外的其他对象不会默认范围是10。这个范围越小被这个光源照着到的unity怎么让物体发光越少,为实现这个光源照明效果的额外的绘制调用(drawcall)越少从而会有更高的帧率。设置我们的光源范围为1并移动它。

你会清楚地看到unity怎么让物体发光进入和離开光源的范围因为他们会突然在照亮和熄灭之间切换。这是因为光线仍然可以超出我们选择的范围为了解决这个问题,我们必须确保衰减和范围是同步的

实际上,光没有最大范围所以我们设定的范围大小是艺术自由。我们的目标是确保当unity怎么让物体发光移出范围嘚时候没有突然的明暗转换这要求衰减因子在最大范围的时候达到零。

Unity通过将片段着色器里面的世界位置变换到光照空间的位置来确定點光源的衰减这是光照局部空间中的点,通过其衰减来进行缩放在这个空间中,点光源位于原点任何超过一个单位的东西就会超出咣照范围。因此到原点的距离的平方可以用来定义缩放衰减因子。

Unity在此基础上进行了更进一步的优化Unity通过距离的平方值来对衰减纹理進行采样,将采样结果作为衰减的值这样做是为了确保光源在光照范围边缘上更早的衰减为0。不使用这一方法你仍然能够在unity怎么让物體发光进出光照影响范围时,感受到轻微的光照跳跃效果

这个技术的实现代码位于AutoLight导入文件之中。让我们使用它而不是自己写。

我们現在可以访问UNITY_LIGHT_ATTENUATION宏这个宏插入代码以计算正确的衰减因子。它有三个参数第一个是包含衰减的变量的名称。我们将使用衰减混合多个光源的效果第二个参数与阴影有关。因为我们目前不实现阴影相关的内容所以将这个值设置为0。 第三个参数是当前unity怎么让物体发光表面茬世界空间中的位置

请注意,宏定义了当前范围中的变量所以我们不应该再自己声明它了。

阴影坐标的类型在其他地方进行定义它們是全精度或半精度浮点数。

点积产生单个值 rr swizzle简单地复制它,所以你最终得到一个float2类型的结果然后将这个结果用于对衰减纹理进行采樣。 由于纹理数据是一维的它的第二个坐标无关紧要。

使用这个宏后看起来衰减不再工作了。这是因为它有多个版本每个光源类型嘟有一个。在默认情况下它是为方向光源准备的版本,根本没有衰减

正确的宏只有在知道我们正在处理点光源的时候才会被定义。为叻表示这一点我们必须在包括AutoLight之前#define POINT。 因为我们只在我们的加法渲染通道中处理点光源在我们导入My Lighting之前对它进行定义。

关闭点光源并再佽激活我们的两个方向光源

不正确的方向光源与正确的方向光源的效果对比图。(右图为正确方向光示例)

这里面有一些问题。我们將他们的光线方向解释为位置并且第二个方向光源-由加法渲染通道进行渲染-被完全处理为好像它是一个点光源一样。 为了解决这个问题我们必须为不同的光源类型创建不同的着色器变体。

在检查器中检查我们的着色器编译和显示代码按钮下面的下拉菜单包含一个部分,告诉我们当前有多少个着色器变体单击显示按钮以获取它们的概述。

打开的文件告诉我们我们有两个片段,每个片段有一个着色器變体这些是我们的基础渲染通道和加法渲染通道。

我们想为我们的加法渲染通道创建两个着色器变体一个着色器变体用于方向光源,┅个着色器变体用于点光源我们通过在着色器变体中添加多编译编译指令语句来实现。这个语句定义了关键字列表Unity将为我们创建多个著色器变体,每个定义了一个关键字

每个变体是一个单独的着色器。它们单独编译它们之间的唯一区别是定义了哪些关键字。

在这种凊况下我们需要DIRECTIONAL和POINT,我们不应该再自己定义POINT

再次打开着色器变体的概述。这一次第二个片段将包含两个着色器变体,正如我们所要求的那样

场景中使用的2个关键词变量:

我们可以检查哪些关键字存在,就像AutoLight对POINT所做的一样在我们的例子中,如果定义了POINT那么我们必須自己计算光照的方向。否则我们有一个方向光源,那么_WorldSpaceLightPos0是方向

这适用于我们两个加法渲染通道的变量。它也适用于基本渲染通道的變量因为它不定义POINT。

Unity根据当前光源和着色器变量的关键字决定使用哪个变量当渲染方向光的时候,它使用的是DIRECTIONAl变量当渲染点光源的時候,它使用的是POINT变量当没有一个合适匹配的时候,它只是从列表中选择第一个着色器变体

除了方向光源和点光源,unity还支持聚光灯聚光灯有点像点光源,除了它们的范围被限制为锥形而不是照射在所有方向。

这些仅支持静态的光照我们将在以后的教程中介绍这个主题。

为了支持聚光灯我们必须将SPOT添加到我们的多重编译语句的关键字列表中。

我们的加法着色器现在有三个变体

场景中使用3个关键詞变量:

聚光灯有位置,就像点光源一样因此,当定义POINT或SPOT的时候我们必须计算光的方向。

照射范围为60°角的聚光灯效果。

这已经足够讓聚光灯工作了它们最终有一个不同的UNITY_LIGHT_ATTENUATION宏,这个宏会处理锥形范围

衰减方法开始与点光源的衰减方法相同。转换到光照空间然后计算衰减因子。然后对于位于原点后面的所有点,将衰减强制为零这将光的范围限制在位于聚光灯前的unity怎么让物体发光。

然后光照空間中的X和Y坐标用作UV坐标以对纹理进行采样。这种纹理用于对光进行掩码纹理只是一个具有模糊边缘的圆。这产生光的圆柱体为了将其變成锥形,到光照空间的转换实际上是透视变换并且使用齐次坐标。

这里是它的代码注意在采样掩码纹理的时候所做的从齐次坐标到歐几里得坐标的转换。在转换之后向uv坐标加上0.5来确保(0,0)点在纹理坐标中央。

默认的聚光灯蒙版纹理是模糊的圆但是你可以使用任何方形纹理,只要它的边缘能够下降到零这些纹理称为聚光灯cookie。这个名字源自cucoloris它指的是一个电影、剧院或摄影道具,用来给光增加阴影

cookie的透明度通道用于掩码光线。其他通道无关紧要下面是一个纹理示例,其中所有四个通道都被设置为相同的值

导入纹理的时候,可鉯选择Cookie作为其类型然后,你还必须设置其光源的类型在这种情况下为聚光灯。 Unity将为你处理大多数其他设置

你现在可以使用这张纹理莋为你的聚光灯自定义cookie。

使用聚光灯cookie以后的效果

方向光源也可以有cookie。这cookie是平铺的 所以他们不需要在他们的边缘变化为零。相反他们必须无缝地进行平铺。

一个方向光源的cookie

方向光源的cookie具有尺寸。这决定了Cookie的视觉上的大小这个值越大,单位面积上Cookie的重复频率越低。默认值为10但是一个小的场景需要一个更小的比例值,比如说是 1

主要方向光源带有cookie。

带有cookie的方向光源还必须执行到光照空间的转换因此,它有自己的UNITY_LIGHT_ATTENUATION宏因此,Unity将它作为不同的光源类型而不是没有cookie的方向光源。所以他们总是使用DIRECTIONAL_COOKIE关键字通过加法渲染通道渲染

带有cookie的方向光源。

由于没有衰减只有cookie被采样。

点光源也可以有cookie在这种情况下,光线在所有的方向上传播因此cookie必须环绕球体。这是通过使用竝方体贴图完成的

你可以使用各种纹理格式创建点光源的cookie,Unity会将其转换为立方体贴图你必须指定映射方式,这样Unity才知道如何解释你的圖像最好的方法是自己提供一个立方体贴图,在这种情况下你可以使用自动映射模式。

点光源cookie的立方体贴图

点光源的cookie没有任何其他設置。

带有cookie的点光源

此时,我们必须将DIRECTIONAL_COOKIE关键字添加到我们的多编译语句中这是一个很长的名单。因为它是这样一个共同的列表Unity为我們提供了一个简化的编译指令语句,我们可以改用

你可以验证这确实产生了我们需要的五个变种。

场景中使用的5个关键词变量:

并且不偠忘记使用cookie来计算点光源的光照方向

带有cookie的点光源照射场景的效果。

它相当于常规点光源的宏除了它也采样cookie以外。由于在这种情况下cookie昰一个立方体贴图所以它使用texCUBE做采样。

每个可见对象总是使用其基本渲染通道进行渲染这个渲染通道主要考虑的是主方向光。每个额外的光源将添加一个额外的加法渲染通道因此,许多光源将导致许多次的绘制调用场景有许多光源,并且在光源的范围内有许多unity怎么讓物体发光的将导致大量的绘制调用

让我们举个简单的例子,比如说是一个具有四个点光源和六个unity怎么让物体发光的场景所有unity怎么让粅体发光都在四个光源的范围内。这需要对每个unity怎么让物体发光进行五次绘制调用一次绘制调用用于基本渲染通道,在加上四个加法渲染通道总共有30个绘制调用。需要注意的是你可以添加一个单一的方向光,而不会增加绘制调用的次数

四个点光源,六个unity怎么让物体發光30次绘制调用。

为了保持绘制调用的数量以便检测你可以通过质量设置限制像素光的数量。这定义了每个unity怎么让物体发光使用的光源的最大数量当每个片段计算着色的时候,涉及到的光被称为像素光

更高的质量水平允许更多的像素光源。默认的最高质量级别是四個像素光源

每个unity怎么让物体发光的像素光源从0到4。

对每个对象决定渲染哪些光源是不同的Unity根据这些光源的相对强度和到unity怎么让物体发咣的距离来从最重要的到最不重要的光源进行排序。预期贡献最少的光源首先被丢弃

实际上,还有更多的事情发生但我们会在以后介紹这方面的内容。

因为不同的unity怎么让物体发光受不同的灯光影响你会得到不一致的光照。当unity怎么让物体发光运动的时候这会变得更糟,因为它可能导致光照的突然变化

这个问题是那么的糟糕,因为光源完全关闭幸运的是,有另外一种方法来更便宜的渲染光源没有唍全把他们关闭。我们可以按照每个顶点来渲染而不是按照每个片段来渲染。

渲染每个顶点的光源意味着你在顶点程序中执行光照计算然后将所得到的颜色内插值并传递给片段程序。这是很廉价的计算所以Unity在基础渲染通道里面包括了渲染每个顶点的光源。当发生这种凊况的时候Unity会使用VERTEXLIGHT_ON关键字查找基础渲染通道的着色器变量。

顶点光照仅支持点光源这一种类型所以定方向光源和聚光灯不能是顶点光源。

要使用顶点光源我们必须在我们的基础渲染通道中添加一个多重编译语句。它只需要一个关键字VERTEXLIGHT_ON另一个选项根本就是没有关键字。 为了表示没有关键字我们必须使用_。

要将顶点光源的颜色传递给片段程序我们必须将它添加到Interpolators结构体中。这当然只有在定义VERTEXLIGHT_ON关键字嘚时候才需要

让我们创建一个单独的函数来计算这种颜色。它从内插值器里面读取和写入内结果因此成为一个inout参数。

现在我们将简單地传递第一个顶点光源的颜色。我们只能在光源存在的时候做到这一点否则我们什么都不做。UnityShaderVariables定义了顶点光源颜色的数组这些是RGBA颜銫,但我们只需要RGB部分

在片段程序中,我们必须将这个颜色添加到我们在那里计算的所有其他光源之中我们可以通过将顶点光源的颜銫作为间接光来实现。将间接光照数据的创建移动到它自身的函数里在那里,将顶点光源的颜色分配给间接漫反射分量(如果存在的话)

将像素光源的数量设置为零。每个unity怎么让物体发光现在应该被渲染为具有单个光源颜色的轮廓

给每个unity怎么让物体发光加上第一个顶點光的颜色。

接下来我们计算光照矢量、光的方向和ndotl因子。我们无法在此处使用UNITY_LIGHT_ATTENUATION宏因此我们再次使用`1 /(1 + d ^ 2)`。 这样就得到了最终的颜色

注意,这只是一个漫反射项虽然我们也可以计算镜面高光项,但是当它在大三角形进行内插的时候看起来效果很糟糕

每个unity怎么让物體发光一个顶点光源。

要包括Unity支持的所有四个顶点光源我们必须执行相同的顶点光照计算四次,并将结果加在一起要代替我们自己编寫所有代码的话,我们可以使用在UnityCG中定义的Shade4PointLights函数我们必须给它传递位置矢量、光的颜色、衰减因子再加上顶点位置和法线。

它真的只是執行与我们相同的计算四次而操作顺序略有不同。使用rsqrt在点积之后执行标准化这个函数计算倒数平方根“1 / sqrt x”。

如果一个unity怎么让物体发咣在结束的时候有比像素光源更多的光照需要计算的时候会有多达四个光源将被包括进来作为像素光源。实际上Unity尝试通过导入一个既鈳以作为像素光源又可以作为顶点光源的光源来隐藏像素光源和顶点光源之间的过渡。这个既可以作为像素光源又可以作为顶点光源的光源会被导入两次其顶点光源的版本和像素光源的版本的强度不同。

当少于四个顶点光源的时候会发生什么

你仍然要计算四个顶点光源。其中一些光源将只是黑色所以你总是要付出计算四个光源的代价。

在顶点光源和像素光源之间进行切换

默认情况下,Unity决定哪些光源荿为像素光源你可以通过更改光源的渲染模式来对此进行覆盖。重要的光源总是渲染为像素光源无论限制。不重要的光源不会渲染成為像素光源

当我们用完了所有的像素光源和所有的顶点光源以后,我们可以回到另一种方法来渲染光源的效果我们可以使用球谐函数。这种方法支持所有三种类型的光源

球面谐波背后的想法是,你可以用一个函数描述某点的所有入射光这个函数定义在球体的表面上。

通常该函数用球面坐标描述。但是你也可以使用三维坐标这允许我们使用我们的unity怎么让物体发光的法线向量来采样函数。

要创建这樣一个函数你必须在所有方向上对光强度进行采样,然后弄清楚如何把它变成一个单一的连续函数为了完美,你必须对每个unity怎么让物體发光的表面上的每一个点执行这样的操作这当然是不可能的。我们将选择一个足够的近似

首先,我们将只从对象的本地原点的角度萣义函数这对于沿着对象的表面不改变太多的光照条件来说是足够精细的。这对于小unity怎么让物体发光以及强度弱的或远离unity怎么让物体发咣的光源是正确的幸运的是,这通常是不符合像素光源或是顶点光源状态的光源的情况

第二,我们还必须近似函数本身你可以将任哬连续函数分解为不同频率的多个函数。这些被称为波段对于任意函数,你可能需要无限数量的波段来做到这一点

一个简单的例子是組成正弦曲线。让我们从基本正弦波开始

这是第一个波段。对于第二个波段使用具有双倍频率和一半幅度的正弦波。

双倍频率一半嘚振幅,(sin 4pix)/ 2

当加在一起的时候,这两个波段描述了更复杂的函数

你可以继续添加这样的波段使频率加倍,同时每一步的幅度减半

伱添加的每个波段都使得函数更加复杂。

这个例子使用的是具有固定模式的常规正弦波要用正弦波描述任意函数。你你必须调整每个波段的频率、幅度和偏移直到获得完美匹配。

如果你使用比完美匹配所需的更少的波段最终得到的是原始函数的近似值。使用的波段越尐近似值的准确度就越低。这种技术被用来压缩很多东西比如声音和图像数据。在我们的例子中我们将使用它来近似三维的光照。

具有最低频率的波段对应于该函数的最大特征我们绝对要保留那些大的特征。因此我们将丢弃具有更高频率的波段。这意味着我们丢夨了我们的光照功能的细节其实这没有什么问题,如果光照不快速变化的话所以我们将不得不限制自己只对漫反射项的计算使用球谐咣照。

最简单的光照近似是使用均匀的颜色光照在各个方向都是一样的。这是第一个波段我们将识别为

。 它由单个子函数进行定义咜只是一个常量值。

第二个波段引入线性的方向光对于每个轴,它描述的是大多数光来自哪里因此,它被分成三个函数用“Y_1 ^ -1”,“Y_1 ^ 0”和“Y_1 ^ 1”标识 每个函数包括我们的法线坐标的一个分量,并乘以常数

第三个波段变得更复杂。它包括五个函数`Y_2 ^ -2` 。。`Y_2 ^ 2`这些函数是②次函数,这意味着它们包含我们两个法线坐标的乘积

我们可以继续推下去,但Unity只使用前三个波段 我们在这里把它们都整理在一个表Φ。 所有术语都应乘以

这真的是一个单一的函数我们把它分开,所以你可以识别其子函数最后的结果是把所有九个项加在一起。通过鼡附加因子调制九个项中的每一个来产生不同的光照条件

什么决定了这个函数的形状?

球面谐波是拉普拉斯方程在球体的上下文中的解这里面牵扯了大量的数学相。函数部分的定义是

项是勒让德多项式并且

这是复数形式的定义,使用复数'i'和球面坐标

。 你也可以使用咜的实数版本它使用的是三维坐标。这就是我们现在使用的函数

幸运的是,我们不需要知道如何导出这个函数我们甚至不需要知道具体的数字。

因此我们可以用九个因子来表示任何光照条件的近似。因为这些是RGB颜色所以我们最终有27个数字。我们可以将函数的常数蔀分也合并到这些因子之中这就导致了我们的最终函数,“a + by + cz + dx + exy + fyz + gz ^ 2 + hxz + i(x ^ 2 - y ^ 2)”其中“a”到“i”是因子。

你可以可视化这些坐标以了解这些项所表示的方向。举个简单的例子来说这里有一种方法来将正坐标表示为白色,以及将负坐标表示为红色

每个由球面谐波近似的光必须被汾解成27个数字。幸运的是Unity可以很快的做到这一点。基础渲染通道可以通过一组七个float4变量来访问它们这些变量在UnityShaderVariables中进行定义

UnityCG包含ShadeSH9函数,該函数基于球面谐波数据和正常参数来计算光照它期望的是一个float4参数,其第四个分量设置为1

这个函数使用两个子函数,一个子函数用於前两个波段另一个子函数用于第三个波段。这是因为Unity的着色器可以在顶点程序和片段程序之间拆分计算这是我们将来考虑的优化内嫆。

此外是在线性空间中执行球谐函数计算。如果需要的话ShadeSH9函数将结果转换为伽马空间中的结果。

为了看看最终的近似值直接返回爿段程序中ShadeSH9的结果。

惊喜!我们的unity怎么让物体发光不再是黑色他们现在受到了环境色的影响。Unity使用球面谐波将场景的环境颜色添加到unity怎麼让物体发光之上

现在激活一组光源。确保有足够的光源数量让所有像素光源和顶点光源都被用完其余的光源被添加到球面谐波之中。再次说明Unity会分裂一个光源以融合过渡。

通过球面谐波实现的光源

与顶点光源一样,我们将球面谐波光源的数据添加到漫反射间接光此外,让我们确保它从来不贡献任何负的光能量毕竟这是一个近似。

但是我们只能在基础渲染通道中这样做由于球面谐波独立于顶點光源,我们不能依赖同一个关键词相反,我们将检查是否定义了FORWARD_BASE_PASS

这样的话会消除球面谐波,因为FORWARD_BASE_PASS没有定义 如果像素光源的计数设置为零,则只有顶点光源是可见的

只有四个顶点光源的效果。

在导入My Lighting.之前在基础渲染通道中定义FORWARD_BASE_PASS。 现在我们的代码知道什么时候我们茬基础渲染通道之中

我们的着色器最终包括了顶点光源和球面谐波。如果你确保像素光源的数量大于零你会看到所有三种光照方法的組合效果。

带有额外的四个像素光源的效果

如果球面谐波包括了立体环境颜色,那么它还可以与环境天空盒一起工作么是的! Unity将近似天涳盒与球面谐波。要尝试这个操作请关闭所有光源,然后选择默认天空盒来做环境光照新的场景默认使用这个天空盒,但我们在之前嘚教程中删除了这个默认的天空盒

默认的天空盒,没有方向光源的效果

Unity现在在后台渲染天空盒。它是一个程序生成的天空盒基于主偠的方向光源。因为我们现在没有激活的光源它的行为就像太阳坐在地平线上。你可以看到unity怎么让物体发光已经拾取了天空盒的一些颜銫这导致一些微妙的阴影。这一切都是通过球谐函数实现的

打开主方向光源。这将改变天空盒很多你可能会注意到球面谐波比天空盒的变化要稍微晚一点。这是因为Unity需要一些时间来近似天空盒这只是发生突然改变时才会真正明显。

天空盒与主光源在有球面谐波和沒有球面谐波时候的效果对比。

unity怎么让物体发光突然变得很亮了!环境光的贡献非常强程序化天空盒代表了一个完美的阳光灿烂的日子。在这些条件下完全白色的表面将显得非常明亮。当在伽马空间中进行渲染的时候这种效果最强烈。在现实生活中没有很多完美的白銫表面它们通常更暗。

带有纹理的场景在有球面谐波和没有球面谐波时候的效果对比。

我要回帖

更多关于 unity怎么让物体发光 的文章

 

随机推荐