mthoutai

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

目标

  • 使用WebgL绘制两个点、一条线、一个面
  • 了解整个绘制的编写流程并进行梳理,并深入进入代码里面

WebGL原理示意图

在这里插入图片描述
从上面这个图依然展示出来,从上图中,可以清晰的看出来,我们在js这边需要准备数据,并传入着色器

  • 顶点数据:我们需要描绘物体的位置信息,用于来确定这个物体,也就是由这些点来组成物体。
  • 矩阵:矩阵我们后面解释,这里简单介绍是用于物体的平移、缩放、旋转等三维变换。
  • 颜色:颜色是在物体对光的反应,也是可以进行运算的,暂时不考虑,每个数都是0-1。

而上面右侧,主要是顶点着色器与片元着色器,不能说其他的不重要,而是,只有这两个,我们编程接触的多,其他的基本不由我们编程完成,最多是调用api。而顶点和片元着色器,我们在shader程序中有写它的源代码,也就是可以编程来让它怎么做。

  • 顶点着色器:在脚本中type=“x-shader/x-vertex”,id是可以自定义的,这里用id=“vertexShader”。下面具体介绍代码。
  • 片元着色器:在脚本中type=“x-shader/x-fragment”,id是可以自定义的,这里用id=“fragmentShader”。下面具体介绍代码。

着色器代码

顶点着色器代码解析

<!-- 顶点着色器源码 -->
  <script id="vertexShader" type="x-shader/x-vertex">
    attribute vec2 a_position;
    void main() {
    gl_Position = vec4(a_position, 0.0, 1.0);
    gl_PointSize = 10.0;
    }
    </script>
  • 属性attribute:通过属性变量对点的位置进行设置。
  • vec2:这里暂时没有涉及到三维,所以用两个标量来确定点的平面位置即可。
  • gl_Position: 在主函数中确定点的位置,也就是把attribute的值给gl_Position。
  • gl_PointSize:在主函数中确定点的大小,这里写死一个值给gl_Position。当然后期是变量赋值,同位置的赋值,之后不再赘述。

片元着色器代码解析

<!-- 片元着色器源码 -->
  <script id="fragmentShader" type="x-shader/x-fragment">
    void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
    </script>
  • gl_FragColor:在主函数中确定点的颜色,这里写死一个值给gl_FragColor。之后会两个着色器之间的通信,进行变量赋值。

js代码

初始化WebGL代码

let canvasElement
let gl
function initWebGL() {
// 获取canvas元素
canvasElement = document.getElementById('canvasId')
// 获取WebGL上下文
gl = canvasElement.getContext('webgl')
}
  • 获取canvas元素:原生js写法,这个几乎不用解释,主要是,初始化时,需要获取,或者创建出来。
  • 获取WebGL上下文:调用getContext这个函数,从canvas元素身上获取webgl。

初始化shaders代码

function initShaders() {
// 获取着色器源码
const vsSource = document.getElementById('vertexShader').innerText
const fsSource = document.getElementById('fragmentShader').innerText
// 初始化着色器
initShader(gl, vsSource, fsSource)
}
// -----------------这些代码之后需要单独封装,因此gl需要传递过来---------------------
function initShader(gl, vsSource, fsSource) {
// 创建着色器对象
const vShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)
const fShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)
// 创建程序对象
const program = gl.createProgram()
// 将着色器对象分配给程序对象
gl.attachShader(program, vShader)
gl.attachShader(program, fShader)
// 链接程序对象
gl.linkProgram(program)
// 使用程序对象
gl.useProgram(program)
// 将程序对象分配给gl.program
gl.program = program
}
function loadShader(gl, type, source) {
// 创建着色器
const shader = gl.createShader(type)
// 加载着色器源码
gl.shaderSource(shader, source)
// 编译着色器
gl.compileShader(shader)
return shader
}
  • 获取着色器源码:有的人在这里直接用模板字符串的写法,也是可以的,我们使用了分离写法,所以获取下。
  • 初始化着色器:调用初始化函数,传入gl, vsSource, fsSource,具体过程分为创建着色器对象创建程序对象
  • 创建着色器对象:创建具体的着色器,编译着色器。
  • 创建程序对象:创建WebGL程序对象,连接并使用。
创建着色器对象
  • 创建着色器:根据类型创建具体的着色器,是片元着色器还是顶点着色器。
  • 加载着色器源码:将传过来的源码与着色器绑定。
  • 编译着色器:对着色器代码进行编译。

流程图

gl.createShader
gl.shaderSource
gl.compileShader
return shader
创建程序对象
  • 创建程序对象:创建WebGL程序对象。
  • 加载具体的着色器对象:将具体的着色器分配给WebGL程序对象。
  • 连接程序对象:连接WebGL程序对象。
  • 使用程序对象:使用WebGL程序对象。
  • 将程序对象挂载到gl上:将WebGL程序对象挂载到gl身上,以备后用。

