概述
godot是所有游戏开发引擎中对独立开发者最友好的游戏开发引擎了,相对比于ue5的庞大资源开销,unity的付费机制,cocso逐渐势微。godot开源且轻量,并且拥有非常完善的文档和社区环境,是独立开发者的首选。如果你想一个人做游戏,或几个人做游戏,使用godot没问题。等待戈多,相信godot会越来越好。
不管你使用什么游戏引擎,所有的游戏引擎都离不开计算机图形学(Computer Graphics),而计算机图形学(CG)的基础之一着色器(Shader)是绕不开的话题。
着色器,顾名思义,是将场景渲染到你的屏幕上的一段程序,作为图形渲染流程(渲染管线)的一个重要节点,他将模型数据(点集)处理成图像数据(像素颜色)。
着色器由来
着色器shader是一段简单程序,只有输入和输出,它的运算相对不复杂。
现代大部分的屏幕的分辨率都支持 1920 x 1080 像素,这意味着你的屏幕上有200万个像素点需要渲染,shader决定每个像素点渲染什么颜色。也就是说一帧画面需要运算shader200万次,而如果希望游戏画面顺畅一秒至少渲染 60-120帧,这意味着shader每秒需要运算数亿次。
shader的运算难度对CPU当然不算什么,但CPU是线性运算的,它计算完上一个任务才会进行下一个任务。通常CPU的核心是8-16个,这对于上亿的运算量来说,几乎可以忽略。于是使用CPU运算shader就会十分浪费资源,因为大部分时间都用在等待前一个程序计算完成上了,CPU无法在短时间完成这个任务。
在这种情况下,GPU( graphics processing unit) 应运而生,它的设计初衷只有一个就是用来渲染图像,运算shader这样的图形渲染程序。所以它有上千的核心可以进行并行运算,即使的数亿的运算量也能在几毫秒内完成。
自此以后,所有的shader程序都有了归宿,它们都会投入显卡的怀抱,在GPU上进行运算。从此GPU接过了图形计算渲染的重任,也因此在图形计算分析上有了得天独厚的优势,而在未来它将为AI事业添砖加瓦。
着色器语言(GLSL)(Graphics Library Shader Language)
着色器语言,最特殊的地方是它在GPU上运行,与CPU运行不一样。它是并行运算的,不同的核心之间不能互相交流信息。同一个核心前后并无无记忆。上一个运算是海水的像素,下一个运算可能是天空的像素。它的运算只有基础的输入,然后输出像素颜色,它的运算是各自完全独立的。
这是它不同于CPU中运算的程序的地方,也是其最重要的特性。
基础语法
变量声明
// 整型变量
int count = 10;
// 浮点数变量
float num = 10.0;
// 声明一个布尔值变量
bool lightBool = true;
// 着色器语言定义一个整形常量
const int count = 10;
// 声明函数有返回的按返回的类型声明
void main(){
float x = 10.0;
}
float add(float x,float y){
return x + y
}
if和for 基本和js一致,不做介绍
向量
四维向量可以用于表示位置颜色和纹理
// 四维向量有四个分量,可以用来表示位置的x、y、z、w
vec4 pos = vec4(1.0, 0.0, 0.0, 1.0);
pos.x; pos.y; pos.z; pos.w;
// 四维向量有四个分量,可以用来表示颜色的r、g、b、a
vec4 color = vec4(1.0, 0.0, 0.0, 1.0);//红色不透明
color.r; color.g; color.b; color.a;
// 四维向量有四个分量,可以用来表示纹理坐标的s、t、p、q
vec4 texture = vec4(1.0, 0.0, 0.0, 1.0);
texture.s; texture.t; texture.p; texture.q;
内置变量
gl_PointSize:点渲染像素大小,数据类型浮点数floatgl_Position:顶点坐标,数据类型四维向量vec4gl_FragColor:像素颜色,数据类型四维向量vec4
代码语句结尾要以分号结尾,不可省略,否则报错
渲染管线 (rendering pipeline)
GPU有上千个核心,每个核心都会同时进行图形渲染工作,这个渲染的流程就被称为渲染流水线。 模型数据经过这个流水线后,才变成了屏幕上的一个个像素点。
我们只介绍其中几个重要的流程:
如图,最顶部的数据流入,它由是CPU提供给GPU的顶点缓冲区数据(存储顶点数据,如位置、法线、纹理坐标等)。
随后顶点缓冲区数据被输入顶点着色器,顶点着色器可以处理和改变这些顶点的属性(位置、法线、纹理等)它的主要任务是根据顶点来完成坐标转换、光照计算纹理生成等顶点的相关操作。
随后经过2个步骤,到达光栅化阶段。这一步会将所有的3D几何图形,变成屏幕上的一个个像素网格,以便展示到显示器,但此时这些像素生成了但没有颜色。
到达片段着色器阶段,这个阶段经过运算后会为每个像素分配颜色。这也是我们编写片段着色器能控制颜色的根本原因。
完成像素的颜色渲染后,它就会等待显示到屏幕上了,也就生产出了帧缓冲区(framebuffer)。
如果你想进一步详细了解渲染的流程可以参考youtube上的视频: How do Video Game Graphics
顶点着色器(vertex)
顶点着色器对每个顶点运行一次,我们可以在这个阶段对顶点做各种操作来实现你想要的任何效果。
什么是顶点?
基本所有的3D模型都是由三角形构成的,这是因为GPU用三角形绘制事物时,速度最快。顶点就是三角形的三个点。
索引缓冲区(Element Buffer Object, EBO)包含顶点的索引,指示顶点缓冲区中的哪些顶点应组合成三角形。 索引缓冲区和顶点缓冲区,会作为输入输入顶点着色器。
当然其实你也可以简单的理解为模型在空间中的一点。
片段着色器(fragment)
其实我认为像素着色器可能更适合它,它本质上就是为每个像素进行着色,已经是渲染流程的最后阶段。
在光栅化后,一个个片段才被分割开(基本上一个像素一个片段),片段着色器在每个需要显示像素的片段上运行。有些像素上可能会运行多次(如启用多重采样抗锯齿时,一个像素可能被分割成16个更小的片段)。
你可以编写不同的片段着色器创建不同的视觉效果。
结语
我们对shader做了一个初步的了解和介绍,了解了着色器语言,在图形渲染中的重要作用。也知道它们能决定最终呈现给玩家的视觉效果。
小结
- 因为GPU的并行运算机制,着色器语言和典型的程序有所不同。
- 着色器代码在每个顶点和片段上独立运行。
- 帧与帧之间不能存储数据,着色器是无记忆的不能知道这条渲染线上之前运行了什么,也不知道相邻的渲染线上是否在运行。
- 基于以上几点,不同于传统的编程思维,需要尤其注意。时刻谨记你写的代码将在数千个核心中批量执行。
闲话
以前学习threejs时,短暂学习过webGl,webgl算是openGl的一个简化适配浏览器的版本。那时学习shader感到十分痛苦,后来,匆匆看完。就去直接使用webGl的框架threejs了,现在我连threejs都不想写,而使用了three的框架 react-three/fiber。
结果在学习和使用游戏引擎时,又回到了shader的学习上来。有些东西是躲避不掉的。原来欠下的技术债最终都要还!