来掌握3D游戏研发技能吧!



在手机游戏兴起的初期(2012),市场上以轻量、2D游戏为主,我和同期的小伙伴就是在这个时候入行,使用cocos-2dx引擎研发游戏。
至今(2024)手机游戏市场已经成熟,用户对游戏画面表现力的需求变得非常高,3D游戏早已是稀松平常,《原神》的成功,更是把手游画质拉高到主机级别。
但是我们这批伙伴却在转型3D游戏研发的过程中普遍遇到一些困难,导致迟迟迈不过这个门槛,身边的一些年轻2D游戏开发者对于开发3D游戏常常也有敬畏和不知如何入门之感。
反思来看,主要是学习路径的错误,有的伙伴直接去研究OpenGL;我则去研究线性代数,都是深入其中不能自拔。
OpenGL和线性代数对了解3D游戏研发的核心原理非常重要,它们是一切的基础。
但是这俩东西研究很久后,会发现自己仍然不具备3D游戏研发能力,仍然无法实现“在一个3D世界,一个小人在世界中运动”的基本Demo。

初学者和资深专家,其实都应该注重一点,即知识体系的搭建。
就像一棵树,需要先长出树干、树枝才能到树叶和果实。
我和伙伴们犯的错误就是在还没有长出“树干与树枝”的情况下就去追求“果实(图形学,数学)”。

本文的目标是提供一个知识体系,即“树干与树枝”;至于“果实”只会一带而过。相信伙伴们会找到自己培育和获取果实的节奏。
从最抽象上来讲,2D游戏的本质就是图片与对图片的操作。在2D世界中任何事物都是一张图片,地图如此,角色如此;酷炫的特效?它是图片的组合;角色的攻击动作?是基于动画系统没错,但本质仍是对图片的操作;物理系统?本质上物理是数值,根据数值操作图片。
可以看出,在2D世界,所有的一切都是图片,是非常容易理解的,图片在引擎的抽象上就是一个Sprite,一切都是Sprite的组合与对其的操纵。

但是3D世界就不一样了,3D世界是对现实世界的模拟,现实世界有的东西,3D世界一个都不能少。
我们设想在现实世界中,拿一个摄像机拍摄一个小人跑步的照片需要哪些东西。
首先,得有个地面吧,这个就是地图
再者,得有个小人吧,小人站在地上,跑啊跑。

这时候按下快门,咔嚓!得到什么呢?

一片漆黑!为什么呢?
因为没有光,那么来个光吧!

这个时候,咔嚓!按下快门,得到了~

还是黑屏!为什么呢?
相机盖没有打开 !!!∑(゚Д゚ノ)ノ,好的,那么打开它,就得到了下图。

成功啦!成功了?
小人没有跑哦,让他跑起来吧

真·成功啦!
可以看出,在现实世界想要拍摄一张小人运动的照片需要的元素如下
1、摄像机
2、物体(地图,小人)
3、光源
4、运动
在一款游戏引擎(全文以CocosCreator举例)中,现实世界要素的对应如下


所以相对于2D世界只有一个图片(Sprite)这么一个简单的概念,组成3D世界的基本元素有多个,它们的原理和关系也需要先搞懂,这就是研发3D游戏的门槛。
跨过它,剩下的无非也是对3D物体的操控,和2D世界是可以一一对应的,就算是入门3D游戏研发了。

下面会展开讲讲,3D世界是怎么模拟现实世界的。
零、渲染管线,3D世界模拟现实世界的基础

一句话理解,渲染管线是计算机图形学中用于将三维场景转换为二维图像的过程。也就是从一堆3D模型到玩家屏幕上的二维图像的过程。
它由一系列的阶段组成,每个阶段都有特定的功能和任务。其中顶点着色器(几何着色器)和片元着色器(片段着色器)分别处理顶点和像素颜色。可以由程序员控制以实现定制的图形效果。
而着色器(Shader)本质上是一小段操作GPU的脚本语言。

