webGL编程指南实战教程

学习路线:

  1. 如果你是在校大学生,有足够的时间去学习:前端>数学(几何+线性代数)>图形学>webgl>shader >threejs>three.js源码
  2. 如果你是工作中使用,需要快速出产成品:前端>threeJs。

教程主要分为四大部分:

  1. webgl容器(坐标系)
  2. webgl渲染管线
  3. webgl关键名词
  4. 案例实战

一、webgl容器(坐标系)

1、canvas坐标系

在2D绘图环境中的坐标系统,默认情况下是与窗口坐标系统相同,它以canvas的左上角为坐标原点,沿x轴向右为正值,沿y轴项下为正值。其中canvas坐标的单位都是"px";
webGL编程指南实战教程_第1张图片

2、webgl坐标系

webgl使用的是正交右手坐标系,且每个方向都有可使用的值的区间,超出该矩形区间的图像不会绘制:

  • x轴最左边为-1,最右边为1;
  • y轴最下边为-1,最上边为1;
  • z轴朝向你的方向最大值为1,远离你的方向最大值为-1;

注:这些值与canvase的尺寸无关,无论canvas的长宽比是多少,webgl的区间值都是一致的
webGL编程指南实战教程_第2张图片

3、webgl与屏幕坐标的关系

在2D绘图环境中的坐标系统,默认情况下是与窗口坐标系统相同,它以canvas的左上角为坐标原点,沿x轴向右为正值,沿y轴向下为正值。其中canvasd坐标的单位都是"px"。
webGL编程指南实战教程_第3张图片

二、webgl渲染管线

渲染管线就像一条流水线,由一系列具有特定功能的数字电路单元组成,下一个功能单元处理上一个功能单元生成的数据,逐级处理数据。

定点着色器和片元着色器是可编程的功能单元,拥有更大自主性,还有光栅器、深度测试等补课表承德功能单元。CPU会通过webgl api和GPU通讯,传递着色器程序和数据,GPU执行的着色器程序
webGL编程指南实战教程_第4张图片

webgl渲染管线其实就是一个流水线,一个方便面为例子:

  1. 生成一个方便面由顶点构成的坐标集合,这个集合可以勾勒方便面的雏形,保存在顶点缓冲区中,待用。
  2. 拿到顶点缓冲区中的顶点数据,使用uniform传给顶点着色器。
  3. 收集数据之后,使用图元装配,将方便面的雏形构建成型。
  4. 然后使用光栅器,将方便面雏形切割成一个个三维的小方块,类似像素化。
  5. 然后使用片元着色器,给像素化后的方便面雏形上色、上纹理。
  6. 然后使用归属测试、模板测试、深度测试 做图形测试。
  7. 融合、抖动,存入颜色缓存区,供用户展现调用。

三、webgl关键名词

1、顶点着色器

顶点着色器是GPU渲染管线上一个可以执行着色器语言的功能单元,具体执行的就是顶点着色器程序,webgl定点着色器程序在javascript中一字符串的形式存在,通过编译处理后传递给顶点着色器执行。定点着色器主要作用就是执行点点着色器程序对定点进行变换计算,比如点点位置坐标执行进行旋转、平移等矩阵变换,变换后新的顶点坐标然后赋值给内置变量gl_Position,作为顶点着色器的输出,图元装配和光栅化环节的输入;
webGL编程指南实战教程_第5张图片
webGL编程指南实战教程_第6张图片
webGL编程指南实战教程_第7张图片

2、图元装配

顶点变换后的操作是图元装配,硬件上具体是怎么回事不用考虑,从程序的角度来看,就是绘制函数drawArray()drawElement()第一个参数绘制模式mode控制定点如何装配为图元,gl.LINES的定义的是把两个定点装配成一个线条图元,gl.TRIANGLES定义的是三个顶点装配为一个三角面图元,gl.POINTS定义的是一个点域图元。
webGL编程指南实战教程_第8张图片

3、光栅化

就是将图元分解成片元
webGL编程指南实战教程_第9张图片

4、片元着色器

