「译」WebGL 系列 01 初识 WebGL 使用
码农天地 -原文地址:Day 1. Intro原文作者:Andrei Lesnitsky
这是 WebGL 系列的第 1 天教程,每天都有新文章发布。
订阅或者加入邮件列表以便及时获取更新内容。
源代码在这里
欢迎来到 WebGL 系列教程的第一天。在本文中,我们将介绍高级渲染概念,这些概念对后面实际使用 WebGL API 来说是很重要的。
WebGL API 通常被视为 3D 渲染 API,这其实是不正确的,那么 WebGL 是做什么的呢?
为了回答这个问题,让我们尝试用平面画布 canvas 2d
渲染某物。
我们先简单准备一个 html
? index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>WebGL Month</title>
</head>
<body></body>
</html>
还有 canvas
画布
? index.html
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>WebGL Month</title>
</head>
- <body></body>
+ <body>
+ <canvas></canvas>
+ </body>
</html>
不要忘记心爱的 JS
? index.html
</head>
<body>
<canvas></canvas>
+ <script src="./src/canvas2d.js"></script>
</body>
</html>
? src/canvas2d.js
console.log('Hello WebGL month');
Let's grab a reference to canvas and get 2d context
接着我们得到对 canvas
的引用,并获取对应的上下文
? src/canvas2d.js
- console.log('Hello WebGL month');+ console.log('Hello WebGL month');
+
+ const canvas = document.querySelector('canvas');
+ const ctx = canvas.getContext('2d');
非常简单的操作,就像画一个黑色的矩形
? src/canvas2d.js
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
+
+ ctx.fillRect(0, 0, 100, 50);
是吧,这很简单对吧?
但是,让我们考虑一下这一行代码的实际干了什么。
它用黑色填充矩形内部的每个像素。
有没有办法不使用 fillRect
就做到这一点呢?
答案是肯定的。
让我们实现自己的版本
? src/canvas2d.js
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
- ctx.fillRect(0, 0, 100, 50);
+ function fillRect(top, left, width, height) {
+
+ }
因此,基本上每个像素都是 4 个整数编码的颜色。R、G、B
通道和 Alpha
。
要存储有关画布的每个像素信息,我们需要一个 Uint8ClampedArray
数组。
该数组的大小为 canvas.width * canvas.height
(像素数)* 4
(每个像素有 4 个通道)。
? src/canvas2d.js
const ctx = canvas.getContext('2d');
function fillRect(top, left, width, height) {
-
+ const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);
}
现在我们可以用颜色填充每个像素存储。请注意,与 CSS 不同,alpha 组件也在范围内
? src/canvas2d.js
function fillRect(top, left, width, height) {
const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);
+
+ for (let i = 0; i < pixelStore.length; i += 4) {
+ pixelStore[i] = 0; // r
+ pixelStore[i + 1] = 0; // g
+ pixelStore[i + 2] = 0; // b
+ pixelStore[i + 3] = 255; // alpha
+ }
}
但是我们如何渲染这个像素呢?有一个特殊的 canvas
能够渲染
? src/canvas2d.js
pixelStore[i + 2] = 0; // b
pixelStore[i + 3] = 255; // alpha
}
+
+ const imageData = new ImageData(pixelStore, canvas.width, canvas.height);
+ ctx.putImageData(imageData, 0, 0);
}
+
+ fillRect();
哇?,我们使用一个手动迭代每个像素的颜色,对画布进行填充,但是我们没有考虑传递参数,需要对其进行修复。
计算矩形内的像素索引
? src/canvas2d.js
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
+ function calculatePixelIndices(top, left, width, height) {
+ const pixelIndices = [];
+
+ for (let x = left; x < left + width; x++) {
+ for (let y = top; y < top + height; y++) {
+ const i =
+ y * canvas.width * 4 + // pixels to skip from top
+ x * 4; // pixels to skip from left
+
+ pixelIndices.push(i);
+ }
+ }
+
+ return pixelIndices;
+ }
+
function fillRect(top, left, width, height) {
const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);
同时遍历这些像素,而不是整个画布。
? src/canvas2d.js
function fillRect(top, left, width, height) {
const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);
+
+ const pixelIndices = calculatePixelIndices(top, left, width, height);
- for (let i = 0; i < pixelStore.length; i += 4) {
+ pixelIndices.forEach((i) => {
pixelStore[i] = 0; // r
pixelStore[i + 1] = 0; // g
pixelStore[i + 2] = 0; // b
pixelStore[i + 3] = 255; // alpha
- }
+ });
const imageData = new ImageData(pixelStore, canvas.width, canvas.height);
ctx.putImageData(imageData, 0, 0);
}
- fillRect();
+ fillRect(10, 10, 100, 50);
酷 ? 我们刚刚重新实现了 fillRect
! 但是,它与 WebGL 有何共同点?
这正是 WebGL API 所做的 – 它计算每个像素的颜色,并用计算出的颜色进行填充。
下一步是什么?
在下一篇文章中,我们将开始使用 WebGL API 并呈现 WebGL “ Hello world”。明天见!
作业
扩展自定义 fillRect
以支持自定义颜色。