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

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

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


实现轨迹动画

<!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>

image.gif

实现缓动效果的轨迹动画

下面我们不使用 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>

56f54bd6c3c04d28ab1c9d701026af6c.gif


线性的颜色过渡

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

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

<!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>

a660444a01b3465d85ca2f855796719e.png


非线性的颜色过渡

在片元着色器中可以采用 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>


73802b000f324050af1cd6f8ab7aaf04.png

贝塞尔曲线插值色带

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


a1c4a12a2f804ad89db2199d6e05952c.png

<!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>

4a62018d69c94895bc25d6ff12d39e03.png


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

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

首先,我们设置随机数用来生成距离场的初始值,然后设置噪声用来形成位移,最后传入 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>

bd8e175440f8483c8a2643df4ccb38f6.gif

目录
相关文章
|
监控 关系型数据库 MySQL
Nacos架构与原理 - 健康检查机制
Nacos架构与原理 - 健康检查机制
411 0
|
前端开发 安全 JavaScript
Python的Flask框架的学习笔记(前后端变量传送,文件上传,网页返回)内含实战:实现一个简单的登录页面
Python的Flask框架的学习笔记(前后端变量传送,文件上传,网页返回)内含实战:实现一个简单的登录页面
361 0
|
存储 Java 关系型数据库
学成在线笔记+踩坑(0)——面试问题
介绍你的项目、项目难点、表是怎么设计的?、断点续传是怎么做的?、如何保证任务不重复执行? 、任务幂等性如何保证、分布式锁的三种实现方式
学成在线笔记+踩坑(0)——面试问题
|
12月前
|
机器学习/深度学习 存储 测试技术
从0到1:如何规划一套流量回放自动化测试方案
本文介绍了流量回放自动化测试的完整方法,从企业战略到交付的四个关键环节:Discovery(深度挖掘)、Define(定义目标)、Design(详细设计)和Delivery(交付与反馈)。通过这些步骤,帮助企业优化系统性能和稳定性,确保产品的高质量。
270 4
|
算法 测试技术 编译器
【算法 | 实验18】在字符矩阵中查找给定字符串的所有匹配项
题目描述 题目 在字符矩阵中查找给定字符串的所有匹配项 给定一个M×N字符矩阵,以及一个字符串S,找到在矩阵中所有可能的连续字符组成的S的次数。所谓的连续字符,是指一个字符可以和位于其上下左右,左上左下,右上右下8个方向的字符组成字符串。用回溯法求解。
220 1
|
JavaScript
vue项目使用可选链操作符编译报错问题
vue项目使用可选链操作符编译报错问题
1311 1
|
存储 前端开发 Java
【源码共读】在前端如何操作 Cookie
【源码共读】在前端如何操作 Cookie
217 1
|
存储 安全 关系型数据库
PostgreSQL透明数据加密
PostgreSQL透明数据加密
438 0
|
JavaScript 前端开发
原生js实现ajax请求带请求头header
原生js实现ajax请求带请求头header
278 0
|
Rust 前端开发 JavaScript
用Rust搭建React Server Components 的Web服务器(二)
用Rust搭建React Server Components 的Web服务器(二)
225 0