HTML5 WebGL:使用 JavaScript 进行 3D 图形渲染入门

HTML5 WebGL:让你的浏览器“画”出新世界

嘿!有没有想过,有一天你能用浏览器“画”出栩栩如生的 3D 模型,让你的网页不再只是呆板的文字和图片,而是充满动感与想象力的数字空间?

这就是 WebGL 的魅力所在。

WebGL (Web Graphics Library) 是一种 JavaScript API,它允许你在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,而无需安装任何插件。换句话说,它赋予了你的浏览器“画”出 3D 世界的能力。

听起来很酷,对吧?但你可能会觉得:“3D 图形?那不是游戏引擎干的事情吗?我一个前端工程师,好像离得很远啊!”

别担心,WebGL 其实并没有你想的那么神秘。它更像是一把强大的画笔,而 JavaScript 就是你手中的画笔刷。 你只需要学习如何使用这把画笔,就能在你的网页上创造出令人惊叹的视觉效果。

为什么要学习 WebGL?

也许你现在正在纠结,我已经掌握了 HTML、CSS 和 JavaScript,为什么还要学习 WebGL 呢?答案很简单:

  • 提升你的前端技能: WebGL 是前端技术栈中一个非常重要的补充,掌握它可以让你在众多前端工程师中脱颖而出。
  • 创造更具吸引力的用户体验: 3D 图形能够带来更沉浸式、更具互动性的用户体验,让你的网站或应用更具吸引力。
  • 拓展你的创作空间: WebGL 不仅仅可以用来创建游戏,还可以用于数据可视化、虚拟现实、增强现实、产品展示等各种领域,为你打开无限的创作空间。
  • 满足你的好奇心: 学习 WebGL 是一个充满乐趣的过程,你会发现 3D 图形的世界比你想象的更加精彩。

WebGL 的基本概念:

在开始编写代码之前,我们需要了解一些 WebGL 的基本概念。这就像学习绘画之前,需要了解画布、颜料和画笔一样。

  • Canvas: WebGL 的画布,所有的 3D 图形都会绘制在这个画布上。你可以把它想象成一块神奇的黑板,WebGL 负责在上面“画画”。
  • WebGL 上下文 (Context): WebGL 上下文是 WebGL API 的接口,通过它可以访问 WebGL 的各种功能。就像画家需要拿着画笔才能在画布上作画一样,我们需要通过 WebGL 上下文才能控制 WebGL。
  • 着色器 (Shader): 着色器是 WebGL 中最核心的概念之一。它们是用 GLSL (OpenGL Shading Language) 编写的小程序,运行在 GPU (Graphics Processing Unit) 上,负责处理图形的顶点和像素。你可以把着色器想象成画家手中的颜料和调色板,它们决定了图形的颜色、光照和纹理。
    • 顶点着色器 (Vertex Shader): 负责处理图形的顶点数据,例如顶点的位置、颜色和法线。它决定了图形的形状和大小。
    • 片元着色器 (Fragment Shader): 负责处理图形的像素数据,例如像素的颜色和光照。它决定了图形的最终外观。
  • 缓冲区 (Buffer): 用于存储顶点数据、颜色数据和纹理数据等。你可以把缓冲区想象成画家存放颜料的容器。
  • 纹理 (Texture): 用于给 3D 模型添加表面细节,例如木纹、砖纹或照片。你可以把纹理想象成贴在模型上的“壁纸”。
  • 矩阵 (Matrix): 用于进行 3D 变换,例如平移、旋转和缩放。你可以把矩阵想象成一个“魔术棒”,它可以改变模型的位置、方向和大小。

你的第一个 WebGL 程序:绘制一个三角形

理论知识说了这么多,不如让我们直接开始动手,编写你的第一个 WebGL 程序:绘制一个简单的三角形。

首先,在你的 HTML 文件中添加一个 <canvas> 元素:

<!DOCTYPE html>
<html>
<head>
    <title>我的第一个 WebGL 程序</title>
    <style>
        body {
            margin: 0;
            overflow: hidden; /* 隐藏滚动条 */
        }
        canvas {
            width: 100%;
            height: 100%;
            display: block; /* 确保 canvas 占据全部空间 */
        }
    </style>
