编者按:
本文转载于公众号:
作者:
如需转载请与原公众号或原作者联系。
(无光模式下,也可以看出高光效果。这高光,果然不是正版的)
本篇文章原理简单,篇幅短,效果上来说没有特别大的用处,但很适合新手学习一些技巧。(其实我有点懒得写,复杂的东西要准备太久啦,只有空搞点简单的)
主要能学到的内容:
translucency 半透材质,其它物体对自身投影看不见的问题
translucency 半透材质的阴影和高光不兼容问题
phong 光照模型高光实现原理与操作方法
gamma 居然是这样的东西
glossiness 原来是这么来的
通过蓝图与 MPC(material parameter collection),把灯光位置或者朝向传递给材质
为什么需要做假高光:
你可能会觉得,UE4 的高光挺好的了,为什么还要自己算假的呢?纯为了学习吗? 也不光是。
我研究这个东西是遇到一个这样的问题:
在做半透材质的时候,如果 Translucency 下面的 Lighting Mode 选项为 Surface TranslucencyVolume,则材质表现上是有阴影但没有高光的。( skylight 可以产生一定的高光,但效果不够好)
如果你的透明物体上看不到其他物体产生的投影。那是正常的,但应该是不正确的。这是因为UE4工程的默认设置,虽然确实可以对半透物体产生阴影,但有可能看不见。
想要看见,要重新调整一些参数。使用 ~ 键,打开控制台,输入
r.TranslucencyLightingVolumeInnerDistance 给到100左右,
r.TranslucencyLightingVolumeOuterDistance 给到500左右。
这两个参数是控制的摄像机能看见半透阴影的距离,数值给得比较接近的时候,阴影会显得精度高一些。这个参数根据自己项目的具体情况给了。
r.TranslucencyLightingVolumeDim 这个我没查到具体是什么,通过改变参数看起来像是阴影贴图尺寸,越高阴影效果越精细。开到 64 好像就挺高了,开到 128 有非常显著的掉帧。
而如果 Lighting Mode 是 Surface ForwardShading 模式,则各种灯光都可以产生高光。但是没有之前的阴影效果了。
也就是说两种模式各有各的毛病,都不够完美。
所以我当时就想到了一个很 hack 的解决方法,Lighting Mode 选则 Surface TranslucencyVolume,获得阴影效果,然后再手动算高光做到自发光通道里,以此获得一个理想的半透材质。
以上是我做这一切尝试的初衷。
高光制作原理,获取镜面反射方向
我使用的是 phong 的高光模型,这个计算方法比较直观,容易理解。
如图所示,N 向量是模型表面某点(设为 P )的法线方向,L 向量是 P 点指向灯光的向量( L 指代light),R 为灯光对 P 点的镜面反射方向向量( R 指代 Reflection)
对于某一个确定的模型来说,物体表面的法线方向都是已知信息;我们在场景里手动布置灯光的话,灯光的位置也是已知的,L 向量也就已知了。
那么现在我们的目标就是通过已知的 N 向量和 L 向量 求解R向量,获得反射的方向。
怎么算呢,这就是初中数学知识了。
如图所示,我们先将L向量 乘以 -1,那么,根据反射定律可知, -L 向量在水平方向的分量和 R 向量必然相等。
然后只要在 -L 向量上加两次,L 向量在 N 向量上的投影向量,就可以获得 R 向量。
而L向量在 N 向量上的投影向量可以用 向量点乘 的数学工具获得(这是在 CG 领域里用得非常非常多的一个操作)。
最后计算出来的公式是 R = 2(L · N)*N – L
其中 · 表示点乘,点乘完的结果是一个标量,也就是普通的数值,代表 L 向量在 N 向量方向上投影的长度,用这个长度乘以 N 向量本身,才是投影向量。
(以上计算都假设所有向量都是单位向量)
因为是很基础的数学知识,这里就不展开说了,不了解详情的要自己去补课了。
高光制作原理,计算高光强度
计算出反射方向还不够,我们还需要将反射方向和我们的观察者进行关联,才能真正计算出反射的亮度。
按照我们的生活经验,我们的观察者,如果直接对着反射方向看,感觉到的反射是最强烈的。而观察角度越偏离这个反射方向,反射的强度也就越弱。
极端情形就是你对着镜子看灯,只有正对着反射方向看,才能看到东西,背离了就看不到。
设观察者(或者说摄像机)的观察方向向量为 E(E 代表 Eye),怎么样算出 E 向量 和 R 向量重合度的关系呢?
我们又要用到向量点乘了。
当(-E)· R的值越接近 1, 那么二者方向重合度越高,点乘的值越接近 0,则二者越接近 90 度的垂直关系。
下图标注出了 -E 向量在 转动过程中,与 R 向量点乘完的结果。需要注意的一点是,点乘的结果不是线性渐变,而是 cos 变化,-E 与 R 夹角在 45 度的时候,点乘的结果并不是 0.5,而是 √2/2 也就是 0.707 左右。 这个我老是忘,实际制作过程中犯了好几次错,而且属于意识上的错误,非常难以排查,很多时候根本不会往这方面想。在这里给大家提个醒。
于是通过 -E 与 R 向量的点乘,我们就可以很方便地获取反射亮度的变化了。
现在的公式可以这样表达 :
spec = (-E) · R
R = 2(L · N)*N – L
高光制作原理,控制高光范围
前面虽然我们把高光大概的样子算出来了,但其实还是有问题的。问题就在于默认高光的扩散范围会非常宽广,看着不像高光。这部分的示例在后面的引擎演示部分会非常显著。现在为了保持文章结构,我先截取一张按目前算的公式制作的效果动图给大家看看问题所在。然后我们再剖析问题产生的原因和解决方法。
如图所示,白色区域就是目前公式计算出来的高光效果,看着是完全不像高光的。感觉范围明显太广了,要是能聚拢点或许就好了。
怎么去聚拢高光表现呢?因为现在算出来的高光亮度范围在 0-1,之间,这时候我们就要使用 材质 制作里非常常用的一个函数了—— power。
power 就是次方。
这个函数有意思的地方在于,当输入值的范围在 0-1 之间的时候,0 和 1 都不改变,而小数部分会产生类似对比度的变化。
这个 power 在我们平时使用的软件中极其常见,比如 色阶工具的中间滑竿,有些朋友可能知道中间滑竿代表的是 gamma,实际上用精确的数学语言描述应该是 input 的1/mid 次方,其中 mid 是色阶的中间滑竿输入值。 而某某的多少次方,其实就是 power 函数。
不信的话可以做一个简单的试验,做一个色阶,中间滑竿输入值为 0.5。
然后将原始图案复制一份,用 multiple 乘法的方式自己与自己叠加,意思是自己乘以自己,也就是自己的二次方,也就相当于对自己做了一个 power2 的函数。
最后二者结果完全一致。也就是说,色阶的中间滑竿为 0.5 时,相当于 power2。
所以总结起来就一句话,CG 领域里很多软件中的 gamma,实际本质上就是 power;power 几乎无处不在,经常用来调节画面对比度。
关于更多的 gamma 和线性工作流的一些问题,再次来安利一下老韩的这个视频
https://www.bilibili.com/video/av38607808
为了更直观地观察高光的表现在经过 power 函数处理以后的变化,我在 substance designer 中做了一些简单的图形示例。
这个图可以理解成一个函数图像,横轴是 -E 向量与 R 向量点乘的结果,纵轴是我们算出来的高光亮度变化。
可以看出,整个过度比较均匀,哪怕是观察视角和反射角度夹角快要接近 90 了,依然有一定程度的高光强度,只有完全垂直的时候,才是 0。这就是导致我们现在高光看起来范围很广的原因。
然后我们通过一个 power 函数来调节这个表现。我们可以先来尝试计算一下,比如我们心里先假设,原来 spec 强度为 0.5 的地方,如果做 power2,那么结果就是 0.25,值会变小,所有 0-1 的范围内的小数,做 power2,数值都会变小;而如果做 power0.5 ,所有 0-1 范围的小数被开方,数值都会变大。这是大概的 power 函数的影响。
下面看具体的变化:
可以看出,当 power 为大于 1 的值,并且在增大的过程中,高光的表现确实是越来越紧致的,符合我们的要求。
为了观察得更加直观,我做了一张俯视的 GIF,这样看的效果更接近实际运用中高光最直接裸眼观察的效果,不需要经过一道抽象的想象。
可能会有人好奇这个在 SD 里咋做的,发一下节点图;跟本文内容无关,就不讲具体了。
以上的变化效果就完成了我们全部的公式推演。现在的公式如下:
spec = power( specOrig , glossiness)
specOrig = (-E) · R
R = 2(L · N)*N – L
大家有没有觉得,下面那个 GIF 在变化的过程中,很像是在调节材质的粗糙度?
其实粗糙度这个东西真的就是这么发展出来的,本文中介绍的就是它的雏形。是不是又学到了?
在 UE4 中的实现假高光材质
原理和公式都搞明白了,下面就是在 UE4 中开始玩连连看了。我们只要找到公式里所有的变量以及函数在 UE4 里对应的东西,就可以非常简单搞定这一步的操作。
已知变量:N ,L ,E
需要使用的函数:dot , power(加减乘除就不说了吧)
N 是模型表面法线方向,注意,这里有两个节点可以使用。一个是 PixelNormalWS,一个是 VertexNormalWS。
前者会计算法线贴图对法线方向的改变,后者只计算模型上点的法线方向,会忽略法线贴图。而我们实际使用中,经常是需要用法线贴图的,所以这里的 N 向量,我们需要使用 PixelNormalWS 来计算。
L 是指向灯光的向量。这个我们先不急着搞,用一个 float3 自己定义一个向量表示灯光方向。
E 是指摄像机的朝向向量,我们使用 CameraDirectionVector 节点来计算。
目前三个变量如下图:
dot 和 power 都有相同名称的函数:
接着我们就开始套公式,先来计算向量 R:
R = 2(L · N)*N – L
这个应该非常简单了吧,稍微要注意的是,我们无法确保后面输入的灯光向量是否是单位向量。所以这里要做一个 Normalize,将灯光向量强制变成单位向量。
接着是:
specOrig = (-E) · R
同样的,无法确保是单位向量的 R 也要做一下 normalize。
把目前做好的节点,输入到自发光,贴一张法线贴图,将材质原本的 specular 关闭。编译一下。
观察目前的效果就是这样。
接着我们要修正高光的扩散范围:
spec = power( specOrig , glossiness)
完整节点图如下:
以上,大致的功能就都实现了。现在唯一遗留的一个问题是是L向量输入的问题。现在我们是手动定义了一个 float3,假装这是一个灯光的方向。
我默认使用的是(0,0,1),大家观察图中的蓝色轴向,相当于沿着蓝轴方向,我设置了一个不存在的虚拟灯光。
现在我们想要更直观地操作,想要把假高光和灯光位置进行关联。我们需要添加更多设置。
通过蓝图和 MPC,对材质输入信息
如果我们想要关联场景里确实存在的一盏灯到我们做的假高光效果上。那么我们一定需要想办法把灯光本身的一些 transform 信息传输给材质系统。
这一节我们来讨论怎么通过蓝图和 MPC 来传递这个信息。
创建一个新的蓝图,选择 actor 类型
在蓝图左上方添加 component,这里我们先做一个点光的。
选择 construction script
然后把这个点光拖到蓝图面板中,我们要对即将创建的点光进行操作。
从这个点光上拖出来一个节点,叫 GetWorldLocation,意思是要获得这个点光在场景里的世界坐标。
以上这些简单的操作,我们就可以获取到这个点光在场景里的世界坐标,但是怎么把这个信息给到材质系统呢?
这里要使用 material parameter collection 了。
https://docs.unrealengine.com/en-US/Engine/Rendering/Materials/ParameterCollections/index.html
右键创建一个 MPC,这个东西一般是整体控制材质参数,或者从 bp 向材质系统传数据用的。
创建好以后,我们在这个 MPC 里新建一个向量变量。给个名字。之后我们就可以同时在蓝图和材质系统里访问这个变量了。
蓝图里创建一个 Set Vector Parameter Value 节点,通过这个节点,我们对刚刚定义的 MPC 上的参数赋值。
找到我们自己定义的 MPC 的名字,以及 MPC 中变量的名字,都设置好。
然后把节点都连上,让它们会生效。
compile save 以后,我们就要进到材质面板来拿这个信息了。
在材质面板里,我们可以直接把 MPC 拖进去使用,记得要选择正确自己设置的参数。
然后用点光的位置,减去模型本身的位置(world position 节点获取),获得从模型表面每个点指向点光的方向向量。
这个计算的结果就是之前一直说的 L 向量,我们用这一套取代之前的 float3 节点。(这里莫名有点像 SD 做东西的逻辑——做好不同的逻辑模块,每个单一模块都可以随便替换)
然后把做好的蓝图拖到场景中使用。这个点光就不是一般的点光了,而是可以向我们的材质系统 “通风报信” 的点光。
使用无关模式查看。我们自己做的这一套材质上是有自发光生成的假高光效果的。
如果我们想要对平行光做同样的事情,也是差不多的逻辑。
要对平行光重新做一个蓝图,用 Get Forward Vector 节点来获取平行光的朝向。
再在材质里面设置一下就可以了。
如果想要多盏灯光起作用的话, 可以把中间的一套节点做成 material function ,多输入几个灯光信息,计算完的高光效果全部 add 在一起就可以了。这样规划出来的材质系统就非常舒服。
最后
今天介绍的内容都是比较简单基础的知识。不知道怎么回事,写着写着就有点长。
可能是中间发散了不少东西吧。
做的效果没有太大用。但确实是很棒的学习资料。而且中间发散的内容是不是也还有点意思?