问:
怎么基于渲染管线搞一个3D世界出来?
答:有一个委员会,叫做OpenGL架构评审委员会(ARB),它提供一系列可以实现渲染管线的API规范。
1>这个规范首先面向显卡生产商,由其实现具体的API内容。
2>然后,再面向工具开发者(比如3D游戏引擎的开发商),由其调用API,实现引擎。
3>最后,到我们这批游戏业务开发者,调用游戏引擎使用OpenGL API封装出的新接口就行。
很明显,先学会用引擎,然后再去学习OpenGL,用于深入理解游戏引擎、用好引擎才是正确的学习路径。
为了方便介绍下面的概念,会涉及到一些具体代码,引擎用CocosCreator;OpenGL的部分用其分支WebGL举例,源码来自《WebGL编程指南》,可自行搜索下载。
一、3D模型!->本质是顶点与贴图

从最本质上来讲,模型的基本构成只有一个个“顶点”,通过传入顶点间的“连线顺序”,会得到一个由三角形拼成的3D框架,可以粗略地理解为花灯的骨架。

第二步就是用贴图给它上色,就像花灯的蒙皮,控制骨架和蒙皮创建过程的就是渲染管线的一部分。

在游戏引擎中,对上面的过程再做封装,最核心的是抽象出“材质”这个概念,
一个好的抽象是非常利于学习和传播的,“材质”即是如此,所有引擎都使用和实现材质的概念。
材质将顶点、贴图、着色器整合在一起,如下面CocosCreator引擎中的样子。
具体就不展开讲了,很简单的,实际研发中把美术给到的模型,往场景上一拖,所有的一切就被自动赋值、联系好了。我们要做的多是写操纵其运动的代码,如果团队中没有TA(技术美术),那么也会兼任写点着色器(Shader,CocosCreator中被封装为Effect)



接着我们看一个在WebGL中创建立方体的代码,案例目录是ch07/HelloCube.html

只摘取最有代表性的代码,具体细节读《WebGL编程指南》这本书就好了~
1、要指定立方体每个顶点的坐标,这里把数据直接写死在代码中,实际研发中美术制作的模型文件中会包含所有顶点和顶点之间的连线顺序数据,引擎会读取数据把模型创建出来的。

2、通过顶点数据绘制的“图元”不只有三角形,所以要手动指定一下。

3、在顶点着色器对顶点进行变换,顶点颜色原样传递到片元着色器。

如果需要为模型赋予纹理,只需要在片元着色器中从纹理采样颜色即可。见ch05/TexturedQuad.html


创建物体的核心原理即是如此。
可以看出,引擎把所有的一切都封装了,连着色器都提供了默认的。我们需要关心的也只有根据业务来改改着色器罢了。

二、光!->本质是颜色的混合
现实世界中,物体被光线照亮,会反射一部分光。这个反射的光线就是我们看到的物体颜色。
根据光源和光线方向,物体不同表面的明暗程度变得不一致,这就是物体立体感的来源,即“阴影”。
在图形学中,根据光照条件重建物体“阴影”即为着色,影响着色的因素有:
1>发出光线的光源类型
2>物体表面如何反射光线
在引擎中封装得太好,以至于完全意识不到这一点,只是像现实世界一样在各处摆上光源就行。

CocosCreator引擎封装了平行光、球形光,聚光灯三种类型的灯光,一般由美术去摆放灯光位置。注意不要摆放太多,会很消耗性能。最好使用光照烘焙技术来避免实时的光照计算,具体参考官方文档。
下面看看在WebGL中怎么实现光照,会用到一些线性代数的知识,刚好看下数学的应用场景。
平行光下的漫反射


平行光类似现实世界的太阳光,只有方向,但没有位置信息,所以可以由一个方向和一个颜色定义。
漫反射,其反射光在所有方向都是均匀的,现实世界除了镜子等光滑物体,都是漫反射。
漫反射的计算公式是
漫反射颜色 = 入射光颜色 * 表面颜色 * cosθ
案例见ch08/LightedCube.html

这里有技术含量的也只有求入射角的余弦值。
因为向量点积a·b = (||a|| * ||b||) * cos(θ)
所以cos(θ) = (a·b) / (||a|| * ||b||)
又因为向量a和b都是单位向量,所以 (||a|| * ||b||) = (1 * 1) = 1
所以cos(θ) = (a·b)/1 = (a·b)
也就是代码中的dot(u_LightDirection, normal)
环境光

环境光是模拟真实世界的非直射光,也就是由光源发出后经过墙壁或其他物体反射后的光。
由于可以认为环境光照射物体的方式是各方向均匀,强度相等的,所以反射光也是各向均匀的。

