功能简介
可以播放,暂停shader代码,可以在js中配置shader参数(下面案例列举了所有可用参数形式)
缺点
这个是固定机位,没有自定义顶点着色器部分的功能,有需要可直接在class中改,或者修改后调用更新片元着色器的方法。
工具
class WebGLShaderToy {constructor(canvas, fragmentShaderSource, customUniforms) {this.canvas = canvas;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;}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)}}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);}}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);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);};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()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);}
`;
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" }, { name: "inputSampler2D2", type: "sampler2D", value: "./texture2.png" }, { name: "inputSamplerCube", type: "samplerCube", value: ["./texture2.png", "./texture2.png", "./texture2.png", "./texture2.png", "./texture2.png", "./texture2.png"] }
];
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(1920, 1080);
}, 10000);
setTimeout(() => {console.log("pauseAnimation");webGLShaderToy.pauseAnimation();
}, 15000);
setTimeout(() => {console.log("resumeAnimation");webGLShaderToy.resumeAnimation();
}, 20000);