WebGL打开 3D 世界的大门(一):基础概念

用GPU的强大性能WebGL(Web Graphics Library)是在浏览器中渲染交互的3D图形引擎,使得前端程序员能够利用GPU的强大性能,创造出丰富的视觉体验。

渲染流程

它利用GLSL语言代码片段,在GPU上运行,代码块里面必须包括顶点着色器和片元着色器,顶点着色器用来计算一系列顶点的位置,片元着色器用来计算每个像素点的颜色插值。然后调用gl.drawArrays或gl.drawElements在GPU上执行。GPU通过以下四种方式获取数据:

  1. 属性(Attributes)和缓冲:属性用来指明怎么从缓冲中获取所需数据并将它提供给顶点着色器。 例如你可能在缓冲中用三个32位的浮点型数据存储一个位置值。
  2. 全局变量(Uniforms)
  3. 纹理(Textures):多数情况存放的是图像数据
  4. 变量(Varyings):线还是三角形,顶点着色器中设置的可变量会在片段着色器运行中获取不同的插值。

说了这么多不如直接上代码先画一个简单的正方形如图:

gitee.com/feng-lianxi... 这里面的代码不用全部都懂,只要注意哪里是片元着色器,那个是顶点着色器,怎么传递个GPU的,然后调用什么方法渲染出来就行了。

css 复制代码
     // Set clear color to black, fully opaque
     gl.clearColor(0.0, 0.0, 0.0, 1.0);
     // Clear the color buffer with specified clear color
     gl.clear(gl.COLOR_BUFFER_BIT);

这两句话的意思是将画布用黑色初始化。顶点着色器:

ini 复制代码
      const vsSource = `
        attribute vec4 a_position; //一个4维的向量的顶点属性变量
        void main() {
          gl_Position = a_position; //gl_Position为系统默认变量,
        }
    `;

片元着色器:

ini 复制代码
       const fsSource = `
        void main() {
          gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); //白色,gl_FragColor为系统默认变量
        }
      `;

接下来就是怎么给顶点着色器定义值和赋值的问题了:GLSL通过定义缓冲区,向缓冲区绑定数据,然后规定顶点着色器以什么样的方式取缓冲区的数据。最后规定以什么样的方式在画布上画出这些点。

  1. 建立缓冲区并且缓冲区绑定数据

    javascript 复制代码
       // Create a buffer and put three 2d clip space points in it
       var positionBuffer = gl.createBuffer();
       // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
       gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
       var positions = [0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5];
       //gl.bufferData(target, sizeOrData, usage);
       //target:gl.ARRAY_BUFFER:存储顶点数据,gl.ELEMENT_ARRAY_BUFFER:存储索引数据
       //usage:`gl.STATIC_DRAW`: 数据内容不会或很少更改。`gl.DYNAMIC_DRAW`: 数据内容会频繁更改。`l.STREAM_DRAW`: 数据内容每次绘制时都会更改
       gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); 
       
  2. 规定顶点着色器以什么样的方式取缓冲区的数据

    go 复制代码
      // look up where the vertex data needs to go.
      var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
    
      // Turn on the attribute
      gl.enableVertexAttribArray(positionAttributeLocation);
    
      // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
      var size = 2;          // 2 components per iteration
      var type = gl.FLOAT;   // the data is 32bit floats
      var normalize = false; // don't normalize the data
      var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
      var offset = 0;        // start at the beginning of the buffer
      gl.vertexAttribPointer(
          positionAttributeLocation, size, type, normalize, stride, offset);

    这里面规定的是从缓冲区的头部开始,一次取两个,不进行归一化处理。

  3. 规定以什么样的方式在画布上画出这些点

    ini 复制代码
      var primitiveType = gl.TRIANGLE_STRIP;
      var offset = 0;
      var count = 4;
      gl.drawArrays(gl.TRIANGLE_STRIP, offset, count);
      

    有必要单独讲一下 drawArrays 方法:gl.drawArrays(mode, first, count):
    mode: 指定要绘制的图元类型。常见的模式包括:

    markdown 复制代码
    -   `gl.POINTS`: 绘制一系列点。
    -   `gl.LINES`: 绘制一系列单独的线段。
    -   `gl.LINE_STRIP`: 绘制一条连续的折线。
    -   `gl.LINE_LOOP`: 绘制一条闭合的折线。
    -   `gl.TRIANGLES`: 绘制一系列单独的三角形。
    -   `gl.TRIANGLE_STRIP`: 绘制一系列连续的三角形(共享顶点)。
    -   `gl.TRIANGLE_FAN`: 绘制一系列以第一个顶点为中心的三角形。

    first:

    diff 复制代码
    -   指定从哪个顶点开始绘制。它是一个偏移量,表示从顶点数组的第一个顶点开始跳过的顶点数。

    count:

    diff 复制代码
    -   指定要绘制的顶点数量。