片元着色器和顶点着色器一样是GPU渲染管线上一个可以执行着色器程序的功能单元,顶点着色器处理的是逐顶点处理顶点数据,片元着色器是逐片元处理片源数据。通过给内置变量gl.FragColor赋值可以给每一个片元进行着色,值可以是一个确定的RGBA值,可以是一个和片元位置相关的值,也可以是炒制后的顶点颜色。除了给片元进行着色之外,通过关键字discard还可以实现那些偏远可以被丢弃,被丢弃的片元不会出现在帧缓冲区,自然不会显示在canvas画布上。
webGL编程指南实战教程_第10张图片

片元着色器的功能可以简单理解成,给顶点着色器着色。

四、鼠标动态绘制点

实现思路:

  1. 绘制单个点
  2. 鼠标事件监听点击事件
  3. 将点推送到数组中
  4. 绘制数组中所有点

1、创建一个空白画布

<template>
    <canvas id="webglCanvas" ref="webglCanvas" width="500" height="500"></canvas>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
const webglCanvas = ref(null);
// 入口函数
const init = () => {
    const gl = webglCanvas.value.getContext("webgl");   //拿到webgl实例
    if (!gl) {
        console.log("fail to get the rendering context of webgl");
        return;
    }
    gl.clearColor(0.0, 0.0, 0.0, 1.0);  //设置背景色
    gl.clear(gl.COLOR_BUFFER_BIT);      //清空背景
}

onMounted(() => {
    init();
})
</script>
  1. gl.clearColor(red,green.blue,alpha)

    执行绘图区域背景色。

    • red,green.blue设置都是从0.0到1.0
    • alpha指定透明度,值是从0.0到1.0

    在我们css颜色系统中,设置都是从0到255,webgl的色值是从0-1,这是因为继承自openGL,越大颜色越是亮。一旦指定了背景色之后,颜色就会驻留在webgl系统中,在下次调用gl.clearColor()之前不会改变。

  2. gl.clear(buffer)

    将指定缓冲区设定为预定的值。如果清空的是颜色缓冲区,那么将使用gl.clearColor()指定的值(作为预定值)

    • bugger
      • gl.COLOR_BUFFER_BIT:指定颜色缓存-clearColor(red,green,blue,alpha)
      • gl.DEPTH_BUFFER_BIT:指定深度缓冲区-clearDepth(depth)
      • gl.STENCLL_BUFFER_BIT:指定模板缓冲区-clearStencil(s)

2、画一个点

<script setup lang="ts">
import { ref, onMounted } from "vue";
const webglCanvas = ref(null);
// 顶点着色器
var VSHADER_SOURCE =
    'void main() {\n' +
    '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // Set the vertex coordinates of the point
    '  gl_PointSize = 10.0;\n' +                    // Set the point size
    '}\n';

// 片元着色器
var FSHADER_SOURCE =
    'void main() {\n' +
    '  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // Set the point color
    '}\n';
// 入口函数
const init = () => {
    const gl = getWebGLContext(webglCanvas.value);   //拿到webgl实例
    if (!gl) {
        console.log("fail to get the rendering context of webgl");
        return;
    }
    // 初始化着色器
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to intialize shaders.');
        return;
    }

    gl.clearColor(0.0, 0.0, 0.0, 1.0);  //设置背景色
    gl.clear(gl.COLOR_BUFFER_BIT);      //清空背景

    // 画一个点
    gl.drawArrays(gl.POINTS, 0, 1);
}

onMounted(() => {
    init();
})
</script>
  • gl.drawArrays(mode,first,count)

    可以用于绘制各种图形。实际是执行着色器,按照mode参数指定的方式绘制图形。

    • mode:指定绘制方式,可接收一下常量符号:gl.POINTS、gl.LINEs、gl.LINE_STRIP、gl.LINE_LOOP、gl.TRIANGLES、gl.TRIANGLE_STRIP、gl.TRIANGLE_FAN。
    • first:指定从哪个顶点开始绘制(整型)
    • count:指定绘制需要用到多少个顶点(整型)
  • getWebGLContext

    获取webgl实例(具体见源码)

  • initShaders

    初始化着色器(具体见源码)

  • VSHADER_SOURCE

    gl_Position:设置位置

    gl_PointSize:设置尺寸

  • FSHADER_SOURCE

    gl_FragColor:设置颜色

