疯狂的技术宅

以前出于工作目的,编写和翻译了大量的技术文章,以前端为主,删掉了过时的、毫无营养的内容,留下的都是精华。


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于本站

  • 回到主站

  • 搜索

学习WebGL的第一步

时间: 2020-09-07 分类: 前端技术   字数: 2218 字 阅读: 5分钟
标签: #WebGL# #图形学#
  • 本文译自:https://aralroca.com/blog/first-steps-in-webgl
  • 译者:疯狂的技术宅

在本文中,我们将了解什么是 WebGL,以及如何通过与 GPU 进行对话来绘制“三角形”。尽管有更好的方法来实现本文中的例子,例如用具有 2d 上下文的 canvas 甚至可以用 CSS,但我们要从 WebGL 开始。就像 “hello world” 一样,了解它是如何工作的。

Triangle

什么是WebGL?

WebGL 的字面定义是 Web Graphics Library(Web图形库)。但这并不意味着我们可以通过 3D 库提供的一个好用的 API 去指挥电脑:“在这里放个茶杯,在这里放摄像机,在这里画一个字符等”。

它属于低级 API,可以将 vertices(顶点)转换为pixels(像素)。你可以把 WebGL 理解为栅格化引擎。它基于 OpenGL ES 3.0 图形 API。

WebGL架构

网上现有的 3d 库(例如THREE.js 或 Babylon.js )使用图中最下面的 WebGL。他们需要一种能够与 GPU 通讯来告知绘制内容的方法。

当然也可以通过 THREE.js 的 THREE.Triangle 直接解决。但是本文的目的是了解其在内部的工作方式,即这些 3d 库时如何通过 WebGL 与 GPU 通信的。我们要在没有任何 3D 库的帮助下渲染出一个三角形。

如何在没有3D库的情况下做三角形

创建 WebGL 画布

在绘制三角形之前,需要先定义 WebGL 渲染的三角形的区域。

我们将使用 HTML5 的元素 canvas,将上下文设置为 webgl2。

import { useRef, useEffect } from 'preact/hooks'

export default function Triangle() {
  const canvas = useRef()

  useEffect(() => {
    const bgColor = [0.47, 0.7, 0.78, 1] // r,g,b,a as 0-1
    const gl = canvas.current.getContext('webgl2') // WebGL 2.0

    gl.clearColor(bgColor) // 设置 canvas 的背景颜色
    gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT) // clear buffers
    // @todo: 渲染三角形...
  }, [])

  return <canvas style={{ width: '100vw', height: '100vh' }} ref={canvas} />
}

clearColor 方法用 RGBA(取值范围:0 到 1)设置画布的背景色。

clear 方法用来把缓冲区初始化为预设值。其对应的常数值将取决于你的 GPU。

创建 canvas 后,就可以用 WebGL 渲染三角形了。

顶点坐标

首先要知道我们所使用的这些向量的取值范围是 -1 至 1。

画布的坐标范围:

坐标

  • (0,0):中心
  • (1,1):右上
  • (1,-1):右下
  • (-1,1):左上方
  • (-1,-1):左下

要绘制的三角形的三个顶点为**(-1,-1),(0,1)和(1,-1)**。把三角的顶点坐标存储到数组中::

三角形顶点坐标

const coordinates = [-1, -1, 0, 1, 1, -1]

GLSL 和着色器

着色器是计算机图形学中的一种程序,可以高度灵活地计算渲染效果。这些着色器用类似于 C 或 C++ 的 OpenGL ES 着色语言(GLSL ES)编写,并在GPU上编码并运行。

WebGL 着色器

每个 WebGL 程序都由两个着色器函数组成; 顶点着色器(vertex shader)和片段着色器(fragment shader)。几乎所有的 WebGL API 都以不同的方式运行这两个函数。

顶点着色器

顶点着色器的工作是计算顶点的位置。有了这个结果(gl_Position)GPU 就在视口上定位了点、线和三角形。所以要先创建这个顶点着色器:

const vertexShader = `#version 300 es
  precision mediump float;
  in vec2 position;

  void main () {
      gl_Position = vec4(position.x, position.y, 0.0, 1.0); // x,y,z,w
  }
`

可以将其作为模板字符串保存在我们的 JavaScript 代码中。

第一行( #version 300 es)说明了正在使用的 GLSL 版本。

第二行( precision mediump float;)确定 GPU 用于计算浮点数的精度。可用的选项有 highp, mediump 和 lowp),但是某些系统不支持 highp。

在第三行(in vec2 position;)为 GPU 定义了两个二维 (X,Y) 的输入变量。三角形的每个向量都是二维的。

初始化后在程序启动时调用 main 函数(和 C/C++ 语言一样)。 GPU 将通过将当前顶点的位置保存到 gl_Position 来运行其内容(gl_Position = vec4(position.x, position.y, 0.0, 1.0);)。第一个和第二个参数是 vec2 的位置 x 和 y。第三个参数是 z 轴,在本例中为 0.0,因为我们是在2D而非3D中创建几何体。最后一个参数是 w,默认情况下应将其设置为 1.0。

GLSL 识别并在内部使用 gl_Position 的值。

创建着色器后,应对其进行编译:

const vs = gl.createShader(gl.VERTEX_SHADER)

gl.shaderSource(vs, vertexShader)
gl.compileShader(vs)