在我们这里的意思就是,从数据开始进行绘制,绘制一系列三角形。并使用片元着色器进行填充。

坐标系统

上面注意到我们设定的坐标都是-1到1之间的数,webgl的坐标系统是右手坐标系,正中心是0点,最小值是-1,最大值是1,但是我们写的时候,大部分想基于屏幕的像素,这就需要对顶点坐标进行处理:

java 复制代码
  // an attribute will receive data from a buffer
  attribute vec2 a_position;
  uniform vec2 u_resolution;
  // attribute vec4 a_position;
  // all shaders have a main function
  void main() {

    // gl_Position is a special variable a vertex shader
    // is responsible for setting
    vec2 zeroToOne = a_position - u_resolution / 2.0;
    vec2 zeroToTwo = zeroToOne / u_resolution * 2.0;
    gl_Position = vec4(zeroToTwo, 0, 1);
  }

给属性赋值:

ini 复制代码
      var resolutionUniformLocation = gl.getUniformLocation(program, "u_resolution");
      gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
      

这样就可以使用屏幕坐标系赋值了:

ini 复制代码
    var positions = [320, 240, 0, 240, 320, 0, 0, 0];

修改正方形颜色

首先定义个全局的颜色变量,片元着色器如下:

csharp 复制代码
  precision mediump float;
  uniform vec4 u_color;
  void main() {
    // gl_FragColor is a special variable a fragment shader
    // is responsible for setting
    gl_FragColor = u_color; // return redish-purple
  }

然后给片元着色器赋随机值:

javascript 复制代码
  // get the color
  var colorUniformLocation = gl.getUniformLocation(program, "u_color");
  // random color
  gl.uniform4fv(colorUniformLocation, [Math.random(), Math.random(), Math.random(), 1]);

这样阵正方形每次都被渲染成不同的颜色。

通过点击事件画点

接下来我们可以思考怎么通过点击事件在画布上画点了。无非就是点击后获取屏幕坐标,转换成canvas上的坐标位置,然后赋值给bufferData,然后调用gl.drawArrays方法,将这些这个点画出来就行了:

ini 复制代码
  canvas.addEventListener('click', function(e) {
    let x = e.clientX;
    let y = e.clientY;
    let rect = e.target.getBoundingClientRect();
    x = x - rect.left;
    y = rect.bottom - y;
    console.log(x, y);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([x, y, 0, 1]), gl.STATIC_DRAW);
    gl.drawArrays(gl.POINTS, 0, 1);
  });
  

总结

你可能已经体会到webgl的工作其实比较简单,就是根据顶点着色器和片元着色器,去绘制点、线、三角形。虽然三维图形很复杂,只是程序员的计算复杂了。webgl的api只做光栅化处理,这个概念还是比较容易理解的。这一节有了大概的认识后,我们下一节将深入讲解webgl的基本原理。

相关推荐
泽_浪里白条9 小时前
我在 Superset 6.x 做自定义图表 + Embedded SDK 集成的实战复盘(附踩坑清单)
前端·数据可视化
山海鲸实战案例分享13 小时前
【数字孪生实战案例】如何配置下拉菜单,实现与电子地图飞线的数据联动查询?~山海鲸可视化
数字孪生·数据可视化·零代码·实战案例·山海鲸可视化·电子地图·数据联动
无心使然16 小时前
Openlayers调用ArcGis要素服务之一 ——要素查询 (/query)
前端·javascript·数据可视化
无心使然云中漫步17 小时前
Openlayers调用ArcGis地图服务之五 —— 要素识别(/identify)
前端·arcgis·vue·数据可视化
无心使然1 天前
Openlayers调用ArcGis影像服务之一动态地图、地图切片(/exportImage)
前端·javascript·数据可视化
SL-staff1 天前
中小企业 BI 选型:帆软、Power BI、JVS-BI 性价比与架构对比
数据分析·数据可视化·powerbi·帆软·bi工具·部署架构·jvs-bi
山海鲸实战案例分享2 天前
【数字孪生实战案例】怎样开启三维场景,实现场景内所有模型自动转动?~山海鲸可视化
数字孪生·数据可视化·零代码·实战案例·山海鲸可视化·三维场景·自动旋转
国产化创客2 天前
龙芯 2K0300-- 实现工业网关监控仪表盘项目
嵌入式硬件·物联网·数据可视化
无心使然3 天前
Openlayers调用ArcGis地图服务之五 —— 要素识别(/identify)
前端·vue.js·数据可视化
柳杉3 天前
有了大屏设计稿还不够,我又用 gpt-image-2把里面的素材扒了出来
前端·three.js·数据可视化