关于Wasm和webGL的一个小案例(C生成RGBA图片数据,前端使用webGL纹理绘制)

1,177 阅读1分钟

1、前提需要安装 Emscripten

安装部分参考:emcc.zcopy.site/

2、C语言部分

1658310385765.png createImg方法返回给js的是一个结构体指针ImgData(test.c)。

2、Emscripten编译指令

emcc test.c -o test.js -s EXPORTED_FUNCTIONS=["_malloc","_free","_createImg","_main"] -s EXPORTED_RUNTIME_METHODS=["cwrap"] -s ALLOW_MEMORY_GROWTH=1

-s EXPORTED_FUNCTIONS里面是C暴露给js的各种方法(注意需要在C方法名称前加_)

-s EXPORTED_RUNTIME_METHODS里面是Emscripten暴露给js的各种方法

CMD进入test.c所在目录运行编译指令,将在本目录下生成test.js 和 test.wasm文件。

1658311251723.png

3、前端部分

项目目录结构

1658311815682.png

前端代码

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./test.js"></script>
    <style>
        canvas {
            width: 1920px;
            height: 1080px;
            border: 1px solid;
        }
    </style>
</head>
<body>
    <canvas id="myCanvas" width="1920" height="1080"></canvas>
    <br />
    <button id="getImg">获取图片</button>
    <button id="stopImg">暂停</button>
    <script>
       
        let webgl = null;
        let num = 0;//记录绘制的图片数量

        //顶点着色器
        //attribute变量,用来表示和顶点相关的数据,只可以在顶点着色器中使用。
        //varying变量,作用是从顶点着色器向片元着色器传值,只要在片元着色器中也声明同名varying变量,顶点着色器赋给该变量的值就会自动传入片元着色器。
        let vertexstring = `
            attribute vec2 a_position;
            attribute vec2 outUV;
            varying vec2 inUV;
            void main(void){
                gl_Position = vec4(a_position, 0, 1.0);
                inUV = outUV;
            }
        `;

        //片元着色器
        let fragmentstring = `
            precision mediump float;
            uniform sampler2D texture;
            varying vec2 inUV;
            void main(void){
                gl_FragColor = texture2D(texture,inUV);
            }
        `;

        Module.onRuntimeInitialized = function () {
            createImg = Module["cwrap"]('createImg', "number", null);
        }

        window.onload = function () {
            const myCanvas = document.querySelector("#myCanvas");
            webgl = myCanvas.getContext("webgl");
            webgl.viewport(0, 0, myCanvas.width, myCanvas.height);
            initWebGL();
            const getImgBtn = document.querySelector("#getImg");
            const stopImgBtn = document.querySelector("#stopImg");
            getImgBtn.onclick = getImgFun;
            stopImgBtn.onclick = function () {
                window.cancelAnimationFrame(thisTime);
            }
        };

        function getImgFun(timestamp) {
            console.log(num++);
            console.log(timestamp);
            let ptr = createImg();
            let width = Module.HEAPU32[ptr / 4];
            let height = Module.HEAPU32[ptr / 4 + 1];
            let imgBufferPtr = Module.HEAPU32[ptr / 4 + 2];
            let imageBuffer = Module.HEAPU8.subarray(imgBufferPtr, imgBufferPtr + width * height * 4);
            if (!width || !height) {
                console.log("获取图片失败,图片宽高为0");
                return;
            }
            //console.log("ptr", width);
            //console.log("ptr", height);
            //console.log("imageBuffer", imageBuffer);
            drawImage(width, height, imageBuffer);
            Module._free(ptr);
            Module._free(imgBufferPtr);
            thisTime = window.requestAnimationFrame(getImgFun);
        }

        //canvas 2d 绘制RGBA图片数据
        // function drawImage(width, height, buffer) {
        //     let imageData = ctx.createImageData(width, height);
        //     imageData.data.set(buffer);
        //     //myCanvas.width = width;
        //     //myCanvas.height = height;
        //     ctx.putImageData(imageData, 0, 0, 0, 0, width, height);
        // }

        function drawImage(width, height, buffer) {
            webgl.texImage2D(webgl.TEXTURE_2D, 0, webgl.RGBA, 1920,1080,0,webgl.RGBA, webgl.UNSIGNED_BYTE, buffer);
            //webgl.clearColor(0.0, 1.0, 0.0, 1.0);
            //webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);
            webgl.drawArrays(webgl.TRIANGLE_STRIP, 0, 4);
        }

        function initWebGL() {
                //创建顶点着色器和片元着色器
                let vsshader = webgl.createShader(webgl.VERTEX_SHADER);
                let fsshader = webgl.createShader(webgl.FRAGMENT_SHADER);
                //关联GLSL源代码
                webgl.shaderSource(vsshader, vertexstring);
                webgl.shaderSource(fsshader, fragmentstring);
                //编译源代码
                webgl.compileShader(vsshader);
                webgl.compileShader(fsshader);
                if (!webgl.getShaderParameter(vsshader, webgl.COMPILE_STATUS)) {
                    var err = webgl.getShaderInfoLog(vsshader);
                    alert(err);
                    return;
                }
                if (!webgl.getShaderParameter(fsshader, webgl.COMPILE_STATUS)) {
                    var err = webgl.getShaderInfoLog(fsshader);
                    alert(err);
                    return;
                }
                let program = webgl.createProgram();
                webgl.attachShader(program, vsshader);
                webgl.attachShader(program, fsshader)
                webgl.linkProgram(program);
                webgl.useProgram(program);
                //给顶点着色器设置顶点坐标(和canvas一样大)
                const vertexData = [
                    -1.0,
                    -1.0,
                    1.0,
                    -1.0,
                    -1.0,
                    1.0,
                    1.0,
                    1.0,
                ];
                let triangleBuffer = webgl.createBuffer();
                webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
                webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(vertexData), webgl.STATIC_DRAW);
                let aPsotion = webgl.getAttribLocation(program, "a_position");
                webgl.enableVertexAttribArray(aPsotion);
                webgl.vertexAttribPointer(aPsotion, 2, webgl.FLOAT, false, 0, 0);
                //设置纹理坐标
                //js不能直接给varying变量赋值,只能先给attribute变量赋值,
                const txtCoordData = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
                let triangleBuffers = webgl.createBuffer();
                webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffers);
                webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(txtCoordData), webgl.STATIC_DRAW);
                let aPsotions = webgl.getAttribLocation(program, "outUV");
                webgl.enableVertexAttribArray(aPsotions);
                webgl.vertexAttribPointer(aPsotions, 2, webgl.FLOAT, false, 0, 0);
                webgl.activeTexture(webgl.TEXTURE0); // 激活
                let texture = webgl.createTexture(); // 创建纹理对象 
                webgl.bindTexture(webgl.TEXTURE_2D, texture);
                const sampler = webgl.getUniformLocation(program, "u_image");// 获取纹理采样器索引 
                webgl.uniform1i(sampler, 0);// 绑定纹理单元 
                //纹理参数设置
                webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_S, webgl.CLAMP_TO_EDGE);
                webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_T, webgl.CLAMP_TO_EDGE);
                webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MIN_FILTER, webgl.LINEAR);
                webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MAG_FILTER, webgl.LINEAR);
                webgl.pixelStorei(webgl.UNPACK_FLIP_Y_WEBGL, true);
        }
    </script>
</body>
</html>

4、运行结果展示

1658367686315.png

5、结束语

以上是本案例的全部代码,此案例适合像我一样刚刚接触相关知识的小白参考学习(C、Wasm、webGL),其中涉及到的大多数知识我也是一知半解,代码中有需要优化的部分希望大神可以指出。