前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >快速入门 WebGL

快速入门 WebGL

作者头像
羽月
发布2022-11-11 18:50:10
2.3K0
发布2022-11-11 18:50:10
举报
文章被收录于专栏:羽月技术羽月技术

WebGL 是 Web 3D 渲染引擎的基础,它作为非常底层的 API,学习上手难度非常大,这是因为 WebGL 要求的背景知识比较多。而网上的教程一般没有过多介绍直接就介绍 API 开始渲染了,容易让人云里雾里,很容易被劝退,就算学到了 API 使用,也是只懂表面知识,没有了解背后的原理,很容易就忘记了。

《从 0 实现 3D 渲染引擎》系列教程将从最基本知识开始,渐进的讲解 WebGL 使用和 WebGL 背后原理还有必不可少的数学知识,真正的从 0 开始,只要了解 JS 就行,不需要要任何图形学或者数学基础。学完之后除了能够自己从 0 实现自己的 3D 渲染引擎还能熟悉 three.js 的源码,因为本系列文章实现的 3D 渲染引擎和 three.js 很相似。

什么是 WebGL?

WebGL(Web Graphics Library)是一个 Web 标准 JavaScript API,通过 HTML5 的 canvas 元素进行暴露,无需使用插件,即可在浏览器中渲染高性能的交互式 3D 和 2D 图形。目前是由非营利 Khronos Group 设计和维护。

使用 WebGL 的方式和 canvas 2d 类似,都是通过 getContext 方法获取渲染上下文,如下所示。

代码语言:javascript
复制
const canvas = document.createElement('canvas')const gl = (
  canvas.getContext('webgl2') ||
  canvas.getContext('webgl') ||
  canvas.getContext('experimental-webgl')
)

上面代码中是按照 webgl2webglexperimental-webgl 的顺序获取 WebGL 渲染上下文。webgl2 是最新版本,它几乎完全兼容 WebGL1。experimental-webgl 用来兼容老浏览器,如 IE 11。

兼容性

大多数浏览器都支持 WebGL1。也有很多现代浏览器支持 WebGL2,但是苹果还不支持 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 for Embedded Systems), 它是 OpenGL 的子集。下图展示了 OpenGL 和 OpenGL ES 的时间线。

WebGL 基于 OpenGL,是 OpenGL 的子集。WebGL1 基于 OpenGL ES 2.0。WebGL2 基于 OpenGL ES 3.0。

GPU

WebGL 性能高的原因是它使用到了 GPU。GPU 和 CPU 针对的是两种不同的应用场景,大家可以把 CPU 想象为一个切图专家,而 GPU 是一群初级切图仔,现在有一大堆非常简单的页面,大街上随便抓个人都能切。那么对于这个任务不用想就知道一群初级切图仔更快,切图专家当然厉害,但是也奈不了对面人多。所以对于大量简单计算 GPU 的执行速度是远大于 CPU 的。

上面图片中,第一个是 CPU,第二是 GPU,CPU 只有一杆枪,而 GPU 有一大捆枪。CPU 要一下一下的打,就像切图专家一个一个的切,而 GPU 一次性全打了,就像一群初级切图仔,没人切一个,一次性全切完了。

上图是显卡 3090 的配置参数,我们可以看到它有 1 万多个核心,24G 显存。支持 3D API,DirectX 12 Ultimate 和 OpenGL 4.6 (DirectX 是微软的图形 API)。

坐标系

WebGL 的坐标系和 canvas 2d 中的是不太一样的。因为 WebGL 是 OpenGL 子集,所以 WebGL 坐标系和 OpenGL 坐标系性值一样。

canvas 2d 中的坐标系如下所示。

代码语言:javascript
复制
const canvas = document.createElement('canvas')const ctx = canvas.getContext('2d')

canvas 2d 的坐标原点在左上角,X 轴和 Y 轴的正值分别向右和向下。

而 WebGL 的坐标系和 OpenGL 一样,它更符合我们的常识一点。

原点在正中间,右边为 X 轴正方向,上面为 Y 轴正方向,就和数学中的一样。

需要注意的是 WebGL 中坐标值的范围是 -11,而 canvas 2d 是根据 canvas 的宽高大小来的。如果 canvas 宽度为 500,那么 WebGL 中的 1 就相当于 5000.5 就相当于 250,这样的好处是我们无需关心 canvas 的宽高,无论 canvas 多大对于渲染图形来说范围都是 -11

当然 WebGL 中还有一个 Z 轴。Z 轴有两种形式,一种是正值朝外,另一种是正值朝内。

