Shader课程201 ---- 使用数学来创造艺术

发表于2017-06-21
评论2 5.5k浏览

BenCloward (CG Supervisor Bioware), 翻译:乔晨


谢谢大家,这是Shader 201。我们十分幸运,因为接下来的45分钟我们将要讨论shader。这个课程叫做shader201的原因是我去年的课程叫做shader101,我们可以将这个课程当作去年课程的进阶。我去年的课程有谁来过?(若干人举手)哇,你们回来了(笑)。今年的课程,我将一步一步的完成几个相对复杂些的例子。如果你是shader的新手,我希望这些内容对你不会十分的困难。我今天非常高兴能与大家一起完成这些内容。

我将课程的副标题命名为使用数学来创造艺术(Making Art With Math)。

这句话十分的吸引我,因为这正是当你在写shader时,你在做的事情。你通过数学计算最终得到一些漂亮的可视化的效果。这是我喜欢shader的原因。

作为一个技术美术(technical artist),我们经常要写一些工具来节省美术师的开发时间,或者在处理蒙皮与rig,但是这些工具/工作产生bug是难免的,总会被人们吐槽抱怨。

但是当工作涉及到shader时,人们总是觉得你在实现比较酷的效果。这是我喜欢shader的另一个原因。


 

让我做一下自我介绍,我在Bioware已经工作了10年,你们可以看到在幻灯片中《星球大战:旧共和国》的图更大一些,因为我在这个项目中工作时间较长。抱歉我不能透露现在的工作,希望将来能与大家分享。(译注,结合这次E3Xbox One X的信息,他参与的项目应该是《圣歌》项目)


 

之前我写了一个3ds max的插件,ShaderFXShaderFXAutodesk收购,现在它是3ds maxMaya的内置功能。ShaderFX是一个基于结点GUI创建实时shader的编辑工具。我同时是一个讲授写shaderhlslDVD系列课程的作者,不幸的是这个DVD课程的出版公司已经倒闭了。


 

这是我们今天将要分享的内容,首先我会快速复习一下去年课程的shader,这不会占用很多时间,主要复习的是今天课程会涉及到的一些内容。我会使用UE4的结点shader编辑器,基于结点的编辑比直接写代码编辑更加直观些,希望这样更便于新手学习。在每一个示例的最后,我仍会把与结点编辑的shader相同作用hlsl的代码给出来。我在工作中使用寒霜引擎,我很喜欢这个引擎,但是在这个公开的课程中使用寒霜引擎并不十分合适,所以我选择了UE。虽然在此之前我并没有使用过UE,但是我发现它的结点shader编辑器能够很快上手。


 

 好的,让我们复习一下去年分享中的shader


 

我们将要复习的第一个去年课程中的shader是扭曲shader。在扭曲shader中,我有两张纹理,这两张纹理以各自不同的速度和方向在运动,这两个纹理的效果组合起来将实现最终的扭曲效果。我去年的课程中首先演示了只是用一个纹理进行计算的情况,如果只是用一个速度和方向能够明显看到纹理循环的情况。如果使用两个纹理并以不同的速度与方向运行,能够避免这种循环效果的产生。在后面的分享中,我们将使用这个shader的效果。


我去年分享的第二个shaderAtlas Walk(译注,即通过shader播放合并在同一张纹理上的序列帧)Atlas纹理是打包到同一张纹理上的一组序列帧的集合。示例中我将爆炸的效果放在同一张纹理上,可以看到爆炸的纹理是按照66列的形式保存,在实时引擎中计算完成这样的效果是很困难的。这个shader的工作是在一个时刻显示这一组纹理中的一帧。因为这些纹理逐帧显示,所以我们能够得到一个相对平滑的效果。为了实现这一效果,我们用到了能够对浮点数向下取整的floor结点。

在示例中,为了一秒钟现实24帧动画,我们将时间乘以系数1/24。一般情况下线性的时间值,在经过floor结点计算之后,会变成阶梯状的分段函数。这样显示的纹理能够从一帧的区域跳到另一帧的区域,而不是UV滚动,每展示6帧纹理后,显示的纹理向下走一层。这是我们例子中所做的事情。感兴趣的听众可以看一下去年的分享来了解更多细节。


我去年分享中的最后一个效果是投影融合(Projection Blend)。

