其他
技术贴│如何利用顶点着色器中的纹理?
我们是马里奥·默罗和诺曼·沙尔,来自Tequila Works公司,虽然在发表文章的时候还是这个情况,但是可能在你看到这篇文章的时候,诺曼·沙尔可能已经离开了公司去开始新的冒险了。
马里奥·默罗是一个游戏程序员,并对艺术、图形和游戏设计感兴趣,他试图涉足游戏开发中的一切领域并会在一天结束的时候用他的好奇心骚扰每一个人。诺曼·沙尔以前是一个艺术家,现在转为技术美术。我们互相分享创建新事物的渴望和热情,并尽我们的所能来推动周围的一切。
马里奥·默罗了解诺曼·沙尔在顶点着色器领域所做的工作并把他工作的一些想法应用在《Rime》上(《Rime》是一款开放世界的动作冒险游戏,最初是在在2013年科隆游戏展的索尼发布会上公布,《RIME》的游戏艺术风格独特,有人用《塞尔达传说:风之杖》和《ICO》与之相比。玩家操作的小男孩要发现小岛上的秘密并且逃出可怕的诅咒。但是似乎最近产品出现了一些问题,开发商Tequila Works在最新一期官方日志中宣布,他们将从索尼那边取回游戏的所有版权,独立完成剩余的开发工作。),这些事情发生在诺曼·沙尔加入Tequila Works几个月之前。所以诺曼是一个受人尊敬的艺术家,尽管在诺曼和马里奥相识以后一切都改变了。而马里奥对于诺曼来说,直到诺曼加入Tequila Works之前都是一个默默无闻的人,但不幸的是现在却不是这种情况。(这段话是作者的幽默,只是表达下满满的基情,可以忽略掉)。 什么是着色器
着色器应用于计算机图形学领域,指一组供计算机图形资源在执行渲染任务时使用的指令,用于计算图像的颜色或明暗。但进来,它也能用于处理一些特殊效果,或者视频后处理。用layman的话来说,着色器告诉电脑如何用特有的一种方法去绘制物体。
程序员将着色器应用于图形处理器的可编程流水线,来实现三维应用程序。这样的图形处理器有别于传统的固定流水线处理器,为图形处理器编程带来更高的灵活性和适应性。以前固有的流水线只能进行一些几何变换和像素灰度计算。现在可编程流水线还能处理所有像素、顶点、纹理的位置、色调、饱和度、明度、对比度并实时地绘制图像。着色器还能产生如模糊、高光、有体积光源、失焦、卡通渲染、色调分离、畸变、凹凸贴图、边缘检测、运动检测等效果。
Direct3D和OpenGL都使用了以下三种着色器:
·顶点着色器处理每个顶点,将顶点的空间位置投影在屏幕上,即计算顶点的二维坐标。同时,它也负责顶点的深度缓冲(Z-Buffer)的计算。顶点着色器可以掌控顶点的位置、颜色和纹理坐标等属性,但无法生成新的顶点。顶点着色器的输出传递到流水线的下一步。如果有之后定义了几何着色器,则几何着色器会处理顶点着色器的输出数据,否则,光栅化器继续流水线任务。
·几何着色器可以从多边形网格中增删顶点。它能够执行对CPU来说过于繁重的生成几何结构和增加模型细节的工作。Direct3D版本10增加了支持几何着色器的API, 成为ShaderModel 4.0的组成部分。OpenGL只可通过它的一个插件来使用几何着色器,但极有可能在3.1版本中该功能将会归并。几何着色器的输出连接光栅化器的输入。
·像素着色器(Direct3D),常常又称为片断着色器(OpenGL),处理来自光栅化器的数据。光栅化器已经将多边形填满并通过流水线传送至像素着色器,后者逐像素计算颜色。像素着色器常用来处理场景光照和与之相关的效果,如凸凹纹理映射和调色。名称片断着色器似乎更为准确,因为对于着色器的调用和屏幕上像素的显示并非一一对应。举个例子,对于一个像素,片断着色器可能会被调用若干次来决定它最终的颜色,那些被遮挡的物体也会被计算,直到最后的深度缓冲才将各物体前后排序。
统一着色器模型将上述三种着色器统一起来,发布于OpenGL和Direct3D 10里面。 在顶点着色器可以使用纹理之前的黑色时代
在以前的时代里,也就是DirectX 11之前,没有简单快捷的办法可以在顶点着色器里面进行纹理的采样。你仍然可以在顶点着色器里面做很多花哨的东西,你可以使用顶点属性来存储你的三维数据包的额外的数据并把存储的数据用在顶点着色器的计算中。所以你可以把关键的变形目标存储在顶点的颜色中并做一些有趣的纹理变形、以UV通道的点为中心来独立的移动和旋转子物体,我在过去已经使用这些技术在两年前的手机上生成几千个显卡的图形处理器控制的粒子。就算从现在的标准来看,这样的性能也是相当强大的!但是到底有多少属性可以存储在你的几何体里面仍然是受限的。
但是呢,当顶点着色器里面可以使用纹理的时候,这一切就完全不同了。。。 可以在顶点着色器里面使用纹理的美丽新世界
在顶点着色器里面使用纹理主要是允许你可以存储大量的数据,并且大量的数据可以轻易的被显卡的图形处理器检索到。位置的XYZ信息可以被表示为RGB颜色。旋转信息可以被保存为一个四元数,四元数可以被保存到RGBA格式的纹理中。
考虑到这一点,你可以做很多有趣的事情,举个简单的例子来说,你可以在Pflow里面疯狂的创建粒子动画并把它们都烘焙出来。你可以把粒子的属性存储为颜色:大小信息可以放在纹理的红色颜色通道里面,而”温度“信息可以放在纹理的绿色颜色通道里面等等。你只需要确保你的着色器代码对这些颜色进行采样并知道如何使用这些信息。
举个简单的例子来说,在一张4096x4096的纹理里面, 如果使用RGB信息来存储粒子的位置信息,使用透明度通道的信息来存储粒子的大小信息,那么你就可以有最多139,810个粒子并有最长120帧的动画信息。对于只有2048个粒子的小粒子动画系统来说,这个动画的长度可以高达8,192帧。如果一秒钟播放15帧的话,那么这个粒子动画的长度就可以高达9分钟。我们利用了纹理的双线性过滤来在这些属性之间进行插值。
以同样的方法,你可以把刚体物理动画的信息烘焙到纹理中去。不是将每个顶点的位置烘焙成一个像素,其实你可以存储元素的位置和旋转信息在一起,从而在一张小的多的纹理上得到完全相同的结果。你可以使用两张1024x1024的纹理(一张纹理是拥有位置信息,一张纹理是拥有旋转信息)来存储一个由1024块砖组成的砖墙,它们会在1024帧的跨度内塌陷。此外,你还必须存储这些元素的锚点位置,可以存储在UV通道里面也可以存储在顶点颜色中。哦,别忘了,就算顶点着色器里面可以使用纹理了,我们还是可以使用顶点属性的!
有诱惑力的与顶点数无关的变形目标
·三角形网格的优化。序列中的每个三角形网格被优化成更合理的东西。让我们假设三角形的数据大约在3000-10000之间。
·三角形的配对用于尝试这将三角形组合在一起。越多的三角形配对成功,效果越好。因为作为成对的两个三角形只需要定义4个顶点,而两个未配对的三角形将需要定义6个顶点。所以三角形配对成功越多,我们就可以使用纹理上越少的像素来存储信息。
·纹理。会生成两个纹理,一个纹理会包含顶点的位置信息,另外一个纹理会包含顶点的法向量信息。
·三角形网格。三角形网格是由配对成功的三角形和未配对成功的三角形生成的。网格的UV信息被用来在纹理的适当位置进行采样。
·着色器。着色器只需要从纹理中检索顶点的位置信息和法向量信息就可以了。另外它还有一个工作要做,就是要确保三角形网格的UV信息正确的与纹理中的三角形网格信息对应起来,因为插值在纹理中是不起作用的。如果要达到这个目的,我们也可以使用该纹理的最近点采样方法。
作为奖励可以使用这两张纹理的透明度通道来存储一些额外的信息,比如温度、厚度甚至还有两种颜色的比例来制造出两个不同颜色的流体混合的效果。、 它看上去闪闪发光,但它是黄金么?
纹理作为数据容器有一些很酷的优势,但也有一些弊端。让我们来看看第一个优势:
·可访问性。所有的数据可以立刻直接反馈给显卡的图像处理器而不需要受到显卡的图像处理器的干预。通常情况下,这两个系统之间的通信会称为一个瓶颈。顶点着色器可以使用纹理以后,我们就避免了这个问题。
·共享和通用性。纹理的格式非常很规范了,引擎通常有一些工具可以轻松地配置它们。而且它具有一种通用的原生格式可以在多个软件包中进行编辑。
·顺序性。纹理基于空间相干性提供了一个内在的顺序性,我们可以利用这一点。
现在,让我们来看下纹理作为数据容器的弊端
·可调试性。如果有任何东西失败了,那么会非常难以追踪这个问题。我们可以向你保证,你只能提高你的智慧来在渲染管线里面找到问题的所在。(作者的意思是没有任何的调试方法,也没有什么输出信息,只能依赖思考和观察来找到问题的所在。)
·数据的精度问题。这是要处理的纹理的格式带来的数据的精度问题。尽可能的把数据保存在本地。如果是更差的情况下,你可以使用更新的纹理格式用更多的位来保存数据。
·制作流程。为了要将数据转换成颜色,我们需要创建一些自定义的工具。最流行的那些开发套件并没有合适的方式来做这些转换。 它的感觉就像是烟火
现在是时候来讨论下当初激励这我们写这篇文章的想法了。那时候我们在Tequila Works公司为一个虚拟现实的项目设置各种参数,并参考了诺曼之前在顶点着色器方面的工作,我们在讨论使用顶点数量无关的形变来把动画和布料模拟的数据烘焙到纹理里面去。
“我们可以把场景动画的布料动画信息烘焙到纹理里面么?我们可以把令人吃惊的面部动画信息烘焙到纹理里面么?“
“是的!当然可以!但是这样做的话,数据量将是巨大的。”
“所以,我们可以根据移动量来压缩数据么?也许我们可以值存储那些我们不能通过插值的到的数据。“
“这个想法听起来是可行的!”
这大概就是我们当时的想法了。我们已经有了一个脚本来把顶点信息烘焙到一张纹理中去(感谢诺曼之前的工作),我们正在与这些数据进行搏斗(这些纹理数据大概有几G这么多)并且试图对压缩方法进行一些提高,另外脑海中还会闪过汉斯·戈达尔采取的解决蒙皮的想法。。。
当马里奥出来上厕所的时候他想到了一个主意:
马里奥:”如果我们不存储顶点位置,而是把那些骨骼的数据存储到纹理里面呢?“
诺曼:”。。。“
马里奥:” 顶点着色器已经处理了蒙皮,我们只需要在不同的地方存储相同的信息就可以了。“
诺曼:”我觉得这个主意太疯狂了,我们要试试看才知道是否能起作用。。。“
经过几个时间激动和充满肾上腺素的工作以后,我们终于对我们能做什么有了一个比较清晰的图像。而在办公室的所有人都认为那两个家伙疯了,因为我们一直在讨论如何把动画数据放到纹理里面。 我们如同野兽一般的胆量
我们是如何真正做到这一点的?其实非常的简单,我们只需要保存足够蒙皮算法使用的信息,并把蒙皮算法需要的纹理和三角形网格的信息传递给它就好,所以中央处理器根本就不需要传递所有的数据,让我们来具体看下整个的工作过程:
·在哪里存储骨骼的位移信息呢?让我们把这些向量放到一张纹理里面。每个像素将是一个骨骼的位置,每一行对应着具体的一帧,而每一列会对应一根具体的骨骼。
·在哪里存储骨骼的旋转信息呢?这很简单不是么?只需要用另外一张纹理就好了,还是每个像素将是一个骨骼的位置,每一行对应着具体的一帧,而每一列会对应一根具体的骨骼。
·如何存储不同骨骼对每个顶点的权重信息?这是一个棘手的问题。我们是用了两个不同的额外UV通道来解决这个问题的。
·在哪里存储相关骨骼的序号呢?这是另外一个棘手的问题。我们使用的是顶点的颜色信息来存储这个东西,如果需要更多的骨骼的话可以使用另外一个UV通道来解决,因为UV通道有更高的精度。
·还有一个并非无关紧要的问题。在哪里存储这些顶点的最初偏移量呢?可以使用更多的纹理!
让我们把整个处理过程一步步的细化,用顶点着色器中的纹理来提供动画数据:
1. 对于每个顶点,我们需要知道都有哪几根骨骼影响了它。我们会从顶点的颜色信息里面得到这个数据,因为一共有四个颜色(红蓝绿三种颜色还有透明度通道),所以一个顶点最多可以受到4个骨骼的影响。这个数量上的限制可以靠使用更多的纹理来解决。
2. 一旦我们有了影响每个顶点的骨骼信息,以及每个顶点的位置和旋转信息,我们就只缺少每个骨骼对这个顶点最终位移的影响权重信息了。正如我们所提到的那样,这个权重信息会被存储到一张额外的UV通道里面。第一个通道中U坐标存储的是每个骨骼的权重,而索引信息存储在顶点颜色的R通道中等等(顶点颜色的R通道信息对应着第一个UV通道的U坐标,顶点颜色的G 通道信息对应着第一个UV通道的V坐标,顶点颜色的B 通道信息对应着第二个UV通道的U坐标,顶点颜色的A 通道信息对应着第二个UV通道的V坐标这样)。
3. 我们已经有了所有的信息,所以剩下的事情只是使用线性蒙皮算法就可以了。
下面是一个例子,是使用了上面我们描述的方法以后看上去的效果:
通过使用这个技术,我们可以使用两张4096x4096的纹理(一张纹理保存随时间变化的旋转信息,一张纹理保存随时间变化的位移信息)来保存166分钟的人脸动画(一共有56根骨骼),运行速度是每秒30帧。也就是可以一共保存166* 60 *30帧。
如果我们希望保存的是一个让人震惊的那种人脸动画(一共有450根骨骼),那么两张4096x4096的纹理(一张纹理保存随时间变化的旋转信息,一张纹理保存随时间变化的位移信息)就足以保存21分钟的人脸动画了,运行速度还是每秒30帧。
想象一下,把你游戏中的主角所有的动画打包成单独的一对纹理!
在未来可以提高的地方
我们认为可以应用到我们目前的工作的主要改进有以下这些:
解决法向量的问题。通过骨骼的四元数来旋转法向量是重新计算法线的最简单的方法,但是还有其他更加准确也更加有效率的方法来做同样的事情,我们需要去调查并尝试这些方法。
动画混合。之前提出的解决方案只是对影视方面的动画是有用的,但也可以做一些改进以支持动画混合。我们可以把一个角色的所有动画存储到一张纹理中并读取几个位置的数据来混合它们。在实践中采取这个办法出现了一些问题,但我们认为这些问题可以得到妥善解决。(举个简单的例子来说明下动画混合,如果在人物奔跑的同时,在人物奔跑的同时,人物会在奔跑的同时做出其他动作这就是动画混合。猎人中奔跑时开始拉弓时,身体会过渡到拉弓的动作,而不是瞬间变换为拉弓姿势。一般的解决方案是使用带权重的骨骼融合,为每个动作的每根骨骼标识它的权重,即标识每根骨骼在每个动作中的重要性,可以用1.0标识腿部骨骼在奔跑动作中的权重,用0.0标识上半身在奔跑动作中的权重。与另一动作混合时,两个动作骨骼分别计算自身当前帧的骨骼运动数据,然后依据动作权重进行插值混合两者的运动数据。)
支持骨骼的缩放。我们可以通过使用另外一张纹理来控制缩放,这个功能应该很容易实现。
压缩方面的增强。我们存储用于每一帧的信息,但是可以通过一个更加聪明的压缩方法和插值系统来改进这一面点我们的目的是不用再存储用于每一帧的信息了,可以通过插值对一些帧的信息进行计算得来,这样就能减少我们的存储量)。
开发流程的提升。我们可以对开发流程做一定的改进,所有的位移和变换只有到真正打包游戏的时候才会发生。如果能这样的话,艺术家和程序员可以不用管动画是被存储到一张纹理里面的事实。
写在最后的话
我们要感谢很多很多的人,他们在我们的这次写作之旅中给了我们大量的启发和支持,但是我们要特别感谢下汉斯·戈达尔,他的很多思路给了我们很多的启迪,也要特别感谢下Tequila Works的团队,感谢他们忍受我们关于这个主题的各种长时间的对话和对这个主题孜孜不倦、穷根究底的热情。
近期热文:
经验分享丨项目实践项目孵化丨渠道发行做有梦想的游戏人
-GAME AND DREAM-