webGL编程指南实战教程_第11张图片

webgl遵守右手坐标系,所以我们看到修改gl_Position = vec4(0.0, 0.0, 0.0, 1.0);中的坐标值,对应不同的效果,具体如下:

  1. vec4(1.0, 0.0, 0.0, 1.0):移动到最右边
  2. vec4(0.0, 1.0, 0.0, 1.0):移动到最上边
  3. vec4(0.0, 0.0, 1.0, 1.0):移动到最前边(靠近屏幕前的你)

3、使用attribute传值gl_Position

<script setup lang="ts">
...
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +//定义a_Position
    'void main() {\n' +
    '  gl_Position = a_Position;\n' + // a_Position传给gl_Position
    '  gl_PointSize = 10.0;\n' +            
    '}\n';
...
// 入口函数
const init = () => {
	...
    // 获取着色器中a_Position变量的存储位置
    const a_Position = gl.getAttribLocation(gl.program, "a_Position");

    // 将顶点位置传入a_Position位置
    gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
	...
}

onMounted(() => {
    init();
})
</script>

如上,我们使用attribute作为一个将js中变量传入GLSL着色器语法的媒介。

  • getAttribLocation(program,name)

    获取由name参数指定的attribute变量的存储地址。返回值如果等于-1,则表示变量不存在。正常>=0;

  • vertexAttrib3f(location,v0,v1,v2)

    将数据(v0,v1,v2)传给由location指定的attribute变量;

给attribute变量赋值的方法,除了vertexAttrib3f,还有:vertexAttrib1fvertexAttrib2fvertexAttrib4f,用法都一样。以上这些都是浮点类型的入参。还有四个整型的入参,名字和上面的四个类似:vertexAttrib1ivertexAttrib2ivertexAttrib3ivertexAttrib4i

4、使用attribute传值gl_PointSize

<script setup lang="ts">
...
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +//定义a_Position
    'attribute float a_PointSize;\n' +//定义a_PointSize
    'void main() {\n' +
    '  gl_Position = a_Position;\n' + // a_Position传给gl_Position
    '  gl_PointSize = a_PointSize;\n' +            
    '}\n';
...
// 入口函数
const init = () => {
	...
    // 获取着色器中a_Position变量的存储位置
    const a_Position = gl.getAttribLocation(gl.program, "a_Position");
    const a_PointSize = gl.getAttribLocation(gl.program, "a_PointSize");

    // 将顶点位置传入a_Position位置
    gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
    gl.vertexAttrib1f(a_PointSize, 10.0);
	...
}

onMounted(() => {
    init();
})
</script>

代码逻辑和gl_Position的传值类似,归纳如下:

在着色器中定义变量,并在main中传入赋值,然后在js代码逻辑中用getAttribLocation获取拿到变量地址,然后使用vertexAttrib1f给变量地址塞入值;

5、动态绘制一个点

到目前位置,绘制的点事js代码中写死的,我们这里改成根据鼠标在画布上点击,点击在哪儿,就在哪儿画上点。代码逻辑如下:

  • 监听canvas上的鼠标点击事件
  • 点击时获取鼠标坐标,并转化为webgl坐标系
  • 清空画布,遍历点集合,并一一绘制
<script setup lang="ts">
import { Canvas } from "fabric/fabric-impl";
import { ref, onMounted } from "vue";
const webglCanvas = ref(null);
// 顶点着色器
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +//定义a_Position
    'attribute float a_PointSize;\n' +//定义a_PointSize
    'void main() {\n' +
    '  gl_Position = a_Position;\n' + // a_Position传给gl_Position
    '  gl_PointSize = a_PointSize;\n' +
    '}\n';