这种方法能够避免美术师创建过多的效果相近的美术资源。在这个示例中,我使用世界空间中Y的方向,来在物体网格表面投射材质。材质能够是各种有实际意义的类型,比如沙子,雪,在这个示例中是苔藓。一般情况下,美术是在制作苔藓覆盖的石头时,会制作很多的资源来实现不同的效果表现。而使用这个shader,我们可以只创建几个石头模型,旋转这些石头,由shader来确保方向朝上的网格上投射有苔藓的纹理,从而实现不同的效果。这是非常方便的一个方法,它能够给我们节省很多资源量。在shader的左侧,我们将法线变换到世界空间中,按照y方向进行投射。shader的右边是纹理和蒙板,我们按照蒙板混合石头与苔藓的漫反射纹理与凹凸纹理。在这个示例中我们使用了lerp结点,lerp结点能够很方便的在两个输入之间进行融合。

 

 

好的,让我们进入今年课程我将要展示的例子。第一个例子是布料的着色(Cloth Shading)。当会议人员布置会场的时候,他们非常慷慨的给我提供了视觉效果的参考(译注:Ben Cloward转身指身后的幕布布料,笑,好冷的笑话)。在实际生活中有很多种布料,这些布料有不同的属性。比如,幻灯片中最左边的窗帘布料,这种布料的边缘是亮的。这是因为在布料表面有很多最右边的材质细小的纤维,这些纤维获得比较多的光线。所以在实现窗帘材质的时候需要注意这种视觉效果。在幻灯片的最右边的材质效果是与这种效果完全相反的材质。最右边的材质会有比较明显的朝向观察者的高光,而不是在边缘上有较亮的光照,比如丝绸。我在这里的所展示的是我在John Hable介绍的给神秘海域游戏所使用的布料着色的演讲中所学到的方法。


 

这是我们常用的菲涅尔项(Fresnel Term),在这页幻灯片中,我按照三种方式来向大家展示菲涅尔项。最左边是计算菲涅尔项的代码,中间的是菲涅尔项的视觉表现,最右边是Shader结点编辑器中,视向量与法向量进行点乘计算。我们这里所做的是计算两个向量之间的夹角,这里的摄像机向量(camera vector)是观察者到物体表面的连线,法线是模型表面的朝向。所以当观察者正对着物体表面的时候,法向量与视向量的点乘结果是1。而法线与视线垂直的时候,点乘结果将会是0。这页幻灯片所展示的图片是点乘结果的取反(译注,即1-dot(N,V)),之后我们会详细的解释这些。


 

在我们的shader中,我们先计算视向量与法向量的点乘,结果如右图所示,物体的中央(译注:物体表面朝向观察者的中央,不是几何的中央)是白色的,越往边缘颜色越暗。因为这里是物体的lit照明效果,所以这里的边缘不是绝对的黑色(译注,存在其他的环境光源)


 

如之前所说,我们需要的是点乘结果的取反。对于一个0-1范围的值的取反,我们只需要用1减这个值就可以了。这是一个常用的基础方法。这样给出了这种中间是黑色的,但是边缘是亮的效果。


 

我们需要的效果是将这个越高近边缘越亮的效果往边缘的方向上推。我们需要使用一个幂函数来加强这一效果。所以我们在shader编辑器中加入了一个power结点。(译注:将取反的点乘结果作为底,进行了4次幂的计算)


 

之前我们已经将越往边缘越亮的效果放物体边缘推了,现在我们需要加量这一效果,所以我们将之前计算的到的值乘以2。到这里我们已经有两个参数了,第一个参数是power计算所使用的幂指数,第二个参数是这里加强亮度的倍数。我们用这两个参数控制效果亮暗衰减的程度和效果的亮度。当调试布料效果的时候我们需要调节这两个值,来得到我们想要的效果。


 

在这一主题的第一张幻灯片中,我还展示了布料材质的另一种,与这个中间暗边缘亮效果完全相反的,边缘暗中间亮的效果。

我们将菲涅尔项未经取反的点乘结果进行与上面相同的计算,并将这个新的结果与之前的结果加起来。这样我们得到了一个边缘相对亮一些,中央也相对亮一些的效果。这里总共有四个值来控制,边缘/中央的明暗衰减效果与亮度。


 

当我让我妻子看这个效果,她觉得这个效果并不像布料。我需要使用一些纹理。我扫描了我的牛仔裤得到了漫反射纹理与法线纹理。我希望能让布料更加真实些。


 