</head>
<body>
    <canvas id="glcanvas"></canvas>
    <script>
        // 这里将放置我们的 JavaScript 代码
    </script>
</body>
</html>

然后,在 <script> 标签中添加以下 JavaScript 代码:

// 获取 canvas 元素
const canvas = document.getElementById('glcanvas');

// 获取 WebGL 上下文
const gl = canvas.getContext('webgl');

// 检查 WebGL 是否可用
if (!gl) {
    alert('你的浏览器不支持 WebGL!');
}

// 顶点着色器代码
const vertexShaderSource = `
    attribute vec4 aVertexPosition;

    void main() {
        gl_Position = aVertexPosition;
    }
`;

// 片元着色器代码
const fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色
    }
`;

// 创建着色器
function createShader(gl, type, source) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert('编译着色器时出错: ' + gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }

    return shader;
}

const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

// 创建着色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);

if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
    alert('链接着色器程序时出错: ' + gl.getProgramInfoLog(shaderProgram));
    gl.deleteProgram(shaderProgram);
    return null;
}

gl.useProgram(shaderProgram);

// 顶点数据
const vertices = [
    0.0,  0.5, 0.0,
    -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0
];

// 创建缓冲区
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

// 获取顶点属性位置
const aVertexPosition = gl.getAttribLocation(shaderProgram, 'aVertexPosition');
gl.enableVertexAttribArray(aVertexPosition);
gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0);

// 设置视口
gl.viewport(0, 0, canvas.width, canvas.height);

// 清除画布
gl.clearColor(0.0, 0.0, 0.0, 1.0); // 黑色背景
gl.clear(gl.COLOR_BUFFER_BIT);

// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);

将以上代码保存为 index.html 文件,然后在浏览器中打开它。如果一切顺利,你将会看到一个红色的三角形出现在你的网页上!

代码解释:

让我们来一行一行地解释这段代码:

  1. 获取 canvas 元素和 WebGL 上下文: 首先,我们获取 HTML 文件中的 <canvas> 元素,然后通过 getContext('webgl') 方法获取 WebGL 上下文。WebGL 上下文是 WebGL API 的接口,通过它可以访问 WebGL 的各种功能。
  2. 检查 WebGL 是否可用: 为了确保代码能够在用户的浏览器上正常运行,我们需要检查 WebGL 是否可用。如果用户的浏览器不支持 WebGL,我们会弹出一个警告框。
  3. 编写着色器代码: 着色器是 WebGL 中最核心的概念之一。它们是用 GLSL (OpenGL Shading Language) 编写的小程序,运行在 GPU 上,负责处理图形的顶点和像素。
    • 顶点着色器: 我们的顶点着色器非常简单,它只是简单地将顶点的位置传递给 gl_Position 变量,这个变量是 WebGL 内置的,用于指定顶点在屏幕上的位置。
    • 片元着色器: 我们的片元着色器也很简单,它只是将每个像素的颜色设置为红色。gl_FragColor 变量也是 WebGL 内置的,用于指定像素的颜色。
  4. 创建着色器: createShader 函数用于创建着色器。它首先创建一个着色器对象,然后将着色器代码传递给它,并编译着色器。如果编译过程中出现错误,我们会弹出一个警告框。
  5. 创建着色器程序: 着色器程序是将顶点着色器和片元着色器组合在一起的对象。我们需要创建一个着色器程序,然后将顶点着色器和片元着色器附加到它上面,并链接着色器程序。如果链接过程中出现错误,我们会弹出一个警告框。
  6. 使用着色器程序: 在绘制图形之前,我们需要使用着色器程序。gl.useProgram(shaderProgram) 方法用于指定当前使用的着色器程序。
  7. 顶点数据: 顶点数据定义了三角形的三个顶点的位置。每个顶点都有三个坐标值:x、y 和 z。
  8. 创建缓冲区: 缓冲区用于存储顶点数据。我们需要创建一个缓冲区对象,然后将顶点数据传递给它。
  9. 获取顶点属性位置: gl.getAttribLocation(shaderProgram, 'aVertexPosition') 方法用于获取顶点着色器中 aVertexPosition 属性的位置。aVertexPosition 属性用于接收顶点数据。
  10. 启用顶点属性数组: gl.enableVertexAttribArray(aVertexPosition) 方法用于启用顶点属性数组。
  11. 指定顶点属性指针: gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0) 方法用于指定顶点属性指针。这个方法告诉 WebGL 如何从缓冲区中读取顶点数据。
  12. 设置视口: gl.viewport(0, 0, canvas.width, canvas.height) 方法用于设置视口。视口是 canvas 中用于显示 WebGL 内容的区域。
  13. 清除画布: 在绘制图形之前,我们需要清除画布。gl.clearColor(0.0, 0.0, 0.0, 1.0) 方法用于设置清除画布的颜色为黑色。gl.clear(gl.COLOR_BUFFER_BIT) 方法用于清除画布。
  14. 绘制三角形: gl.drawArrays(gl.TRIANGLES, 0, 3) 方法用于绘制三角形。gl.TRIANGLES 参数指定绘制三角形的方式。0 参数指定从缓冲区的哪个位置开始读取顶点数据。3 参数指定绘制多少个顶点。

更进一步:让三角形动起来!

现在你已经成功绘制了一个红色的三角形,是不是觉得有点单调?让我们给它加点料,让它动起来!

修改你的 JavaScript 代码,添加以下内容:

// ... 之前的代码 ...

// 定义一个旋转角度
let angle = 0;

// 渲染循环
function render() {
    // 更新旋转角度
    angle += 0.01;

    // 创建一个旋转矩阵
    const rotationMatrix = new Float32Array([
        Math.cos(angle), -Math.sin(angle), 0, 0,
        Math.sin(angle), Math.cos(angle), 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    ]);

    // 获取 uniform 变量的位置
    const uRotationMatrix = gl.getUniformLocation(shaderProgram, 'uRotationMatrix');

    // 将旋转矩阵传递给 uniform 变量
    gl.uniformMatrix4fv(uRotationMatrix, false, rotationMatrix);

    // 清除画布
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // 绘制三角形
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    // 请求下一帧动画
    requestAnimationFrame(render);
}

// 修改顶点着色器
const vertexShaderSource = `
    attribute vec4 aVertexPosition;
    uniform mat4 uRotationMatrix;

    void main() {
        gl_Position = uRotationMatrix * aVertexPosition;
    }
