从零开始实现3D软光栅渲染器 (1) 简介

如何在2D屏幕上表示3D物体?这是学习3D编程必须要搞明白的事情。大家都知道,调用OpenGL的函数,给定三角形的3个顶点位置,颜色,就能在屏幕上画一个三角形,再加载一幅图片,就可以给这个三角形附上纹理,还能让这个三角形绕某个坐标轴发生旋转… 这些看似简单的问题的背后,实则是3D编程的内功。大家都知道,学武之人,拼的是内力,花里胡哨的招式的确很博人眼球,但是从长远来看,收益远没有修炼内力高。

而一个软光栅渲染器几乎涵盖了所有的3D渲染知识,从本篇开始,我们将从零开始实现一个3D软光栅渲染器,学习3D渲染背后的数学原理。

光栅化

所谓光栅化就是将你想画的东西转换到2D屏幕上的像素的过程。这个过程涉及到很多光栅化算法,比如说,画一条直线,大家都知道直线的方程:y = k * x + b (斜截式) 这里的 x , y 的取值范围是全体实数,但是屏幕是像素组成的,你要想在屏幕上显示,你就得使用一些算法将这些实数集映射到相应的像素集(离散化)。

3D流水线

光栅化是OpenGL渲染流水线的一个阶段,而这个阶段是由GPU完成的。一方面,由于现在很多成熟的光栅化算法已经被集成到GPU中,基本不用开发人员手动实现了。另一方面,由于GPU具有很强的并行计算能力,相比在CPU中实现这些算法,图形的渲染会大大提升。

那么什么是渲染管线呢?你可以想象一下iphone的生成车间,先制作地板,然后焊接电路,再安装电池…这就是一条流水线。3D编程中,将物体最终显示到屏幕上也要经历类似的过程。

我们以游戏开发为例,简单介绍一下这个流程:

首先,我们需要一个坐标系来描述场景中各物体的位置,否则你根本无法确定游戏角色、道具等的位置,这个坐标就叫世界坐标。就像它的名字那样,我们可以把它理解成描述我们构建的3D世界的坐标系,它是唯一的,它是固定不变的。

而我们的游戏模型一般都是在3D软件中创建的,一般建模的时候,也需要一个坐标系用来描述各个顶点的位置,这就是局部坐标系。如下图就是blender中的局部坐标系,顺便说一下,它是右手坐标系,红绿蓝三个箭头分别对应x,y,z三个坐标轴。局部坐标系的原点一般是由建模者设置的,比如建一个游戏人物的模型,有的人喜欢把局部坐标系的原点放到模型双脚中心,而有的人喜欢把它放在角色的腰部位置,这都是可以的。

从零开始实现3D软光栅渲染器 (1) 简介

一个游戏场景一般包含很多模型(房屋,角色,道具,树木 etc.),而要将这些分别来自不同建模者的模型绘制到同一个场景(同一个世界坐标系)中来,就需要进行坐标变换。没有找到合适的模型,就拿我珍藏的一张合影来举个例子吧。一图胜前言,不需要解释。

从零开始实现3D软光栅渲染器 (1) 简介

好了,现在模型们已经变换到世界坐标系了。你玩游戏的时候,是不是可以控制人物走动,走到不同的位置,会看到不同的景象?此时,你看到视图,是有一个叫摄像机的东东控制的,又叫虚拟相机。这其实是一个概念,就是为了方便观察世界坐标系中而抽象出来的一个模型(这里的模型是一种概念上的模型)。即使不同摄像机,我们照样也可以观察世界坐标系中各个物体,这个我们在后面介绍。现在你只要知道,有一个叫摄像机东西,我们可以很方便的通过控制它来观察世界坐标系中的物体。此时,我们看到的物体是相对摄像机的位置而言的。比如,你拿手机拍照的时候,虽然你拍的是上海的东方明珠,等你拍下来了,就是你手机上的东方明珠,同一个物体,只是描述的方式不同,这个好好品一下。为什么这么干,我们以后再说。

现实世界是3D的,而计算机屏幕是2D的。我们怎么将3D的世界绘制到2D的屏幕上呢?此时,我们就需要选择一种投影算法,将3D世界中的坐标点投影到2D屏幕上。

3D渲染最注重的就是效率。我们其实只要绘制我们人眼能看见的东西就好了。一个三角形,其实是有2个面的,一般在某一时刻,我们只能看到一个面,所以,另一个面就不需要绘制了。比如,一个精细的人物模型,可能有几千万个三角形构成的,而我们只能看到游戏人物的外表,其内部的三角形面片其实就不用绘制了,因为没人看。

到这里,我们就已经拿到了要进行绘制的顶点数据了。接下来,就是前面说的进行光栅化操作了。也就是说,我给你一堆顶点以及顶点之间的连接关系,你要能够在屏幕上正确显示出来(决定到底屏幕上哪些像素被着色,哪些不被着色)。假设我就绘制一个三角形,给3个顶点,光栅化的过程,就是将这三个顶点之间的连线映射到屏幕上相应的像素,如果给了颜色信息,还要给三角形区域着色。到这里,就能够在屏幕上现实3D图形了。

接下来,就是一些优化操作了,比如各种测试(像素包含测试、裁剪测试、alpha测试、模板测试、深度测试 etc.)、混合操作等,反正目的就是提升性能、优化渲染效果。

这就是大概的3D渲染流水线。我们的软光栅渲染器就是要自己编码实现这一套流程,这样在调用OpenGL函数的时候,你才知道它背后到底发生了什么,当你遇到问题的时候,你才有能力尝试猜测可能什么地方出了问题。

开发环境

最后,说一下开发环境。因为我们是学习3D渲染流水线,这个本身已经够复杂了。为了能把注意力放到软光栅的实现上,我们的开发环境越简单越好,所以,我们选择JavaScript开发一个Web-based软光栅渲染器,开发环境VS code,记事本也可以。

其实,只要你掌握了这一套流程,随便什么语言,只是显示环境不同,核心原理都是相同的。对着我们的教程,你完全可以实现其他语言的软光栅渲染器。

欢迎大家关注我的公众号【OpenGL编程】,定期分享OpenGL相关的3D编程教程、算法、小项目。欢迎大家一起交流。

从零开始实现3D软光栅渲染器 (1) 简介