当 Z 轴正值朝外时,我们称为右手坐标系,当 Z 轴正值朝内时称为左手坐标系。可以伸出双手像下图一样比划下,就知道为什么称为左手坐标系和右手坐标系了。

那么 WebGL 是左手坐标系还是右手坐标系呢?答案为都不是。但是在实际开发中是使用 右手坐标系,当然并不是右手坐标系比左手坐标系好,而是右手坐标系是 OpenGL 的惯例。例如微软的 DirectX 中惯用的是左手坐标系。

这里为什么说 WebGL 既不是左手坐标系也不是右手坐标系,原因将在后续文章中讲解,现在只用知道 WebGL 中使用的是右手坐标系,也就是 Z 轴正值朝外。

三角形

WebGL 算是比较底层的图形 API,不同于 canvas 2d,WebGL 只能用它来渲染点,线和三角形。那些复杂的 3D 模型其实都是由一个个三角形组成。

比如上方这辆汽车模型,它其实是由 267300 个三角形组成。

点击这个链接查看模型详情https://sketchfab.com/3d-models/the-argonaut-4982efe9a03e42e6a867c33afd863ca5

可能有同学会问了,为什么就是三角形,而不是 5 边形,6 边形呢?

因为三角形有很多的优势,比如三角形一定在一个平面上,任何多边形都可以使用三角形组成等性值。

渲染一个三角形

了解了这么多背景知识,现在让我们来实际使用 WebGL 来渲染一个最简单的三角形吧。

代码语言:javascript
复制
const canvas = document.createElement('canvas')
canvas.width = canvas.height = 300document.body.append(canvas)const gl = canvas.getContext('webgl')

gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)// 设置 webgl 视口,将 -1 到 1 映射为 canvas 上的坐标const vertexShader = gl.createShader(gl.VERTEX_SHADER) // 创建一个顶点着色器gl.shaderSource(vertexShader, `
  attribute vec4 a_position;

  void main() {
    gl_Position = a_position; // 设置顶点位置
  }
`) // 编写顶点着色器代码gl.compileShader(vertexShader) // 编译着色器const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) // 创建一个片元着色器gl.shaderSource(fragmentShader, `
  precision mediump float;
  uniform vec4 u_color;

  void main() {
    gl_FragColor = u_color; // 设置片元颜色
  }
