【视觉高级篇】19 # 如何用着色器实现像素动画?

阅读 19

2022-11-12


说明

【跟月影学可视化】学习笔记。

如何用着色器实现固定帧动画

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>如何用着色器实现固定帧动画</title>
<style>
canvas {
border: 1px dashed salmon;
}
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script>
const vertex = `
attribute vec2 a_vertexPosition;
attribute vec2 uv;

varying vec2 vUv;

void main() {
gl_PointSize = 1.0;
vUv = uv;
gl_Position = vec4(a_vertexPosition, 1, 1);
}
`;

const fragment = `
#ifdef GL_ES
precision highp float;
#endif

varying vec2 vUv;
uniform sampler2D tMap;
uniform float fWidth;
uniform vec2 vFrames[3];
uniform int frameIndex;

void main() {
vec2 uv = vUv;
// 动画只有 3 帧,所以最多只需要循环 3 次。
for (int i = 0; i < 3; i++) {
// vFrames 是每一帧动画的图片起始 x 和结束 x 坐标
uv.x = mix(vFrames[i].x, vFrames[i].y, vUv.x) / fWidth;
if(float(i) == mod(float(frameIndex), 3.0)) break;
}
vec4 color = texture2D(tMap, uv);
gl_FragColor = color;
}
`;

const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);

