使用 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 绘制单个点的全部内容,如有错误之处,请在评论区留言指出,感谢大家的指点。谢谢大家了。

相关推荐
晚安7201 分钟前
Ajax相关
前端·javascript·ajax
图书馆钉子户3 分钟前
怎么使用ajax实现局部刷新
前端·ajax·okhttp
bin915319 分钟前
DeepSeek 助力 Vue 开发:打造丝滑的单选按钮(Radio Button)
前端·javascript·vue.js·ecmascript·deepseek
qianmoQ23 分钟前
第五章:工程化实践 - 第五节 - Tailwind CSS 常见问题解决方案
前端·css
那就可爱多一点点36 分钟前
超高清大图渲染性能优化实战:从页面卡死到流畅加载
前端·javascript·性能优化
不能只会打代码2 小时前
六十天前端强化训练之第一天HTML5语义化标签深度解析与博客搭建实战
前端·html·html5
OpenTiny社区2 小时前
Node.js技术原理分析系列——Node.js的perf_hooks模块作用和用法
前端·node.js
菲力蒲LY2 小时前
输入搜索、分组展示选项、下拉选取,全局跳转页,el-select 实现 —— 后端数据处理代码,抛砖引玉展思路
java·前端·mybatis
MickeyCV3 小时前
Nginx学习笔记:常用命令&端口占用报错解决&Nginx核心配置文件解读
前端·nginx
祈澈菇凉4 小时前
webpack和grunt以及gulp有什么不同?
前端·webpack·gulp