// 片元着色器
var FSHADER_SOURCE =
    'void main() {\n' +
    '  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // Set the point color
    '}\n';
// 入口函数
const init = () => {
    const gl = getWebGLContext(webglCanvas.value);   //拿到webgl实例
    if (!gl) {
        console.log("fail to get the rendering context of webgl");
        return;
    }
    // 初始化着色器
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to intialize shaders.');
        return;
    }

    // 获取a_Position的存储位置
    const a_Position = gl.getAttribLocation(gl.program, "a_Position");
    const a_PointSize = gl.getAttribLocation(gl.program, "a_PointSize");

    // 将点的位置传到attribute变量中
    gl.vertexAttrib1f(a_PointSize, 10.0);

    gl.clearColor(0.0, 0.0, 0.0, 1.0);  //设置背景色
    gl.clear(gl.COLOR_BUFFER_BIT);      //清空背景

    let g_points = [];
    const canvas = webglCanvas.value;
    canvas.onmousedown = function (ev: any) {
        let { x, y } = ev;//x、y光标在整个可视区域的坐标
        let rect = canvas.getBoundingClientRect();//用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
        let coordsX = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2);
        let coordsY = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2);
        // 将坐标保存g_points
        g_points.push([coordsX, coordsY]);
        gl.clear(gl.COLOR_BUFFER_BIT);      //清空背景
        // debugger
        g_points.forEach(point => {
            // 将点的位置传到attribute变量中
            gl.vertexAttrib3f(a_Position, ...point, 0.0);

            // 绘制点
            gl.drawArrays(gl.POINTS, 0, 1);
        });
    }
}

onMounted(() => {
    init();
})
</script>

这里有个canvas坐标转换为webgl的公式,看代码可能比较抽象,下面上一张图:

webGL编程指南实战教程_第12张图片

最终绘制效果如下:

webGL编程指南实战教程_第13张图片

6、改变点的颜色

类似使用attributegl_position传值,颜色的传值使用uniform来传值,具体传值逻辑如下:

  • 片元着色器中定义uniform颜色变量u_FragColor
  • js逻辑中拿到u_FragColor的地址
  • 在绘制每个点的时候,使用gl.uniform4f(u_FragColor, ...point, 0.0, 1.0);传值,设置点的颜色

整体代码如下:

<script setup lang="ts">
import { Canvas } from "fabric/fabric-impl";
import { ref, onMounted } from "vue";
const webglCanvas = ref(null);
// 顶点着色器
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +//定义a_Position
    'attribute float a_PointSize;\n' +//定义a_PointSize
    'void main() {\n' +
    '  gl_Position = a_Position;\n' + // a_Position传给gl_Position
    '  gl_PointSize = a_PointSize;\n' +
    '}\n';

// 片元着色器
var FSHADER_SOURCE =
    'precision mediump float;\n' +
    'uniform vec4 u_FragColor;\n' +
    'void main() {\n' +
    '  gl_FragColor = u_FragColor;\n' + // Set the point color
    '}\n';
// 入口函数
const init = () => {
    const gl = getWebGLContext(webglCanvas.value);   //拿到webgl实例
    if (!gl) {
        console.log("fail to get the rendering context of webgl");
        return;
    }
    // 初始化着色器
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to intialize shaders.');
        return;
    }

    // 获取a_Position的存储位置
    const a_Position = gl.getAttribLocation(gl.program, "a_Position");
    const a_PointSize = gl.getAttribLocation(gl.program, "a_PointSize");
    const u_FragColor = gl.getUniformLocation(gl.program, "u_FragColor");

    // 将点的位置传到attribute变量中
    gl.vertexAttrib1f(a_PointSize, 10.0);

    gl.clearColor(0.0, 0.0, 0.0, 1.0);  //设置背景色
    gl.clear(gl.COLOR_BUFFER_BIT);      //清空背景

    let g_points = [];
    const canvas = webglCanvas.value;
    canvas.onmousedown = function (ev: any) {
        let { x, y } = ev;//x、y光标在整个可视区域的坐标
        let rect = canvas.getBoundingClientRect();//用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
        let coordsX = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2);
        let coordsY = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2);
        // 将坐标保存g_points
        g_points.push([coordsX, coordsY]);
        gl.clear(gl.COLOR_BUFFER_BIT);      //清空背景
        // debugger
        g_points.forEach(point => {
            // 将点的位置传到attribute变量中
            gl.vertexAttrib3f(a_Position, ...point, 0.0);

            gl.uniform4f(u_FragColor, ...point, 0.0, 1.0);

            // 绘制点
            gl.drawArrays(gl.POINTS, 0, 1);
        });
    }
}