流程图

gl.createProgram
gl.attachShader/gl.attachShader
gl.linkProgram
gl.useProgram
gl.program = program

初始化buffers代码

function initBuffers() {
setPointPosition(0.5, 0.5)
}
function setPointPosition(x, y) {
// 获取位置属性
const a_position = gl.getAttribLocation(gl.program, 'a_position')
// 设置位置属性
gl.vertexAttrib2f(a_position, x, y)
}
  • 初始化buffers:这里用来初始化各种buffer,绘制一个点没有用到,我们在这里调用了设置点位置的函数,充当了顶点缓冲区。
  • 获取位置属性:使用gl身上获取本地attribute变量的方法获取位置属性。
  • 设置位置属性:使用gl身上设置设置顶点属性的方法进行设置,这里有很多同族函数,之后会说到。

绘制代码

function draw() {
// 清空画布颜色
gl.clearColor(0.0, 0.0, 0.0, 1.0)
// 清空画布
gl.clear(gl.COLOR_BUFFER_BIT)
// 绘制
gl.drawArrays(gl.POINTS, 0, 1)
}
  • 清空画布颜色:设置用什么颜色来清空画布。
  • 清空画布:使用上面的颜色来清空画布,或者说是初始化画布颜色。
  • 绘制:使用gl身上的绘制方法drawArrays进行绘制,参数分别代表绘制的类型、从哪个点开始、画几个点。

代码总结

以上两个板块,着色器代码与js代码板块,基本上把现有的代码就走了一遍,采取的是,打码+下面解释的方式,之后我们会逐步进入细节。大的流程方面,参考前三期的示意图、流程图等地方。如有需要,之后在具体的地方,再去画出来具体的流程,供日后参考与修正。

绘制多个点

接下来我们来绘制多个点,其实有很多细节,我们捡主要的说,绘制多个点,从画画的角度,就是再多画一个点,但是在这里不行了。
我们需要同时描绘出来多个点的信息,进行绘制,否则就会覆盖前面的点。
有两种方案,第一种是使用同步绘制方案,第二种就是使用顶点缓冲区。我们采用第二种。

完整代码

<!doctype html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>绘制多个点</title>
            <!-- 顶点着色器源码 -->
              <script type="x-shader/x-vertex" id="vertexShaderId">
                attribute vec2 a_position;
                void main() {
                gl_Position = vec4(a_position, 0.0, 1.0);
                gl_PointSize = 10.0;
                }
                </script>
                  <!-- 片元着色器源码 -->
                    <script type="x-shader/x-fragment" id="fragmentShaderId">
                      void main() {
                      gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
                      }
                      </script>
                        <script>
                          let canvasElement
                          let gl
                          const vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5])
                          function init() {
                          initWebGL()
                          initShaders()
                          initBuffers()
                          draw()
                          }
                          function initWebGL() {
                          // 获取canvas
                          canvasElement = document.getElementById('canvasId')
                          // 设置canvas
                          canvasElement.width = 600
                          canvasElement.height = 600
                          // 获取webgl上下文
                          gl = canvasElement.getContext('webgl')
                          }
                          function initShaders() {
                          // 获取着色器源代码
                          const vsSource = document.getElementById('vertexShaderId').innerText
                          const fsSource = document.getElementById('fragmentShaderId').innerText
                          // 初始化着色器程序
                          initShader(gl, vsSource, fsSource)
                          }
                          function initBuffers() {
                          // 创建顶点位置缓冲区
                          const positionBuffer = gl.createBuffer()
                          // 将缓冲区绑定到目标
                          gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
                          // 为缓冲区提供数据positionArray,STATIC_DRAW代表数据为静态的
                          gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW)
                          // 获取顶点位置
                          const a_position = gl.getAttribLocation(gl.program, 'a_position')
                          // 指定如何解析顶点数据数组
                          gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)
                          // 启用顶点属性数组
                          gl.enableVertexAttribArray(a_position)
                          }
                          function draw() {
                          // 设置清屏颜色
                          gl.clearColor(0.0, 0.0, 0.0, 1.0)
                          // 清屏
                          gl.clear(gl.COLOR_BUFFER_BIT)
                          // 绘制
                          gl.drawArrays(gl.POINTS, 0, vertexData.length / 2)
                          }
                          // -------------------------------------------------
                          function initShader(gl, vsSource, fsSource) {
                          // 创建着色器对象
                          const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)
                          const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)
                          // 创建着色器程序
                          const program = gl.createProgram()
                          // 添加着色器对象
                          gl.attachShader(program, vertexShader)
                          gl.attachShader(program, fragmentShader)
                          // 连接着色器程序
                          gl.linkProgram(program)
                          // 使用着色器程序
                          gl.useProgram(program)
                          // 存储着色器程序
                          gl.program = program
                          }
                          function loadShader(gl, type, source) {
                          // 创建着色器
                          const shader = gl.createShader(type)
                          // 绑定着色器源代码
                          gl.shaderSource(shader, source)
                          // 编译着色器
                          gl.compileShader(shader)
                          return shader
                          }
                          </script>
                            </head>
                              <body onload="init()">
                                <canvas id="canvasId"></canvas>
                                  </body>
                                    </html>

