问题
一个多月前,当我们再次思考相机时,一个问题不得不先解决:如何避免相机与角色穿插?当环境变的复杂、游戏内容变得复杂时,似乎没有一个令人满意的相机运动规则既能避开角色又能兼顾体验。
于是我们选择了另一条路:让角色靠近相机时变的半透明直到消失。
在 3D 游戏中,“半透明”永远是开发者的痛(因为半透明物体不能直接在 deferred 中完成绘制,以及排序困难)。而角色这样的非凸多面体存在透视问题——比如透过人体表面看到眼球、舌头或另一条手臂,这会严重影响体验的。因此不能通过使用半透明 shader 解决问题。
这样的半透明是不是超刺激?
Ordered Dithering
一种模拟半透明的经典方法是 Screen-Door Transparency,简单来说就是通过在物体上“挖洞”模拟半透明,洞的密度就是透明度。如何挖也有很多不同方式,我们所用到的的称为 Ordered Dithering,这种算法本来是设计用来处理图像的,在模拟半透明上道理相同。这种算法的特点是结果稳定、(模拟的)透明度相同的物体在屏幕空间挖的洞位置也相同。
这是两倍大小看到的 Ordered Dithering 模拟半透明效果,注意不同深度、不同部位挖的洞在屏幕上是规则排列的
原理
在角色 shader 中可以通过 clip / discard 抛弃像素,这会取消像素输出(包括深度,此为一坑),可以用来实现挖洞。然后问题就是,挖的规则是什么?
Ordered Dithering 使用一个矩阵,矩阵每个元素代表不透明度的临界值,当绘制一个像素时,将每个屏幕像素坐标对应到矩阵中一个位置(就是对 x 和 y 分别取余),如果该位置的值大于需要设置的不透明度就抛弃此像素。
现在唯一需要确定的就是矩阵大小和内容了。矩阵大小决定可以控制的不透明度等级数,比如常见的 4x4 矩阵只能产生 17 个级别(除了 16 个半透明级别还有一个完全透明级别),在实际应用中效果不够理想。我最开始想使用 16x16 的矩阵,可惜没有找到。于是写了一个算法用来产生各种级别的 Ordered Dithering 矩阵:
/// /// 创建 Ordered Dithering 矩阵 /// /// size 必须为 2 的整数次幂,至少为 2 public static int[,] CreateOrderedDitheringMatrix(int size) { if (size <= 1 || !Mathf.IsPowerOfTwo(size)) { return null; } int inputSize = 1; int outputSize; int[,] input = new int[,] { { 0 } }; int[,] output; while (true) { outputSize = inputSize * 2; output = new int[outputSize, outputSize]; for (int i = 0; i < inputSize; i++) { for (int j = 0; j < inputSize; j++) { int value = input[i, j] * 4; output[i, j] = value; output[i + inputSize, j + inputSize] = value + 1; output[i + inputSize, j] = value + 2; output[i, j + inputSize] = value + 3; } } if (outputSize >= size) return output; else { inputSize = outputSize; input = output; } } }
如何应用
现在,我们已经可以在物体上挖洞了。一种最直接的应用想法是,用深度来计算不透明度,然后挖洞模拟。然而这种方法不可行,这与半透明透视问题本质是一样的:会看到不该看的东西。我们使用 Ordered Dithering 的目的就是解决此问题。
如果每个独立的物体,它的各部分具有统一的不透明度,那么在 Ordered Dithering 作用后就不会有透视问题。因此,应将一个物体或角色看成整体,根据这个整体与相机的最短距离计算不透明度,然后应用到整体。
使用胶囊体粗略模拟角色外形
比如对于角色,使用胶囊体粗略模拟外形,然后计算整体与相机最短距离,根据最短距离计算不透明度,应用到身体、衣服等部位。这里有个小优化,就是使用 bounding sphere 快速剔除,避免不必要的计算。对于存在大量角色的游戏,这种优化是十分必要的,因为通常最多也就几个角色会同时靠近相机。
根据距离计算不透明度
到这里,基本功能已经完成,但远没有达到我们的要求。如何达到头图的效果呢?下一篇继续。
谢谢您看到这 :)
暂无关于此日志的评论。