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

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

说明

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



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

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


88be549974b14f25a94c1077986e76dd.gif


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

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>


image.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>
            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>

image.gif


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

利用网格实现了大量的重复动画,充分利用了 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>

image.gif



目录
相关文章
28个残疾人,两个月,被阿里客服改变的命运
十年里,阿里巴巴云客服累计免费培训了35万人,为11万人提供了就业岗位,这28个残疾人也正是这11万人中的一份子。
28个残疾人,两个月,被阿里客服改变的命运
|
JavaScript 前端开发 开发者
Element-UI快速入门
Element-UI 是一个功能强大的 Vue 组件库,非常适合快速开发高质量的企业级前端应用。通过本文的快速入门指南,你应该能够开始使用 Element-UI,并将其组件应用到你的项目中。不断探索和实践将帮助你更好地理解和利用这个工具库为你的应用增添独特的价值。 希望这篇博客能够帮助你快速上手 Element-UI,为你的 Vue 项目加速开发。
674 1
|
机器学习/深度学习 算法 vr&ar
Theta方法:一种时间序列分解与预测的简化方法
Theta方法整合了两个基本概念:分解时间序列和利用基本预测技术来估计未来的价值。
465 0
|
Java Maven 开发者
@EnableFeignClients:简化微服务间调用的艺术
@EnableFeignClients:简化微服务间调用的艺术
1732 2
|
12月前
Cesium制作鹰眼效果
这篇文章介绍了如何在Cesium中实现鹰眼(概览)功能,让用户可以从高空视角俯瞰整个三维地理场景。
234 1
Cesium制作鹰眼效果
|
Java Maven Spring
【操作宝典】IntelliJ IDEA新建maven项目详细教程
【操作宝典】IntelliJ IDEA新建maven项目详细教程
713 1
|
12月前
|
边缘计算 监控 算法
边缘计算的挑战和机遇
讨论了边缘计算面临的挑战和机遇,包括数据安全、网络稳定性、实时性能、异构性兼容性问题,并探索了其在不同应用场景中的潜力和商业模式创新。
510 0
Vue3文字提示(Tooltip)
这是一篇关于 Vue2 文字提示(Tooltip)组件的教程,支持多种自定义属性,如最大宽度、展示文本、提示文本、样式、背景色、箭头显示等,并提供了在线预览示例。组件通过监听插槽和 `requestAnimationFrame` 实现了延迟显示与隐藏效果。文章详细介绍了组件实现代码及在页面中的使用方法。
637 0
Vue3文字提示(Tooltip)
|
传感器 算法 机器人
基于 IMU 的位姿解算
解算 IMU 采样数据的过程与惯导解算技术原理有关,而提高定位精度的方法主要依赖于IMU自身精度的提高和算法改进。
1630 0
|
Java Maven
IntelliJ IDEA的maven如何提高下载速度
IntelliJ IDEA的maven如何提高下载速度
1036 0