初始化缓冲区

function initBuffers() {
// 创建顶点位置缓冲区
const positionBuffer = gl.createBuffer()
// 将缓冲区绑定到目标
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
// 为缓冲区提供数据顶点数据,STATIC_DRAW代表数据为静态的
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW)
// 获取顶点位置
const a_position = gl.getAttribLocation(gl.program, 'a_position')
// 指定如何解析顶点数据数组
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)
// 启用顶点属性数组
gl.enableVertexAttribArray(a_position)
}
  • 创建顶点位置缓冲区:使用gl身上创建buffer的方法创建顶点位置缓冲区。
  • 将缓冲区绑定到目标:使用gl身上的bindBuffer对缓冲区进行绑定。
  • 为缓冲区设置数据:使用gl身上的bufferData的方法为缓冲区设置数据。
  • 获取顶点位置:这个在设置一个点的时候也用到,获取位置属性。
  • 指定如何解析顶点数据:参数含义依次是:属性位置、每个属性的元素数量、数据类型、是否标准化、跨度、偏移量。​
  • 启用顶点属性数组:开启顶点属性数组。​
const vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5])
  • 另外是给了两个点的坐标,由于我们只在平面上画,所以采取了二维向量,因此是四个数据。其他的地方基本没办,参看完整代码即可。

绘制线

接下来我们来绘制线,其实只需要改下绘制方式即可

完整代码

<!doctype html>
    <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>绘制一个点</title>
      <!-- 顶点着色器源码 -->
          <script type="x-shader/x-vertex" id="vertexShaderId">
          attribute vec2 a_position;
          void main() {
          gl_Position = vec4(a_position, 0.0, 1.0);
          gl_PointSize = 10.0;
          }
        </script>
        <!-- 片元着色器源码 -->
            <script type="x-shader/x-fragment" id="fragmentShaderId">
            void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
            }
          </script>
          <script>
            let canvasElement
            let gl
            const vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5])
            function init() {
            initWebGL()
            initShaders()
            initBuffers()
            draw()
            }
            function initWebGL() {
            // 获取canvas
            canvasElement = document.getElementById('canvasId')
            // 设置canvas
            canvasElement.width = 600
            canvasElement.height = 600
            // 获取webgl上下文
            gl = canvasElement.getContext('webgl')
            }
            function initShaders() {
            // 获取着色器源代码
            const vsSource = document.getElementById('vertexShaderId').innerText
            const fsSource = document.getElementById('fragmentShaderId').innerText
            // 初始化着色器程序
            initShader(gl, vsSource, fsSource)
            }
            function initBuffers() {
            // 创建顶点位置缓冲区
            const positionBuffer = gl.createBuffer()
            // 将缓冲区绑定到目标
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
            // 为缓冲区提供数据positionArray,STATIC_DRAW代表数据为静态的
            gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW)
            // 获取顶点位置
            const a_position = gl.getAttribLocation(gl.program, 'a_position')
            // 指定如何解析顶点数据数组
            gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)
            // 启用顶点属性数组
            gl.enableVertexAttribArray(a_position)
            }
            function draw() {
            // 设置清屏颜色
            gl.clearColor(0.0, 0.0, 0.0, 1.0)
            // 清屏
            gl.clear(gl.COLOR_BUFFER_BIT)
            // 绘制
            gl.drawArrays(gl.LINES, 0, vertexData.length / 2)
            }
            // -------------------------------------------------
            function initShader(gl, vsSource, fsSource) {
            // 创建着色器对象
            const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)
            const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)
            // 创建着色器程序
            const program = gl.createProgram()
            // 添加着色器对象
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 连接着色器程序
            gl.linkProgram(program)
            // 使用着色器程序
            gl.useProgram(program)
            // 存储着色器程序
            gl.program = program
            }
            function loadShader(gl, type, source) {
            // 创建着色器
            const shader = gl.createShader(type)
            // 绑定着色器源代码
            gl.shaderSource(shader, source)
            // 编译着色器
            gl.compileShader(shader)
            return shader
            }
          </script>
        </head>
          <body onload="init()">
        <canvas id="canvasId"></canvas>
        </body>
      </html>

