自己封装一套 WebGL 渲染引擎

560 阅读3分钟

封装一套 WebGL 渲染引擎是一个既有挑战又充满乐趣的项目。下面将为你提供一个基础教程,介绍如何从头开始构建一个简单的 WebGL 渲染引擎。这个引擎将实现基本的图形渲染功能,并逐步扩展功能,最终支持三维模型、材质和光照等内容。

GitHub 地址:github.com/FrankWangMi…

跪求 Star 哈哈哈

image.png

1. 环境准备

确保你有一个支持 WebGL 的浏览器(例如 Google Chrome 或 Firefox),并且你已经具备 HTML 和 JavaScript 的基础知识。你可以使用任何文本编辑器来编写代码,比如 VS Code。

2. 创建 HTML 文件

首先,创建一个基础的 HTML 文件来包含我们的 WebGL 渲染引擎:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebGL 渲染引擎</title>
    <style>
        body, html {
            margin: 0;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        canvas {
            border: 1px solid black;
        }
    </style>
</head>
<body>
    <canvas id="webgl-canvas"></canvas>
    <script src="engine.js"></script>
</body>
</html>

在这个文件中,我们创建了一个 canvas 元素,它将用于渲染 WebGL 内容。接下来,我们将在 engine.js 中编写 WebGL 渲染引擎的代码。

3. 设置 WebGL 环境

接下来,我们将初始化 WebGL 上下文,并设置一些基本的渲染配置。

// engine.js
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');

if (!gl) {
    alert('WebGL 未被支持!');
}

// 设置画布大小
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// 清除屏幕,设置背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0); // 黑色背景
gl.clear(gl.COLOR_BUFFER_BIT);

在这段代码中,我们首先通过 getContext('webgl') 获取 WebGL 上下文,然后设置画布的尺寸,最后使用 clearColorclear 函数清除画布并设置背景色。

4. 创建着色器

WebGL 需要着色器(Shader)来定义如何渲染对象。我们将创建一个简单的顶点着色器和一个片段着色器。

顶点着色器(Vertex Shader)

const vertexShaderSource = `
    attribute vec4 a_position;
    void main(void) {
        gl_Position = a_position;
    }
`;

片段着色器(Fragment Shader)

const fragmentShaderSource = `
    void main(void) {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色
    }
`;

5. 编译着色器和创建程序

接下来,我们需要编译着色器,并将它们链接成一个程序。

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

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error("Shader 编译失败:", gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

// 创建着色器程序
const vertexShader = compileShader(vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER);

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error("程序链接失败:", gl.getProgramInfoLog(program));
}
gl.useProgram(program);

6. 创建一个简单的矩形

为了测试渲染效果,我们可以创建一个简单的矩形。

// 创建顶点数据
const vertices = new Float32Array([
    -0.5,  0.5,
    -0.5, -0.5,
     0.5,  0.5,
     0.5, -0.5,
]);

// 创建缓冲区并将数据传递到 WebGL
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

// 获取 a_position 属性位置并启用
const positionAttribLocation = gl.getAttribLocation(program, "a_position");
gl.vertexAttribPointer(positionAttribLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttribLocation);

7. 渲染图形

最后,调用 drawArrays 函数来渲染矩形。

gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

8. 完整代码

将上面所有步骤合并到一起,你将得到一个简单的 WebGL 渲染引擎,它可以渲染一个红色的矩形。

// engine.js
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');

if (!gl) {
    alert('WebGL 未被支持!');
}

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

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

// 顶点着色器
const vertexShaderSource = `
    attribute vec4 a_position;
    void main(void) {
        gl_Position = a_position;
    }
`;

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

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

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error("Shader 编译失败:", gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

// 创建着色器程序
const vertexShader = compileShader(vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER);

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error("程序链接失败:", gl.getProgramInfoLog(program));
}
gl.useProgram(program);

// 创建顶点数据
const vertices = new Float32Array([
    -0.5,  0.5,
    -0.5, -0.5,
     0.5,  0.5,
     0.5, -0.5,
]);

// 创建缓冲区并将数据传递到 WebGL
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

// 获取 a_position 属性位置并启用
const positionAttribLocation = gl.getAttribLocation(program, "a_position");
gl.vertexAttribPointer(positionAttribLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttribLocation);

// 渲染图形
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

9. 后续扩展

  • 加载模型: 可以添加支持加载 3D 模型的功能(如 .obj 或 .glTF 格式)。
  • 光照和材质: 实现不同的光照模型(如 Phong 光照)和支持材质贴图。
  • 相机和投影: 实现相机和视图变换功能。
  • 性能优化: 优化渲染性能,比如使用 VAO(Vertex Array Object)和 VBO(Vertex Buffer Object)进行批量渲染。

这只是一个基础的框架,你可以根据需求逐步扩展它,加入更多功能和优化。