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

目录
相关文章
|
前端开发
妙用滤镜构建高级感拉满的磨砂玻璃渐变背景
妙用滤镜构建高级感拉满的磨砂玻璃渐变背景
192 0
妙用滤镜构建高级感拉满的磨砂玻璃渐变背景
|
数据可视化 异构计算
【视觉高级篇】19 # 如何用着色器实现像素动画?
【视觉高级篇】19 # 如何用着色器实现像素动画?
77 0
【视觉高级篇】19 # 如何用着色器实现像素动画?
|
数据可视化
【视觉高级篇】20 # 如何用WebGL绘制3D物体?
【视觉高级篇】20 # 如何用WebGL绘制3D物体?
147 0
【视觉高级篇】20 # 如何用WebGL绘制3D物体?
|
数据可视化 JavaScript 前端开发
【视觉高级篇】18 # 如何生成简单动画让图形动起来?
【视觉高级篇】18 # 如何生成简单动画让图形动起来?
73 0
【视觉高级篇】18 # 如何生成简单动画让图形动起来?
|
数据可视化 索引
【视觉基础篇】14 # 如何使用片元着色器进行几何造型?
【视觉基础篇】14 # 如何使用片元着色器进行几何造型?
75 0
【视觉基础篇】14 # 如何使用片元着色器进行几何造型?
|
缓存 JavaScript 前端开发
【图形基础篇】04 # GPU与渲染管线:如何用WebGL绘制最简单的几何图形?
【图形基础篇】04 # GPU与渲染管线:如何用WebGL绘制最简单的几何图形?
287 0
【图形基础篇】04 # GPU与渲染管线:如何用WebGL绘制最简单的几何图形?
|
数据可视化
【视觉基础篇】15 # 如何用极坐标系绘制有趣图案?
【视觉基础篇】15 # 如何用极坐标系绘制有趣图案?
135 0
【视觉基础篇】15 # 如何用极坐标系绘制有趣图案?
|
算法 前端开发 JavaScript
【视觉基础篇】10 # 图形系统如何表示颜色?
【视觉基础篇】10 # 图形系统如何表示颜色?
151 0
【视觉基础篇】10 # 图形系统如何表示颜色?
|
传感器 JSON 数据可视化
【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?
【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?
158 0
【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?
|
缓存 图形学
计算机图形学——实验一 VS+OpenGL绘图环境及基本图形绘制
VS+OpenGL绘图环境及基本图形绘制 1、 熟悉OpenGL的主要功能; 2、 掌握OpenGL的绘图流程和原理; 3、 掌握OpenGL核心函数的使用; 4、 熟悉OpenGL基本图形元素的绘制函数。 二. 实验内容 1、 如预备知识所述,创建一个OpenGL工程,修改第一个程序中的Display()函数,如下: 该程序是在窗口中画两条线,分别用黄色和红色绘制。如上所述,OpenGL是一个状态机,glBegin(UINT State)可以设定如下状态: GL_POINTS 画点 GL_LINES 画线
424 0
计算机图形学——实验一 VS+OpenGL绘图环境及基本图形绘制