本文机核版本:https://www.gcores.com/articles/129220

最近我参加了一个GameJam(游戏限时开发挑战)。这个GameJam的一个要求就是复刻老游戏的画面,于是我们选择了复刻PS1时代的画面效果。说到PS1时代,首先人们想到的,当然是数量少的可怜的多边形和低的辣眼睛的分辨率。

但要做到这些都非常的简单,调整一下屏幕分辨率,建模的时候做的粗糙一些。降低一下贴图的分辨率,就可以做到了。我今天主要想讨论的是两个容易被人忽视的,属于那个时代独有的产物。

对比下面两张GIF,你也许就会发现问题了。(请忽略光照的不同,两张图片使用相同的模型,相同贴图和相同的分辨率。唯一的差别就是渲染方式)

我复刻的PS1画面
使用Unity默认材质的效果

那么我们先来看看PS1时代的画面有哪些问题,以及它为什么会有这些问题。

一、模型抖动

仔细观察球体的表面,你就会感觉球体的表面似乎一直在形变。

PS1的CPU,使用的是一颗名为 R3051 的CPU,采用的是一个现在的大部分人都没听过的R3000A架构。这个架构原本支持四个独特的协处理器,也可以理解为支持选装四个功能模块。但是作为需要节省成本的游戏主机,可不能给你上满。所以PS1只选装了两个。

而大法没有装的两个协处理器的其中之一,就是浮点计算加速单元。说人话就是让CPU算带小数的数字算的更快一点的功能模块。而不装这个模块,就导致了PS1要尽可能的减少计算带有小数点的数字,不然计算速度非常的缓慢。而进行模型绘制的时候,对于小数点的计算需求是非常大的,如果可以在绘制模型这一步上把小数点省去,那就可以节省非常多的性能了。

这里可以给大家讲一下,一个模型是被如何被渲染到屏幕上的。也可以帮助大家理解模型抖动的产生。首先,模型里其实只包含了顶点信息,也就是一个一个的多边形的顶点的位置信息,和它们都与哪些顶点相连接的信息。然后游戏需要将模型里的顶点逐个逐个的进行坐标的转换,从一个三维空间的坐标信息转换成二维屏幕上的坐标信息。

而大法偷鸡的地方,就是在这个坐标转换上。坐标转换一共分为五个步骤,我这里就不展开写具体是哪五个步骤了,感兴趣的点这里。PS1在坐标转换的最后一个步骤上,舍去了坐标信息的小数部分。

想象一下,要让一个物体的移动看起来平滑,那么这个物体坐标的精度越高,自然这个物体移动起来也就越平滑,你看到他所处的位置也就越能代表其真正的位置。所以如果你把小数点之后的数字都忽略的话,自然这个物体的移动就变得非常的跳跃,并且没有办法反应其真实位置了。

二、贴图的透视错误

仔细观察球体下的地面,你可以看出地面贴图产生了一定的扭曲。而且这个扭曲会根据你观察地面的角度而变化。

可以通过观察上图发现,一个平面,从斜向的角度观察的话。没有透视矫正的平面上的贴图出现了明显的错误。这是因为贴图被用错误的方式贴在了平面上。

按照我们一般的想法,一个游戏模型的贴图要怎么被贴在模型上,当然应该是由模型本身来决定的。比如,我们做了一个人物模型,人物模型衣服的胸前有一个徽章。那这个徽章就应该在那个位置,它不应该会跑到别的地方去。但事实上,怎么把贴图正确的贴在模型上,是一个略有复杂的数学计算过程。

而PS1在这个数学计算的过程中使用了一个错误的方法。不知是因为机器性能的限制,为了节省那怕一点点的计算量,还是因为技术的限制,没有办法实现正确的贴图位置的计算,又或者是当时没有考虑到。但,问题就这么产生了。

上图中,Eye是我们的眼睛,Near是屏幕,3D line是我们要为其贴上贴图的一个平面。一般来说,我们计算贴图的位置,当然是要根据模型的顶点数据来计算。但在PS1中,他是根据屏幕的位置来计算的。这非常的违反我们的直觉,但是PS1就是这么计算的,这也导致了PS1的贴图会出现扭曲的问题。(具体计算方法请看这里)

开始复刻

知道了产生问题的原理是什么,那我们就好办了。开始在Unity中尝试复刻其效果(文末提供Shader文件的下载)。

首先我们来复刻模型的抖动,我们这里使用一个更加灵活的办法,我们不在坐标转换的最后一步进行去掉小数,而是在将坐标转换到摄像机的这一步(第三步)中进行一个处理。

这便是我们进行转换的核心代码。我们使用Unity的mul功能,把顶点的坐标直接转换成摄像机位置坐标,然后我们做一个非常神奇的操作:我们把顶点坐标乘以一个数之后,把小数部分舍去,然后再把顶点坐标除以我们刚刚乘的数字。

也就是说,如果原来的坐标是123.456,我们将这个数乘以100,变成12345.6;舍去小数,变成12345;再除以一百,变成123.45。这样我们就实现了将部分小数舍去。这要做的好处是什么呢?是我们可以任意的调整模型抖动的程度,我们想保留多少位小数就保留多少位小数。这样我们既可以将特效做的非常夸张比如这样:

也可以让其表现的非常不明显:

当我们把小数剔除之后,不要忘了,我们现在只进行到了第三步。

我们再一次利用mul,将上一步获得的vp转换到屏幕坐标上(第五步)。

然后,我们就开始复原透视错误。

这一步,我们将贴图先利用TRANDORM_TEX转换成和模型匹配的贴图坐标系。然后我们直接将我们上一步得到的sp带入到我们贴图坐标系的计算过程中,这里和PS1的原理就是一样的了。直接利用屏幕来算贴图。

然后我们在片元着色器(fragment)中将这个贴图的xy除去其自身的z,这样我们才能让贴图较为正确的贴在物体上。

那么,至此,我们就成功的复刻出了两个模型的效果了。

但是我们利用的是vertex/fragment 的shader,而不是unity提供的surface。所以我还需要自己编写一些阴影,漫反射和环境光的效果。

将效果加上之后的代码就长这样了。

我们可以在材质面板调节Geometric Resolution来实现不同程度的模型抖动效果。

代码下载: https://github.com/ShenKSPZ/PS1_Graphic

我们在推特@iezons进行每天的进度更新。现在已经进行到了10天。

参考:

冯乐乐. (2016). 2.1.2 什么是渲染流水线. Unity Shader 入门精要 (pp. 9-18). 北京, 中国: 人民邮电出版社.

Gt小新. (2019, 十月 25). 初代PlayStation处理性能解析. Retrieved October 10, 2020, from https://www.bilibili.com/video/BV1VE41117AJ

-兮. (2018, 十一月 30). Unity: Shader Lab语法基础 坐标系转换 顶点片元着色器 语义修饰 Cg. Retrieved October 10, 2020, from https://blog.csdn.net/qq_33373173/article/details/84286717

Wikipedia. (2020, 九月 28). Texture mapping. Retrieved October 10, 2020, from https://en.wikipedia.org/wiki/Texture_mapping

知乎用户. (2016, 二月 26). 3D 图形光栅化的透视校正问题. Retrieved October 10, 2020, from https://www.zhihu.com/question/40624282

Unity Technology. (2020). Vertex and fragment shader examples. Retrieved October 10, 2020, from https://docs.unity3d.com/Manual/SL-VertexFragmentShaderExamples.html

Categories:

Tags:

Comments are closed

作品类型