我先将菲涅尔相关的参数放在一边,将漫反射纹理与法线纹理设置好。可以看到在这里,我希望将纹理平铺,所以我在纹理的UV上乘了3。这是我们在进行布料着色之前的效果。在下一页幻灯片,让我们将菲涅尔相关的内容从新接入。


 

我将diffuse纹理得到的颜色与我菲涅尔相关计算的到的值相乘。我妻子觉得这与牛仔裤的细帆布效果仍有出入,她比较难搞,我不得不向她解释我这个shader并不是只针对与细帆布材质,而是能够适用于不同的布料材质,我将这个shader给美术师,由他们来调节参数,来实现不同的布料效果。


 

我自己调了几个效果。左侧的是棉布的效果,这种材质主要使用了边缘的效果。细帆布内外两种效果都有使用。右侧的丝绸中间比较亮的效果比较重,边缘比较亮的效果相对轻一些。这样我们能够实现不同的变化效果。

但是我需要指出的是,这种shader并不是数学与物理正确的,这是一个视线方向相关的效果,但是这个shader能够模拟布料的效果,而不需要图形程序员修改引擎与实现布料专用的BRDF模型。TA能够独立的完成这个功能。


 

这是布料着色的HLSL代码。


 

我们的下一个例子是雨滴波纹的效果。

这是我自己拍摄的雨滴的效果。

在这里我们能够看到雨滴波纹的效果这样的。每一个雨滴波纹由中央开始,逐渐向周围扩展,在扩展中还有一个由中央到边缘的衰减。并且场景中同时存在多个雨滴波纹。

这是我们接下来将要实现的效果。

我这里使用的方法主要来自 Seblagarde的博客,他的博客中有他对下雨效果的研究与实现。他现在在制作一个叫做RememberMe的游戏。


 

我们将从这张纹理开始,这张纹理是高斯径向渐变纹理(译注,原文是GaussRadial Gradient Texture)。实现雨滴的另一种方法是我去年分享中提到的Atlas walk(译注,即合并到一个纹理上的序列帧方式),但是这样需要一张高分辨率的雨滴效果的序列帧纹理,这会占用较多的显存,并且这种动画并不是很平滑。所以我们放弃这种序列帧的方式,而通过数学计算来实现。

我首先需要介绍的是实现这个径向渐变纹理从中间到四周的扩张。


 

在这里时间结点的Time值在0-1之间循环,我首先将Time值减1,这样它将在-10之间循环,之后再将这个值加到纹理中的到的值上。这样得到了径向渐变的纹理从中间向四周扩展。


 

因为我现在渐变值是0-1单调连续的,为了实现雨滴效果,我们接下来需要的是正弦波来实现水波的效果。


 

所以我将渐变值乘以pi,并将这个值传入正弦函数,得到了右边的静态效果。我将这个与之前的结果结合起来。


 

shader中左侧是我之前的内容,我将这个值乘以pi,并传入到正弦函数中,得到了现在的效果。

我妻子认为这是一个催眠的效果。这个效果不像水波效果的主要原因是在水波的边缘并没有衰减。我们需要更多的数学计算。


 

我们需要做的是将衰减的结果乘到正弦的结果之上。这样我们得到了一个很好的蒙版(mask),但是我们希望得到的是雨滴的形状。我们需要将法线纹理引入我们的方程中。


 

我们将之前使用的渐变纹理放到R通道中,将法线纹理的XY保存到GB通道中。这张法线纹理是从一个圆锥的顶部俯视看到的法线方向编码得到的。

我妻子对这张图的颜色有些困惑。这张纹理保存的并不是颜色信息,而是将几何信息保存打包保存到这里。


 

将法线纹理放入我们的shader中。因为R通道保存的是径向渐变信息,所以法线纹理的R通道数据被用于之前的mask的计算。法线纹理的GB通道之后经过缩放(因为纹理数据范围是0-1,需要的法线方向是-1-+1,所以有一个乘21的过程)后得到方向信息,将这个方向与前面的到的mask相乘,并将之前没有加上的Z方向信息加入,来得到完整的水波法线方向。这样我们得到了一个完成的雨滴水波形状。但是我们需要更多的雨滴,所以我们需要改变我们所使用的纹理。


 

我将之前的纹理复制,并按照不同大小平铺,得到了一张新的循环纹理。我们将这张纹理用到现有的shader中。


 

我们得到的效果是这些雨滴是同步的(即,所有雨滴同时从中央开始向四周扩散)。这不是一个好效果。为了打破这种同步,我们需要额外的信息。


 

