OpenGL 是一套规范,不是接口,学习这套规范,就可以在支持 OpenGL 的机器上正常使用这些规范,在显示器上看到绘制的结果。
这套接口是 Khronos 这个组织在维护。怎么维护呢?就是写一个说明书,指导各个 GPU 厂家,如果他们要支持 OpenGL 的话,要怎么实现一个具体的 OpenGL 库。比如 Khronos 说要实现 glDrawArray这 个接口,那么硬件厂家就得在它的库里实现这个接口。如果不实现,那么就不算支持 OpenGL。当然也有一些接口不一定要实现。
厂家实现的 OpenGL 库的内容,其实就是厂家自己的团队整合自己的图形知识以及 GPU 硬件的指令,这些 OpenGL 的实现通常被称为“驱动”,它们负责将 OpenGL 定义的 API 命令翻译为 GPU 指令。因此使用时只需要安装显卡的驱动。
既然是在 GPU 上运行的 OpenGL,那么接下来我们来了解一下 GPU ~
显卡处理器称为图形处理器(即 GPU ),它是显卡的“心脏”,与 CPU 类似,只不过 GPU 是专为执行复杂的数学和几何计算而设计的,这些计算是图形渲染所必需的。一些最快速的 GPU 集成的晶体管数甚至超过了普通 CPU。
现代的 GPU 功能涵盖了图形显示的方方面面,这里只取一个简单的方向作为例子。这个立方体渲染的例子,会有助于理解接下来会讲到的 GLSL(OpenGL着色器) 语言。
大家可能都见过上面这张图,这是老版本 Direct X (是由微软公司创建的一系列专为多媒体以及游戏开发的应用程序接口)的一项测试,就是一个旋转的立方体。显示出一个这样的立方体要经过很多步骤,我们先考虑简单一点的,想象一下他只是一个线框,没有侧面的“X”图像。再简化一点,连线都没有,就是八个点(立方体有八个顶点的)。那么问题就简化成如何让这八个点转起来。
首先,在创造这个立方体的时候,肯定有八个顶点的坐标,坐标都是用向量表示的,因而至少也是个三维向量。然后“旋转”这个变换,在线性代数里面是用一个矩阵来表示的。向量旋转,是用向量乘以这个矩阵。把这八个点转一下,就是进行八次向量与矩阵的乘法而已。
这种计算并不复杂,拆开来看无非就是几次乘积加一起,就是计算量比较大。八个点就要算八次,2000个点就要算2000次。这就是 GPU 工作的一部分,顶点变换,这也是最简单的一部分。
通过这个例子可以先思考一下,想要渲染出一个图形,就需要告诉 GPU 图形的顶点(即坐标向量),如果需要变化(如:平移、旋转、缩放等),就需要告之对应的矩阵,这也就是文章后面要说的 GLSL 语言核心需要做的事情~
CPU 和 GPU 因为最初用来处理的任务就不同,所以设计上有很大的区别。它们分别针对了两种不同的应用场景。
CPU 需要很强的通用性来处理各种不同的数据类型,同时又要逻辑判断又会引入大量的分支跳转和中断的处理。这些都使得 CPU 的内部结构异常复杂。而 GPU 面对的则是类型高度统一的、相互无依赖的大规模数据和不需要被打断的纯净的计算环境。
于是 CPU 和 GPU 就呈现出非常不同的架构(示意图):
其中绿色的是计算单元,橙红色的是存储单元,橙黄色的是控制单元。
GPU 采用了数量众多的计算单元和超长的流水线,但只有非常简单的控制逻辑并省去了 Cache。而 CPU 不仅被 Cache 占据了大量空间,而且还有有复杂的控制逻辑和诸多优化电路,相比之下计算能力只是 CPU 很小的一部分。
而 GPU 的工作大部分就是这样,计算量大,但没什么技术含量,而且需要重复很多次。就像你有个工作需要算几亿次一百以内加减乘除一样,最好的办法就是雇上几十个小学生一起算,一人算一部分,反正这些计算也没什么技术含量,纯粹体力活,人海战术而已。而 CPU 则像老教授,积分微分都会算,一个老教授资顶二十个小学生。GPU 就是这样,用很多简单的计算单元去完成大量的计算任务。不过这种策略基于一个前提,就是每个小学生工作没有什么依赖性,是互相独立的,即 GPU 的计算单元所做的事情是互相独立的。
还有一些任务涉及到步骤的问题,不能把执行顺序颠倒了。这种比较复杂的问题都是 CPU 来做的。
GPU 的运算速度取决于雇了多少小学生,CPU 的运算速度取决于请了多厉害的教授。教授处理复杂任务的能力是碾压小学生的,但是对于没那么复杂的任务,还是人多力量大。不过现在的 GPU 也能做一些稍微复杂的工作,但还是需要 CPU 把数据给 GPU 才能开始干活,因此还是靠 CPU 来管的。
至此为止,GPU 的内容先了解到这里,接下来我们继续回到 OpenGL。
OpenGL ES 与 WebGL 有关,WebGL 是基于 OpenGL ES 2.0 的 Javascript API,因此我们在这里先来了解一下OpenGL ES。
OpenGL ES 是 OpenGL 的子集,专门针对手机/PDA(掌上电脑,如: 条形扫码器,POS机等)/游戏主机等嵌入式设备设计的。OpenGL ES 主要直接提供 C api,各自平台根据习惯提供一层包装(比如Android提供了Java的包装,iOS提供了obj-c的包装)。
虽然 OpenGL ES 是 OpenGL 的子集,但是 OpenGL 与 OpenGL ES 还是有一点区别,比如他们的数据类型会存在一些不一样:
这里可以简单看一些直接使用 OpenGL 实现的滤镜效果
注意:这些直接使用 OpenGL 实现滤镜效果的例子可以了解一下,但是团队项目中使用到的滤镜效果是通过 effect sdk 统一支持(Windows/Mac/Android/iOS),因此如果需要实现 effect creator for web,则需要在浏览器支持 effect sdk 的使用,以保证多端统一,且支持现有功能效果。
至此,除了 GLSL 语言以及具体API,OpenGL 的基础知识就这么多了。OpenGL 是在移动端/桌面端使用,那么在 Web 端呢?就是大家熟悉的 WebGL 了,我们一起来看一下~
WebGL 是一种 3D 绘图标准,这种绘图技术标准把 JavaScript 和 OpenGL ES 2.0 结合在一起,通过 HTML5 的 Canvas 来和 DOM 打交道,为HTML5 Canvas 提供硬件 3D 加速渲染。WebGL 技术标准免去了开发网页专用渲染插件的麻烦,可被用于创建具有复杂 3D 结构的网站页面,甚至可以用来设计 3D 网页游戏等。
通过上述概念可以看出,WebGL 将 JavaScript 和 OpenGL ES 2.0 结合在一起,因此也会使用 GLSL(OpenGL Shader Language) 作为 Shading Language(一种顶点计算和着色的语言,缓存编译到 GPU,由 GPU 来执行)。
说白了,就是通过浏览器提供的接口,我们能够直接和底层的 OpenGL 库打交道。由于能直接调用底层接口,并且有硬件加速,因此 WebGL 要比普通的 Canvas 2D Api 性能要高出不少。这里有一个WebGL 和 Canvas 2D Api 性能的对比实验结果,横坐标是绘制任务数量,纵坐标是页面的 FPS(画面每秒传输帧数)。
从结果中可见,当需要执行大量绘制任务时,WebGL 的性能远远超越了 Canvas 2D Api,达到了后者的3~5倍。
为什么会介绍一下这个库,是因为在学习 WebGL知识时 总会看到一个库:Three.js,那我们这里也来简单的了解一下。Three.js 是一个用于在浏览器中绘制3D图形的JS库,其底层实际是对浏览器提供的 WebGL Api 进行了封装,类似于 JS 与 JQuery 的关系,甚至不需要 WebGL 基础就能够上手使用,但是由于是以 WebGL 为基础,所以遇到问题还得回来查看 WebGL,而 WebGL 的基础又是 OpenGL ES,因此 OpenGL 就显得至关重要了。
OpenGL 很重要,而 OpenGL 还有一个重要部分就是前面多次提到的 GLSL(OpenGL 着色器语言),接下来我们就来看看这个着色器语言究竟是什么吧~~
首先要明白,着色器(Shader)是运行在 GPU 上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序,比如我们要画一个三角形,着色器只是通过读取我们传给它的顶点,颜色,变化等输入,然后经过一系列计算,最终输出图形。
着色器主要分为顶点着色器和片段(像素)着色器,这也是主要的两种着色器,还有一种是几何着色器。每个着色器是非常独立的程序,它们之间不能相互通信,唯一的沟通只能通过输入和输出。通常一个WebGL 应用会有多个着色程序。我们可以根据着色起的名字来思考一下他们的作用。顶点着色器,顾名思义就是为了渲染图形的顶点所使用的,回想一下我们刚才讲的 GPU 的工作,一个立方体的渲染,肯定是先要找到立方体的顶点,这个就是顶点着色器的作用了。顶点找到后,就会连接成线,以及形成平面,那么线段/平面的颜色等就是片段着色器的工作了。
着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。数据类型:
修饰符:
这里只是简单介绍了一下常用概念,关于 GLSL 概念的详解,可以看一下这里
我们在 GPU 的工作一节提到过,坐标都是向量表示,变化(比如:旋转/平移/缩放等)都是通过矩阵表示,回到大学线性代数知识,向量(a1=[x1, y1, z1])与矩阵相乘,就会得到另一个向量(a2=[x2, y2, z2]),a1通过矩阵运算,得到了a2,这样就改变了坐标的位置。看到这里就明白了,如何通过计算得出我们想要的结果,就需要线性代数的知识了。(PS:矩阵真的很神奇,几乎一切变化都从这里来,在最后的例子中带大家来看看矩阵带来的魔法吧)
看完着色器的基本知识后,我们就可以看一下渲染的过程了。
WebGL API 在了解一门新技术前,我们都会先看看它的开发文档或者API。于是,我们查看WebGL绘图API,发现:
是的,它只能画点、线、三角形。就算是像下面这样的复杂模型,也是一个个三角形画出来的。
简单说来,WebGL绘制过程包括以下三步:
接下来,我们分步讲解每个步骤。
顶点坐标从何而来呢?一个立方体还好说,但如果是像上边复杂的茶壶呢?想一下,每个三角形都有三个顶点,而一个茶壶就会有成千上万个顶点,而且还需要精密的计算,显然人的肉眼以及精力是不允许一个一个写这些坐标的。往往它使用三维软件(C4D、MAYA、3DS max等)导出,或者是框架生成。
获取顶点坐标过程图:
前面两个步骤都很好理解,但是第三部写入缓存区是什么意思呢?由于顶点数据往往成千上万,在获取到顶点坐标后,我们通常会将它存储在缓存区内,方便 GPU 更快的读取。
我们已经知道,图元装配就是由顶点生成一个个图元(即点/线/三角形)。那这个过程是自动完成的吗?答案是并非完全如此。WebGL 需要我们先处理顶点,那怎么处理呢?我们先看下图:
第一步就是将上面缓存中的顶点坐标传入了顶点着色器,顶点着色器根据传入的gl.POINTS/gl.LINES/gl.TRIANGLES参数,进行图元装配(通俗一点讲,就是要画点,还是线,还是三角形)
下面是一段顶点着色器代码:
attribute vec4 position;
void main() {
gl_Position = position;
}
这里具体解释一下:attribute 修饰符,是由浏览器(javascript)传输给顶点着色器的变量值修饰符;vec4 就是包含4个元素的浮点型向量(坐标);position 即我们定义的顶点坐标,传入到着色器的;gl_Position 是一个内建的传出变量。
这段代码其实就是 GPU 通过传入的数据找顶点的过程。
和图元装配类似,光栅化也是可控的。
在图元生成完毕之后,我们需要给模型“上色”,模型看起来是什么质地(颜色、漫反射、贴图等)、灯光等,而完成这部分工作的,则是运行在 GPU 的“片元着色器”来完成。如下是一段简单的片元着色器代码:
precision mediump float;
void main(void) {
gl_FragColor = vec4(1.0, 0.5, 1.0, 1.0);
}
precision 是指向一个整数的指针,返回的该整数是对应格式的精度的位数,用 log2 取对数的值,暂不做深究。gl_FragColor 是一个内建的传出变量,即输出的颜色值,这段代码就是紫粉色。
片元着色器具体是如何控制颜色生成的呢?
如上图,顶点着色器是有多少顶点,运行了多少次,而片元着色器则是,有多少片元(像素),运行多少次。
至此,实质上,WebGL经历了如下处理流程(这里我们涉及到的前面没讲到的名词稍微多一点,但是大概涵盖了所有涉及到的内容):
如何传入?
<script id="2d-vertex-shader" type="notjs">
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
</script>
<script id="2d-fragment-shader" type="notjs">
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0.5, 1, 1);}
</script>
function onload() {
...
// 着色器字符串程序
const vertexShaderSource = document.getElementById("2d-vertex-shader").text;
const fragmentShaderSource = document.getElementById("2d-fragment-shader").text;
// start 将字符串传入,创建顶点以及片段着色器
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
// end 创建着色器...}
通过一些小例子,学会使用 WebGL 基础知识
例1:简单的画一个三角形,学会从 WebGL 到着色器的全过程 [可参看这里] 步骤:
例2:通过例1进行简单的修改,一个变色三角形 [可参看这里https://webglfundamentals.org/webgl/lessons/zhcn/webgl-how-it-works.html]
例3:只能画 点/线/三角形 的 WebGL 如何画一个矩形呢?[可参看这里https://webglfundamentals.org/webgl/lessons/zhcn/webgl-fundamentals.html]
例4:绘制一张图片呀,再加一点魔法 [可参看这里https://webglfundamentals.org/webgl/lessons/zhcn/webgl-image-processing.html]
例5:给图片施更多的魔法(矩阵的神奇力量) [可参看这里https://webglfundamentals.org/webgl/lessons/zhcn/webgl-image-processing-continued.html]
例6:二维平移/旋转/缩放(二维矩阵的由来看这里https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-2d-matrices.html) 其实最重要的就是顶点坐标,因为片段着色器只是将顶点按照所需图元连线,因此 平移/旋转/缩放 只需计算出变化后的顶点坐标即可
WebGL 入门篇大概就讲到这里,相信大家对基础已经有了一定的了解,但是 WebGL 还有很多知识,比如 投影/光源/相机/三维呈现 等,有兴趣的可以接下来了解。
OpenGL中文教程OpenGL ESOpenGL书籍:
一个易懂的WebGL教程:WebGLFundamentalsWebGL MDN
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有