📜  webgl 多维数据集示例 (1)

📅  最后修改于: 2023-12-03 14:48:25.785000             🧑  作者: Mango

WebGL 多维数据集示例

介绍

WebGL 是一种基于 OpenGL ES 的 3D 图形库,可以在网页上使用 JavaScript 进行 GPU 加速的渲染。多维数据集(Multidimensional Dataset)则是指由多维数组构成的数据集合,如图像、声音等。在使用 WebGL 进行数据可视化时,可以使用“纹理”(texture)的方式将多维数据集渲染到网页上,实现更加丰富的可视化效果。

本文将介绍如何使用 WebGL 渲染多维数据集。

示例

以下是一个使用 WebGL 渲染多维数据集的示例程序:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>WebGL 多维数据集示例</title>
</head>
<body>
    <canvas id="glcanvas" width="800" height="600"></canvas>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
    <script>
        // 创建 WebGL 上下文对象
        const gl = document.getElementById('glcanvas').getContext('webgl');

        // 定义顶点着色器
        const vertexShaderSource = `
            attribute vec3 aVertexPosition;
            attribute vec2 aTextureCoord;
            varying highp vec2 vTextureCoord;
            void main() {
                gl_Position = vec4(aVertexPosition, 1.0);
                vTextureCoord = aTextureCoord;
            }
        `;

        // 定义片元着色器
        const fragmentShaderSource = `
            precision highp float;
            uniform sampler2D uSampler;
            varying highp vec2 vTextureCoord;
            void main() {
                gl_FragColor = texture2D(uSampler, vTextureCoord);
            }
        `;

        // 创建程序对象
        const program = createProgram(gl, vertexShaderSource, fragmentShaderSource);

        // 获取属性和 uniform 变量
        const positionAttributeLocation = gl.getAttribLocation(program, 'aVertexPosition');
        const textureCoordAttributeLocation = gl.getAttribLocation(program, 'aTextureCoord');
        const samplerUniformLocation = gl.getUniformLocation(program, 'uSampler');

        // 创建纹理对象
        const texture = createTexture(gl);

        // 绑定纹理到纹理单元 0
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, texture);

        // 设置纹理参数
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

        // 加载数据集到纹理
        const dataset = new Float32Array([
            1.0, 0.0, 0.0,   0.0, 1.0, 0.0,
            0.0, 0.0, 1.0,   1.0, 1.0, 1.0
        ]);
        const numCols = 3;
        const numRows = 2;
        loadTextureData(gl, texture, numCols, numRows, dataset);

        // 设置顶点数据和纹理坐标数据
        const positions = new Float32Array([
            -1.0,  1.0,  0.0,
            -1.0, -1.0,  0.0,
             1.0, -1.0,  0.0,
             1.0,  1.0,  0.0
        ]);
        const textureCoords = new Float32Array([
             0.0,  0.0,
             0.0,  1.0,
             1.0,  1.0,
             1.0,  0.0
        ]);

        // 创建顶点数组对象
        const vao = createVertexArray(gl, program, positionAttributeLocation, textureCoordAttributeLocation);

        // 渲染
        render(gl, vao, program, samplerUniformLocation, texture);

        // 创建程序对象
        function createProgram(gl, vertexShaderSource, fragmentShaderSource) {
            const vertexShader = gl.createShader(gl.VERTEX_SHADER);
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
            gl.shaderSource(vertexShader, vertexShaderSource);
            gl.shaderSource(fragmentShader, fragmentShaderSource);
            gl.compileShader(vertexShader);
            gl.compileShader(fragmentShader);
            const program = gl.createProgram();
            gl.attachShader(program, vertexShader);
            gl.attachShader(program, fragmentShader);
            gl.linkProgram(program);
            return program;
        }

        // 创建纹理对象
        function createTexture(gl) {
            const texture = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, texture);
            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
            return texture;
        }

        // 加载数据集到纹理
        function loadTextureData(gl, texture, numCols, numRows, data) {
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, numCols, numRows, 0, gl.RGB, gl.FLOAT, data);
        }

        // 创建顶点数组对象
        function createVertexArray(gl, program, positionAttributeLocation, textureCoordAttributeLocation) {
            const vao = gl.createVertexArray();
            gl.bindVertexArray(vao);
            const positionBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
            gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(positionAttributeLocation);
            const textureCoordBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, textureCoords, gl.STATIC_DRAW);
            gl.vertexAttribPointer(textureCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(textureCoordAttributeLocation);
            return vao;
        }

        // 渲染
        function render(gl, vao, program, samplerUniformLocation, texture) {
            gl.useProgram(program);
            gl.bindVertexArray(vao);
            gl.uniform1i(samplerUniformLocation, 0);
            gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
        }
    </script>
