拒绝黯淡无神!用 Cocos Creator 给眼睛一点「灵魂」

在介绍了皮肤头发的实现之后,我们进入本次人物渲染实战的最后一环:眼睛。

眼睛往往是项目中腾挪空间较大的资产:它可以很简单,用一张贴图即可;它也可以非常复杂,美术大神会手动雕刻虹膜的每一条沟壑。作为通俗的“灵魂的窗口”,即便是风格化的卡通美术项目,眼睛的重要性也不容忽视。关于眼睛的美术资产制作流程,可以参考这篇文档:

Alfred Roettinger / Realtime Eye:

https://texturing.xyz/pages/alfred-roettinger-realtime-eye

确立目标

与之前一样,我们将基于 Cocos Creator 的 PBR 流程实现引擎中的眼球渲染效果。我们的美术资源包括一张表现“眼白”(学名是巩膜)部分的颜色贴图,一张表现“眼眸”(学名是虹膜)部分的颜色贴图,一张法线贴图和一张 MatCap 贴图。其中,虹膜圆形的边缘用虹膜贴图的 alpha 通道表达。除此之外,我们还需要一些小技巧来表现眼珠在眼眶中的遮蔽关系,这将会在后文中详说。

奠定理论

眼睛的结构需要我们关注哪些点呢?我们仍然需要求教于参考图:

5d62964eb961d1d93ecf34bd8eddd290.jpeg

  • 虹膜直径大约等于整个眼球的半径;

  • 瞳孔的直径大约等于虹膜的半径;

  • 眼球并不是正球体,在虹膜前方又突起的液泡结构。

首先我们需要了解的是:眼球不是一个正球形,在虹膜的正前方位置有一个圆形的突起。这是因为虹膜正前方有一个液泡的结构,而整个眼球又包裹在透明的巩膜里,所以眼球是一个整体流线型、在正前方有小突起的球体。这些细节,美术的同学会进行表现。

综合来说,虹膜将会是我们的核心,我们需要重点处理虹膜和巩膜、瞳孔以及它正前方的液泡的关系。

UV 的处理和归一化

在头发篇中,我们已经聊到了 UV 数据和其他类型的数据一样,可以对它进行算数运算。我们熟悉的 UV Tiling 的功能就是通过用 UV 乘以一个常量实现的。对于虹膜贴图,我们也可以采用相同的处理:

vec2 offsetUV = v_uv * irisSize;

我们新建了一个浮点参数 irisSize,并让他与 UV 数据直接相乘。结果和 UV Tiling 是一样的:虹膜贴图在 UV 上的比例缩小了(在 irisSize 取值大于1的情况下),并且在 UV 空出来的部分叠加上了同样的虹膜贴图。

当然,我们的眼珠只需要一个虹膜。我们既希望利用常量相乘的办法缩放 UV,又不需要贴图的叠加,只需要在贴图的属性中将 Wrap Mode 设为 clamp-to-edge 即可。

1df630d2d2b34998336d8cf16391c321.png

叠加消除了,我们又遇到了新的问题:贴图似乎缩放到了左下方的角落里。我们需要对坐标系做归一化处理,让我们在缩放 UV 的同时,贴图可以保持在 UDIM 正中心。

vec2 offsetUV = (v_uv - 0.5) * irisSize + 0.5;

我们的虹膜大小和位置已经差不多了,下面我们需要将虹膜向后“推”进眼球里,以表现液泡和虹膜的前后关系。我们可以使用视差贴图的方法实现这个效果。

视差贴图

1fb9ce5375f87b9470b2bf64efc1d709.png

如上图所示,灰色平面代表物体的基本网格平面,在此基础上物体有突起的表面结构,用红色曲线表示。当我们以上图 V 向量的方向观察物体时,我们理应观察到红色曲线上的 B 点,当突起的表面结构不存在时,我们则会观察到基本网格上的 A 点。换言之:我们需要 A 点上的网格数据,去实现高度在 B 点的渲染效果。

我们知道,高度贴图(Height Map)表达的是物体切线空间的高度数据。也就是说,A 点的切线空间高度数值(H(A))是可以通过贴图获得的。但是 B 点呢?我们通常会以 A 点的切线空间高度作为数值权重,以观察向量 V 的反方向(从片元指向摄像机)进行缩放,就可以大致得到 B 的位置坐标。这样的计算当然不能做到完全精准,但效果是我们可以接受的。

43707814e53661b881e55928fec1ba4d.png

方法有了,我们需要做的第一步是获得从片元指向摄像机的向量,并将其转化到切线空间当中:

vec3 worldDirPosToCam = normalize(cc_cameraPos.xyz - v_position);
vec3 tangentDirPosToCam = vec3(dot(worldDirPosToCam, v_tangent), dot(worldDirPosToCam, v_bitangent), dot(worldDirPosToCam, v_normal));