const textureURL = "./assets/img/bird.png";
(async function () {
const texture = await renderer.loadTexture(textureURL);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
renderer.uniforms.tMap = texture;
renderer.uniforms.fWidth = 272; // 图片的总宽度
renderer.uniforms.vFrames = [2, 88, 90, 176, 178, 264];
renderer.uniforms.frameIndex = 0;
setInterval(() => {
renderer.uniforms.frameIndex++;
}, 200);
const x = 43 / canvas.width;
const y = 30 / canvas.height;
renderer.setMeshData([
{
positions: [
[-x, -y],
[-x, y],
[x, y],
[x, -y],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
],
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();
})();
</script>
</body>
</html>

【视觉高级篇】19 # 如何用着色器实现像素动画?_html

如何用着色器实现非固定帧动画

WebGL 有两种 Shader,分别是顶点着色器和片元着色器,它们都可以用来实现动画。在绘制一帧画面的时候,顶点着色器的运算量会大大少于片元着色器,所以使用顶点着色器消耗的性能更少。片元着色器可以使用重复、随机、噪声等技巧来绘制更加复杂的效果。

用顶点着色器实现非固定帧动画

在顶点着色器中,我们直接改变了顶点坐标,所以这样实现的旋转动画和 WebGL 坐标系(右手系)的方向一致,角度增大呈逆时针方向旋转。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>用顶点着色器实现非固定帧动画</title>
<style>
canvas {
border: 1px dashed salmon;
}
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script>
const vertex = `
attribute vec2 a_vertexPosition;
attribute vec2 uv;

varying vec2 vUv;
uniform float rotation;

void main() {
gl_PointSize = 1.0;
vUv = uv;
float c = cos(rotation);
float s = sin(rotation);
mat3 transformMatrix = mat3(
c, s, 0,
-s, c, 0,
0, 0, 1
);
vec3 pos = transformMatrix * vec3(a_vertexPosition, 1);
gl_Position = vec4(pos, 1);
}
`;

const fragment = `
#ifdef GL_ES
precision highp float;
#endif

varying vec2 vUv;
uniform vec4 color;

void main() {
gl_FragColor = color;
}
`;

const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);

renderer.uniforms.color = [250/255, 128/255, 114/255, 1];
renderer.uniforms.rotation = 0.0;

renderer.setMeshData([
{
positions: [
[-0.5, -0.5],
[-0.5, 0.5],
[0.5, 0.5],
[0.5, -0.5],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
],
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();

function update() {
renderer.uniforms.rotation += 0.05;
requestAnimationFrame(update);
}

update();
</script>
</body>
</html>

【视觉高级篇】19 # 如何用着色器实现像素动画?_动画_02

用片元着色器实现非固定帧动画

在片元着色器中,我们的绘制原理是通过距离场着色来实现的,所以这里的旋转实际上改变的是距离场的角度而不是图形角度,最终绘制的图形也是相对于距离场的。又因为距离场逆时针旋转,所以图形就顺时针旋转了。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>用片元着色器实现非固定帧动画</title>
<style>
canvas {
border: 1px dashed salmon;
}
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script>
const vertex = `
attribute vec2 a_vertexPosition;
attribute vec2 uv;

varying vec2 vUv;

void main() {
gl_PointSize = 1.0;
vUv = uv;
gl_Position = vec4(a_vertexPosition, 1, 1);
}
`;

const fragment = `
#ifdef GL_ES
precision highp float;
#endif

varying vec2 vUv;
uniform vec4 color;
uniform float rotation;

void main() {
vec2 st = 2.0 * (vUv - vec2(0.5));
float c = cos(rotation);
float s = sin(rotation);
mat3 transformMatrix = mat3(
c, s, 0,
-s, c, 0,
0, 0, 1
);
vec3 pos = transformMatrix * vec3(st, 1.0);
float d1 = 1.0 - smoothstep(0.5, 0.505, abs(pos.x));
float d2 = 1.0 - smoothstep(0.5, 0.505, abs(pos.y));
gl_FragColor = d1 * d2 * color;
}
`;

const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);

renderer.uniforms.color = [250/255, 128/255, 114/255, 1];
renderer.uniforms.rotation = 0.0;

renderer.setMeshData([
{
positions: [
[-1, -1],
[-1, 1],
[1, 1],
[1, -1],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
],
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();

function update() {
renderer.uniforms.rotation += 0.05;
requestAnimationFrame(update);
}

update();
</script>
</body>
</html>

【视觉高级篇】19 # 如何用着色器实现像素动画?_html_03

绘制大量重复的旋转正方形

利用网格实现了大量的重复动画,充分利用了 GPU 的并行效率,比用其他方式把图形一个一个地绘制出来性能要高得多。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>大量重复的旋转正方形</title>
<style>
canvas {
border: 1px dashed salmon;
}
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script>
const vertex = `
attribute vec2 a_vertexPosition;
attribute vec2 uv;

varying vec2 vUv;

void main() {
gl_PointSize = 1.0;
vUv = uv;
gl_Position = vec4(a_vertexPosition, 1, 1);
}
`;

const fragment = `
#ifdef GL_ES
precision highp float;
#endif

varying vec2 vUv;
uniform float rotation;

float random (vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123);
}

vec3 hsb2rgb(vec3 c){
vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 6.0)-3.0)-1.0, 0.0, 1.0);
rgb = rgb * rgb * (3.0 - 2.0 * rgb);
return c.z * mix(vec3(1.0), rgb, c.y);
}

void main() {
vec2 f_uv = fract(vUv * 10.0);
vec2 i_uv = floor(vUv * 10.0);
vec2 st = 2.0 * (f_uv - vec2(0.5));
float c = 0.7 * cos(rotation);
float s = 0.7 * sin(rotation);
mat3 transformMatrix = mat3(
c, s, 0,
-s, c, 0,
0, 0, 1
);
vec3 pos = transformMatrix * vec3(st, 1.0);
float d1 = 1.0 - smoothstep(0.5, 0.505, abs(pos.x));
float d2 = 1.0 - smoothstep(0.5, 0.505, abs(pos.y));
gl_FragColor = d1 * d2 * vec4(hsb2rgb(vec3(random(i_uv), 1.0, 1.0)), 1.0);
}
`;

const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);

renderer.uniforms.rotation = 0.0;

renderer.setMeshData([
{
positions: [
[-1, -1],
[-1, 1],
[1, 1],
[1, -1],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
],
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();

function update() {
renderer.uniforms.rotation += 0.05;
requestAnimationFrame(update);
}

update();
</script>
</body>
</html>

【视觉高级篇】19 # 如何用着色器实现像素动画?_帧动画_04

如何在着色器中实现缓动函数与非线性插值

实现轨迹动画

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>实现轨迹动画</title>
<style>
canvas {
border: 1px dashed salmon;
}
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script type="module">
import { Animator } from "./common/lib/animator/index.js";

const vertex = `
attribute vec2 a_vertexPosition;
attribute vec2 uv;

varying vec2 vUv;
uniform vec2 translation;

void main() {
gl_PointSize = 1.0;
vUv = uv;
mat3 transformMatrix = mat3(
1, 0, 0,
0, 1, 0,
translation, 1
);
vec3 pos = transformMatrix * vec3(a_vertexPosition, 1);
gl_Position = vec4(pos, 1);
}
`;

const fragment = `
#ifdef GL_ES
precision highp float;
#endif

varying vec2 vUv;
uniform vec4 color;

void main() {
gl_FragColor = color;
}
`;

const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
renderer.uniforms.color = [250/255, 128/255, 114/255, 1];
renderer.uniforms.translation = [-0.5, 0];

const animator = new Animator({ duration: 2000 });
animator.animate(renderer, ({ target, timing }) => {
target.uniforms.translation = [
-0.5 * (1 - timing.p) + 0.5 * timing.p,
0,
];
});

renderer.setMeshData([
{
positions: [
[-0.25, -0.25],
[-0.25, 0.25],
[0.25, 0.25],
[0.25, -0.25],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
],
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();
</script>
</body>
</html>

【视觉高级篇】19 # 如何用着色器实现像素动画?_动画_05

实现缓动效果的轨迹动画

下面我们不使用 Animator,而是直接将时间 uTime 参数传入 Shader,然后在 Shader 中加入缓动函数。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>实现缓动效果的轨迹动画</title>
<style>
canvas {
border: 1px dashed salmon;
}
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script type="module">
const vertex = `
attribute vec2 a_vertexPosition;
attribute vec2 uv;

varying vec2 vUv;
uniform vec4 uFromTo;
uniform float uTime;

// 缓动函数
float easing(in float p) {
// return smoothstep(0.0, 1.0, p);
// return clamp(p * p, 0.0, 1.0);
return clamp(p * (2.0 - p), 0.0, 1.0);
}

void main() {
gl_PointSize = 1.0;
vUv = uv;
vec2 from = uFromTo.xy;
vec2 to = uFromTo.zw;
float p = easing(uTime / 2.0);
vec2 translation = mix(from, to, p);
mat3 transformMatrix = mat3(
1, 0, 0,
0, 1, 0,
translation, 1
);
vec3 pos = transformMatrix * vec3(a_vertexPosition, 1);
gl_Position = vec4(pos, 1);
}
`;

const fragment = `
#ifdef GL_ES
precision highp float;
#endif

varying vec2 vUv;
uniform vec4 color;

void main() {
gl_FragColor = color;
}
`;

const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
renderer.uniforms.color = [250/255, 128/255, 114/255, 1];
renderer.uniforms.uTime = 0;
renderer.uniforms.uFromTo = [-0.5, 0, 0.5, 0];

let startTime = null;
requestAnimationFrame(function update() {
startTime = startTime || Date.now();
renderer.uniforms.uTime = (Date.now() - startTime) / 1000;
requestAnimationFrame(update);
});

renderer.setMeshData([
{
positions: [
[-0.25, -0.25],
[-0.25, 0.25],
[0.25, 0.25],
[0.25, -0.25],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
],
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();
</script>
</body>
</html>

【视觉高级篇】19 # 如何用着色器实现像素动画?_着色器_06

线性的颜色过渡

在正常情况下,顶点着色器定义的变量在片元着色器中,都会被线性插值。

下面一个长方形,它的颜色会从左到右,由红色线性地过渡到绿色。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>线性的颜色过渡</title>
<style>
canvas {
border: 1px dashed salmon;
}
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script type="module">
const vertex = `
attribute vec2 a_vertexPosition;
attribute vec2 uv;
attribute vec4 color;

varying vec2 vUv;
varying vec4 vColor;
uniform vec4 uFromTo;
uniform float uTime;

void main() {
gl_PointSize = 1.0;
vUv = uv;
vColor = color;
gl_Position = vec4(a_vertexPosition, 1, 1);
}
`;

const fragment = `
#ifdef GL_ES
precision highp float;
#endif
varying vec2 vUv;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`;

const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);

renderer.setMeshData([
{
positions: [
[-0.5, -0.25],
[-0.5, 0.25],
[0.5, 0.25],
[0.5, -0.25],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
],
color: [
[1, 0, 0, 1],
[1, 0, 0, 1],
[0, 0.5, 0, 1],
[0, 0.5, 0, 1],
],
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();
</script>
</body>
</html>

【视觉高级篇】19 # 如何用着色器实现像素动画?_帧动画_07

非线性的颜色过渡

在片元着色器中可以采用 uniform 的方式,用 easing 来实现非线性的插值。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>非线性的颜色过渡</title>
<style>
canvas {
border: 1px dashed salmon;
}
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script type="module">
const vertex = `
attribute vec2 a_vertexPosition;
attribute vec2 uv;

varying vec2 vUv;
uniform float uTime;

void main() {
gl_PointSize = 1.0;
vUv = uv;
gl_Position = vec4(a_vertexPosition, 1, 1);
}
`;

const fragment = `
#ifdef GL_ES
precision highp float;
#endif

float easing(in float p) {
// return smoothstep(0.0, 1.0, p);
// return clamp(p * p, 0.0, 1.0);
return clamp(p * (2.0 - p), 0.0, 1.0);
}

varying vec2 vUv;
uniform vec4 fromColor;
uniform vec4 toColor;

void main() {
float d = easing(vUv.x);
gl_FragColor = mix(fromColor, toColor, d);
}
`;

const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);

renderer.uniforms.fromColor = [1, 0, 0, 1];
renderer.uniforms.toColor = [0, 0.5, 0, 1];

renderer.setMeshData([
{
positions: [
[-0.5, -0.25],
[-0.5, 0.25],
[0.5, 0.25],
[0.5, -0.25],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
]
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();
</script>
</body>
</html>

【视觉高级篇】19 # 如何用着色器实现像素动画?_帧动画_08

贝塞尔曲线插值色带

可以参考:​​http://flong.com/archive/texts/code/shapers_bez/index.html​​

【视觉高级篇】19 # 如何用着色器实现像素动画?_着色器_09

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>贝塞尔曲线插值色带</title>
<style>
canvas {
border: 1px dashed salmon;
}
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script type="module">
const vertex = `
attribute vec2 a_vertexPosition;
attribute vec2 uv;

varying vec2 vUv;
uniform float uTime;

void main() {
gl_PointSize = 1.0;
vUv = uv;
gl_Position = vec4(a_vertexPosition, 1, 1);
}
`;

const fragment = `
#ifdef GL_ES
precision highp float;
#endif

// http://flong.com/archive/texts/code/shapers_bez/index.html
// Helper functions:
float slope_from_t (float t, float A, float B, float C){
float dtdx = 1.0/(3.0*A*t*t + 2.0*B*t + C);
return dtdx;
}

float x_from_t (float t, float A, float B, float C, float D){
float x = A*(t*t*t) + B*(t*t) + C*t + D;
return x;
}

float y_from_t (float t, float E, float F, float G, float H){
float y = E*(t*t*t) + F*(t*t) + G*t + H;
return y;
}

float cubic_bezier (float x, float a, float b, float c, float d){
float y0a = 0.00; // initial y
float x0a = 0.00; // initial x
float y1a = b; // 1st influence y
float x1a = a; // 1st influence x
float y2a = d; // 2nd influence y
float x2a = c; // 2nd influence x
float y3a = 1.00; // final y
float x3a = 1.00; // final x

float A = x3a - 3.0 *x2a + 3.0 * x1a - x0a;
float B = 3.0 * x2a - 6.0 * x1a + 3.0 * x0a;
float C = 3.0 * x1a - 3.0 * x0a;
float D = x0a;

float E = y3a - 3.0 * y2a + 3.0 * y1a - y0a;
float F = 3.0 * y2a - 6.0 * y1a + 3.0 * y0a;
float G = 3.0 * y1a - 3.0 * y0a;
float H = y0a;

// Solve for t given x (using Newton-Raphelson), then solve for y given t.
// Assume for the first guess that t = x.
float currentt = x;
const int nRefinementIterations = 5;
for (int i=0; i < nRefinementIterations; i++){
float currentx = x_from_t(currentt, A,B,C,D);
float currentslope = slope_from_t(currentt, A,B,C);
currentt -= (currentx - x)*(currentslope);
currentt = clamp(currentt, 0.0, 1.0);
}

float y = y_from_t(currentt, E,F,G,H);
return y;
}

vec3 hsb2rgb(vec3 c){
vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 6.0)-3.0)-1.0, 0.0, 1.0);
rgb = rgb * rgb * (3.0 - 2.0 * rgb);
return c.z * mix(vec3(1.0), rgb, c.y);
}

varying vec2 vUv;

void main() {
// float d = vUv.x;
float d = cubic_bezier(vUv.x, 0.5, -1.5, 0.5, 2.5);
gl_FragColor.rgb = hsb2rgb(vec3(d, 1.0, 1.0));
gl_FragColor.a = 1.0;
}
`;

const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);

renderer.setMeshData([
{
positions: [
[-0.5, -0.25],
[-0.5, 0.25],
[0.5, 0.25],
[0.5, -0.25],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
]
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();
</script>
</body>
</html>

【视觉高级篇】19 # 如何用着色器实现像素动画?_帧动画_10

如何在片元着色器中实现随机粒子动画

可以使用随机 + 噪声来实现一个粒子效果。

首先,我们设置随机数用来生成距离场的初始值,然后设置噪声用来形成位移,最后传入 uTime 变量来实现动画。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>如何在片元着色器中实现随机粒子动画</title>
<style>
canvas {
border: 1px dashed salmon;
}
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script>
const vertex = `
attribute vec2 a_vertexPosition;
attribute vec2 uv;
attribute vec4 color;

varying vec2 vUv;

void main() {
gl_PointSize = 1.0;
vUv = uv;
gl_Position = vec4(a_vertexPosition, 1, 1);
}
`;
const fragment = `
#ifdef GL_ES
precision highp float;
#endif

highp float random(vec2 co) {
highp float a = 12.9898;
highp float b = 78.233;
highp float c = 43758.5453;
highp float dt= dot(co.xy ,vec2(a,b));
highp float sn= mod(dt,3.14);
return fract(sin(sn) * c);
}

// Value Noise by Inigo Quilez - iq/2013
// https://www.shadertoy.com/view/lsf3WH
highp float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix( mix( random( i + vec2(0.0,0.0) ),
random( i + vec2(1.0,0.0) ), u.x),
mix( random( i + vec2(0.0,1.0) ),
random( i + vec2(1.0,1.0) ), u.x), u.y
);
}

float sdf_circle(vec2 st, vec2 c, float r) {
return 1.0 - length(st - c) / r;
}

varying vec2 vUv;
uniform float uTime;

void main() {
vec2 st = vUv;
float rx = mix(-0.2, 0.2, noise(vec2(7881.32, 0) + random(st) + uTime));
float ry = mix(-0.2, 0.2, noise(vec2(0, 1433.59) + random(st) + uTime));

float dis = distance(st, vec2(0.5));
dis = pow((1.0 - dis), 2.0);

float d = sdf_circle(st + vec2(rx, ry), vec2(0.5), 0.2);
d = smoothstep(0.0, 0.1, d);

gl_FragColor = vec4(dis * d * vec3(1.0), 1.0);
}
`;
const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);

renderer.uniforms.uTime = 0;
requestAnimationFrame(function update(t) {
renderer.uniforms.uTime = 0.001 * t;
requestAnimationFrame(update);
});

renderer.setMeshData([
{
positions: [
[-1, -1],
[-1, 1],
[1, 1],
[1, -1],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
],
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();
</script>
</body>
</html>

【视觉高级篇】19 # 如何用着色器实现像素动画?_着色器_11


精彩评论(0)

0 0 举报