环境光计算公式是
漫反射颜色 = 入射光颜色 * 表面颜色
所以当既有漫反射又有环境光时,公式是
环境光颜色 + 漫反射颜色  = (入射光颜色 * 表面颜色 * cosθ) + (入射光颜色 * 表面颜色)
代码,见ch08/LightedCube_ambient.html

球形光的漫反射


点光源的方向不再是恒定不变的,而是根据每个顶点的位置逐一计算,着色器需要只奥光源自身的位置,然后自行计算出光线方向。
代码,见ch08/PointLightedCube.html

其中有两处应用线性代数的有趣地方:
1>矩阵与向量(顶点)乘法,把顶点从局部坐标系变换到世界坐标系,即所谓的模型矩阵变换。
u_ModelMatrix * a_Position
2>向量减法,其一个几何意义就是求从a点指向b点的向量。
u_LightPosition - vec3(vertexPosition);//得出从顶点指向光源的向量vertexPosition指向u_LightPosition

三、摄像机!->本质是矩阵变换
在图形学中是没有预设摄像机概念的,在引擎有那么好用而直观的摄像机完全是引擎封装的功劳~

太简单了,没啥好讲的。
设想,有一个奇妙世界,这个世界中只有一台摄像机,它被焊死在台子上动弹不得,无法移动也无法旋转。
然后这个世界还有一个刚性需求,就是必须用这台相机拍摄任何物体的任何角度的照片。
怎么解?
把摄像机拆下来?抱歉拆不动,这个奇妙世界的真理之一就是摄像机无法移动分毫。
直到有一天,有一个脑洞大开的小伙伴,把物体拿到摄像机镜头前,将物体摆好姿势,咔嚓,拍照!
这个奇妙的世界就是图形学的世界——“我无法向山走去,便让山向我走来”。

案例见ch07/PerspectiveView_mvpMatrix.html

先看矩阵变换公式:
从摄像机看到的顶点 = 投影矩阵 * 视图矩阵 * 模型矩阵 * 原始顶点
在3D世界中物体很多,物体的每个顶点也都在它自己的局部坐标系中。所以需要先通过模型矩阵将它们都变换到世界坐标系中。

视图矩阵的生成需要

视点、观察目标点、上方向3个信息,如上图,清晰明了。
调用接口生成即可。

这里讨论透视投影,其作用是模拟现实世界“近大远小”的效果。
事实上,透视投影对组成模型的三角形做了两次变换
1>根据三角形与视点的距离,按比例对三角形进行了缩小变换
2>对三角形进行平移变换,使其贴近视线。

矩阵乘法的意义就是“变换的组合”嘛~ 所以所有矩阵以乘法连接在一起。


搞定,摄像机原理~

四、运动控制!->本质是矩阵变换
按照现实世界的直观感受,运动控制就是改变物体的x、y、z坐标而已,缩放指定比例即可,旋转用欧拉角。
在引擎中也是如此,做了非常符合直觉的设计

在图形学中则没那么直观,位移、缩放需要用矩阵;旋转可以用矩阵、欧拉角和四元数,三选一。
案例见ch04/RotatingTranslatedTriangle.html




打完收工~ 附上一份学习路径的建议,如果伙伴有掌握3D游戏研发能力的强烈欲望,则祝愿伙伴成功,欢迎私信与我交流。

一个学习路径的建议
1、拿cocos商店里面的《幽灵射手》和《KylinsEasyController》学习,学的差不多了就爆改它,目的是在实际编码中加深理解。

2、学习图形学和线性代数,不要一下子学得太深入,时读时新即可。
3、循环往复,搭建起自己的知识体系,可以用OneNote工具,也可以写几篇文章沉淀下来。
学习资料
《3D数学基础:图形与游戏开发》《WebGL编程指南》(本文使用的案例来源于此,可以网上搜索下载)
附——
《WebGL编程指南》中的案例需要在本地搭建一个Web服务器才能正确运行,步骤如下
1、安装本地web服务器环境
1>下载安装nodejs
2>在命令行中cd到案例目录
3>执行npm install -g http-server
4>执行npm install --save-dev http-server
5>执行http-server
6>在浏览器访问http://127.0.0.1:8080
7>修改案例代码后,需要使用ctrl + F5 来刷新网页,修改才会生效。

可爱的伙伴,都看到这了~ 右下角点个“赞”和“在看”呗
到顶部