onMounted(() => {
    init();
})
</script>

修改片段着色器,定义uniform变量,用于接收js传入的值:

var FSHADER_SOURCE =
    'precision mediump float;\n' +//精度限定词来指定变量的范围(最大值和最小值)和精度,这里为中精度。
    'uniform vec4 u_FragColor;\n' +
    'void main() {\n' +
    '  gl_FragColor = u_FragColor;\n' + // Set the point color
    '}\n';

拿到uniform变量u_FragColor的值:

const u_FragColor = gl.getUniformLocation(gl.program, "u_FragColor");

设置颜色:

gl.uniform4f(u_FragColor, ...point, 0.0, 1.0);

最终效果如下:

webGL编程指南实战教程_第14张图片

在js代码中向着色器传值时,attribute用于向顶点着色器传值,uniform用于向片元着色器传值;

  • uniform4f(location,v0,v1,v2)

    和vertexAttrib类似,uniform4f也有总计4个同类方法,分别是:uniform1funiform2funiform3funiform4f

五、绘制和变换三角形

1、绘制多个点

不管三维模型的形状多么复杂,其基本组成部分都是三角形,只不过复杂的模型有更多的三角形构成而已。通过创建更细小和更大量的三角形,就可以创建更复杂和更逼真的三维模型。前面我们绘制多个点的时候,每鼠标点击一次,就把坐标存储在g_points中,最后对g_points进行遍历,并使用gl.drawArrays()绘制。这种方式只能绘制一个点,对于复杂图形如果也是这么遍历一一绘制,效率会很差。

webGL提供了一种很方便的机制,即缓冲区对象(buffer object),他可以一次性的想着色器传入多个顶点的数据。缓冲区对象是webGl系统中的一块内存区域,我们可以一次性的想缓冲区对象中填充大量的顶点数据,然后将这些数据保存在其中,共顶点着色器使用。

使用缓冲区对象向顶点着色器传入多个顶点的数据,需要遵循一下五个步骤。处理其他对象,如纹理对象、帧缓冲区对象时的步骤也比较类似,

创建的五个步骤具体如下:

  1. 创建缓冲区对象(gl.creaeBuffer())
  2. 绑定缓冲区对象(gl.bindBuffer())
  3. 将数据写入缓冲区对象(gl.bufferData())
  4. 将缓冲区对象分配给一个attribute变量(gl.vertexAttribPointer())
  5. 开启attribute变量(gl.enableVertexAttribArray())

