如何在WebGL/GLSL中实现类似ShaderToy的iTime时间变量动画?
嘿,要实现类似ShaderToy里的iTime动画效果其实很简单,核心就是给着色器添加一个时间uniform变量,然后在JS里循环更新这个变量并重新绘制画面。我给你一步步拆解修改:
1. 修改着色器,添加时间变量
首先在顶点或片元着色器里声明一个全局的uniform float变量(对应ShaderToy的iTime),你可以用它修改顶点位置、颜色等属性来实现动画。下面是修改后的代码示例:
修改后的顶点着色器(可选添加顶点动画)
attribute vec4 a_position; uniform float u_time; // 新增时间uniform变量 varying vec4 v_color; void main() { // 用时间让顶点做小幅偏移动画 vec2 animatedPos = a_position.xy + vec2(sin(u_time) * 0.2, cos(u_time) * 0.2); gl_Position = vec4(animatedPos, 0.0, 1.0); v_color = gl_Position * 0.5 + 0.5; }
修改后的片元着色器(用时间修改颜色)
precision mediump float; uniform float u_time; // 新增时间uniform变量 varying vec4 v_color; void main() { // 让颜色随时间做色相偏移 vec3 hueCycle = vec3(sin(u_time), cos(u_time), sin(u_time + 1.57)); gl_FragColor = vec4(v_color.rgb * hueCycle * 0.5 + 0.5, 1.0); }
2. 在JS代码中实现动画循环
接下来要在JS里完成三个关键操作:获取uniform变量的位置、创建动画循环、每一帧更新时间并重新绘制。修改后的JS代码如下:
var gl = document.getElementById("canvas").getContext('webgl'); var vertexShader = createShader(gl, gl.VERTEX_SHADER, window.vert); var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, window.frag); var program = createProgram(gl, vertexShader, fragmentShader); // --- 新增:获取时间uniform的位置 --- var timeUniformLocation = gl.getUniformLocation(program, "u_time"); var positionAttributeLocation = gl.getAttribLocation(program, "a_position"); var positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); var positions = [ -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); resizeCanvas(gl); gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); // --- 新增:动画循环函数 --- function animate(currentTime) { // 将毫秒转成秒,和ShaderToy的iTime单位保持一致 var timeInSeconds = currentTime * 0.001; gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); gl.useProgram(program); // --- 新增:把当前时间传入着色器 --- gl.uniform1f(timeUniformLocation, timeInSeconds); gl.enableVertexAttribArray(positionAttributeLocation); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); var size = 2; var type = gl.FLOAT; var normalize = false; var stride = 0; var offset = 0; gl.vertexAttribPointer( positionAttributeLocation, size, type, normalize, stride, offset); var primitiveType = gl.TRIANGLES; var offset = 0; var count = 6; gl.drawArrays(primitiveType, offset, count); // 持续请求下一帧动画 requestAnimationFrame(animate); } // --- 启动动画循环 --- requestAnimationFrame(animate);
3. 关键细节说明
- Uniform变量:这是JS向着色器传递全局数据的标准方式,适合时间、画布分辨率这类每一帧都可能变化的全局参数。
- 时间单位转换:
requestAnimationFrame传入的currentTime是毫秒数,转成秒后和ShaderToy的iTime行为完全一致,方便你复用ShaderToy里的时间相关逻辑。 - 动画循环:用
requestAnimationFrame替代setInterval,它会和浏览器的刷新频率同步,避免画面卡顿。
这样修改后,你就能看到画面随时间动起来了!你可以自由调整着色器里的时间逻辑,比如用u_time计算缩放、旋转、波形动画等,玩法和ShaderToy几乎一致。
内容的提问来源于stack exchange,提问作者Mudassir Ali
相关产品推荐
相关产品推荐

