• 【视觉基础篇】15 # 如何用极坐标系绘制有趣图案?


    说明

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

    极坐标示意图

    极坐标系使用相对极点的距离,以及与 x 轴正向的夹角来表示点的坐标,如(3,60°)

    在这里插入图片描述

    直角坐标和极坐标相互转换

    // 直角坐标影射为极坐标
    function toPolar(x, y) {
      const r = Math.hypot(x, y);
      const θ= Math.atan2(y, x);
      return [r, θ];
    }
    
    // 极坐标映射为直角坐标
    function fromPolar(r, θ) {
      const x = r * cos(θ);
      const y = r * sin(θ);
      return [x, y];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    如何用极坐标方程绘制曲线

    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 type="module">
                import { parametric } from "./common/lib/parametric.js";
    
                const canvas = document.querySelector("canvas");
                const ctx = canvas.getContext("2d");
                const { width, height } = canvas;
                const w = 0.5 * width,
                    h = 0.5 * height;
                ctx.translate(w, h);
                ctx.scale(1, -1);
    
                function drawAxis() {
                    ctx.save();
                    ctx.strokeStyle = "#ccc";
                    ctx.beginPath();
                    ctx.moveTo(-w, 0);
                    ctx.lineTo(w, 0);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(0, -h);
                    ctx.lineTo(0, h);
                    ctx.stroke();
                    ctx.restore();
                }
    
                drawAxis();
    
                // fromPolar 作为 parametric 的参数是坐标映射函数,通过它可以将任意坐标映射为直角坐标
                const fromPolar = (r, theta) => {
                    return [r * Math.cos(theta), r * Math.sin(theta)];
                };
    
                // 画一个半径为 200 的半圆
                const arc = parametric(
                    (t) => 200,
                    (t) => t,
                    fromPolar
                );
                arc(0, Math.PI).draw(ctx);
    
                // 玫瑰线
                const rose = parametric(
                    (t, a, k) => a * Math.cos(k * t),
                    (t) => t,
                    fromPolar
                );
                rose(0, Math.PI, 100, 200, 5).draw(ctx, { strokeStyle: "blue" });
    
                // 心形线
                const heart = parametric(
                    (t, a) => a - a * Math.sin(t),
                    (t) => t,
                    fromPolar
                );
                heart(0, 2 * Math.PI, 100, 100).draw(ctx, { strokeStyle: "red" });
    
                // 双纽线
                const foliumRight = parametric(
                    (t, a) => Math.sqrt(2 * a ** 2 * Math.cos(2 * t)),
                    (t) => t,
                    fromPolar
                );
                const foliumLeft = parametric(
                    (t, a) => -Math.sqrt(2 * a ** 2 * Math.cos(2 * t)),
                    (t) => t,
                    fromPolar
                );
                foliumRight(-Math.PI / 4, Math.PI / 4, 100, 100).draw(ctx, {
                    strokeStyle: "green",
                });
                foliumLeft(-Math.PI / 4, Math.PI / 4, 100, 100).draw(ctx, {
                    strokeStyle: "green",
                });
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    在这里插入图片描述

    如何使用片元着色器与极坐标系绘制图案?

    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>
            <script src="./common/lib/gl-renderer.js">script>
            <canvas width="512" height="512">canvas>
            <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;
    
                //     vec2 polar(vec2 st) {
                //         return vec2(length(st), atan(st.y, st.x));
                //     }
    
                //     void main() {
                //         vec2 st = vUv - vec2(0.5);
                //         st = polar(st);
                //         float d = 0.5 * cos(st.y * 3.0) - st.x;
                //         gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0);
                //         gl_FragColor.a = 1.0;
                //     }
                // `;
                // // 不同瓣数的玫瑰线图案
                // const fragment = `
                //     #ifdef GL_ES
                //     precision highp float;
                //     #endif
    
                //     varying vec2 vUv;
                //     uniform float u_k;
    
                //     vec2 polar(vec2 st) {
                //         return vec2(length(st), atan(st.y, st.x));
                //     }
    
                //     void main() {
                //         vec2 st = vUv - vec2(0.5);
                //         st = polar(st);
                //         float d = 0.5 * cos(st.y * u_k) - st.x;
                //         gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0);
                //         gl_FragColor.a = 1.0;
                //     }
                // `;
    
                // 花瓣线
                const fragment = `
                    #ifdef GL_ES
                    precision highp float;
                    #endif
    
                    varying vec2 vUv;
                    uniform float u_k;
    
                    vec2 polar(vec2 st) {
                        return vec2(length(st), atan(st.y, st.x));
                    }
    
                    void main() {
                        vec2 st = vUv - vec2(0.5);
                        st = polar(st);
                        float d = 0.5 * abs(cos(st.y * u_k * 0.5)) - st.x;
                        gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0);
                        gl_FragColor.a = 1.0;
                    }
                `;
    
                // // 葫芦图案
                // const fragment = `
                //     #ifdef GL_ES
                //     precision highp float;
                //     #endif
                    
                //     varying vec2 vUv;
                //     uniform float u_k;
                //     uniform float u_scale;
                //     uniform float u_offset;
                        
                //     vec2 polar(vec2 st) {
                //         return vec2(length(st), atan(st.y, st.x));
                //     }
    
                //     void main() {
                //         vec2 st = vUv - vec2(0.5);
                //         st = polar(st);
                //         float d = u_scale * 0.5 * abs(cos(st.y * u_k * 0.5)) - st.x + u_offset;
                //         gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0);
                //         gl_FragColor.a = 1.0;
                //     }
                // `;
                // 花苞图案
                // const fragment = `
                //     #ifdef GL_ES
                //     precision highp float;
                //     #endif
    
                //     varying vec2 vUv;
                //     uniform float u_k;
                //     uniform float u_scale;
                //     uniform float u_offset;
    
                //     vec2 polar(vec2 st) {
                //         return vec2(length(st), atan(st.y, st.x));
                //     }
    
                //     void main() {
                //         vec2 st = vUv - vec2(0.5);
                //         st = polar(st);
                //         float d = smoothstep(-0.3, 1.0, u_scale * 0.5 * cos(st.y * u_k) + u_offset) - st.x;
                //         gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(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.uniforms.u_k = 2;
                // setInterval(() => {
                //     renderer.uniforms.u_k += 2;
                // }, 200);
    
                // 花瓣线
                // renderer.uniforms.u_k = 3;
                renderer.uniforms.u_k = 1.3; // 1.3 的情况下是苹果
    
                // // 葫芦图案
                // renderer.uniforms.u_k = 1.7;
                // renderer.uniforms.u_scale = 0.5; // default 1.0
                // renderer.uniforms.u_offset = 0.2; // default 0.0
    
                // // 花苞图案
                // renderer.uniforms.u_k = 5;
                // renderer.uniforms.u_scale = 0.2; // default 1.0
                // renderer.uniforms.u_offset = 0.2; // default 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();
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192

    在这里插入图片描述

    极坐标系如何实现角向渐变?

    角向渐变Conic Gradients)就是以图形中心为轴,顺时针地实现渐变效果。

    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;
            }
            
            div.conic {
                display: inline-block;
                width: 150px;
                height: 150px;
                border-radius: 50%;
                background: conic-gradient(red 0%, green 45%, blue);
            }
        style>
    head>
    <body>
        <script src="./common/lib/gl-renderer.js">script>
        <canvas width="512" height="512">canvas>
        <div class="conic">div>
        <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;
    
                vec2 polar(vec2 st) {
                    return vec2(length(st), atan(st.y, st.x));
                }
                
                void main() {
                    vec2 st = vUv - vec2(0.5);
                    st = polar(st);
                    float d = smoothstep(st.x, st.x + 0.01, 0.2);
                    // 将角度范围转换到0到2pi之间
                    if(st.y < 0.0) st.y += 6.28;
                    // 计算p的值,也就是相对角度,p取值0到1
                    float p = st.y / 6.28;
                    if(p < 0.45) {
                        // p取0到0.45时从红色线性过渡到绿色
                        gl_FragColor.rgb = d * mix(vec3(1.0, 0, 0), vec3(0, 0.5, 0), p /  0.45);
                    } else {
                        // p超过0.45从绿色过渡到蓝色
                        gl_FragColor.rgb = d * mix(vec3(0, 0.5, 0), vec3(0, 0, 1.0), (p - 0.45) / (1.0 - 0.45));
                    }
                    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: [
                        [-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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    在这里插入图片描述

    极坐标如何绘制 HSV 色轮?

    只需要将像素坐标转换为极坐标,再除以 2π,就能得到 HSV 的 H 值。然后用鼠标位置的 x、y 坐标来决定 S 和 V 的值。

    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>极坐标如何绘制 HSV 色轮title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        style>
    head>
    <body>
        <script src="./common/lib/gl-renderer.js">script>
        <canvas width="512" height="512">canvas>
        <div class="conic">div>
        <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 vec2 uMouse;
    
                vec3 hsv2rgb(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);
                }
    
                vec2 polar(vec2 st) {
                return vec2(length(st), atan(st.y, st.x));
                }
    
                void main() {
                    vec2 st = vUv - vec2(0.5);
                    st = polar(st);
                    float d = smoothstep(st.x, st.x + 0.01, 0.2);
                    if(st.y < 0.0) st.y += 6.28;
                    float p = st.y / 6.28;
                    gl_FragColor.rgb = d * hsv2rgb(vec3(p, uMouse.x, uMouse.y));
                    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.uniforms.uMouse = [0.5, 0.5];
    
            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();
    
            canvas.addEventListener('mousemove', (e) => {
                const {x, y, width, height} = e.target.getBoundingClientRect();
                renderer.uniforms.uMouse = [(e.x - x) / width, 1.0 - (e.y - y) / height];
            });
        script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    在这里插入图片描述

    圆柱坐标与球坐标

    圆柱坐标系是一种三维坐标系,又被称为半极坐标系。可以用来绘制一些三维曲线,比如螺旋线、圆内螺旋线、费马曲线等等。

    在这里插入图片描述

    直角坐标系和圆柱坐标系也可以相互转换,公式如下:

    在这里插入图片描述

    球坐标系用在三维图形绘制、球面定位、碰撞检测等。

    在这里插入图片描述

    圆柱坐标系可以和球坐标系相互转换,公式如下:

    在这里插入图片描述

  • 相关阅读:
    Part16:Pandas的分层索引MultiIndex怎么用?【详解】
    evnoy协议转换关键日志
    深入理解Java正则表达式及其应用
    Django内置模型查询讲解
    idea永久设置maven配置,新项目不用再设置
    外汇天眼:假冒违法平台害人害己,监管“铁拳”打击!
    01-JVM-类加载篇
    linux下对sh文件的基本操作总结
    又重新搭了个个人博客
    JavaScript 基础第四天笔记
  • 原文地址:https://blog.csdn.net/kaimo313/article/details/126834137