当前位置: 首页 > news >正文

【Javascript】在canvas中加载shader着色器的方法(开箱即用)

功能简介

可以播放,暂停shader代码,可以在js中配置shader参数(下面案例列举了所有可用参数形式)

缺点

这个是固定机位,没有自定义顶点着色器部分的功能,有需要可直接在class中改,或者修改后调用更新片元着色器的方法。

工具

class WebGLShaderToy {
    constructor(canvas, fragmentShaderSource, customUniforms) {
        // 获取 Canvas 元素
        this.canvas = canvas;
        // 获取 WebGL 上下文
        this.gl = this.canvas.getContext('webgl');
      
        // 顶点着色器代码
        this.vertexShaderSource = `
            attribute vec2 a_position;
            void main() {
                gl_Position = vec4(a_position, 0, 1);
            }
        `;
        this.customUniforms = []

        // 片段着色器代码
        this.fragmentShaderSource = this.createHeader(customUniforms) + fragmentShaderSource;

        console.log("fragmentShaderSource", this.fragmentShaderSource);


        // 编译着色器
        this.vertexShader = this.compileShader(this.gl, this.vertexShaderSource, this.gl.VERTEX_SHADER);
        this.fragmentShader = this.compileShader(this.gl, this.fragmentShaderSource, this.gl.FRAGMENT_SHADER);

        // 创建着色器程序
        this.program = this.createProgram(this.gl, this.vertexShader, this.fragmentShader);

        // 获取属性和制服的位置
        this.positionAttributeLocation = this.gl.getAttribLocation(this.program, 'a_position');
        this.resolutionUniformLocation = this.gl.getUniformLocation(this.program, 'iResolution');
        this.timeUniformLocation = this.gl.getUniformLocation(this.program, 'iTime');

        // 获取颜色制服的位置
        this.colorUniformLocations = [];
        for (let i = 1; i <= 10; i++) {
            this.colorUniformLocations.push(this.gl.getUniformLocation(this.program, `iColor${i}`));
        }

        // 创建缓冲区并绑定顶点数据
        this.positionBuffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
        const positions = [
            -1, -1,
            1, -1,
            -1, 1,
            -1, 1,
            1, -1,
            1, 1
        ];
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(positions), this.gl.STATIC_DRAW);

        // 启用属性
        this.gl.enableVertexAttribArray(this.positionAttributeLocation);
        this.gl.vertexAttribPointer(this.positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0);

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

        // 清除画布
        this.gl.clearColor(0, 0, 0, 1);
        this.gl.clear(this.gl.COLOR_BUFFER_BIT);

        // 使用着色器程序
        this.gl.useProgram(this.program);

        // 设置分辨率制服
        this.gl.uniform2f(this.resolutionUniformLocation, this.gl.canvas.width, this.gl.canvas.height);