`;

// 启动渲染循环
render();

你需要将新的 vertexShaderSource 替换原来的。

这段代码做了以下修改:

  • 添加了一个 angle 变量: 用于存储旋转角度。
  • 创建了一个 render 函数: 这个函数负责更新旋转角度、创建旋转矩阵、将旋转矩阵传递给顶点着色器、清除画布、绘制三角形和请求下一帧动画。
  • 修改了顶点着色器: 在顶点着色器中添加了一个 uRotationMatrix uniform 变量,用于接收旋转矩阵。然后,我们将顶点的位置乘以旋转矩阵,以实现旋转效果。
  • 使用 requestAnimationFrame 函数: 这个函数用于请求下一帧动画。它会在浏览器准备好重新绘制屏幕时调用 render 函数,从而实现动画效果。

现在刷新你的浏览器,你会看到红色的三角形开始旋转起来了!

结语:

恭喜你!你已经成功地编写了你的第一个 WebGL 程序,并让它动了起来。这只是 WebGL 世界的冰山一角。 WebGL 还有很多强大的功能等待你去探索,例如纹理贴图、光照效果、阴影效果等等。

学习 WebGL 需要耐心和实践,不要害怕犯错,每一次尝试都是一次进步。 希望这篇文章能够帮助你入门 WebGL,并激发你对 3D 图形的热情。

记住,WebGL 不仅仅是一项技术,更是一种创造的工具。 它可以让你将你的想象力变成现实,在你的网页上创造出令人惊叹的数字世界。 拿起你的画笔,开始你的 WebGL 之旅吧! 祝你玩得开心!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注