图示如下:
webGL编程指南实战教程_第15张图片

  • gl.createBuffer()

    创建缓冲区对象

  • gl.deleteBuffer(buffer)

    删除参数buffer表示的缓冲区对象;buffer是待删除的缓冲区对象。

  • gl.bindBuffer(target,buffer)

    绑定缓冲区。创建完成后就是绑定缓冲区到指定的目标,这个目标表示缓冲区对象的用途(在这里,就是向定点着色器提供传给attribute变量的数据),这样webgl才能处理其中的内容。

    • target:参数可以是一下中的一个:
      • gl.ARRAY_BUFFER 表示缓冲区对象中包含了顶点的数据
      • gl.ELEMENT 表示缓冲区对象中包含了顶点的索引值
  • gl.bufferData(target,data,usage)

    向缓冲区写入数据。

    • target:gl.ARRAY_BUFFERgl.ELEMENT_ARRAY_BUFFER
    • data:写入缓冲区对象的数据(类型化数组)
    • usage:
      • gl.STATIC_DRAW:只会向缓冲区对象中写入一次数据,但需要绘制很多次
      • gl.STREAM_DRAW:只会相缓冲区对象中写入一次数据,然后绘制若干次
      • gl.DYNAMIC_DRAW:回想缓冲区对象中多次写入数据,并绘制很多次
  • 类型化数据

    为了优化性能,webgl为每种基本数据类型引入了一种特殊的数组(JavaScript 类型化数组)。浏览器事先知道数组中的数据类型,所以处理起来也更加有效率。

  • gl.vertexAttribPointer()

    将缓冲区对象分配给attribute;

  • gl.enableVertexAttribArray()

    开启attribute变量。为了顶点着色器能够访问缓冲区内的数据,我们需要开启attribute变量。开启后缓冲区对象和attribute变量之间的链接就真正建立起来了。也可以使用gl.disableVertexAttribArray()来关闭分配。开启attribute变量后,即不能用gl.vertexAttrib[1234]f()向他传数据了,除非显示的关闭改attribute变量,你无法同时使用这两个函数。

下面,我们在上面绘制多个点的案例基础上,改用缓存对象来实现,并且鼠标点击制作坐标收集保存,不做渲染,点击渲染按钮时,一次性对缓冲区中的顶点数据做渲染。

代码如下:

<template>
    <div class="main">
        <ol>
            <li v-for="(point, index) in g_points" :key="index">
                {{ point }}</li>
        </ol>
        <div>
            <p>
                使用缓冲对象机制实现
            </p>

            <canvas id="webglCanvas" ref="webglCanvas" width="500" height="250"></canvas>
            <div>
                <el-button @click="draw">绘制</el-button>
            </div>
        </div>
    </div>
</template>
<script setup lang="ts">
import { Canvas } from "fabric/fabric-impl";
import { ref, onMounted } from "vue";
const webglCanvas = ref(null);
// 顶点着色器
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +//定义a_Position
    'attribute float a_PointSize;\n' +//定义a_PointSize
    'void main() {\n' +
    '  gl_Position = a_Position;\n' + // a_Position传给gl_Position
    '  gl_PointSize = a_PointSize;\n' +
    '}\n';

// 片元着色器
var FSHADER_SOURCE =
    'precision mediump float;\n' +
    'uniform vec4 u_FragColor;\n' +
    'void main() {\n' +
    '  gl_FragColor = u_FragColor;\n' + // Set the point color
    '}\n';
// 入口函数
let gl = null;
const g_points = ref([]);
const init = () => {
    gl = getWebGLContext(webglCanvas.value);   //拿到webgl实例
    if (!gl) {
        console.log("fail to get the rendering context of webgl");
        return;
    }
    // 初始化着色器
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to intialize shaders.');
        return;
    }

    // 获取a_Position的存储位置
    const a_PointSize = gl.getAttribLocation(gl.program, "a_PointSize");
    const u_FragColor = gl.getUniformLocation(gl.program, "u_FragColor");

    // 将点的位置传到attribute变量中
    gl.vertexAttrib1f(a_PointSize, 10.0);
    gl.uniform4f(u_FragColor, 1.0, 1.0, 1.0, 1.0);

    gl.clearColor(0.0, 0.0, 0.0, 1.0);  //设置背景色
    gl.clear(gl.COLOR_BUFFER_BIT);      //清空背景

    const canvas = webglCanvas.value;
    canvas.onmousedown = function (ev: any) {
        let { x, y } = ev;//x、y光标在整个可视区域的坐标
        let rect = canvas.getBoundingClientRect();//用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
        let coordsX = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2);
        let coordsY = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2);
        // 将坐标保存g_points
        g_points.value.push([coordsX, coordsY]);
    }
}