</body>
</html>

该程序使用纹理对象将一个 3x2 的数据集渲染到屏幕上,效果如下:

WebGL 多维数据集示例

解释

我们对上述代码进行解释:

步骤 1:创建 WebGL 上下文对象
const gl = document.getElementById('glcanvas').getContext('webgl');

在这一步中,通过 document.getElementById 方法获取到 <canvas> 元素,并调用其 getContext 方法创建一个 WebGL 上下文对象。如果创建失败,则返回 null

步骤 2:定义着色器

在 WebGL 中,我们需要使用着色器(shader)来对顶点和片元进行处理。在本示例中,我们使用了两个着色器来完成渲染。

顶点着色器:

attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
varying highp vec2 vTextureCoord;
void main() {
    gl_Position = vec4(aVertexPosition, 1.0);
    vTextureCoord = aTextureCoord;
}

片元着色器:

precision highp float;
uniform sampler2D uSampler;
varying highp vec2 vTextureCoord;
void main() {
    gl_FragColor = texture2D(uSampler, vTextureCoord);
}

我们使用 gl.createShader 方法创建着色器对象,使用 gl.shaderSource 方法为其指定源代码,使用 gl.compileShader 方法编译着色器,最后使用 gl.createProgram 方法将两个着色器链接成一个程序对象。

步骤 3:获取属性和 uniform 变量

程序对象创建完成后,我们需要通过其 gl.getAttribLocationgl.getUniformLocation 方法获取其属性和 uniform 变量的位置。

const positionAttributeLocation = gl.getAttribLocation(program, 'aVertexPosition');
const textureCoordAttributeLocation = gl.getAttribLocation(program, 'aTextureCoord');
const samplerUniformLocation = gl.getUniformLocation(program, 'uSampler');
步骤 4:创建纹理对象

在本示例中,我们使用了纹理对象将数据集渲染到屏幕上。首先,我们需要使用 gl.createTexture 方法创建纹理对象。然后,我们使用 gl.bindTexture 方法将其绑定到当前纹理单元(texture unit)上。在本示例中,我们使用纹理单元 0。

const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);

在本示例中,我们使用了浮点型的纹理数据。因此,我们需要使用 gl.texImage2D 方法将数据集上传到纹理对象中。

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, numCols, numRows, 0, gl.RGB, gl.FLOAT, data);

注意,这里的 data 参数应该是一个 Float32Array 对象。

步骤 5:设置顶点数据

我们使用两个数组来存储顶点数据和纹理坐标数据:

const positions = new Float32Array([
    -1.0,  1.0,  0.0,
    -1.0, -1.0,  0.0,
     1.0, -1.0,  0.0,
     1.0,  1.0,  0.0
]);
const textureCoords = new Float32Array([
     0.0,  0.0,
     0.0,  1.0,
     1.0,  1.0,
     1.0,  0.0
]);

注意,顶点数据应该能准确地映射到纹理数据。在本示例中,顶点数据是一个正方形,而纹理数据是一个长方形,因此纹理坐标的范围不是 $[0,1]$,而是 $[0,\frac{n}{m}]$,其中 $n$ 是列数,$m$ 是行数。

步骤 6:创建顶点数组对象

这一步中,我们使用 gl.createVertexArray 方法创建一个顶点数组对象。然后,我们使用 gl.bindVertexArray 方法将其绑定到当前上下文中。

接着,我们使用 gl.createBuffer 方法创建缓冲区对象,并使用 gl.bindBuffer 方法将其绑定到当前上下文中。然后,我们使用 gl.bufferData 方法将数据上传到缓冲区中,并使用 gl.vertexAttribPointer 方法将缓冲区中的数据关联到程序对象中的属性变量上。

const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLocation);
const textureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, textureCoords, gl.STATIC_DRAW);
gl.vertexAttribPointer(textureCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(textureCoordAttributeLocation);
步骤 7:渲染

渲染时,我们需要使用 gl.useProgram 方法将程序对象绑定到当前上下文中,使用 gl.bindVertexArray 方法将顶点数组对象绑定到当前上下文中,使用 gl.uniform1i 方法将纹理单元绑定到该程序对象中的 uniform 变量上,最后使用 gl.drawArrays 方法进行渲染。

gl.useProgram(program);
gl.bindVertexArray(vao);
gl.uniform1i(samplerUniformLocation, 0);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);

注意,我们使用了 gl.TRIANGLE_FAN 作为渲染类型,因为我们只有一个四边形。在实际情况中,渲染类型应该根据实际情况进行选择。

至此,创建一个使用 WebGL 渲染多维数据集的程序就完成了。