我们可以利用得到的切线空间向量,对 UV 进行偏移,以偏移后的 UV 坐标读取切线空间的高度信息。这样我们就在 A 点得到了 B 点的高度输出:

vec2 parallaxUV( vec3 V, vec2 uv, float iniHeight, float scalar ){
    vec2 delta = V.xy / V.z * iniHeight * scalar;
    return uv - delta;
  }

上面的代码需要带入四个参数:V 为我们刚求得的切线空间中的从片元指向摄像机的向量,uv 为物体的原uv(即我们已经在皮肤篇和头发篇中使用过的“v_uv”),scalar 为自定义的权重参数,iniHeight 是片元的原高度数据,这个数据应该由一张贴图提供。在我们的着色器中,我们只需要用视差贴图做一些简单的像素偏移,因此没有准备专门的高度贴图,我们可以用颜色贴图的任意一个通道,或者直接使用一个常量0.5作为代替。

得到了视差贴图的函数,我们就可以把它用在虹膜上面了。

vec2 offsetUV = (v_uv - 0.5) * irisSize + 0.5;
vec4 irisTex = texture(irisMap, offsetUV);
vec2 irispUV = parallaxUV( tangentDirPosToCam, offsetUV, irisTex.r, parallaxScale );
vec3 irisColor = SRGBToLinear(texture(irisMap, irispUV).xyz);

我们可以用之前的缩放归一后的 UV 得到有视差效果的虹膜 UV,用这套新 UV 赋予我们的虹膜贴图,得到的结果应该类似下图:

a32dee25082ddd6a6303d11f4d7e5dfe.gif

如图所示,随着权重数值的变化,我们的虹膜贴图应该能够沿着法线方向向前“推”或向后“缩”,同时我们也发现,我们目前的视差贴图只能达到一种近似的效果,随着权重数值增大,视差的效果也会越来越失真。因此我们在使用它时,需要注意将数值控制在比较低的范围内。

完成虹膜

虹膜的处理已经差不多了,下面我们需要处理一下瞳孔。

完成了虹膜的视差,我们如法炮制,对我们得到的视差 UV 做归一化处理。区别在于,这次我们将 UV 归一化,这相当于将所有的 UV 塌陷到归一化坐标的原点上。使用这个 UV 采样贴图,得到像素向坐标中心拉伸的效果。

接下来,就是制作一个遮罩将虹膜和瞳孔混合在一起了。

vec2 pupilpUV = normalize(irispUV - 0.5) + 0.5;
float pupilIndex = (1.0 - length(v_uv - 0.5) * 2.0 * irisSize) * (0.8 * pupilSize);
vec2 irisUV = mix(irispUV, pupilpUV, pupilIndex);
vec3 irisColor = SRGBToLinear(texture(irisMap, irisUV).xyz) * irisColor.xyz;

通过自定义参数 irisSize 和 pupilSize,我们可以分别控制虹膜和瞳孔的大小。我们也可以为虹膜贴图自定义一个偏转的颜色 irisColor,快速制作出不同颜色的眼眸。

下面我们可以把虹膜贴到眼球上了。眼球的基本材质使用巩膜贴图,我们只需要把虹膜的部分叠加在上面即可。虹膜贴图的边缘部分是用 alpha 通道的渐变完成的,我们可以用指数运算控制渐变的曲线强度,从而控制虹膜边缘的硬度:

vec3 scleraTex = SRGBToLinear(texture(scleraMap, v_uv).xyz);
float irisEdgeIndex = clamp(pow(irisTex.a, irisEdge), 0.0, 1.0);
vec3 eyeBase = mix(scleraTex, irisColor, irisEdgeIndex) * irisColor.xyz;

目前眼球的固有颜色信息已经得到了。但是我们的眼球看上去和直接贴了一张颜色贴图没有什么区别。下面我们需要做的是:为眼球赋予“神”。

047e4bf24e05a23c12433196b1e6d527.png

MatCap 贴图

eb66b11b7923feee9eba62bde28fda93.png

所谓有“神”的眼睛,可以简单概括为“有高光和/或有反光的眼睛”。如参考图所示,上面两张参考图中的眼睛显得更加生动和有活力,而下面两张则看上去非常死板,好似无机物。

然而,游戏中角色的眼睛并不是总能恰好反射环境中的光照,当环境有某些特定的需求或者从某些特定的角度观察时,眼睛很有可能没有足够的高光或反光。更何况,眼睛固然重要,但毕竟是一个较小的反射面,为此专门进行反射的光照计算似乎有点得不偿失。一个常见的折中办法是:把高光和反光作为贴图,永久地“贴”在眼睛表面。这样无论任何环境和角度,角色的眼睛里永远有星辰大海。