这里只需要将绘制的方式改为LINES,绘制数量取vertexData.length / 2,其他的地方没有改变。线还有其他绘制方式,如首尾连接等,下面代码有给出并注释。

gl.drawArrays(gl.LINES, 0, vertexData.length / 2) // 绘制线段: 两两顶点为一条线
// gl.drawArrays(gl.LINE_STRIP, 0, vertexData.length / 2) // 绘制线段: 连续连接为线
// gl.drawArrays(gl.LINE_LOOP, 0, vertexData.length / 2) // 绘制线段: 连接后自动闭合

绘制面

接下来我们来绘制线,其实只需要改下绘制方式即可

完整代码

<!doctype html>
    <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>绘制一个点</title>
      <!-- 顶点着色器源码 -->
          <script type="x-shader/x-vertex" id="vertexShaderId">
          attribute vec2 a_position;
          void main() {
          gl_Position = vec4(a_position, 0.0, 1.0);
          gl_PointSize = 10.0;
          }
        </script>
        <!-- 片元着色器源码 -->
            <script type="x-shader/x-fragment" id="fragmentShaderId">
            void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
            }
          </script>
          <script>
            let canvasElement
            let gl
            const vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5, 0.5, -0.5])
            function init() {
            initWebGL()
            initShaders()
            initBuffers()
            draw()
            }
            function initWebGL() {
            // 获取canvas
            canvasElement = document.getElementById('canvasId')
            // 设置canvas
            canvasElement.width = 600
            canvasElement.height = 600
            // 获取webgl上下文
            gl = canvasElement.getContext('webgl')
            }
            function initShaders() {
            // 获取着色器源代码
            const vsSource = document.getElementById('vertexShaderId').innerText
            const fsSource = document.getElementById('fragmentShaderId').innerText
            // 初始化着色器程序
            initShader(gl, vsSource, fsSource)
            }
            function initBuffers() {
            // 创建顶点位置缓冲区
            const positionBuffer = gl.createBuffer()
            // 将缓冲区绑定到目标
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
            // 为缓冲区提供数据positionArray,STATIC_DRAW代表数据为静态的
            gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW)
            // 获取顶点位置
            const a_position = gl.getAttribLocation(gl.program, 'a_position')
            // 指定如何解析顶点数据数组
            gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)
            // 启用顶点属性数组
            gl.enableVertexAttribArray(a_position)
            }
            function draw() {
            // 设置清屏颜色
            gl.clearColor(0.0, 0.0, 0.0, 1.0)
            // 清屏
            gl.clear(gl.COLOR_BUFFER_BIT)
            // 绘制
            gl.drawArrays(gl.TRIANGLES, 0, vertexData.length / 2)
            }
            // -------------------------------------------------
            function initShader(gl, vsSource, fsSource) {
            // 创建着色器对象
            const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)
            const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)
            // 创建着色器程序
            const program = gl.createProgram()
            // 添加着色器对象
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 连接着色器程序
            gl.linkProgram(program)
            // 使用着色器程序
            gl.useProgram(program)
            // 存储着色器程序
            gl.program = program
            }
            function loadShader(gl, type, source) {
            // 创建着色器
            const shader = gl.createShader(type)
            // 绑定着色器源代码
            gl.shaderSource(shader, source)
            // 编译着色器
            gl.compileShader(shader)
            return shader
            }
          </script>
        </head>
          <body onload="init()">
        <canvas id="canvasId"></canvas>
        </body>
      </html>

这里只需要将绘制的方式改为TRIANGLES,由于组基本的面就是三角形,因此是TRIANGLES,其他的地方就是点的数量上有变化。面还有其他绘制方式,如连续连接为三角形等,下面代码有给出并注释。

gl.drawArrays(gl.TRIANGLES, 0, vertexData.length / 2) // 绘制三角形: 三个顶点为三角形
// gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexData.length / 2) // 绘制三角形: 连续连接为三角形
// gl.drawArrays(gl.TRIANGLE_FAN, 0, vertexData.length / 2) // 绘制三角形: 扇形

总结:本文主要梳理了在Webgl中如何绘制一个点的所有具体代码,解释了具体的功能。然后基于此,引入了buffer,添加了点的数量,绘制了多个点,以及更改绘制方式和点的数量来绘制了线和面,给出了多种绘制点与面的方式。具体细节,大家可以尝试多增加几个点,用多种方式来绘制,作为文章记录,基本到这里就可以了。致此,我们将每个代码走一遍的任务也就完成了,之后主要是思路以及变换上的事情多一些了。下次我们来说,三维变换。

上一篇:WebGL学习及项目实战(第02期:绘制一个点)
返回系列首页

posted on 2025-10-29 14:52  mthoutai  阅读(9)  评论(0)    收藏  举报