// Catch some possible errors on vertex shader
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
  console.error(gl.getShaderInfoLog(vs))
}

片段着色器

“片段着色器”在“顶点着色器”之后执行。着色器的工作是计算与每个位置对应的像素点的颜色。

我们用相同的颜色填充这个三角形:

const fragmentShader = `#version 300 es
  precision mediump float;
  out vec4 color;

  void main () {
      color = vec4(0.7, 0.89, 0.98, 1.0); // r,g,b,a
  }
`
const fs = gl.createShader(gl.FRAGMENT_SHADER)

gl.shaderSource(fs, fragmentShader)
gl.compileShader(fs)

// 在片段着色器上捕获一些可能出现的错误
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
  console.error(gl.getShaderInfoLog(fs))
}

尽管在这里返回的 vect4 是指每个像素的颜色,但其语法与上一个非常相似。由于要用 rgba(179, 229, 252, 1) 填充三角形,所以要把每个 RGB 数字除以 255。

从着色器创建程序

编译好着色器后,需要创建要运行 GPU 的程序,同时添加两个着色器。

const program = gl.createProgram()
gl.attachShader(program, vs) // 附加顶点着色器
gl.attachShader(program, fs) // 附加片段着色器
gl.linkProgram(program) // 将两个着色器链接在一起
gl.useProgram(program) // 使用创建的程序

// 捕获在程序中可能出现的错误
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  console.error(gl.getProgramInfoLog(program))
}

创建缓冲区

我们将使用缓冲区把内存分配给 GPU,并把内存绑定到用于 CPU-GPU 通信的通道。用此通道将三角形坐标发送到GPU。

// 为gpu分配内存
const buffer = gl.createBuffer()

// 将此内存绑定到通道
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

// 使用此通道将数据发送到GPU(三角形坐标)
gl.bufferData(
  gl.ARRAY_BUFFER,
  new Float32Array(coordinates),
  // 在例子中是一个静态三角形,
  // 所以最好说明我们要如何使用数据,
  // 以便 WebGL 可以优化某些内容。
  gl.STATIC_DRAW
)

// 发送数据后释放内存,避免内存泄漏问题
gl.bindBuffer(gl.ARRAY_BUFFER, null)

buffer

把数据从 CPU 链接到 GPU

在我们的顶点着色器 中定义了一个名为 position 的输入变量。但是尚未指定此变量应采用我们要通过缓冲区的值。必须通过以下方式表明它:

const position = gl.getAttribLocation(program, 'position')
gl.enableVertexAttribArray(position)
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.vertexAttribPointer(
  position, //顶点属性的位置
  2, // 维度 - 2D
  gl.FLOAT, // 要发送到 GPU 的数据类型
  gl.FALSE, // 是否应将数据标准化
  0, // 步长
  0 // 偏移量
)

绘制三角形

一旦为三角形创建了带有着色器的程序,并创建了将数据从CPU发送到GPU的链接缓冲区之后,最后就可以告诉 GPU 渲染三角形了。

绘制出的三角形

gl.drawArrays(
  gl.TRIANGLES, // 基本类型
  0, // 向量点数组中的起始索引
  3 // 要渲染的顶点数
)

这个方法用来从数组数据渲染图元。图元可以是点、线或三角形。在这里指定 gl.TRIANGLES。

结论

用 WebGL 只能绘制三角形、直线或点,因为它只能栅格化,所以只能进行向量可以执行的操作。这意味着 WebGL 从概念上讲是简单的,而过程却相当复杂,而且根据你要开发的内容会变得越来越复杂。光栅化 2D 三角形并不像在 3D 游戏中使用纹理、变化,变换那样。

标签: #WebGL# #图形学#

标题:学习WebGL的第一步

链接:https://fe-tech.viewnode.com/post/202009/07/

作者:疯狂的技术宅

声明: 本博客文章除特别声明外,均采用 CC BY-NC-ND 4.0 国际许可协议( 知识共享署名-非商业性使用-禁止演绎 4.0),转载请注明出处!

用 WebGL 做一个齿轮动画
Async/Await有什么用?
  • 文章目录
  • 站点概览
疯狂的技术宅

疯狂的技术宅

退休程序员,硬件发烧友,人工智能爱好者。写写代码喝喝茶,晒晒太阳带带娃。

457 日志
8 分类
583 标签
GitHub
友情链接
  • viewnode
  • mofish
标签云
  • Javascript 172
  • Node.Js 62
  • Vue 36
  • Typescript 28
  • 实战项目 28
  • 面试 21
  • React 20
  • Css 17
  • 面试题 16
  • 教程 13
  • Promise 12
  • Chrome 9
  • Debug 9
  • 调试 9
  • 资源 9
  • Deno 8
  • Dom 8
  • 杂谈 8
  • 正则表达式 8
  • 测试 8
  • 什么是WebGL?
  • 创建 WebGL 画布
  • 顶点坐标
  • GLSL 和着色器
    • 顶点着色器
    • 片段着色器
  • 从着色器创建程序
  • 创建缓冲区
  • 把数据从 CPU 链接到 GPU
  • 绘制三角形
  • 结论
© 2018 - 2022 疯狂的技术宅 All Rights Reserved
Powered by - Hugo v0.99.0 / Theme by - NexT
Storage by 俺的服务器 / 冀ICP备2022010157号
0%