所谓 MatCap 贴图,顾名思义,就是一张把整个材质(“Mat”-erial)的特性捕捉(“Cap”-ture)到像素内的贴图。MatCap 贴图通常绘制的是一个球体,着色器会根据球体上的明暗面、高光和反射,为整个材质绘制明暗关系和高反光。美术的同学应该对 MatCap 并不陌生——ZBrush 中用于渲染动辄上百万个多边形的材质正是使用 MatCap 着色器。因此 MatCap 有着效率极高,又足够能表现明暗关系和质感的优点。同时,它的缺点也是显而易见的:无论从哪个角度观察,MatCap 材质的明暗关系和高反光永远一成不变。

在我们的着色器中实现 MatCap 材质也非常简单:我们知道 MatCap 的特性是它永远正对观察方向,既然如此,得到一套永远正对摄像机的 UV,用它来采样 MatCap 贴图即可。我们知道,法线数据表达的是物体表面片元正对的方向,因此把法线数据转换到视图空间,只取 X 和 Y 轴数据,就能得到我们想要的 UV:

vec4 matCapUV = (cc_matView * vec4(v_normal, 0.0)) * 0.5 + 0.5;
vec4 matCapUV = (cc_matView * vec4(v_normal, 0.0)) * 0.5 + 0.5;

确定了 UV,剩下的工作就水到渠成了:

vec3 matCapColor = SRGBToLinear(texture(reflecMap, matCapUV.xy).xyz) * reflecAmt;
vec3 eyeColor = eyeBase + matCapColor;

我们的着色器已经编写完成了,让我们来看看效果:

8c9767636e8ec65557a23348114fcbcf.png

按理来说,我们该参考的图都参考了,该考虑的变量都考虑了,该做的工作都做了,但这白森森的眼神,还是直接营造出一种纸人既视感。尤其是从较远距离观察的时候,白的发亮的眼珠更是莫名惊悚。

这是因为,眼珠和身体的其他部位一样,应该相互产生遮蔽的关系。我们的眼珠是单独制作的,所以和眼皮没有暗部遮蔽,因此在整张脸上特别出挑。这也是角色渲染的一个常见问题:我们对人脸都太熟悉不过了,以至于人脸上如果出现异于常理的现象都会触发本能的警觉。而且当其他的部分越趋近于真实时,这种恐怖感越严重。

如果是一个静态的部位,这个问题非常好解决:烘培一张 AO 即可。但是对于角色来说,绝大多数的角色眼球是需要骨骼动画的,直接把 AO 烘培在眼球上显然不可取。我们需要做的是在眼球的模型前方再新建一个遮蔽的模型,给它赋予一个 AO 的透明贴图,单独作为 AO 保留在模型上。这个模型除了 AO 将不会起任何其他作用,因此只需要给予一个基本的 Unlit 材质,也不会消耗额外资源。这种做法,也是包括 UE4 在内的许多引擎选择的做法。

fd172fb5e6852643f7ee4964c5439891.png

增加了 AO 之后,我们角色的眼神柔和了许多,眼球和眼眶的衔接也更自然了。

8519cf281d1975f0afcef426dbd2d2b0.png


系列小结

我们对人物渲染的探索到这里就可以成功收官了。在我们试图解答一个个渲染问题的过程中,我们获得的不仅是皮肤头发和眼睛,同时也包括了:

  • 了解方形模糊和高斯模糊的原理,并实现高效率的模糊效果;

  • 探索人类皮肤的 Diffuse Profile 并用代码重现现实观测的数据;

  • 了解次表面散射和各向异性高光的逻辑和原理;

  • 学习纵横行业30余年的 Kajiya-Kay 模型;

  • 尝试视差贴图的渲染方法;

  • 学习和使用 MatCap 贴图。

虽然我们只是在 Cocos Creator 现有的着色器上进行修改,但相信你也已经发现:在 Cocos Creator 着色器的基础上编写自己的着色器,不仅省去了大量 GLSL 基础工作,而且可以一步到位地获得 PBR 的基础渲染效果。

在一个稳固的基础上,我们可以自由发挥,尝试各种各样的方法和模型, 实现丰富多样的渲染需求。如果有人物渲染方面的问题或是其他想要交流的内容,都欢迎扫描下方二维码添加江船长的个人微信,一起交流吧↓

>> 江船长个人微信


📢📢Cocos Star Meetings「杭州站」报名开启!11月6日(本周六)下午14:30,我们在杭州网易大厦二期等待各位!独立游戏开发者 阿信OL、「Star Writer」异名、每日给力主程陈炫烨为大家准备了满满游戏开发干货,还有 Cocos 生态总监大表姐、网易易盾李鹤仙空降现场!活动限额80人,赶快点击文末【阅读原文】或扫码报名吧↓

往期精彩

1f1372598fdb14f6835efe1443a675c6.png

b8fb9104af15a3a0f03d8bbfe71c6439.png

f287dc9ae4ea8393f7019c87be9a44fb.png

5bd7b7887811c5859d7ba66da29a806b.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值