本文将介绍如何使用WebGL2创建一个动态的图像效果,该效果基于一个经典的着色器。我们将使用JavaScript和GLSL编写代码,并通过使用顶点着色器和片段着色器将其传递给WebGL上下文。通过学习这个例子,你将了解一些基本的WebGL概念,如着色器编程、顶点缓冲区和Uniform变量。 在本文中,我们首先创建一个用于渲染的canvas元素并获取WebGL上下文。然后,我们设置一些基本的样式和初始化参数。接下来,我们编写顶点着色器和片段着色器的源代码,并将其编译为WebGL着色器对象。我们还创建了一个程序对象,并将顶点着色器和片段着色器附加到该程序对象上,并链接它们。 通过使用缓冲区对象,我们将顶点数据发送到顶点着色器中,并通过属性变量将其与顶点着色器关联起来。然后,我们设置一些Uniform变量,以便在渲染过程中传递给片段着色器。最后,我们使用requestAnimationFrame函数循环调用渲染函数,以达到动画效果。 我们还添加了鼠标和触摸事件监听器,以便在用户交互时更新鼠标坐标和触摸信息。这样,我们可以根据鼠标和触摸的位置和数量来改变片段着色器中的图像效果。
canvas
canvas.getContext("webgl2")
获取WebGL上下文对象,并赋值给变量gl
const canvas = document.createElement("canvas")
const gl = canvas.getContext("webgl2")
document.title = "??"
document.body.innerHTML = ""
document.body.appendChild(canvas)
document.body.style = "margin:0;touch-action:none;overflow:hidden;"
canvas.style.width = "100%"
canvas.style.height = "auto"
canvas.style.userSelect = "none"
Math.max(1, .5*window.devicePixelRatio)
计算设备像素比,并赋值给变量dpr
resize
的函数,用于在窗口大小变化时调整canvas的大小和视口const dpr = Math.max(1, .5*window.devicePixelRatio)
function resize() {
const {
innerWidth: width,
innerHeight: height
} = window
canvas.width = width * dpr
canvas.height = height * dpr
gl.viewport(0, 0, width * dpr, height * dpr)
}
window.onresize = resize
const vertexSource = `#version 300 es
// 省略部分代码...
`
const fragmentSource = `#version 300 es
// 省略部分代码...
`
compile
的函数用于编译着色器源代码setup
的函数,用于创建并链接程序对象,并将着色器附加到程序中function compile(shader, source) {
gl.shaderSource(shader, source)
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader))
}
}
let program
function setup() {
const vs = gl.createShader(gl.VERTEX_SHADER)
const fs = gl.createShader(gl.FRAGMENT_SHADER)
compile(vs, vertexSource)
compile(fs, fragmentSource)
program = gl.createProgram()
gl.attachShader(program, vs)
gl.attachShader(program, fs)
gl.linkProgram(program)
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program))
}
}
vertices
,表示一个矩形的四个顶点坐标let vertices, buffer
function init() {
vertices = [
-1., -1., 1.,
-1., -1., 1.,
-1., 1., 1.,
-1., 1., 1.,
]
buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
const position = gl.getAttribLocation(program, "position")
gl.enableVertexAttribArray(position)
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)
program.resolution = gl.getUniformLocation(program, "resolution")
program.time = gl.getUniformLocation(program, "time")
program.touch = gl.getUniformLocation(program, "touch")
program.pointerCount = gl.getUniformLocation(program, "pointerCount")
}
mouse
的对象,包含鼠标的x、y坐标和触摸点的集合mouse
对象的update
方法,用于更新鼠标坐标和触摸点集合mouse
对象的remove
方法,用于移除触摸点mouse
对象的相应方法更新鼠标信息setup
函数创建程序对象并编译着色器init
函数初始化顶点数据和缓冲区resize
函数调整canvas的大小和视口loop
函数开始渲染循环mouse.update
方法更新鼠标信息mouse.remove
方法移除触摸点mouse.update
方法更新鼠标信息const mouse = {
x: 0,
y: 0,
touches: new Set(),
update: function(x, y, pointerId) {
this.x = x * dpr;
this.y = (innerHeight - y) * dpr;
this.touches.add(pointerId)
},
remove: function(pointerId) { this.touches.delete(pointerId) }
}
function loop(now) {
gl.clearColor(0, 0, 0, 1)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.useProgram(program)
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.uniform2f(program.resolution, canvas.width, canvas.height)
gl.uniform1f(program.time, now * 1e-3)
gl.uniform2f(program.touch, mouse.x, mouse.y)
gl.uniform1i(program.pointerCount, mouse.touches.size)
gl.drawArrays(gl.TRIANGLES, 0, vertices.length * .5)
requestAnimationFrame(loop)
}
setup()
init()
resize()
loop(0)
window.addEventListener("pointerdown", e => mouse.update(e.clientX, e.clientY, e.pointerId))
window.addEventListener("pointerup", e => mouse.remove(e.pointerId))
window.addEventListener("pointermove", e => {
if (mouse.touches.has(e.pointerId))
mouse.update(e.clientX, e.clientY, e.pointerId)
})
对下面的代码进行分点分标题讲解说明, 并且代码与标题要对应 ;
const canvas = document.createElement("canvas")
const gl = canvas.getContext("webgl2")
document.title = "??"
document.body.innerHTML = ""
document.body.appendChild(canvas)
document.body.style = "margin:0;touch-action:none;overflow:hidden;"
canvas.style.width = "100%"
canvas.style.height = "auto"
canvas.style.userSelect = "none"
const dpr = Math.max(1, .5*window.devicePixelRatio)
function resize() {
const {
innerWidth: width,
innerHeight: height
} = window
canvas.width = width * dpr
canvas.height = height * dpr
gl.viewport(0, 0, width * dpr, height * dpr)
}
window.onresize = resize
const vertexSource = `#version 300 es
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
in vec4 position;
void main(void) {
gl_Position = position;
}
`
const fragmentSource = `#version 300 es
/*********
* made by Matthias Hurrle (@atzedent)
*
* Adaptation of "Quasar" by @kishimisu
* Source: https://www.shadertoy.com/view/msGyzc
*/
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
out vec4 fragColor;
uniform vec2 resolution;
uniform float time;
uniform vec2 touch;
uniform int pointerCount;
#define mouse (touch/resolution)
#define P pointerCount
#define T (10.+time*.5)
#define S smoothstep
#define hue(a) (.6+.6*cos(6.3*(a)+vec3(0,23,21)))
mat2 rot(float a) {
float c = cos(a), s = sin(a);
return mat2(c, -s, s, c);
}
float orbit(vec2 p, float s) {
return floor(atan(p.x, p.y)*s+.5)/s;
}
void cam(inout vec3 p) {
if (P > 0) {
p.yz *= rot(mouse.y*acos(-1.)+acos(.0));
p.xz *= rot(-mouse.x*acos(-1.)*2.);
}
}
void main(void) {
vec2 uv = (
gl_FragCoord.xy-.5*resolution
)/min(resolution.x, resolution.y);
vec3 col = vec3(0), p = vec3(0),
rd = normalize(vec3(uv, 1));
cam(p);
cam(rd);
const float steps = 30.;
float dd = .0;
for (float i=.0; i<steps; i++) {
p.z -= 4.;
p.xz *= rot(T*.2);
p.yz *= rot(sin(T*.2)*.5);
p.zx *= rot(orbit(p.zx, 12.));
float a = p.x;
p.yx *= rot(orbit(p.yx, 2.));
float b = p.x-T;
p.x = fract(b-.5)-.5;
float d = length(p)-(a-S(b+.05,b,T)*30.)*(cos(T)*5e-2+1e-1)*1e-1;
dd += d;
col += (hue(dd)*.04)/(1.+abs(d)*40.);
p = rd * dd;
}
fragColor = vec4(col, 1);
}
`
function compile(shader, source) {
gl.shaderSource(shader, source)
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader))
}
}
let program
function setup() {
const vs = gl.createShader(gl.VERTEX_SHADER)
const fs = gl.createShader(gl.FRAGMENT_SHADER)
compile(vs, vertexSource)
compile(fs, fragmentSource)
program = gl.createProgram()
gl.attachShader(program, vs)
gl.attachShader(program, fs)
gl.linkProgram(program)
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program))
}
}
let vertices, buffer
function init() {
vertices = [
-1., -1., 1.,
-1., -1., 1.,
-1., 1., 1.,
-1., 1., 1.,
]
buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
const position = gl.getAttribLocation(program, "position")
gl.enableVertexAttribArray(position)
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)
program.resolution = gl.getUniformLocation(program, "resolution")
program.time = gl.getUniformLocation(program, "time")
program.touch = gl.getUniformLocation(program, "touch")
program.pointerCount = gl.getUniformLocation(program, "pointerCount")
}
const mouse = {
x: 0,
y: 0,
touches: new Set(),
update: function(x, y, pointerId) {
this.x = x * dpr;
this.y = (innerHeight - y) * dpr;
this.touches.add(pointerId)
},
remove: function(pointerId) { this.touches.delete(pointerId) }
}
function loop(now) {
gl.clearColor(0, 0, 0, 1)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.useProgram(program)
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.uniform2f(program.resolution, canvas.width, canvas.height)
gl.uniform1f(program.time, now * 1e-3)
gl.uniform2f(program.touch, mouse.x, mouse.y)
gl.uniform1i(program.pointerCount, mouse.touches.size)
gl.drawArrays(gl.TRIANGLES, 0, vertices.length * .5)
requestAnimationFrame(loop)
}
setup()
init()
resize()
loop(0)
window.addEventListener("pointerdown", e => mouse.update(e.clientX, e.clientY, e.pointerId))
window.addEventListener("pointerup", e => mouse.remove(e.pointerId))
window.addEventListener("pointermove", e => {
if (mouse.touches.has(e.pointerId))
mouse.update(e.clientX, e.clientY, e.pointerId)
})
css 动画效果是最适合小白学习的案例, 希望本文可以给各位带来帮助哦!! 关注若城, 带你遨游代码的海洋!!