WebGL 入门 | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 16 天

0x1 初识 WebGL

  1. WebGL

    是一种运用 GPU 能力的渲染技术

  2. Modern Graphics System

    • 光栅(Raster):是指构成图像的像素阵列
    • 像素(Pixel):通常保存图像上的某个具体位置的颜色等信息
    • 帧缓存(Frame Buffer):是一块内存地址,用于存放像素信息
    • CPU(Central Processing Unit):中央处理单元,负责逻辑计算
    • GPU(Graphics Processing Unit):图形处理单元,负责图形计算

    MGS 工作流程:

    轮廓提取 / Meshing
    光栅化
    帧缓存
    渲染
  3. The Pipeline

    Data
    Processor
    Frame buffer
    Pixels

0x2 WebGL 实战

  1. WebGL Startup

    1. 创建 WebGL 上下文
    2. 创建 WebGL Program
    3. 将数据存入缓冲区
    4. 将缓冲区数据读取到 GPU
    5. GPU 执行 WebGL 程序并输出结果

    举例:绘制三角形

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8" />
            <title>1</title>
            <style>
                html,body{
                    padding: 0;
                    margin: 0;
                }
                canvas{
                    width: 100vw;
                    height: 100vw;
                    max-width: 800px;
                    max-height: 800px;
                }
            </style>
        </head>
        <body>
            <canvas width="800" height="800"></canvas>
            <script>
                // 创建上下文
                const canvas = document.querySelector('canvas');
                const gl = canvas.getContext('webgl');
                
                // 定义着色器,添加运行在 GPU 中的 GLSL 代码
                // 顶点着色器
                const vertex = `attribute vec2 position;
                void main(){
                    gl_PointSize = 1.0;
                    gl_Position = vec4(position, 1.0, 1.0);
                }`;
                // 片段着色器
                const fragment = `precision highp float;
                void main(){
                    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
                }`;
    
                // 创建项目
                const vertexShader = gl.createShader(gl.VERTEX_SHADER);
                gl.shaderSource(vertexShader, vertex);
                gl.compileShader(vertexShader);
    
                const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
                gl.shaderSource(fragmentShader, fragment);
                gl.compileShader(fragmentShader);
    
                const program = gl.createProgram();
                gl.attachShader(program, vertexShader);
                gl.attachShader(program, fragmentShader);
                gl.linkProgram(program);
    
                gl.useProgram(program);
    
                // 定义顶点,原点在画布正中央,右向 x 正半轴,上向 y 正半轴
                const points = new Float32Array([
                    -1, -1,
                    0, 1,
                    1, -1
                ]);
    
                // 创建缓冲区
                const bufferId = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
                gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
    
                // 数据读取
                const vPosition = gl.getAttribLocation(program, 'position');
                gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(vPosition);
    
                // 清理缓存区并绘图
                gl.clear(gl.COLOR_BUFFER_BIT);
                gl.drawArrays(gl.TRIANGLES, 0, points.length/2);
            </script>
        </body>
    </html>
    
  2. 其他绘制方法

    1. 2D

      const canvas = document.querySelector('canvas');
      const ctx = canvas.getContext('2d');
      
      ctx.beginPath();
      ctx.moveTo(250, 0);
      ctx.lineTo(500, 500);
      ctx.lineTo(0, 500);
      ctx.fillStyle = 'red';
      ctx.fill();
      
    2. mesh.js

      const { Renderer, Figure2D, Mesh2D } = meshjs;
      
      const canvas = document.querySelector('canvas');
      const renderer = new Renderer(canvas);
      
      const figure = new Figure2D();
      figure.beginPath();
      figure.moveTo(250, 0);
      figure.lineTo(500, 500);
      figure.lineTo(0, 500);
      
      const mesh = new Mesh2D(figure, canvas);
      mesh.setFill({
          color: [1, 0, 0, 1]
      });
      
      renderer.drawMeshes([mesh]);
      
  3. 绘制多边形:将多边形进行三角剖分

    const vertices = [
        [-0.7, 0.5],
        [-0.3, 0.4],
        [-0.2, 0.5],
        [-0.1, 0.9],
        [-0.3, 0.6],
        [-0.1, 0.1]
    ];
    const points = vertices.flat();
    cosnt triangles = earcut(points);
    
    const cells = new Uint16Array(triangles); 
    
    const cellsBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cellsBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, cells, gl.STATIC_DRAW);
    
    gl.drawElements(gl.TRIANGLES, cells.length, gl.UNSIGNED_SHORT, 0);
    
  4. 3D Meshing:通过三角剖分绘制 3D 图形

  5. 图形移动(Transforms)

    1. 平移

      { x=x0+x1 y=y0+y1\begin{cases} \ x = x_0 + x_1 \\ \ y = y_0 + y_1 \end{cases}
    2. 旋转

      { x=x0cosθy0sinθ y=x0sinθy0cosθ(xy)=(cosθsinθsinθcosθ)×(x0y0)\begin{cases} \ x = x_0\cos\theta - y_0\sin\theta \\ \ y = x_0\sin\theta - y_0\cos\theta \end{cases}\qquad \begin{pmatrix} x \\ y \end{pmatrix}= \begin{pmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{pmatrix}× \begin{pmatrix} x_0 \\ y_0 \end{pmatrix}
    3. 缩放

      { x=sxx0 y=syy0(xy)=(sx00sy)×(x0y0)\begin{cases} \ x = s_xx_0 \\ \ y = s_yy_0 \end{cases}\qquad \begin{pmatrix} x \\ y \end{pmatrix}= \begin{pmatrix} s_x & 0 \\ 0 & s_y \end{pmatrix}× \begin{pmatrix} x_0 \\ y_0 \end{pmatrix}
    4. 平移的线性变换

      1. 旋转 + 缩放是线性变换,存在矩阵相乘的关系

        P=M1×M2×M3××Mn×P0=M×P0(M=M1×M2×M3××Mn)P = M_1×M_2×M_3×\cdots ×M_n×P_0 \\ = M×P_0\quad (M=M_1×M_2×M_3×\cdots ×M_n)
      2. 假设平移为 P1P_1 ,此时

        P=M×P0+P1P=M × P_0 + P_1
      3. 此时平移的线性变换表达式为

        (P1)=(MP101)×(P01)\begin{pmatrix} P \\ 1 \end{pmatrix}= \begin{pmatrix} M & P_1 \\ 0 & 1 \end{pmatrix}× \begin{pmatrix} P_0 \\ 1 \end{pmatrix}
    5. 实现变换

      attribute vec2 position;
      uniform mat3 modelMatrix;
      
      void main(){
          gl_PointSize = 1.0;
          vec3 pos = modelMatrix * vec3(position, 1.0);
          gl_Position = vec4(pos, 1.0);
      }
      
      let transform = gl.getUniformLocation(program, 'modelMatrix');
      gl.uniformMatrix3fv(transform, false,[
          0.5, 0, 0,
          0, 0.5, 0,
          0, 0, 1
      ]);
      
  6. 3D Matrix

    3D 标准模型的四个齐次矩阵(mat4):

    1. 投影矩阵:Projection Matrix
    2. 模型矩阵:Model Matrix
    3. 视图矩阵:View Matrix
    4. 法向量矩阵:Normal Matrix

0x3 Shader

  1. 纯色

    #version 300 es
    precision highp float;
    
    out vec4 fragColor;
    
    void main(){
    	fragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
    
    • 通过修改vec4(R, G, B, A)中的参数(0~1)调整颜色
  2. 渐变

    #version 300 es
    precision highp float;
    
    uniform vec2 dd_resolution;
    out vec4 fragColor;
    
    void main(){
    	vec2 st = gl_FragCoord.xy / dd_resolution;
    	fragColor = vec4(st, 0.0, 1.0);
    }
    
    • dd_resolution:实时获取页面分辨率
    • 经计算后st的横纵坐标在 0~1 之间渐变,代入vec4()中实现渐变效果
  3. 画圆

    1. 基础圆

      #version 300 es
      precision highp float;
      
      uniform vec2 dd_resolution;
      out vec4 fragColor;
      
      void main(){
      	vec2 st = gl_FragCoord.xy / dd_resolution;
      	vec2 center = vec2(0.5);
      	float r = 0.2;
      	fragColor.rgb = step(length(st - center), r) * vec3(1.0);
      	fragColor.a = 1.0;
      }
      
    2. 平滑圆

      float d = length(st - center);
      fragColor.rgb = smoothstep(d - 0.01, d, r) * vec3(1.0);
      
    3. 圆环

      vec3 circle1 = smoothstep(d - 0.01, d, r) * vec3(1.0);
      vec3 circle2 = smoothstep(d, d + 0.01, r - 0.02) * vec3(1.0);
      fragColor.rgb = circle1 - circle2;
      
      • 将画圆环的操作封装成函数

        float stroke(float d, float d0, float w, float smth){
        	float th = 0.5 * w;
        	smth = smth * w;
        	float start = d0 - th;
        	float end = d0 + th;
        	return smoothstep(start, start + smth, d) - smoothstep(end - smth, end, d);
        }
        void main(){
        	vec2 st = gl_FragCoord.xy / dd_resolution;
        	vec2 center = vec2(0.5);
        	float r = 0.2;
        	float d = length(st - center);
        	float d1 = stroke(d, r, 0.02, 0.3);
        	fragColor.rgb = d1 * vec3(1.0);
        	fragColor.a = 1.0;
        }
        
  4. 斜线、曲线

    #version 300 es
    precision highp float;
    
    uniform vec2 dd_resolution;
    out vec4 fragColor;
    
    float stroke(float d, float d0, float w, float smth){
    	float th = 0.5 * w;
    	smth = smth * w;
    	float start = d0 - th;
    	float end = d0 + th;
    	return smoothstep(start, start + smth, d) - smoothstep(end - smth, end, d);
    }
    
    void main(){
    	vec2 st = gl_FragCoord.xy / dd_resolution;
        
    	// 斜线
    	float d1 = stroke(st.y, st.x, 0.02, 0.1);
    	// 曲线
    	float d2 = stroke(st.y, 4.0 * (st.x - 0.5) * (st.x - 0.5), 0.02, 0.1);
        float d3 = stroke(st.y * 2.0, 1.0 - sin(30.0 * st.x), 0.02, 0.1);
    	fragColor.rgb = d1 * vec3(1.0, 1.0, 0.0) + d2 * vec3(0.0, 1.0, 1.0) + d3 * vec3(0.5, 1.0, 0.5);
    	fragColor.a = 1.0;
    }
    	
        fragColor.rgb = d1 * vec3(1.0, 1.0, 0.0) + d2 * vec3(0.0, 1.0, 1.0);
    	fragColor.a = 1.0;
    }
    
posted @ 2023-02-14 21:43  SRIGT  阅读(6)  评论(0编辑  收藏  举报  来源