        // 初始化颜色制服
        this.setColors([
            [1.0, 0.0, 0.0], // 红色
            [0.0, 1.0, 0.0], // 绿色
            [0.0, 0.0, 1.0], // 蓝色
            [1.0, 1.0, 0.0], // 黄色
            [1.0, 0.0, 1.0], // 紫色
            [0.0, 1.0, 1.0], // 青色
            [1.0, 0.5, 0.0], // 橙色
            [0.5, 0.0, 1.0], // 紫罗兰色
            [0.5, 1.0, 0.0], // 黄绿色
            [0.0, 0.5, 1.0]  // 天蓝色
        ]);
        this.loadTexture(this.gl,"./texture1.jpg")
        this.isAnimating = false;
    }
    // 创建着色器开头

    createHeader(customUniforms) {
        let header = `
            precision mediump float;
            uniform vec2 iResolution;
            uniform float iTime;
            uniform vec3 iColor1;
            uniform vec3 iColor2;
            uniform vec3 iColor3;
            uniform vec3 iColor4;
            uniform vec3 iColor5;
            uniform vec3 iColor6;
            uniform vec3 iColor7;
            uniform vec3 iColor8;
            uniform vec3 iColor9;
            uniform vec3 iColor10;
    `
        for (let i = 0; i < customUniforms.length; i++) {
            let { name, type, value } = customUniforms[i]
            let str = `
            uniform ${type} ${name};`

            header += str
        }
        console.log('header', header, customUniforms);

        return header

    }

    // 编译着色器的函数
    compileShader(gl, shaderSource, shaderType) {
        try {
            const shader = gl.createShader(shaderType);
            gl.shaderSource(shader, shaderSource);
            gl.compileShader(shader);
            const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
            if (!success) {
                console.log(gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }
            return shader;
        } catch (error) {
            console.error("片元着色器编码异常,请检查着色器代码", error)
        }
    }


    // 创建着色器程序的函数
    createProgram(gl, vertexShader, fragmentShader) {
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
        const success = gl.getProgramParameter(program, gl.LINK_STATUS);
        if (!success) {
            console.log(gl.getProgramInfoLog(program));
            gl.deleteProgram(program);
            return null;
        }
        return program;
    }
    // 赋值customUniforms
    setCustomUniforms(customUniforms) {
        for (let i = 1; i <= 10; i++) {
            this.colorUniformLocations.push(this.gl.getUniformLocation(this.program, `iColor${i}`));
        }

        let unifromBuffer = []
        let textureLoadingArray = []
        for (let i = 0; i < customUniforms.length; i++) {
            let { name, type, value } = customUniforms[i]
            let uniform = this.gl.getUniformLocation(this.program, `${name}`)
            switch (type) {
                // 标量类型
                case "float":
                    this.gl.uniform1f(uniform, value);
                    break;
                case "int":
                    this.gl.uniform1i(uniform, value);
                    break;
                case "bool":
                    this.gl.uniform1i(uniform, value ? 1 : 0);
                    break;
                // 向量类型
                case "vec2":
                    this.gl.uniform2fv(uniform, value);
                    break;
                case "vec3":
                    this.gl.uniform3fv(uniform, value);
                    break;
                case "vec4":
                    this.gl.uniform4fv(uniform, value);
                    break;
                case "ivec2":
                    this.gl.uniform2iv(uniform, value);
                    break;
                case "ivec3":
                    this.gl.uniform3iv(uniform, value);
                    break;
                case "ivec4":
                    this.gl.uniform4iv(uniform, value);
                    break;
                case "bvec2":
                    this.gl.uniform2iv(uniform, value.map(v => v ? 1 : 0));
                    break;
                case "bvec3":
                    this.gl.uniform3iv(uniform, value.map(v => v ? 1 : 0));
                    break;
                case "bvec4":
                    this.gl.uniform4iv(uniform, value.map(v => v ? 1 : 0));
                    break;
                // 矩阵类型
                case "mat2":
                    this.gl.uniformMatrix2fv(uniform, false, value);
                    break;
                case "mat3":
                    this.gl.uniformMatrix3fv(uniform, false, value);
                    break;
                case "mat4":
                    this.gl.uniformMatrix4fv(uniform, false, value);
                    break;
                // 采样器类型
                case "sampler2D":
                case "samplerCube":
                    this.gl.uniform1i(uniform, textureLoadingArray.length);
                    textureLoadingArray.push({ type, value })
                    break;
                default:
                    console.error(`Unsupported uniform type: ${type}`);
            }
            unifromBuffer.push(uniform)

        //    // 清除gl的全部贴图缓存
            // this.clearTextureCache()
        //     // 使用textureLoadingArray加载所有贴图
        //     textureLoadingArray.forEach(({ type, value }) => {
        //         if (type == "sampler2D") {
        //             this.loadTexture(this.gl, value)
        //         } else {
        //             // this.loadCubeTexture(this.gl, value)
        //         }
        //     })
        }

    }



    // 动画循环
    draw(time) {
        if (!this.isAnimating) return;
        // 设置时间制服
        this.gl.uniform1f(this.timeUniformLocation, time * 0.001);

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

        // 请求下一帧动画
        requestAnimationFrame(this.draw.bind(this));
    }

    // 开始动画
    startAnimation() {
        this.isAnimating = true;
        requestAnimationFrame(this.draw.bind(this));
    }

    // 暂停动画
    pauseAnimation() {
        this.isAnimating = false;
    }

    // 恢复动画
    resumeAnimation() {
        if (!this.isAnimating) {
            this.isAnimating = true;
            requestAnimationFrame(this.draw.bind(this));
        }
    }

    // 修改片段着色器源代码
    changeFragmentShader(fragmentShaderSource, config) {
        this.fragmentShaderSource = this.createHeader(config.customUniforms) + fragmentShaderSource;;
        console.log("will compileShader", this.fragmentShaderSource);

        this.fragmentShader = this.compileShader(this.gl, this.fragmentShaderSource, this.gl.FRAGMENT_SHADER);
        if (this.fragmentShader) {
            // 删除旧的着色器程序
            this.gl.deleteProgram(this.program);
            // 创建新的着色器程序
            this.program = this.createProgram(this.gl, this.vertexShader, this.fragmentShader);
            if (this.program) {
                // 获取新的属性和制服的位置
                this.positionAttributeLocation = this.gl.getAttribLocation(this.program, 'a_position');
                this.resolutionUniformLocation = this.gl.getUniformLocation(this.program, 'iResolution');
                this.timeUniformLocation = this.gl.getUniformLocation(this.program, 'iTime');

                // 获取新的颜色制服的位置
                this.colorUniformLocations = [];
                for (let i = 1; i <= 10; i++) {
                    this.colorUniformLocations.push(this.gl.getUniformLocation(this.program, `iColor${i}`));
                }

                // 启用属性
                this.gl.enableVertexAttribArray(this.positionAttributeLocation);
                this.gl.vertexAttribPointer(this.positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0);

                // 使用新的着色器程序
                this.gl.useProgram(this.program);

                // 设置分辨率制服
                this.gl.uniform2f(this.resolutionUniformLocation, this.gl.canvas.width, this.gl.canvas.height);

                this.setCustomUniforms(config.customUniforms)

                if (config && config.colors) {
                    this.setColors(colors)
                } else {
                    this.setColors([
                        [1.0, 0.0, 0.0], // 红色
                        [0.0, 1.0, 0.0], // 绿色
                        [0.0, 0.0, 1.0], // 蓝色
                        [1.0, 1.0, 0.0], // 黄色
                        [1.0, 0.0, 1.0], // 紫色
                        [0.0, 1.0, 1.0], // 青色
                        [1.0, 0.5, 0.0], // 橙色
                        [0.5, 0.0, 1.0], // 紫罗兰色
                        [0.5, 1.0, 0.0], // 黄绿色
                        [0.0, 0.5, 1.0]  // 天蓝色
                    ])
                }
            }
        }
    }
    // 清除纹理缓存的辅助方法
    clearTextureCache() {
        const gl = this.gl;
        // 解绑所有纹理单元
        for (let i = 0; i < gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); i++) {
            gl.activeTexture(gl.TEXTURE0 + i);
            gl.bindTexture(gl.TEXTURE_2D, null);
            gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
        }
        // 这里可以扩展为记录所有创建的纹理对象并删除它们
        // 例如,如果有一个 this.textureObjects 数组存储了所有纹理对象
        // this.textureObjects.forEach(texture => gl.deleteTexture(texture));
        // this.textureObjects = [];
    }

    // 加载二维纹理
    loadTexture(gl, url) {
        console.log("加载二维纹理", gl, url);

        const texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
    
        // 设置默认纹理数据,避免在图像加载完成前使用未初始化的纹理
        const level = 0;
        const internalFormat = gl.RGBA;
        const width = 1;
        const height = 1;
        const border = 0;
        const srcFormat = gl.RGBA;
        const srcType = gl.UNSIGNED_BYTE;
        const pixel = new Uint8Array([0, 0, 255, 255]);  // 蓝色像素
        gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
                      width, height, border, srcFormat, srcType,
                      pixel);
    
        const image = new Image();
        image.onload = function () {
            gl.bindTexture(gl.TEXTURE_2D, texture);
            // 设置纹理参数以支持非 2 的幂次方纹理
            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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
            // 非 2 的幂次方纹理不支持生成 mipmap,所以这里不调用 gl.generateMipmap
            // gl.generateMipmap(gl.TEXTURE_2D); 
        };
        image.src = url;
    
        return texture;
    }

    // 加载立方体纹理
    loadCubeTexture(gl, urls) {
        const texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);

        const faceTargets = [
            gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
            gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
            gl.TEXTURE_CUBE_MAP_POSITIVE_Z, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
        ];

        urls.forEach((url, i) => {
            const image = new Image();
            image.onload = function () {
                gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
                gl.texImage2D(faceTargets[i], 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
                gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
            };
            image.src = url;
        });

        return texture;
    }
    // 更新画布大小
    updateCanvasSize(width, height) {
        this.canvas.width = width;
        this.canvas.height = height;
        this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
        this.gl.uniform2f(this.resolutionUniformLocation, this.gl.canvas.width, this.gl.canvas.height);
    }

    // 设置颜色制服
    setColors(colors) {
        console.log('setColors', colors);

        for (let i = 0; i < colors.length; i++) {
            this.gl.uniform3fv(this.colorUniformLocations[i], colors[i]);
        }
    }
    // 销毁
    dispose() {
        this.pauseAnimation()
        // 清理WebGL资源
        if (this.gl) {
            // 删除缓冲区
            const buffers = this.gl.getParameter(this.gl.ARRAY_BUFFER_BINDING);
            if (buffers) {
                this.gl.deleteBuffer(buffers);
            }

            // 删除纹理
            const textures = this.gl.getParameter(this.gl.TEXTURE_BINDING_2D);
            if (textures) {
                this.gl.deleteTexture(textures);
            }

            // 删除着色器程序
            if (this.program) {
                this.gl.deleteProgram(this.program);
            }

            // 删除帧缓冲区
            const framebuffers = this.gl.getParameter(this.gl.FRAMEBUFFER_BINDING);
            if (framebuffers) {
                this.gl.deleteFramebuffer(framebuffers);
            }
        }
    }




