Unity3D

创建于:2016-05-27

创建人: mutoo

186 信息 1791 成员
与 Unity3D 有关的都可以在这里讨论
CocosEngine@Unity3D 的内容(查看所有内容
Cocos Creator次世代人物渲染实战:眼睛篇
CocosEngine 2021-11-04

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

确立目标

与皮肤篇和头发篇一样,我们将基于 Cocos Creator 的 PBR 流程实现引擎中的眼球渲染效果。

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

奠定理论

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

Image title


  • 虹膜直径大约等于整个眼球的半径;
  • 瞳孔的直径大约等于虹膜的半径;
  • 眼球并不是正球体,在虹膜前方又突起的液泡结构;

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

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


UV的处理和归一化

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

vec2 offsetUV = v_uv * irisSize;

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

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

Image title


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

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

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

视差贴图

Image title


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

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

Image title


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

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赋予我们的虹膜贴图,得到的结果应该类似下图:

Image title


(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;

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

Image title


MatCap贴图

Image title


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

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

       所谓MatCap贴图,顾名思义,是一张把整个材质(“Mat”-erial)的特性捕捉(“Cap”-ture)到像素内的贴图。MatCap贴图通常绘制的是一个球体,着色器会根据球体上的明暗面、高光和反射,为整个材质绘制明暗关系和高反光。美术的同学应该对MatCap并不陌生——ZBrush中用于渲染动辄上百万个多边形的材质正是使用MatCap着色器。因此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;

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

      Image title

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

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

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

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

Image title


结语

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

  • 了解方形模糊和高斯模糊的原理,并实现高效率的模糊效果;
  • 探索人类皮肤的Diffuse Profile并用代码重现现实观测的数据;
  • 了解次表面散射和各向异性高光的逻辑和原理;
  • 学习纵横行业30余年的Kajiya-Kay模型;
  • 尝试视差贴图的渲染方法;
  • 学习和使用MatCap贴图;

虽然我们只是在 Cocos Creator 现有的着色器上进行修改,但相信你也已经发现:在Cocos Creator着色器的基础上编写自己的着色器,不仅省去了大量GLSL基础工作,而且可以一步到位地获得PBR的基础渲染效果。在一个稳固的基础上,我们可以自由发挥,尝试各种各样的方法和模型, 实现丰富多样的渲染需求。

(转发自:原日志地址
Cocos Creator次世代人物渲染实战:皮肤篇
CocosEngine 2021-10-22

无论何等类型或规模,人物渲染都是项目中难以替代的重要组成部分。

有趣的是,对比世间万物,我们理应对自己的身体更加熟悉,然而远自文艺复兴以来,无论在美术层面还是技术层面,人物渲染一直是一个令人挠头的痒点——即便是从真人模特身上以尖端仪器扫描,并赋予极高贴图精度的加持下,我们依然时常难以摆脱“恐怖谷”的困扰。那么,有什么“奇技淫巧”,能够让我们在Cocos Creator中更容易地产出可信的人物渲染效果?

让我们今天从皮肤说起。


确立目标

我们将在Cocos Effect中编写一个次表面散射着色器,用于表现人物渲染中的皮肤材质效果。Cocos Creator已经自带了标准Metal/Roughness流程的PBR 着色器,我们将在此基础上增加新的GLSL代码,这样既可以使用所有Metal/Roughness流程的PBR功能和特性,同时也兼备次表面散射的效果呈现。

所谓次表面散射,最直观的视觉观感是:物体内部自发光由内而外把物体照亮了,因此使用我们的着色器除了可以配合环境制作写实的次表面散射效果之外,在特定数值的配合下,还可以产生更特别、更戏剧化的效果:

Image title

我们的着色器将配合两张新贴图Thickness和Curvature使用。这两个名词对于美术,尤其是角色美术的同学,想必是不陌生了。同时,我们会附加上菲涅尔反射的功能,在PBR反射算法的基础上赋予更灵活的调节选项。最后,我们会赋予一个Diffuse Profile功能,其细节会在后文详说。目前我们只需要知道它是一个随数值调节颜色输出的功能。

那么,Cocos Effect又如何使用呢?


快速上手

Cocos Effect是Cocos Creator存储着色器的一种格式,它使用YAML编写。Cocos Effect将顶点着色器、片元着色器和编辑器中的参数整合在一个文件中,并且会根据目标平台不同转换为不同版本的OpenGL ES Shader。YAML只传输数据,它本身不包含逻辑,毕竟YAML的全称是“YAML Ain't a Markup Language” (YAML不是一种标记语言),真正表达逻辑的还是GLSL顶点和片元着色器。

Cocos Effect的详细信息何以从这个文档中获得,以Cocos Creator内置的标准PBR 着色器为例,我们可以参考以下的信息,快速开始编写自己的着色器:

  • 所有顶点着色器代码需要在“CCProgram standard-vs”标签下用GLSL编写,同时也可以使用Cocos Creator内置的Shader参数,具体的列表可以在这里获得;
  • Image title
  • 类似的,所有的片元着色器代码需要在“CCProgram standard-fs”标签下用GLSL编写;
  • Image title
  • 自定义参数需要在“properties”标签下声明,在“properties”标签下声明的变量都会在Cocos Creator内的编辑面板中出现;
  • Image title
  • 我们需要为新声明的参数声明一个相应的uniform,这需要在“CCProgram shared-ubo”标签下实现,声明的uniform无论顶点着色器还是片元着色器都可以访问;

Image title

我们也可以声明自定义函数,但是记得:YAML是不包含逻辑的。所以自定义函数应该放在顶点着色器(“CCProgram standard-vs”)或者片元着色器(“CCProgram standard-fs”)之内。


奠定理论

首先,我们需要解答一个问题:什么样的效果能够让皮肤更真实?

我们可以从现实生活中找到一些参考:

Image title

观察上图,首先你可能会注意到的是:他的耳朵是红色的。而且在结构越陡峭、线条越坚硬的部分,红色显得更加浓艳。另外,在他的鼻梁明暗交界线的部分,也可以看到一条红色聚集的色带。

Image title

在上图中,我们同样能够在鼻梁的明暗交界线上观察到红色的色带,她的鼻梁的线条并不是特别陡峭,因此色带似乎也比上一个例子更宽一些。

Image title

在这个例子中,我们同样可以观察到红色聚集的部分,不过在她的脸上,红色聚集在鼻翼和鼻尖的位置。

Image title

而在这个例子当中,我们可以在他的脸颊明暗交界线的位置看到大面积的粉红色聚集(至于为什么是粉红色,是因为这张照片在后期调色中混入了冷色调的缘故)。而这种粉红色在他鼻梁线条锋利的部分同样可以看到。

综合起来,我们似乎可以观察到一定的规律:

  • 人的皮肤在明暗交接线的部分,会出现红色聚集;
  • 红色聚集在人脸结构陡峭,线条锋利的部分,会更明显和鲜艳;
  • 耳朵、鼻尖、鼻翼等区域是红色聚集常见的地方。

  • 那么,为什么会出现这种现象?这些红色是从何而来的?

Image title

我们知道,当光线从一种介质(A)射入另一种介质(B)时,一部分光线被介质B吸收,另一部分在介质B中经过多次反射和折射,最终一部分光线从介质B折返重新射入介质A中。而这些在介质B中经过反复反射折射而重新回到介质A的光线被人眼所捕捉到,所以人眼可以观察到介质B的颜色。这些光线虽然原理上属于反射光线(Specular),但实际表现的是散射(Diffuse)的特质,所以被称为漫反射(Diffuse Reflectance)光线

你大概已经注意到:当漫反射光线经过在介质B中的种种流程,重新射入介质A时,距离原来射入介质B的入射点已经有了一段距离。对于绝大多数材质来说,这个距离非常微小,完全可以忽略不记,所以我们可以理解为漫反射光线是由原入射点射出的。

Image title

然而对一小部分材质来说,这个距离就不能忽略了。当光线射入时,这类材质会表现一种透光的特质,仿佛物体内部自带光源,把物体从内部照亮了。自然界中有很多有机材质会表现这种特质,比如蜂蜡、树叶、果蔬等,当然也包括人的皮肤。

这种特别的散射特质,即被称为次表面散射(Sub-surface Scattering)。

Image title


原理似乎挺简单,但我们应该如何实现呢?

在计算机图形学的历史上,我们可以找到多种多样表现次表面散射的技巧和手法。其中一个较早的例子来自与电影《黑客帝国》(The Matrix)。特效人员发现,可以简单地对皮肤的Diffuse贴图做一次模糊,再叠加到原贴图上,就可以有效降低贴图的人工质感,做出光线在表皮下散射的效果。

而对于明暗交界线上的红色堆积,你大概已经想到可以轻松利用“N·L”方法,通过光照方向和物体表面法线计算得明暗交界线的位置,再叠加以一个颜色即可。这种方法也被称为Wrap Lighting方法,在经典游戏《半衰期2》(Half-life 2)中广泛应用。

而与“N·L”非常类似的“N·V”方法,可以通过摄像机方向和物体表面法线得到物体正对摄像机观察角度的部分,这可以帮助我们轻松获得菲涅尔(Fresnel)反射的效果。

讲到这里,我们的目标已经比较明确了:

  • 我们需要一个模糊,用于达到Diffuse贴图的漫反射效果;
  • 我们需要一张Thickness贴图和一张Curvature贴图,帮助我们识别物体那些部位容易出现次表面散射;
  • 我们需要一个菲涅尔反射效果,这将帮助我们实现皮肤的Specular部分;
  • 最后,我们需要一个Diffuse Profile,这将帮助我们确定次表面散射的强度和颜色。

实现模糊效果

如何实现模糊的效果?其背后的逻辑其实很简单:我们只要把需要被模糊的图像的UV向各个方向偏移一点距离,把所有偏移的结果相加,求一个平均值即可:

Image title


在上面的示例代码中,“v_uv”是Vertex Shader传递的UV数据,我们基于Cocos Creator的内置PBR 着色器编写我们的Shader,所以有很多准备工作已经做好了,我们直接拿来用即可。“SRGBToLinear”是Cocos Creator内置函数,将sRGB空间的颜色数据转换为线性空间,在PBR流程当中,所有的颜色计算需要在线性空间中进行。

当然,仅仅做一次平均值的计算,最终效果可能不是特别好。你也可以用同样的方法循环2-3次,以获得更细腻的效果:

Image title


这种单刀直入的模糊方式,称之为方形模糊(Box Blur),其特点就是所有像素一视同仁,被处于同样等级的模糊处理。虽然简洁明了效率高,但视觉上不一定是我们想要的。

如果你用过Adobe系列的图像处理工具,你一定很熟悉最常用的模糊工具“高斯模糊”(Gaussian Blur)。高斯模糊的逻辑与方形模糊一脉相承,不同的是:像素偏移的距离越大,模糊的程度越高,反之则越小,而模糊程度的权重则呈正态分布排列。这样我们得到的结果中间模糊程度低,四周模糊程度高,并且呈现出一种从中间向四周自然衰减的效果。

用代码从头实现一个正态分布函数,似乎还是太麻烦了。所幸的是,我们只需要几个呈正态分布的数值作为我们模糊的权重,直接代入我们的方形模糊函数当中即可,网上有诸多正态分布数值生成器可供挑选。

Image title


探索Diffuse Profile

让我们回到之前观察到的明暗交界线上的红色聚集上面。我们已经想到可以用“N·L”+ 颜色叠加的方法实现这种效果,但这里有一个问题:这里的红色是常量的红色吗?

让我们来看一个实验:有一个呈现次表面散射的球体,被单一光源照射。光源的位置和强度不变,放大和缩小球体的大小,观察次表面散射颜色的变化。

Image title

结果略微出乎意料:在球体极大和极小的情况下,散射颜色较深,近乎于纯黑色;随着球体的缩放接近于极大和极小之间的一个范围,散射的颜色逐渐变得明亮和鲜艳,直到达到一个峰值。

这似乎告诉我们:次表面散射的颜色变化也遵循一条钟形曲线(正态分布曲线),在某个临界点达到峰值,向两边递减。

那么,这个临界点是由什么决定的呢?这就取决于我们如何理解实验中球体大小的变化。

首先,在光源位置不变的情况下,球体的大小变化,意味着光线到达球体表面传播的距离变化,也就是说,光线传播的距离是因素之一。

另外,一个半径较大的球体,可以看作其表面的曲率(Curvature)也更大,反之则更小。一个半径无限小的球体,表面的曲率也无限小,可以看作是一个平面。也就是说,物体表面的曲率,即弯曲的程度,也是因素之一。这也于我们之前在观察参考图中得到的结论相契合。

理论很美好,但是我们如何实现呢?

所幸的是,我们已经有基于现实观测的皮肤次表面散射数据供我们使用:

Image title

Image title


这张表看上去有点不明觉厉,简单地说:我们所观察到的皮肤上的红色,其实是皮肤的不同截层以不同的颜色和强度曲线分别进行散射,再叠加而成。这张表列举了六层不同的颜色和曲线。而所有截层的散射强度都以正态分布排列,所以我们可以在右图看到自然衰减的晕染。

既然数据已经给到我们了,我们就可以根据正态分布公式,计算出散射强度,在叠加以颜色,散射的最终输出就解决了。

Image title


上图即是正态分布(高斯分布)公式,简单地说:μ为中位数,在我们的计算中也就是散射颜色变化的峰值,我们知道它由光线传播的距离和物体表面的曲度相关,目前可以取0;σ^2为方差,它决定了钟形曲线的陡峭程度,这个数值已经为我们提供了。由此,我们可以直接带入公式:

Image title


到目前为止,我们已经得到了实现Diffuse散射效果的模糊,得到了解决次表面散射颜色和强度的Diffuse Profile,现在的问题是:次表面散射应该出现在哪里?

在这里我们需要引入两张贴图:Thickness和Curvature。Thickness通过以法线反方向发射射线的方式计算物体的厚度,Curvature表现的是物体表面的曲率。这两张图,我们无需考虑如何在引擎中计算和生成,因为我们可以在第三方软件,比如Substance Painter中离线渲染他们。

Image title

获得烘焙完成的贴图之后,我们把Curvature放在一边,先处理Thickness。

Image title


我们先用之前的高斯模糊处理Thickness,再用原贴图减去被模糊后的Thickness贴图。回忆一下高斯模糊的原理,我们得到的结果是:模糊后偏离较小的像素被减去了,留下的是偏离较大的像素。这些Δ值可以作为我们次表面散射的遮罩。

我们仍缺的一环是菲涅尔反射。如上所述,我们可以使用“N·V”方法实现这一效果。

Image title



在上面的示例代码中,“cc_matView”是Cocos Creator自带参数,返回的是试图矩阵。“v_normal”是从Vertex Shader传递的法线数据。

所有组件已经基本就绪了,下面是让他们联动起来的环节。


完成Shader

漫反射效果,用我们的模糊处理一下Diffuse贴图,叠加到Albedo通道即可。

Image title

菲涅尔反射的效果,用我们“N·V”计算得出的权重,乘以一个自定义参数,叠加到roughness通道即可。

Image title

漫反射的颜色,我们已经得到了Diffuse Profile的函数,问题是:用什么参数带入这个函数?

我们已经知道光线传播的距离和物体表面的曲率是影响次表面散射的因素。光传播的距离似乎比较难把控,但我们至少知道物体本身的前后关系也算在光传播的距离当中,因此代入顶点位置数据(v_position)是顺理成章的。

至于物体表面的曲率,我们并不知道曲率与次表面散射的直接数值关系,但我们通过观察实验得知:曲率与次表面散射强度大致呈线性关系,因此我们姑且把曲率(由Curvature贴图提供)当作一般的数值权重,再辅以我们自定义的权重加以调节。

除了Diffuse Profile的输出之外,我们还可以利用Cocos Creator内置的cc_mainLitColor参数,在散射中加入光源颜色和强度的影响。

最后,在颜色输出上再叠加上物体自身的Diffuse颜色,就基本确立了次表面散射的颜色和强度。

Image title

然而,问题又出现了:次表面散射该从哪个通道输出?

Cocos Creator遵循标准PBR的Metal/Roughness流程,因而默认Pipeline中并没有Translucency通道,但这并不会对我们造成太大影响:我们可以利用Emissive通道达到相同的效果。

下一个问题是:次表面散射应该在哪里出现?

我们知道次表面散射的成因是入射光线在物体内部反射和折射而产生内部发光现象,所以我们的第一步是计算“N·L”,利用我们已得到的Thickness Δ值,我们可以得到一个明面为0,暗面为Thickness的遮罩。这也符合次表面散射的原理:光线从明面射入,所以我们可以从暗面观察到次表面散射现象。

最后,利用遮罩将次表面散射颜色叠加到暗面上。我们的效果就已经出来了。

Image title

次表面散射确实是一个经久不衰的话题。毫无疑问,我们今天的成果还有许多可以纠正、改进和继续发掘的地方。希望你读到这里,已经激发了一些奇思妙想,开始动手制作属于自己的Cocos Creator 着色器了。

(转发自:原日志地址

加入 indienova

  • 建立个人/工作室档案
  • 建立开发中的游戏档案
  • 关注个人/工作室动态
  • 寻找合作伙伴共同开发
  • 寻求线上发行
  • 更多服务……
登录/注册