使用 webgl 绘制单个点

前言

对于 webgl 的学习,我打算从头开始。从简到繁,希望可以扎实的过一遍。现在我们就从最简单的绘制单个点入手,文中涉及到的 API,我会给大家列出重点,以及在线文档地址。

步骤

引入 glMatrix.js 文件

glMatrix.js 是一个 JavaScript 库,主要用于高性能矩阵和向量操作,这些操作在 WebGL 编程中非常常见。这个库中封装了矩阵和向量相关的方法,我们可以直接调用。

实现过程

初始化 4*4 矩阵

ini 复制代码
 var projMat4 = mat4.create(); // 初始化一个4*4的矩阵

申明 webgl 变量

csharp 复制代码
var webgl; // 声明全局变量

编写顶点着色器代码

顶点着色器主要负责处理 3D 模型的顶点数据。

顶点着色器的主要任务包括: `

  • 变换(Transformations):顶点着色器通常负责将 3D 模型的顶点坐标模型空间(Model Space)变换到裁剪空间(Clip Space)。这个过程通常需要应用几个变换矩阵,包括模型矩阵(Model Matrix)、视图矩阵(View Matrix)和投影矩阵(Projection Matrix)模型矩阵负责将顶点坐标从模型空间变换到世界空间视图矩阵负责将顶点坐标从世界空间变换到视图空间投影矩阵负责将顶点坐标从视图空间变换到裁剪空间

  • 光照计算(Lighting):虽然更复杂的光照模型通常在片元着色器中实现,但是一些简单的光照计算,如 Gouraud 光照,可以在顶点着色器中进行。这种方法的优点是性能开销较小,因为顶点的数量通常比片元的数量要少得多。缺点是光照效果可能不够精细。

  • 传递数据(Passing Data):顶点着色器还负责将数据传递到片元着色器。这些数据可以包括顶点的颜色、纹理坐标、法线等。这些数据在顶点着色器中计算或获取,然后通过 varying 变量传递到片元着色器

  • 其他计算:顶点着色器还可以用于实现一些特效,如顶点位移(Vertex Displacement)、实例化(Instancing)等。

总的来说,顶点着色器是 3D 渲染管线中的第一个阶段,它处理和操作顶点数据,为后续的光栅化、片元着色等阶段做准备

关于几个坐标系空间:

  • 模型空间:也被称为局部空间(Local Space),是指模型本身的坐标空间。例如,如果你有一个立方体模型,那么在模型空间中,这个立方体的中心可能就是原点(0,0,0),而立方体的8个顶点则可能被定义为(±1, ±1, ±1)
  • 世界空间:世界空间(World Space)是指世界坐标系空间在3D图形中,世界空间是一个全局的坐标系统,所有的物体都在这个空间中定义其位置
  • 视图空间:也被称为相机空间(Camera Space),在视图空间中,相机位于原点,朝向通常是Z轴的负方向(这个因具体的坐标系定义可能会有所不同,但在右手坐标系中是这样的)。换句话说,视图空间是以相机的位置和朝向为基准的坐标系。
  • 裁剪空间:可以进行裁剪操作,也就是将那些不在相机视野内的几何体(或者部分)裁剪掉。具体来说,任何在裁剪空间的立方体(通常是从-1 到 1 的立方体)之外的顶点都会被裁剪掉。这个立方体被称为裁剪体(Clipping Volume)或者可视体(Viewing Volume). 在裁剪之后,顶点的坐标会被转换到归一化设备坐标(Normalized Device Coordinates, NDC)空间,这个过程通常被称为透视除法(Perspective Division)。在 NDC 空间中,所有的坐标都在立方体(通常是从-1 到 1 的立方体)内。

最后,NDC坐标会被转换到屏幕空间(Screen Space)坐标,这个过程被称为视口变换(Viewport Transform)屏幕空间的坐标是以像素为单位的,用于最终的光栅化(Rasterization)操作,将3D图形渲染到2D屏幕上

所以:几个空间变化的顺序为:模型空间->世界空间->视图空间->裁剪空间 几个变化矩阵的顺序为:模型矩阵->视图矩阵->投影矩阵

ini 复制代码
 var vertexString = `
        attribute vec4 a_position;
        uniform mat4 proj;
        void main(){
            gl_Position =proj * a_position;
            gl_PointSize = 60.0;
        }
        `;
  1. 首先我们需要了解下,shader 中的变量类型有那些?
  • attribute:这是只能在顶点着色器中使用的变量。它们通常用于存储每个顶点的数据,如位置、颜色、纹理坐标等。
  • uniform: 这是在顶点着色器和片元着色器中都可以使用的变量。它们通常用于存储在一个渲染调用过程中不会改变的值,如变换矩阵、光源位置、材质属性等。所有的顶点和片元都共享相同的 uniform 值。
  • varying: 这是在顶点着色器和片元着色器之间传递数据的变量顶点着色器计算出varying变量的值,然后这些值会被插值并传递给片元着色器。这使得每个片元都可以获取到自己的 varying 值,常用于实现如颜色混合、纹理映射等效果
  • sampler: 这是一种特殊类型的uniform,用于访问纹理。例如,sampler2D和samplerCube分别用于访问2D纹理和立方体贴图
  • const: 这是常量变量其值在编译时就已经确定不能在运行时改变。这些变量可以在顶点着色器和片元着色器中使用。
  • in/out:这是GLSL ES 3.0(WebGL 2.0)中新增的关键字,用于替代GLSL ES 1.0(WebGL 1.0)中的attribute和varying在顶点着色器中,in用于接收输入数据,out用于输出数据到片元着色器;在片元着色器中,in用于接收来自顶点着色器的数据,out用于输出最终的颜色。
  1. 了解了这些变量类型之后,我们再来看下我们写的代码
  • 声明一个四维向量(x,y,z,w),去储存顶点的位置
ini 复制代码
  attribute vec4 a_position;
  • 声明一个 4*4 的变换矩阵 uniform mat4 proj;

  • 主函数

ini 复制代码
  void main(){
            gl_Position =proj * a_position; //点在屏幕上的位置为webgl坐标系中的位置*变换矩阵
            gl_PointSize = 60.0; //点的大小设置为60.0
        }

注意,着色器中说有的数字都是浮点数,所以我们的数字都要加上小数点,否则会报错。

编写片元着色器代码

片元着色器的主要作用是计算渲染到屏幕上每个像素(或者更准确地说,每个片元)的颜色和其他属性。

csharp 复制代码
 var fragmentString = `
        void main(){
            gl_FragColor = vec4(0,0,1.0,1.0); //定义输出的颜色
        }
        `; // 片元着色器

初始化 webgl

javascript 复制代码
      // webgl初始化函数
      function initWebgl() {
        // 获取canvas容器
        let webglDiv = document.querySelector("#webglCanvas");
        // 设置webgl上下文
        webgl = webglDiv.getContext("webgl");
        // 设置可视范围 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/viewport
        webgl.viewport(0, 0, webglDiv.width, webglDiv.height);
        // 定义阴影范围
        mat4.ortho(
          0,
          webglDiv.clientWidth,
          webglDiv.clientHeight,
          0,
          -1,
          1,
          projMat4
        );
      }

mat4.ortho 语法:

css 复制代码
mat4.ortho( left, right, bottom, top, near, far,out);
  • out: 这是一个已经存在的矩阵,新创建的正交投影矩阵会存储在这里。

  • left, right: 这是视图空间的左边界和右边界。

  • bottom, top: 这是视图空间的底边界和顶边界。

  • near, far: 这是视图空间的近裁剪面和远裁剪面。

    该函数会创建一个新的正交投影矩阵,并将其存储在 out 参数中。

初始化 Shader

scss 复制代码
      // shder初始化函数
      function initShader() {
        // createShader() 用于创建一个 WebGLShader 着色器对象,该对象可以使用 shaderSource()和 compileShader() 方法配置着色器代码。
        // 参数为gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER两者中的一个。
        let vsshader = webgl.createShader(webgl.VERTEX_SHADER);
        let fssagder = webgl.createShader(webgl.FRAGMENT_SHADER);

        // 用于将我们创建的 WebGLShader着色器对象和GLSL程序中定义的着色器相关联。
        // 第一个参数为webglShader对象,第二个参数为GLSL中定义的着色器
        webgl.shaderSource(vsshader, vertexString);
        webgl.shaderSource(fssagder, fragmentString);

        // 编译WebGLShader着色器,使其成为为二进制数据,然后就可以被WebGLProgram对象所使用。
        // 参数为一个片元着色器或顶点着色器
        webgl.compileShader(vsshader);
        webgl.compileShader(fssagder);

        // 创建一个webglProgram对象,该对象由两个编译过后的 WebGLShader 组成 - 顶点着色器和片段着色器(均由 GLSL 语言所写)
        let program = webgl.createProgram();

        // attachShader() 方法负责往 WebGLProgram 添加一个片段或者顶点着色器。
        // 第一个参数为webglProgram对象,第二个参数为片段或者顶点的 WebGLShader
        webgl.attachShader(program, vsshader);
        webgl.attachShader(program, fssagder);

        // linkProgram()方法链接给定的WebGLProgram,从而完成为程序的片元和顶点着色器准备 GPU 代码的过程。参数为一个用于链接的WebGLProgram对象
        webgl.linkProgram(program);

        // useProgram() 方法将定义好的WebGLProgram 对象添加到当前的渲染状态中。
        webgl.useProgram(program);

        webgl.program = program;
      }

初始化数据缓冲区

javascript 复制代码
   // 数据缓冲区初始化函数
   function initBuffer() {
       //Float32Array 类型数组代表的是平JS内置的标准对象,为 32 位的浮点数型数组,其内容初始化为 0。一旦建立起来,你可以使用这个对象的方法对其元素进行操作,或者使用标准数组索引语法 (使用方括号)。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Float32Array
       let pointPosition = new Float32Array([100.0, 100.0, 0.0, 1.0]);

       // getAttribLocation() 方法返回给定WebGLProgram对象中某属性的下标指向位置
       // 第一个参数为WebGLProgram,第二个参数为需要获取下标指向位置的GLSL变量名
       let aPosition = webgl.getAttribLocation(webgl.program, "a_position");

       // vertexAttrib4fv()方法可以为顶点 attibute 变量赋值。
       // 第一个参数为指定了待修改顶点 attribute 变量的存储位置。第二个参数为用于设置顶点 attibute 变量的向量值。
       webgl.vertexAttrib4fv(aPosition, pointPosition);

       // getUniformLocation()方法用于获取指定WebGLProgram对象中uniform变量的位置。
       // 第一个参数为要获取uniform变量的WebGLProgram对象,第二个参数为要获取位置的uniform变量的名称。
       let uniforproj = webgl.getUniformLocation(webgl.program, "proj");

       // uniformMatrix4fv()用于设置一个4*4的矩阵类型的uniform变量值。接收4个参数
       // 第一个参数为 要设置值的uniform变量的地址
       // 第二个参数为 是否将矩阵转置,默认false
       // 第三个参数为  要设置的值,应该是一个4*4的矩阵
       // 第四个参数为 矩阵在数组中的偏移量,默认为0
       webgl.uniformMatrix4fv(uniforproj, false, projMat4);
     }

绘制函数

scss 复制代码
    // webgl的绘制函数
     function draw() {
       // clearColor ()方法指定在清除颜色缓冲区时使用的颜色值。接收的4个参数分别表示 r,g,b,a。取值均在0和1之间。
       webgl.clearColor(0.0, 0.0, 0.0, 1.0);
       // clear() 方法使用预设值来清空缓冲。预设值可以使用 clearColor() 、 clearDepth() 或 clearStencil() 设置。裁剪、抖动处理和缓冲写入遮罩会影响 clear() 方法。参数为一个用于指定需要清除的缓冲区的 GLbitfield (en-US) 。可能的值有:gl.COLOR_BUFFER_BIT(颜色缓冲区);gl.DEPTH_BUFFER_BIT (深度缓冲区)       gl.STENCIL_BUFFER_BIT(模板缓冲区)  没有返回值
       webgl.clear(webgl.COLOR_BUFFER_BIT);

       // drawArrays() 方法用于从向量数组中绘制图元。接收3个参数
       // 第一个参数为指定绘制图元的方式,可能值如下:
       // gl.POINTS: 绘制一系列点。
       // gl.LINE_STRIP: 绘制一个线条。即,绘制一系列线段,上一点连接下一点。
       // gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。
       // gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。
       // gl.TRIANGLE_STRIP:绘制一个三角带。
       // gl.TRIANGLE_FAN:绘制一个三角扇。
       // gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。
       // 第二个参数为指定从哪个点开始绘制。
       // 第三个参数为指定绘制需要使用到多少个点。
       webgl.drawArrays(webgl.POINTS, 0, 1);
     }

初始化函数

scss 复制代码
      // 入口函数
      function init() {
        initWebgl(); // 固定流程
        initShader(); //固定流程
        initBuffer();
        draw(); // 自定义绘制
      }

完整代码

xml 复制代码
<!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>
    <script src="./glMatrix-0.9.6.min.js"></script>
    <script>
      var projMat4 = mat4.create(); // 初始化一个4*4的矩阵
      var webgl; // 声明全局变量
      /**
       * GLSL代码
       * 声明一个顶点着色器
       * vec4:四维向量 ,具有xyzw四个分量,分量是浮点数
       * mat4: 4*4矩阵
       * **/
      var vertexString = `
        attribute vec4 a_position;
        uniform mat4 proj;
        void main(){
            gl_Position =proj * a_position;
            gl_PointSize = 60.0;
        }
        `;
      var fragmentString = `
        void main(){
            gl_FragColor = vec4(0,0,1.0,1.0);
        }
        `; // 片元着色器

      // 入口函数
      function init() {
        initWebgl();
        initShader();
        initBuffer();
        draw();
      }
      // webgl初始化函数
      function initWebgl() {
        // 获取canvas容器
        let webglDiv = document.querySelector("#webglCanvas");
        // 设置webgl上下文
        webgl = webglDiv.getContext("webgl");
        // 设置可视范围 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/viewport
        webgl.viewport(0, 0, webglDiv.width, webglDiv.height);
        // 定义阴影范围
        mat4.ortho(
          0,
          webglDiv.clientWidth,
          webglDiv.clientHeight,
          0,
          -1,
          1,
          projMat4
        );
      }
      // shder初始化函数
      function initShader() {
        // createShader() 用于创建一个 WebGLShader 着色器对象,该对象可以使用 shaderSource()和 compileShader() 方法配置着色器代码。
        // 参数为gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER两者中的一个。
        let vsshader = webgl.createShader(webgl.VERTEX_SHADER);
        let fssagder = webgl.createShader(webgl.FRAGMENT_SHADER);

        // 用于将我们创建的 WebGLShader着色器对象和GLSL程序中定义的着色器相关联。
        // 第一个参数为webglShader对象,第二个参数为GLSL中定义的着色器
        webgl.shaderSource(vsshader, vertexString);
        webgl.shaderSource(fssagder, fragmentString);

        // 编译WebGLShader着色器,使其成为为二进制数据,然后就可以被WebGLProgram对象所使用。
        // 参数为一个片元着色器或顶点着色器
        webgl.compileShader(vsshader);
        webgl.compileShader(fssagder);

        // 创建一个webglProgram对象,该对象由两个编译过后的 WebGLShader 组成 - 顶点着色器和片段着色器(均由 GLSL 语言所写)
        let program = webgl.createProgram();

        // attachShader() 方法负责往 WebGLProgram 添加一个片段或者顶点着色器。
        // 第一个参数为webglProgram对象,第二个参数为片段或者顶点的 WebGLShader
        webgl.attachShader(program, vsshader);
        webgl.attachShader(program, fssagder);

        // linkProgram()方法链接给定的WebGLProgram,从而完成为程序的片元和顶点着色器准备 GPU 代码的过程。参数为一个用于链接的WebGLProgram对象
        webgl.linkProgram(program);

        // useProgram() 方法将定义好的WebGLProgram 对象添加到当前的渲染状态中。
        webgl.useProgram(program);

        webgl.program = program;
      }
      // 数据缓冲区初始化函数
      function initBuffer() {
        //Float32Array 类型数组代表的是平JS内置的标准对象,为 32 位的浮点数型数组,其内容初始化为 0。一旦建立起来,你可以使用这个对象的方法对其元素进行操作,或者使用标准数组索引语法 (使用方括号)。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Float32Array
        let pointPosition = new Float32Array([100.0, 100.0, 0.0, 1.0]);

        // getAttribLocation() 方法返回给定WebGLProgram对象中某属性的下标指向位置
        // 第一个参数为WebGLProgram,第二个参数为需要获取下标指向位置的GLSL变量名
        let aPosition = webgl.getAttribLocation(webgl.program, "a_position");

        // vertexAttrib4fv()方法可以为顶点 attibute 变量赋值。
        // 第一个参数为指定了待修改顶点 attribute 变量的存储位置。第二个参数为用于设置顶点 attibute 变量的向量值。
        webgl.vertexAttrib4fv(aPosition, pointPosition);

        // getUniformLocation()方法用于获取指定WebGLProgram对象中uniform变量的位置。
        // 第一个参数为要获取uniform变量的WebGLProgram对象,第二个参数为要获取位置的uniform变量的名称。
        let uniforproj = webgl.getUniformLocation(webgl.program, "proj");

        // uniformMatrix4fv()用于设置一个4*4的矩阵类型的uniform变量值。接收4个参数
        // 第一个参数为 要设置值的uniform变量的地址
        // 第二个参数为 是否将矩阵转置,默认false
        // 第三个参数为  要设置的值,应该是一个4*4的矩阵
        // 第四个参数为 矩阵在数组中的偏移量,默认为0
        webgl.uniformMatrix4fv(uniforproj, false, projMat4);
      }
      // webgl的绘制函数
      function draw() {
        // clearColor ()方法指定在清除颜色缓冲区时使用的颜色值。接收的4个参数分别表示 r,g,b,a。取值均在0和1之间。
        webgl.clearColor(0.0, 0.0, 0.0, 1.0);
        // clear() 方法使用预设值来清空缓冲。预设值可以使用 clearColor() 、 clearDepth() 或 clearStencil() 设置。裁剪、抖动处理和缓冲写入遮罩会影响 clear() 方法。参数为一个用于指定需要清除的缓冲区的 GLbitfield (en-US) 。可能的值有:gl.COLOR_BUFFER_BIT(颜色缓冲区);gl.DEPTH_BUFFER_BIT (深度缓冲区)       gl.STENCIL_BUFFER_BIT(模板缓冲区)  没有返回值
        webgl.clear(webgl.COLOR_BUFFER_BIT);

        // drawArrays() 方法用于从向量数组中绘制图元。接收3个参数
        // 第一个参数为指定绘制图元的方式,可能值如下:
        // gl.POINTS: 绘制一系列点。
        // gl.LINE_STRIP: 绘制一个线条。即,绘制一系列线段,上一点连接下一点。
        // gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。
        // gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。
        // gl.TRIANGLE_STRIP:绘制一个三角带。
        // gl.TRIANGLE_FAN:绘制一个三角扇。
        // gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。
        // 第二个参数为指定从哪个点开始绘制。
        // 第三个参数为指定绘制需要使用到多少个点。
        webgl.drawArrays(webgl.POINTS, 0, 1);
      }
    </script>
  </head>

  <body onload="init()">
    <canvas id="webglCanvas" width="500" height="500"></canvas>
  </body>
</html>

总结

这个例子是个很简单的小例子,但我们可以通过这个小例子去看下 webgl 从定义数据到绘制在屏幕上的整个流程。以上便是,使用 webgl 绘制单个点的全部内容,如有错误之处,请在评论区留言指出,感谢大家的指点。谢谢大家了。

相关推荐
掘金者阿豪1 小时前
把业务数据变成共享仪表盘:Metabase可视化与远程访问实践
前端·后端
kyriewen1 小时前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
蜗牛前端2 小时前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员2 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为2 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid2 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端
Bigger3 小时前
从零搭建 AI 代码审查服务:一份前端也能看懂的 Python 学习笔记
前端·ci/cd·ai编程
lichenyang4533 小时前
JSAPI、NAPI、Biz、Imp:ASCF Demo 如何真正调用系统能力和 C++ 能力
前端
lichenyang4534 小时前
IPC、JSVM、UIThread、libuv:ASCF 架构图里最容易混的几个词
前端
用户059540174464 小时前
Redis记忆存储故障恢复测试踩坑实录:手动测试让我漏掉了2个一致性Bug
前端·css