HarmonyOS鸿蒙Next中WebGL绘制过程

HarmonyOS鸿蒙Next中WebGL绘制过程

WebGL绘制过程

概念

WebGL是一种基于JavaScript API的2D、3D图形库,且无需使用插件即可渲染高性能的交互式2D、3D图形。WebGL使用OpenGL ES 2.0作为其渲染引擎,它可以在任何支持OpenGL ES 2.0的硬件上运行,包括PC、移动设备和游戏机等。WebGL可以用于创建各种类型的3D应用程序,包括游戏、虚拟现实、数据可视化和科学模拟等。

目标

WebGL的绘制过程通常可以分为以下几个步骤,我们先来有个简单概念。后续我们将按照这几个步骤来具体实现如何在华为快游戏中使用WebGL绘制图片。

步骤

  1. 获取WebGL上下文:在HTML中创建一个canvas元素,并使用JavaScript获取WebGL上下文。

    const canvas = document.getElementById('canvas')
    const gl = canvas.getContext('webgl')
    
  2. 编写着色器程序:WebGL使用着色器程序来处理图形数据。着色器程序由顶点着色器和片元着色器组成,分别处理顶点和像素的颜色和位置信息。

    const V_SHADER_SOURCE = `
    attribute vec4 a_Position; 
    void main(){ 
        gl_Position = a_Position; 
        gl_PointSize = 10.0; 
    }
    `
    const F_SHADER_SOURCE = `
    precision mediump float;
    uniform vec4 u_FragColor;
    void main(){
    gl_FragColor = u_FragColor;
    }
    
  3. 初始化着色器:WebGL使用createShader创建着色器对象,并使用shaderSource将第二步的着色器源码分配给着色器对象,并编译。最后使用createProgram方法创建着色器程序对象,并链接和使用当前程序。

    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, V_SHADER_SOURCE);
    gl.compileShader(vertexShader);
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.linkProgram(program);
    gl.useProgram(program);
    
  4. 准备数据并将数据绑定到着色器程序中:WebGL需要将图形数据传递给着色器程序。这些数据包括顶点坐标、颜色、纹理坐标等。如下是传递颜色数据给片元着色器。

    let u_FragColor = gl.getUniformLocation(program, 'u_FragColor')
    gl.uniform4f(u_FragColor, 1.0, 1.0, 0.0, 1.0)
    
  5. 绘制图形:使用WebGL的绘制函数gl.drawArrays将图形绘制到canvas上。

    gl.drawArrays(gl.POINTS, 0, 1);
    

API

详情见:WEBGL API。

代码示例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebGLExample</title>
</head>

<body>
    <canvas id="canvas"></canvas>
    <button onclick="test()">查看</button>
</body>

<script>
    function test() {
        // 1.获取canvas,请求webgl上下文
        let imgSrc = "https://cdn.glitch.com/4c9ebeb9-8b9a-4adc-ad0a-238d9ae00bb5%2Fmdn_logo-only_color.svg?1535749917189";
        let _startDraw = false;
        let texture
        let u_Sampler
        let program
        let vertexTexCoordBuffer
        let FSIZE
        let a_Position
        let a_TexCoord
        let canvas = document.getElementById('canvas')
        let gl = canvas.getContext('webgl')
        if (!gl) {
            console.log('Failed to get the rendering context for WebGL');
            return;
        }

        window.SplashScreen = {
            initShader: function (gl, vsource, fsource) {
                program = SplashScreen.createProgram(gl, vsource, fsource);
                if (!program) {
                    console.log('Failed to create program');
                    return false;
                }
                return true;
            },

            createProgram: function (gl, vsource, fsource) {
                // Create shader object
                const vertexShader = SplashScreen.createShader(gl, gl.VERTEX_SHADER, vsource);
                const fragmentShader = SplashScreen.createShader(gl, gl.FRAGMENT_SHADER, fsource);
                if (!vertexShader || !fragmentShader) {
                    return null;
                }
                // Create a program object
                const program = gl.createProgram();
                if (!program) {
                    return null;
                }
                // Attach the shader objects
                gl.attachShader(program, vertexShader);
                gl.attachShader(program, fragmentShader);
                // Link the program object
                gl.linkProgram(program);
                // Check the result of linking
                const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
                if (!linked) {
                    let error = gl.getProgramInfoLog(program);
                    console.log('Failed to link program: ' + error);
                    gl.deleteProgram(program);
                    gl.deleteShader(fragmentShader);
                    gl.deleteShader(vertexShader);
                    return null;
                }
                return program;
            },

            createShader: function (gl, type, source) {
                // Create shader object
                const shader = gl.createShader(type);
                if (shader == null) {
                    console.log('unable to create shader');
                    return null;
                }
                // Set the shader program
                gl.shaderSource(shader, source);
                // Compile the shader
                gl.compileShader(shader);
                // Check the result of compilation
                const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
                if (!compiled) {
                    let error = gl.getShaderInfoLog(shader);
                    console.log('Failed to compile shader: ' + error);
                    gl.deleteShader(shader);
                    return null;
                }

                return shader;
            },

            initVertexBuffers: function (gl) {
                // 映射顶点坐标系(webgl坐标系)和图片的纹理坐标系
                const verticesTexCoords = new Float32Array([
                    -1, 1, 0.0, 1.0,
                    -1, -1, 0.0, 0.0,
                    1, 1, 1.0, 1.0,
                    1, -1, 1.0, 0.0,
                ]);
                // 图片有4个顶点
                const n = 4;
                // 创建缓冲区对象
                vertexTexCoordBuffer = gl.createBuffer();
                if (!vertexTexCoordBuffer) {
                    console.log('Failed to create the buffer object');
                    return -1;
                }
                // 将缓冲区对象绑定到目标
                gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
                // 将顶点数据写入缓冲区对象
                gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
                // 缓冲区数据里单位大小
                FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
                // 获取顶点属性a_Position的索引地址
                a_Position = gl.getAttribLocation(program, 'a_Position');
                if (a_Position < 0) {
                    console.log('Failed to get the storage location of a_Position');
                    return -1;
                }
                // 设置顶点属性如何从顶点缓冲区对象中取值。每次从数组缓冲对象中读取2个值,步进大小为4,起点偏移量为0
                gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
                // 激活使用
                gl.enableVertexAttribArray(a_Position);

                // 获取顶点属性a_TexCoord的索引地址
                a_TexCoord = gl.getAttribLocation(program, 'a_TexCoord');
                if (a_TexCoord < 0) {
                    console.log('Failed to get the storage location of a_TexCoord');
                    return -1;
                }
                // 设置顶点属性如何从顶点缓冲区对象中取值。每次从数组缓冲对象中读取2个值,步进大小为4,起点偏移量为2
                gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
                gl.enableVertexAttribArray(a_TexCoord);
                return n;
            },

            initTextures: function (gl, n) {
                // 创建图片纹理对象
                texture = gl.createTexture();
                if (!texture) {
                    console.log('Failed to create the texture object');
                    return false;
                }
                // 获取片元属性a_TexCoord的索引地址
                u_Sampler = gl.getUniformLocation(program, 'u_Sampler');
                if (!u_Sampler) {
                    console.log('Failed to get the storage location of u_Sampler');
                    return false;
                }
                // 创建图片对象
                window.image = new Image();
                if (!image) {
                    console.log('Failed to create the image object');
                    return false;
                }
                // 注册图片加载完成的回调事件
                image.onload = function () {
                    SplashScreen.loadTexture(gl, n, texture, u_Sampler, image);
                };
                image.crossOrigin = "anonymous";
                // 设置图片src
                image.src = imgSrc;
                return true;
            },

            loadTexture: function (gl, n, texture, u_Sampler, image) {
                console.log("gx drawImage");
                // 纹理坐标系和canvas坐标Y轴相反,需要翻转Y轴
                gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
                // 设置活动纹理单元为纹理单元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);
                // 将图像image分配给纹理对象
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
                // 将纹理单元0的索引值设置给纹理采样器变量
                gl.uniform1i(u_Sampler, 0);
                _startDraw = true;
            },

            draw: function () {
                if (!_startDraw) {
                    return;
                }
                // 切换shader
                gl.useProgram(program);
                gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
                // 开启attribute变量
                gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
                gl.enableVertexAttribArray(a_Position);
                gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
                gl.enableVertexAttribArray(a_TexCoord);
                // 绑定纹理
                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, texture);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                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.uniform1i(u_Sampler, 0);

                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
            },

            init: function () {
                // 2.指定清空canvas的颜色并清空canvas
                gl.clearColor(0.0, 0.0, 0.0, 1.0)
                gl.clear(gl.COLOR_BUFFER_BIT)
                // 3.初始化着色器(着色器是一种GLSL ES语言,是OpenGL ES API的一部分,基于C语言的程序,用于在GPU上执行计算,以生成2D、3D图形颜色和纹理)
                // 3.1 顶点着色器程序(描述顶点特性,位置,颜色的一种程序)
                const VSHADER_SOURCE = `
                    attribute vec4 a_Position;
                    attribute vec2 a_TexCoord;
                    varying vec2 v_TexCoord;
                    void main() {
                        gl_Position = a_Position;
                        v_TexCoord = a_TexCoord;
                    }
                `;
                // 3.2 片元着色器程序(逐片元处理光栅化后的每个像素的颜色)
                const FSHADER_SOURCE = `
                    #ifdef GL_ES
                    precision mediump float;
                    #endif
                    uniform sampler2D u_Sampler;
                    varying vec2 v_TexCoord;
                    void main() {
                        gl_FragColor= texture2D(u_Sampler, v_TexCoord);
                    }
                `;
                // 3.3 初始化着色器
                if (!SplashScreen.initShader(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
                    console.log('Failed to intialize shaders.');
                    return;
                }

                // 4 初始化顶点坐标信息
                //recordPreviousGLStates()
                const n = SplashScreen.initVertexBuffers(gl);
                if (n < 0) {
                    console.log('Failed to set the vertex information');
                    return;
                }
                // 5 绘制图片
                // Set texture
                //resetPreviousGLStates()
                if (!SplashScreen.initTextures(gl, n)) {
                    console.log('Failed to intialize the texture.');
                    return;
                }
            }
        }
        // 模拟游戏开始
        SplashScreen.init();

        function doDraw() {
            requestAnimationFrame(doDraw);
            window.SplashScreen.draw();
        }

        requestAnimationFrame(doDraw);
    }
</script>
</html>

更多关于HarmonyOS鸿蒙Next中WebGL绘制过程的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS鸿蒙Next中WebGL绘制过程的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,WebGL的绘制过程主要包括以下步骤:

  1. 初始化WebGL上下文:通过<canvas>元素获取WebGL上下文,使用getContext('webgl')getContext('experimental-webgl')

  2. 创建着色器程序:编写顶点着色器和片元着色器代码,编译并链接成着色器程序。

  3. 准备数据:定义顶点数据、颜色数据等,并将其存储在缓冲区中。

  4. 设置绘制状态:配置WebGL的绘制状态,如深度测试、混合模式等。

  5. 绘制图形:使用gl.drawArrays()gl.drawElements()方法进行绘制。

  6. 渲染到屏幕:将绘制结果渲染到<canvas>元素上,显示在屏幕上。

通过这些步骤,开发者可以在HarmonyOS鸿蒙Next中实现复杂的3D图形渲染。

回到顶部