const draw = () => {
    console.log("开始绘制");
    // 创建类型数组
    let vertices = new Float32Array(g_points.value.flat());
    // 创建缓冲区
    let vertexBuffer = gl.createBuffer();
    if (!vertexBuffer) {
        return;
    }
    // 将缓冲区绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    //向缓冲区写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    const a_Position = gl.getAttribLocation(gl.program, "a_Position");
    // 将缓冲区对象分配给a_Position变量
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

    gl.enableVertexAttribArray(a_Position);

    //清空背景
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 绘制点
    gl.drawArrays(gl.POINTS, 0, g_points.value.length);
}

onMounted(() => {
    init();
})
</script>

<style lang="scss">
.main{
    display: flex;
}
ol {
    float: left;
    display: block;
    width: 150px;

    li {
        text-align: left;
    }
}
</style>

效果如下(先在画笔上点击,光标未显示):
webGL编程指南实战教程_第16张图片

2、绘制三角形

绘制三角形的方式,相比于上面第四章,只有两个地方有改动。

  1. 不需要a_PointSize,因为只有在绘制单个点的时候才有效;但是如果不删除,也不会导致报错。
// 顶点着色器
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +//定义a_Position
    //'attribute float a_PointSize;\n' +//定义a_PointSize
    'void main() {\n' +
    '  gl_Position = a_Position;\n' + // a_Position传给gl_Position
    //'  gl_PointSize = a_PointSize;\n' +
    '}\n';
  1. gl.POINTS改成gl.TRIANGLES`。
gl.drawArrays(gl.TRIANGLES, 0, g_points.value.length);

需要注意的是,类型数组中的坐标必须是三的对数倍,如:3、6、9、。。。,因为三角形的顶点是3,只有3的倍数个顶点才能正常绘制三角形。

效果如下:
webGL编程指南实战教程_第17张图片

可以看到,顶点如果是3的倍数个,那么就可以正常画出对应数量的三角形。

3、绘制其他图形

上面演示了绘制gl.POINTSgl.TRIANGLES,webgl一共支持7中图形,下面演示同一批点,不同渲染模式的显示结果。
webGL编程指南实战教程_第18张图片

其他的绘制模式代码,和上面区别就只有一点就是修改mode对应的值。

gl.drawArrays(mode, 0, g_points.value.length);

4、移动

实现移动的基本逻辑是,向顶点着色器中传入一个偏移量,每次渲染的时候对每个顶点坐标加上偏移量

// 顶点着色器
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +//定义 a_Position
    'attribute float a_PointSize;\n' +//定义 a_PointSize
    'attribute float a_Translation;\n' +//定义 偏移量
    'void main() {\n' +
    '  gl_Position = vec4(a_Position.x+a_Translation,a_Position.y+a_Translation,a_Position.z+a_Translation,1.0);\n' + 
    '  gl_PointSize = a_PointSize;\n' +
    '}\n';
    
// 移动
const move = () => {
    T = T + 0.1;
    const a_Translation = gl.getAttribLocation(gl.program, "a_Translation");	//取出偏移量地址
    gl.vertexAttrib1f(a_Translation, T);		//给偏移量赋值
    draw();
}    

webGL编程指南实战教程_第19张图片

webGL基本图形

1、drawArrays

前面讲过,drawArray支持的绘制模型有7种:gl.POINTS、gl.LINEs、gl.LINE_STRIP、gl.LINE_LOOP、gl.TRIANGLES、gl.TRIANGLE_STRIP、gl.TRIANGLE_FAN

drawArrays(mode: number, first: number, count: number): void;

  • gl.POINTS 一系列点,依次绘制
  • gl.LINES 每两个一组绘制线段,若点的数目为奇数,最后一个点会被舍弃
  • gl.LINE_STRIP 所有的点依次相连
  • gl.LINE_LOOP 再线条的基础上,将首尾点相连
  • gl.TRIANGLES 每三个一组绘制三角形,若点的数目无法被三整除,剩余的点会被舍弃
  • gl.TRIANGLE_STRIP 一系列条带状的三角形,每个三角形都存在一条边共享
  • gl.TRIANGLE_FAN 类似于扇形的图形

下面上一张各个模式对应的图形:

webGL编程指南实战教程_第20张图片

你可能感兴趣的:(Web前端,前端,javascript,3d,three.js,webgl)