我们之前已经使用了RGB通道,但是我们的alpha通道还是空的。在这个通道中,我会对每一个雨滴保存一个灰度值,我把这个灰度值当作Time的偏移值(Offset)。


 

可以看到alpha通道的数据与时间结点的Time值相加,并通过了一个frac结点来取得结果的小数部分。

 


 

这是我们最终得到的效果。这里所有雨滴是不重叠的,如果我们需要实现重叠雨滴,可以将之前的雨滴计算加上UV的偏移与缩放之后再计算一次,并将这两次的结果叠加。

 


 

或者我可以将这个效果与我们之前的扭曲效果结合起来。还不错。


 

这是雨滴效果的hlsl代码。这个函数计算得到一个雨滴的法线方向,你能在之后的shader中使用它,最下面的是seblagarde的博客链接。


 

接下来我要分享的是有体积感物体的着色(译注,这里并没有介绍体渲染,而是介绍一种带有体积感的着色方式)。我这里展示的是一个实际生活中冰块的效果。对于这种物体,我们可以看到大量的在物体表面下的信息。

这个方法来自Chris Oat,在很久以前他为ATI工作,他给出了这种实现方式,这种方式与Offset mapping很像,可以理解是朝向物体内部的版本。

 


我们直接把纹理坐标传递给纹理结点,这种效果直接把纹理颜色贴在物体的表面,没有任何基于视差的相对移动,这样并不能表现出任何立方体内部的感觉。我需要做的是找到一种偏移纹理的方式,来产生当前纹理是在物体里面,而不是物体表面的感觉。

 


 

 

图一是我现在效果的示例,绿色的箭头是视线方向,红色的箭头是法线方向,当前的表面并不会移动。我希望实现的一个效果是在物体里面有一个次表面,这个次表面随着我视线方向而移动。我能做的是创建一个反射向量和一个透射向量。透射向量可以简单的认为是反转进物体表面的反射向量。记住这里的向量颜色,绿色,红色和蓝色,在下一页我们仍用的到。

 


绿色的是摄像机视向量,我将它从世界空间转换到切空间,切空间中法线的方向是(001),这样我们能够得到蓝色的反射向量。我将反射向量的xy方向,作为UV的偏移参与纹理的采样。这样我们实现了根据视向量的变化而UV偏移的效果,但是这种表现感觉与物体表面的关系并不大,物体内层的平面像是在物体内部的无限远处。

我将要向shader添加更多控制的内容,让纹理向物体内部的偏移没有那么多。



我将要向现在的shader添加两项控制的内容。

第一项用100除以控制内容是切线空间中反射向量的z分量的绝对值。这里的值100是将要控制得到多少的偏移。另一项是纹理像素的大小(示例中是1/512.我们将这两项与切线空间中反射向量的xy分量相乘得到采样UV的偏移量。这里所做的是缩短偏移量的大小,避免之前偏移量过大的情况。

现在的效果能够看到在物体表面的下面存在另一个平面,这个平面到物体表面有一定距离。



我还能做两件事来改进这个效果。

第一件事是引入一个normalmap,而不是之前的(001)向量作为我的法向量。这使我们的表面有一些法线的细节效果。

另一件事是使用一张噪声纹理来控制次表面偏移的深度。

希望大家喜欢这个效果。不过如果实现一个冰面效果,现在的效果只是一个起点,我们可以增加其他的效果,比如高光和折射的效果。这个带有体积感的shader能够在没有体积数据的情况下,模拟一些体积感出来。

 

 

这个HLSL的代码。如果你喜欢编程可以看一下,如果你不喜欢也不必惊慌。

 

 

至此我们已经聊过了布料的着色,雨滴的波纹效果和 带有体积感的冰的效果。如果你等会写shader感兴趣,你接下来该做什么呢?

1. 学习其他游戏的效果,试着实现他们的效果

2. 读书

3.利用搜索引擎

4.下载Unreal或者 使用在Max/Maya中的ShaderFX

5.Shader

去年有人问我应该都什么书,嗯,今年我列了个书单。

 

 

这是我的个人藏书中的一些书。特别是我推荐的第一本书The Cg Tutorial,我从这本书中学会的如何写shader

现在可能有些更新的书,但是书单中列出的书都是我熟悉的书,所以我向大家推荐这些书,他们都是好书。

谢谢大家。

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论