使用


const canvasDom = document.getElementById('glCanvas');
const fragmentShaderSource = `
    void main() {
        vec2 st = gl_FragCoord.xy/iResolution.xy;
        // 简单示例:根据时间和坐标生成颜色
        vec3 color = mix(iColor1, iColor2, st.x);
        gl_FragColor = vec4(iColor7, 1.0);
    }
`;

// {
//     inputmat22: [1, 2, 3, 4],
//     inputmat33: [1, 2, 3, 4, 5, 6, 7, 8, 9],
//     inputmat44: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
// }
const customUniforms = [
    // 标量类型
    { name: "inputFloat", type: "float", value: 3.14 },
    { name: "inputInt", type: "int", value: 42 },
    { name: "inputBool", type: "bool", value: true },

    // 向量类型
    { name: "inputVec2", type: "vec2", value: [1.0, 2.0] },
    { name: "inputVec3", type: "vec3", value: [1.0, 0.0, 0.0] },
    { name: "inputVec4", type: "vec4", value: [1.0, 2.0, 3.0, 4.0] },

    { name: "inputIVec2", type: "ivec2", value: [1, 2] },
    { name: "inputIVec3", type: "ivec3", value: [1, 2, 3] },
    { name: "inputIVec4", type: "ivec4", value: [1, 2, 3, 4] },

    { name: "inputBVec2", type: "bvec2", value: [true, false] },
    { name: "inputBVec3", type: "bvec3", value: [true, false, true] },
    { name: "inputBVec4", type: "bvec4", value: [true, false, true, false] },

    // 矩阵类型
    { name: "inputMat2", type: "mat2", value: [1, 2, 3, 4] },
    { name: "inputMat3", type: "mat3", value: [1, 2, 3, 4, 5, 6, 7, 8, 9] },
    {
        name: "inputMat4", type: "mat4", value: [
            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        ]
    },

    // 采样器类型
    { name: "inputSampler2D", type: "sampler2D", value: "./texture1.png" }, // 这里的 0 是纹理单元编号
    { name: "inputSampler2D2", type: "sampler2D", value: "./texture2.png" }, // 这里的 0 是纹理单元编号
    { name: "inputSamplerCube", type: "samplerCube", value: ["./texture2.png", "./texture2.png", "./texture2.png", "./texture2.png", "./texture2.png", "./texture2.png"] } // 这里的 1 是纹理单元编号
];
const webGLShaderToy = new WebGLShaderToy(canvasDom, fragmentShaderSource, customUniforms);
webGLShaderToy.startAnimation();

