【视觉高级篇】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

目录
相关文章
|
网络安全 数据中心
百度搜索:蓝易云【Proxmox软件防火墙的配置教程】
现在,你已经完成了Proxmox软件防火墙的配置。请确保你的防火墙规则设置正确,以保护你的Proxmox VE环境免受未经授权的访问和网络攻击。
390 5
|
存储 负载均衡 网络协议
LVS负载均衡群集部署——DR模式
1、LVS—DR概述 2、部署实验
LVS负载均衡群集部署——DR模式
|
7月前
|
存储 数据管理 数据格式
数据治理 vs. 数据管理:别再傻傻分不清!
数据治理 vs. 数据管理:别再傻傻分不清!
359 10
|
5月前
|
存储 并行计算 算法
CUDA性能优化实战:7个步骤让并行归约算法提升10倍效率
https://avoid.overfit.cn/post/af59d0a6ce474b8fa7a8eafb2117a404
328 1
CUDA性能优化实战:7个步骤让并行归约算法提升10倍效率
|
12月前
|
传感器 存储 算法
《探索机器人自主导航与路径规划技术的热点》
机器人自主导航与路径规划技术是当今科技热点,广泛应用于工业生产、物流配送及日常生活服务。传感器(如激光雷达、摄像头)、定位技术(如GPS、IPS)和地图构建为机器人提供环境感知能力。路径规划涉及搜索算法(如DFS、BFS、A*)和优化算法,确保机器人在复杂环境中高效、准确地完成任务。实时规划则使机器人能动态调整路径,适应环境变化。这些技术的不断进步正推动机器人在各领域的广泛应用,提升效率与便利性。
503 9
|
机器学习/深度学习 文字识别 算法
OCR -- 非极大值抑制(NMS)算法详解
OCR -- 非极大值抑制(NMS)算法详解
502 0
OCR -- 非极大值抑制(NMS)算法详解
|
机器学习/深度学习 自然语言处理 语音技术
深度学习中的迁移学习:优势与应用探索
传统深度学习模型在数据不足或特定任务下表现不佳,迁移学习则通过利用预训练模型的知识来解决这一问题。本文探讨了迁移学习的基本原理、不同方法以及在实际应用中的案例分析,旨在帮助读者更好地理解和应用迁移学习技术。 【7月更文挑战第6天】
509 5
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp微信小程序的工作流程管理系统的详细设计和实现
基于SpringBoot+Vue+uniapp微信小程序的工作流程管理系统的详细设计和实现
272 1
|
缓存 easyexcel Java
狂神说POI,EasyExcel笔记及源码资料(一)
狂神说POI,EasyExcel笔记及源码资料(一)
1178 0
狂神说POI,EasyExcel笔记及源码资料(一)