零基础使用 WebGL — 快速入门
WebGL 作为一个非常底层的 API,学习和使用起来非常困难,因为 WebGL 需要大量的背景知识。网上教程一般都是介绍API就开始渲染,介绍不多,容易让人迷惑,也容易被劝退。即使你学会了如何使用API,也只是了解了表面知识,却不了解其背后的原理。很容易忘记。
《零基础玩转WebGL》系列教程将从最基础的知识开始,逐步讲解WebGL的使用和WebGL背后的原理,以及必备的数学知识。学习后不仅可以开发WebGL应用,还可以轻松阅读Three.js等渲染引擎的源码,开发自己的3D渲染引擎库。
这是“零基础玩 WebGL”的第一篇文章。请查看系列文章目录: 从零基础开始使用 WebGL — 目录
什么是 WebGL?
WebGL(Web Graphics Library)是一种通过 HTML5 画布元素暴露的 Web 标准 JavaScript API,无需使用插件即可在浏览器中渲染高性能交互式 3D 和 2D 图形。它目前由非营利组织 Khronos Group 设计和维护。
WebGL的使用方式和canvas 2d类似,都是通过 获取上下文
方法来获取渲染上下文,如下所示。
常量画布 = 文档。创建元素(“画布”)
常量 gl = (
帆布。 getContext('webgl2') ||
帆布。 getContext('webgl') ||
帆布。 getContext('实验-webgl')
)
复制代码
上面的代码是根据 webgl2
, 网页浏览器
, 实验-webgl
为了获取 WebGL 渲染上下文。 webgl2
是最新版本,几乎与 WebGL1 完全兼容。 实验-webgl
曾经与旧版浏览器兼容,例如 IE 11。
兼容性
大多数浏览器都支持 WebGL1。支持WebGL2的现代浏览器也有很多,但是Apple还不支持WebGL2,所以在编写WebGL程序时,需要降级到WebGL1。
OpenGL
在深入 WebGL 之前,我们还需要了解 OpenGL,因为 WebGL 是基于 OpenGL 的。 OpenGL (Open Graphics Library) 是一种跨语言、跨平台的应用程序编程接口,用于渲染 2D 和 3D 矢量图形,常用于 CAD、虚拟现实、科学可视化程序和视频游戏开发。 OpenGL通常由显卡厂商根据规范实现。
OpenGL的前身是SGI的IRIS GL API,在当时被认为是最先进的技术,成为事实上的行业标准,后来被SGI转化为开放标准的OpenGL。 1992 年,SGI 创建了 OpenGL 架构审查委员会,并于 2006 年将 OpenGL API 标准的控制权移交给了 Khronos Group。
OpenGL 是跨平台的。在移动设备上,使用 OpenGL ES(用于嵌入式系统的 OpenGL),它是 OpenGL 的子集。下图显示了 OpenGL 和 OpenGL ES 的时间线。
WebGL 基于 OpenGL,OpenGL 的一个子集。 WebGL1 基于 OpenGL ES 2.0。 WebGL2 基于 OpenGL ES 3.0。
图形处理器
WebGL 高性能的原因在于它使用了 GPU。 GPU和CPU针对两种不同的应用场景。你可以把 CPU 想象成一个无所不知的教授,而 GPU 是一群只会做一些简单计算的小学生,所以对于大量的简单计算,GPU 的执行速度要比 CPU 大得多。
上图是显卡3090的配置参数,我们可以看到它拥有10000多核,24G显存。支持 3D API、DirectX 12 Ultimate 和 OpenGL 4.6(DirectX 是微软的图形 API)。
坐标系
WebGL的坐标系和canvas 2d的坐标系不一样。因为 WebGL 是 OpenGL 的子集,所以 WebGL 坐标系与 OpenGL 坐标系具有相同的值。
画布 2d 中的坐标系如下所示。
常量画布 = 文档。创建元素(“画布”)
常量 ctx = 画布。获取上下文('2d')
复制代码
canvas 2d的坐标原点在左上角,X轴和Y轴的正值分别向右和向下。
WebGL的坐标系与OpenGL相同,更符合我们的常识。
原点在中间,右边是X轴正方向,上边是Y轴正方向,就像在数学中一样。
需要注意的是,WebGL中坐标值的范围是 -1
到达 1
, canvas 2d 基于画布的宽度和高度。如果画布宽度是 500
, 然后在 WebGL 1
相当于 500
, 0.5
相当于 250
,这样做的好处是我们不需要关心画布的宽高,不管画布多大,用于渲染图形 -1
到达 1
.
当然,WebGL 中也有 Z 轴。 Z轴有两种形式,一种正值朝外,一种正值朝内。
Z 轴正向朝外时,我们称其为右手坐标系,Z 正轴朝内时,我们称其为左手坐标系。你可以像下图那样伸出双手做手势,你就知道为什么叫左手坐标系和右手坐标系了。
那么 WebGL 是左手坐标系还是右手坐标系?答案是 两者都不 .但在实际开发中使用 右手坐标系 当然,并不是右手坐标系比左手坐标系好,而是右手坐标系是OpenGL的约定。例如,微软的 DirectX 使用左手坐标系。
为什么说WebGL既不是左手坐标系也不是右手坐标系,原因会在后续文章中解释,现在只需要知道WebGL使用右手坐标系,也就是Z 轴的正值朝外。
三角形
WebGL 是一个相对低级的图形 API。与 canvas 2d 不同,WebGL 只能用它来渲染点、线和三角形。那些复杂的 3D 模型实际上是由三角形组成的。
例如,上面的汽车模型实际上是由 267,300 个三角形组成的。
点击此链接查看模型详情 sketchfab.com/3d-models/t… .
有同学可能会问,为什么是三角形,而不是5边、6边的形状?
因为三角形有很多优点,比如三角形必须在一个平面内,所以任何多边形都可以用三角形组成等值线。
渲染一个三角形
有了这么多的背景知识,让我们实际使用 WebGL 来渲染最简单的三角形之一。
常量画布 = 文档。创建元素(“画布”)
帆布。宽度 = 画布。高度 = 300
文档。身体。附加(画布)
常量 gl = 画布。获取上下文('webgl')
冰川视口(0、0、gl.canvas.width、gl.canvas.height)
// 设置 webgl 视口并将 -1 映射到 1 到画布上的坐标
const vertexShader = gl. createShader(gl.VERTEX_SHADER) // 创建一个顶点着色器
冰川shaderSource(vertexShader, ` attribute vec4 a_position; void main() { gl_Position = a_position; // 设置顶点位置} `) // 编写顶点着色器代码
冰川compileShader(vertexShader) // 编译着色器
常量片段着色器 = gl。 createShader(gl. FRAGMENT_SHADER) // 创建片段着色器
冰川shaderSource(fragmentShader, `precision mediump float; uniform vec4 u_color; void main() { gl_FragColor = u_color; // 设置片段颜色} `) // 编写片段着色器代码
冰川compileShader(fragmentShader) // 编译着色器
常量程序 = gl。 createProgram() // 创建一个程序
冰川attachShader(program, vertexShader) // 添加顶点着色器
冰川attachShader(program, fragmentShader) // 添加片段着色器
冰川linkProgram(program) // 连接程序中的着色器
冰川useProgram(program) // 告诉 webgl 使用这个程序进行渲染
常量颜色位置 = gl。 getUniformLocation(program, 'u_color') // 获取 u_color 变量的位置
冰川uniform4f(colorLocation, 0.93, 0, 0.56, 1) // 设置它的值
常量位置位置 = gl。 getAttribLocation(程序,'a_position')
// 获取 a_position 位置
常量位置缓冲区 = gl。创建缓冲区()
// 创建一个顶点缓冲对象,返回它的ID,用来放三角形顶点数据,
冰川bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
// 将此顶点缓冲区对象绑定到 gl.ARRAY_BUFFER
// 后续对 gl.ARRAY_BUFFER 的操作会映射到这个缓冲区
冰川bufferData(gl. ARRAY_BUFFER, new Float32Array([
0, 0.5,
0.5, 0,
- 0.5, - 0.5
]), // 三角形的三个顶点
// 因为数据会发给GPU,为了节省数据解析,这里使用Float32Array直接传输数据
冰川STATIC_DRAW //表示缓冲区内容不经常变化
)
// 将顶点数据添加到新创建的缓冲区对象
冰川enableVertexAttribArray(positionLocation);
// 打开属性变量,以便顶点着色器可以访问缓冲区数据
冰川vertexAttribPointer( // 告诉 OpenGL 如何从 Buffer 中获取数据
positionLocation, // 顶点属性的索引
2, // 组件的数量,必须是 1、2、3 或 4。我们只提供了 x 和 y
冰川FLOAT, // 每个元素的数据类型
false, // 是否归一化到特定范围,对 FLOAT 类型数据无效
0, // stride数组中一行的长度,0表示数据紧密无间隙,让OpenGL决定具体的stride
0 // offset 以字节为单位的偏移量,它必须是类型长度(以字节为单位)的倍数。
)
冰川clearColor( 0, 1, 1, 1) // 清除颜色缓冲区时设置颜色值
冰川clear(gl. COLOR_BUFFER_BIT) // 清除颜色缓冲区,即清除画布
冰川drawArrays( // 从数组中绘制图元
冰川TRIANGLES, // 渲染三角形
0, // 从数组中的哪个点开始渲染
3 // 用多少个点,三角形的三个顶点
)
复制代码
渲染结果如下所示。
可以发现WebGL的代码非常复杂繁琐,一个很简单的三角形就需要写这么多代码。
上面的示例代码中有详细的注释,但是相信大家看完之后还是充满了问号。下面我们来看看WebGL渲染的全过程。一般WebGL程序都是JS提供数据(运行在CPU中),然后将数据发送到显存,交给GPU渲染。我们可以用 着色器 控制 GPU 渲染管道的某些阶段。
// 中央处理器
const vertexShader = `shader source code` // 顶点着色器代码
const fragmentShader = `shader source code` // 片段着色器代码
const points = [{ x: 1, y: 1, z: 1 } /* ... */] // 准备数据
冰川draw(points, vertexShader, fragmentShader) // 将数据和着色器发送到 GPU
// 图形处理器
常量位置 = 数据。 map(point => vertexShader(point)) // 运行顶点着色器
const frags = Rasterization(positions) // 光栅化
常量颜色 = 碎片。 map( frag => fragmentShader(frag)) // 运行片段着色器
Display(colors) // 渲染到屏幕
复制代码
上面的伪代码简单的展示了WebGL程序的执行流程。 OpenGL中的着色器是用GLSL编写的,WebGL中也使用了GLSL着色器语言。它的语法有点类似于 C 语言。我们可以通过顶点着色器和片段着色器来控制 GPU 渲染的某些方面。
WebGL 中的两个着色器是顶点着色器和片段(也称为“片段”)着色器。顶点着色器用于处理图形的每个点,也就是上例中三角形的三个顶点。处理后,将进行光栅化。您可以将光栅化理解为将图形转换为像素。我们的显示屏是由像素组成的。要显示图形,我们需要计算图形中的每个像素。 Fragment shaders首先可以理解为像素着色器,即在光栅化中取每个像素,为每个像素计算一个颜色。整个过程如下图所示。
上图中的顶点数据发送到GPU后,顶点着色器计算每个点的位置,光栅化计算图形的每个像素,片段着色器计算每个像素的颜色,然后就可以渲染为显示器。 . (上图中的geometry shader可以忽略,WebGL中没有这个shader)这里简单介绍Shader,不懂shader也没关系。下一篇文章将更详细地解释。
事实上,WebGL 是一个非常大的状态机,它提供的方法就是改变 WebGL 的某种状态。我们需要在 CPU 中使用 JS 来设置 WebGL 的状态,准备好数据和着色器程序,然后发送到 GPU 执行。
上面的代码可以分为以下几个步骤。
- 由于WebGL的坐标是-1到1,首先我们使用viewport来设置viewport的大小信息。
- 创建顶点和片段着色器(下一篇关于着色器案例的文章),然后创建一个程序来连接顶点和片段着色器。
- 然后在着色器中获取变量,并设置如何将值传递给着色器。三角形由3个顶点组成,所以准备了3个点的坐标。
- 设置清屏颜色和清屏,类似于坐标,在WebGL中 颜色是 0 到 1 , 而不是 0 到 255。
- 将数据发送到 GPU 以渲染三角形
例子
上面这个简单的三角形一点也不酷。事实上,WebGL 可以做出很酷的效果。这里有一些很好的例子。如果你有兴趣,你可以看看。
三JS
WebGL 示例
谷歌实验
成人游泳
埃文·华莱士
总结
这篇文章讲解了什么是WebGL,了解了WebGL的大致轮廓,完成了一个最简单的WebGL应用。下一篇文章将详细解释非常重要的 WebGL 着色器。
如果觉得文章不错,欢迎 喜欢 和 专注于 支持,我会尽快更新系列教程的下一篇。
零基础玩WebGL系列文章目录,请查看: 从零基础开始使用 WebGL — 目录
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议。转载请附上原文出处链接和本声明。
这篇文章的链接: https://homecpp.art/5823/10339/1029
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明