功能简介
可以播放,暂停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);