// 示例:修改片段着色器
setTimeout(() => {
    const newFragmentShaderSource = `
        // precision mediump float;

        // uniform vec2 iResolution;
        // uniform float iTime;
        // uniform vec3 iColor1;
        // uniform vec3 iColor2;
        // uniform vec3 iColor3;
        // uniform vec3 iColor4;
        // uniform vec3 iColor5;
        // uniform vec3 iColor6;
        // uniform vec3 iColor7;
        // uniform vec3 iColor8;
        // uniform vec3 iColor9;
        // uniform vec3 iColor10;

        void main() {
              vec2 st = gl_FragCoord.xy / iResolution.xy;  // 归一化纹理坐标
        vec3 color = mix(iColor7, iColor10, st.y);

        // 对纹理进行采样,使用归一化的纹理坐标
        vec4 texColor = texture2D(inputSampler2D, st);

        // 使用采样得到的颜色作为输出
        gl_FragColor = vec4(texColor);
        }
    `;
    webGLShaderToy.changeFragmentShader(newFragmentShaderSource, { customUniforms });
}, 5000);

// // 示例:更新画布大小
// // setTimeout(() => {
// //     webGLShaderToy.updateCanvasSize(800, 600);
// // }, 10000);
// 示例:更新画布大小
setTimeout(() => {
    webGLShaderToy.updateCanvasSize(1920, 1080);
}, 10000);