`) // 编写片元着色器代码gl.compileShader(fragmentShader) // 编译着色器const program = gl.createProgram() // 创建一个程序gl.attachShader(program, vertexShader) // 添加顶点着色器gl.attachShader(program, fragmentShader) // 添加片元着色器gl.linkProgram(program) // 连接 program 中的着色器gl.useProgram(program) // 告诉 webgl 用这个 program 进行渲染const colorLocation = gl.getUniformLocation(program, 'u_color') // 获取 u_color 变量位置gl.uniform4f(colorLocation, 0.93, 0, 0.56, 1) // 设置它的值const positionLocation = gl.getAttribLocation(program, 'a_position') 
// 获取 a_position 位置const positionBuffer = gl.createBuffer() 
// 创建一个顶点缓冲对象,返回其 ID,用来放三角形顶点数据,gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer) 
// 将这个顶点缓冲对象绑定到 gl.ARRAY_BUFFER// 后续对 gl.ARRAY_BUFFER 的操作都会映射到这个缓存gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([    0, 0.5,    0.5, 0,
    -0.5, -0.5]),  // 三角形的三个顶点
     // 因为会将数据发送到 GPU,为了省去数据解析,这里使用 Float32Array 直接传送数据gl.STATIC_DRAW // 表示缓冲区的内容不会经常更改)// 将顶点数据加入的刚刚创建的缓存对象gl.enableVertexAttribArray(positionLocation);// 开启 attribute 变量,使顶点着色器能够访问缓冲区数据gl.vertexAttribPointer( // 告诉 OpenGL 如何从 Buffer 中获取数据
    positionLocation, // 顶点属性的索引
    2, // 组成数量,必须是1,2,3或4。我们只提供了 x 和 y
    gl.FLOAT, // 每个元素的数据类型
    false, // 是否归一化到特定的范围,对 FLOAT 类型数据设置无效
    0, // stride 步长 数组中一行长度,0 表示数据是紧密的没有空隙,让OpenGL决定具体步长
    0 // offset 字节偏移量,必须是类型的字节长度的倍数。)

gl.clearColor(0, 1, 1, 1) // 设置清空颜色缓冲时的颜色值gl.clear(gl.COLOR_BUFFER_BIT) // 清空颜色缓冲区,也就是清空画布gl.drawArrays( // 从数组中绘制图元
    gl.TRIANGLES, // 渲染三角形
    0,  // 从数组中哪个点开始渲染
    3   // 需要用到多少个点,三角形的三个顶点)

渲染结果如下所示。

可以发现 WebGL 的代码非常复杂繁琐,一个非常简单的三角形就需要编写这么多的代码。

上面实例代码中有详细的注释,不过相信大家看了也还是满头问号。我们再来看看 WebGL 渲染的整个流程,一般 WebGL 程序是 JS 提供数据(在 CPU 中运行),然后将数据发送到显存中,交给 GPU 渲染,我们可以使用着色器控制 GPU 渲染管线部分阶段。

代码语言:javascript
复制
// CPUconst vertexShader = `shader source code` // 顶点着色器代码const fragmentShader = `shader source code` // 片段着色器代码const points = [{ x: 1, y: 1, z: 1 }/* ... */]  // 准备数据gl.draw(points, vertexShader, fragmentShader) // 将数据和着色器发送给 GPU// GPUconst positions = data.map(point => vertexShader(point)) // 运行顶点着色器const frags = Rasterization(positions) // 光栅化const colors = frags.map(frag => fragmentShader(frag)) // 运行片段着色器Display(colors) // 渲染到屏幕

上面的伪代码,简单展示了 WebGL 程序的执行流程。OpenGL 中着色器是使用 GLSL 编写,WebGL 中也是使用的 GLSL 着色器语言,它的语法有点类似 C 语言,我们可以通过顶点着色器和片段着色器控制 GPU 渲染的部分环节。

WebGL 中有两个着色器分别是顶点着色器和片段(也可称为“片元”)着色器。顶点着色器用于处理图形的每个点,也就是上面例子中三角形的三个顶点。处理完毕后会进行光栅化,大家可以把光栅化理解成把图形变成一个个像素,我们显示器屏幕是一个个像素组成的,要显示图形就需要计算出图形中的每个像素点。片段着色器可以先理解成像素着色器,也就是将光栅化中的每个像素拿过来,给每个像素计算一个颜色。整个流程如下所示。

上图中顶点数据传送给 GPU 后,顶点着色器计算出每个点的位置,光栅化计算出图形的每个像素,片段着色器计算出每个像素的颜色,然后就可以渲染到显示器上了。(可以忽略上图的几何着色器,WebGL 中没有这个着色器)着色器先简单介绍到这里,还不了解着色器也没有关系,下篇文章会更加详细的讲解。

其实 WebGL 是一个非常大的状态机,它提供的方法都是改变 WebGL 的某个状态。我们需要在 CPU 中使用 JS 设置 WebGL 的状态,准备数据和着色器程序,然后发送给 GPU 执行。

上方代码可以分为如下几步。

  1. 因为 WebGL 的坐标是 -1 到 1,所以首先我们使用 viewport 设置视口大小信息。
  2. 创建顶点和片段着色器(关于着色器情况下篇文章),然后创建一个程序,来连接顶点和片段着色器。
  3. 然后获取着色器中的变量,设置如何将值传递给着色器。三角形是由 3 个顶点组成,所以准备了 3 个点的坐标。
  4. 设置清屏颜色,并清屏,和坐标类似,WebGL 中的颜色是 0 到 1,而不是 0 到 255。
  5. 将数据发送给 GPU 来渲染三角形

例子

上面这个简单的三角形一点都不炫酷,其实 WebGL 可以做出非常炫酷的效果,下面列举一些不错例子,大家感兴趣可以看一看。

ThreeJS

https://threejs.org/

WebGL Samples

http://webglsamples.org/

Experiments with Google

https://experiments.withgoogle.com/

Adult Swim

https://www.adultswim.com/etcetera/

Evan Wallace

http://madebyevan.com/

总结

这篇文章讲解了什么是 WebGL,了解了 WebGL 的大致轮廓,并且完成了一个最简单的 WebGL 应用。

如果觉得不错欢迎点赞转发分享。

系列文章目录:《从 0 实现 3D 渲染引擎》

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-10-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 羽月技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是 WebGL?
  • 兼容性
  • OpenGL
  • GPU
  • 坐标系
  • 三角形
  • 渲染一个三角形
  • 例子
    • ThreeJS
      • WebGL Samples
        • Experiments with Google
          • Adult Swim
            • Evan Wallace
            • 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档