// 示例:暂停动画
setTimeout(() => {
    console.log("pauseAnimation");
    webGLShaderToy.pauseAnimation();
}, 15000);

// 示例:恢复动画
setTimeout(() => {
    console.log("resumeAnimation");
    webGLShaderToy.resumeAnimation();
}, 20000);

相关文章:

  • 102.二叉树的层序遍历- 力扣(LeetCode)
  • JavaScript Number 对象
  • Unity中使用FMETP STREAM传输实时画面
  • python全栈-vue框架
  • Hibernate:让对象与数据库无缝对话的全自动ORM框架
  • CesiumEarth能够本地浏览的三维倾斜模型切片(3DTiles)
  • GESP2025年3月认证C++七级( 第三部分编程题(2)等价消除)
  • 图像形态学操作对比(Opencv)
  • VSCode中选择Anaconda的Python环境
  • java数组06:Arrays类
  • 数据结构--线性表
  • 让你方便快捷实现主题色切换(useCssVar)
  • 【征程 6】工具链 VP 示例中 Cmakelists 解读
  • 创建虚拟环境无法加载到pycharm当conda环境,只能为python环境
  • C语言-字符串操作函数手册:语法、技巧与经典应用
  • FreeRTOS使任务处于挂起态的API
  • 小白学习java第11天(下):多线程详解
  • MergeX亮相GTC2025:开启全球广告流量交易新篇章
  • ​asm汇编源代码之-汉字点阵字库显示程序源代码下载​
  • JAVA——初识JAVA
  • web 网页设计/优化公司哪家好
  • 四川成都疫情最新动态/厦门站长优化工具
  • 网站制作的管理/痘痘该如何去除效果好
  • 引导企业做网站/新品上市怎么做宣传推广
  • 广州做网站建设的公司/网络推广的方式有哪些
  • 重庆光龙网站建设/so导航 抖音