webGL编程指南实战教程

news2024/11/18 3:26:01

学习路线:

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

教程主要分为四大部分:

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

一、webgl容器(坐标系)

1、canvas坐标系

在2D绘图环境中的坐标系统,默认情况下是与窗口坐标系统相同,它以canvas的左上角为坐标原点,沿x轴向右为正值,沿y轴项下为正值。其中canvas坐标的单位都是"px";
在这里插入图片描述

2、webgl坐标系

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

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

注:这些值与canvase的尺寸无关,无论canvas的长宽比是多少,webgl的区间值都是一致的
在这里插入图片描述

3、webgl与屏幕坐标的关系

在2D绘图环境中的坐标系统,默认情况下是与窗口坐标系统相同,它以canvas的左上角为坐标原点,沿x轴向右为正值,沿y轴向下为正值。其中canvasd坐标的单位都是"px"。
在这里插入图片描述

二、webgl渲染管线

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

定点着色器和片元着色器是可编程的功能单元,拥有更大自主性,还有光栅器、深度测试等补课表承德功能单元。CPU会通过webgl api和GPU通讯,传递着色器程序和数据,GPU执行的着色器程序
在这里插入图片描述

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

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

三、webgl关键名词

1、顶点着色器

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

2、图元装配

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

3、光栅化

就是将图元分解成片元
在这里插入图片描述

4、片元着色器

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

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

四、鼠标动态绘制点

实现思路:

  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遵守右手坐标系,所以我们看到修改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的公式,看代码可能比较抽象,下面上一张图:

在这里插入图片描述

最终绘制效果如下:

请添加图片描述

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);

最终效果如下:

请添加图片描述

在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())

图示如下:
在这里插入图片描述

  • 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>

效果如下(先在画笔上点击,光标未显示):
请添加图片描述

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的倍数个顶点才能正常绘制三角形。

效果如下:
请添加图片描述

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

3、绘制其他图形

上面演示了绘制gl.POINTSgl.TRIANGLES,webgl一共支持7中图形,下面演示同一批点,不同渲染模式的显示结果。
在这里插入图片描述

其他的绘制模式代码,和上面区别就只有一点就是修改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基本图形

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 类似于扇形的图形

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/389053.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【博学谷学习记录】超强总结,用心分享 | 架构师 JDK源码学习总结

文章目录HashMap类1.定义2.哈希表3.JDK1.8前HashMap的数据结构4.JDK1.8后HashMap的数据结构5.类构造器6.字段属性①Node<K,V>[] table②size③loadFactor④threshold7.构造函数①默认无参构造函数②指定初始容量的构造函数8.确定哈希桶数组索引位置9.添加元素10.扩容机制…

操作系统重难点笔记

1.信号量机制&#xff0c;读者/写者问题 读者/写者问题分为两种情况&#xff1a; 1.读者和写者互斥&#xff0c;并且不同的读者和写者之间都互斥,一共三个互斥 下面给出伪代码 int m1; int mr1; int mw1; int count0; writer() {while(1){P(mw);........V(mw);} } reader() {…

防止暴力破解ssh的四种方法

一. 方法介绍 防止暴力破解的四种方法&#xff1a; 1 密码要写的足够的复杂&#xff0c;通常建议将密码写16位&#xff0c;并且无连贯的数字或者字母&#xff1b;当然也可以固定一个时间修改一次密码&#xff0c;推荐是一个月修改一次会稳妥一些2 修改ssh的端口号&#xff0c;…

【剧前爆米花--爪哇岛寻宝】MySQL中索引和事务

作者&#xff1a;困了电视剧 专栏&#xff1a;《MySQL数据库》 文章分布&#xff1a;这是一篇关于Java中异常类的文章&#xff0c;在本篇文章中详细讲解了异常的使用逻辑和底层的执行过程&#xff0c;如有疏漏&#xff0c;欢迎大佬指正&#xff01; 目录 索引 用法 底层逻辑…

中医药NER命名实体识别基于SPANNER方式

一个不知名大学生&#xff0c;江湖人称菜狗 original author: Jacky Li Email : 3435673055qq.com Time of completion&#xff1a;2023.3.5 Last edited: 2023.3.5 导读 本文使用SPANNER方式实现对中医药进行实体识别&#xff0c;采用focal loss 进行优化。 本文章作用防止安静…

【微信小程序】计算器案例

&#x1f3c6;今日学习目标&#xff1a;第二十一期——计算器案例 ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰预计时间&#xff1a;30分钟 &#x1f389;专栏系列&#xff1a;我的第一个微信小程序 计算器前言实现效果实现步骤wxmlwxssjs数字按钮事件处理函数计算按钮处理事…

为啥一个 main 方法就能启动项目

在 Spring Boot 出现之前&#xff0c;我们要运行一个 Java Web 应用&#xff0c;首先需要有一个 Web 容器&#xff08;例如 Tomcat 或 Jetty&#xff09;&#xff0c;然后将我们的 Web 应用打包后放到容器的相应目录下&#xff0c;最后再启动容器。 在 IDE 中也需要对 Web 容器…

【WEB前端进阶之路】 HTML 全路线学习知识点梳理(中)

前言 本文是HTML零基础学习系列的第二篇文章&#xff0c;点此阅读 上一篇文章。 文章目录前言六.HTML标题1.HTML标题2.HTML水平线3.HTML 注释七.HTML段落1.HTML段落2.HTML换行八.HTML文本格式化九.HTML链接十.HTML头部十一.HTML图像十二.HTML表格十三.HTML列表十四.HTML区块1.H…

C#:Krypton控件使用方法详解(第十二讲) ——kryptonCheckButton

今天介绍的Krypton控件中的kryptonCheckButton。下面先介绍外观属性&#xff1a;Checked属性&#xff1a;表示控件是否处于已启用状态&#xff0c;属性值为Bool类型&#xff0c;属性值为true时&#xff0c;表示控件处于已选中状态。属性值为false时&#xff0c;表示控件处于不选…

黄河流域公安院校网络空间安全技能挑战赛 QAQ 题解

目录 一.获取pyc文件 二.反编译出.py源码 三.程序逻辑 1.第一个限制条件 2.第二段 3.第三段 这题是对python打包成的可执行程序逆向 如果对如何反编译.pyc和.py文件有疑问可以参考: Python逆向基本操作步骤——以杭电新生赛hgame week2 reverse stream(python3.10逆向)…

IOC(概念和原理)

文章目录1. IOC容器概念2. IOC底层原理3. IOC&#xff08;接口&#xff09;4. IOC操作Bean管理&#xff08;概念&#xff09;5. IOC操作Bean管理&#xff08;基于xml方式&#xff09;5.1 基于xml创建对象5.2 基于xml方式注入属性5.2.1 DI&#xff1a;依赖注入&#xff0c;就是注…

Unable to find a valid cuDNN algorithm to run convolution

Unable to find a valid cuDNN algorithm to run convolution 今天在复习HumanNerf的时候发现了这个报错&#xff0c; import torch print(torch.cuda.is_available()) 使用上面的代码发现GPU是可以用的&#xff0c;可自己的torch版本对应。 后面继续看帖子&#xff0c;总结有…

【C++】30h速成C++从入门到精通(STL介绍、string类)

STL简介什么是STLSTL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架。STL的版本原始版本Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本&…

2D图像处理:九点标定_上(机械手轴线与法兰轴线重合)(附源码)

文章目录 1. 九点标定2. 九点标定流程2.1 机械手轴线与法兰轴线重合代码实现1. 九点标定 在2D视觉抓取项目中,如果想要让机械手准确的抓取到工件,前提是需要知道机械手应该移动到哪里(位姿)。而移动到哪里(位姿)的获取就需要对相机和机械手进行标定。因此,九点标定(2D视…

ESP32设备驱动-MAX6675冷端补偿K热电偶数字转换器

MAX6675冷端补偿K热电偶数字转换器 1、MAX6675介绍 MAX6675执行冷端补偿并将来自K型热电偶的信号数字化。 数据以 12 位分辨率、SPI™ 兼容的只读格式输出。 该转换器可将温度解析为 0.25C,读数高达 +1024C,并且在 0C 至 +700C 的温度范围内具有 8 LSB 的热电偶精度。 MAX…

力扣旋转字符串

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f; c语言初阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:介绍字符串旋转,左旋,右旋即旋转结果. 金句分享: ✨好好干&…

如何通过Java将Word转换为PDF

Word是我们日常编辑文档内容时十分常用的一种文档格式。但相比之下&#xff0c;PDF文档的格式、布局更为固定&#xff0c;不易被更改。在保存或传输较为重要的文档内容时&#xff0c;PDF文档格式也时很多人的不二选择。很多时候我们都会遇到需要将Word转换为PDF的情况。下面我就…

放弃node-sass,启用sass

在下载一个新项目时运行&#xff1a;npm run install 发现报错 npm uninstall 异常 Error: Could not find any Visual Studio installation to use 或是 ------------------------- You need to install the latest version of Visual Studio npm ERR! gyp ERR! find VS incl…

嵌入式Linux(二十四)系统烧写

将uboot&#xff0c;linux kernel&#xff0c;.dtb&#xff0c;rootfs烧写到板子上的EMMC上&#xff0c;避免断网导致不能运行。 1. MfgTool工具介绍 一路解压之后&#xff0c;得到以下两项&#xff1a; ①Profiles文件夹&#xff1a;后续烧写文件放到这个文件夹。  其中关注…

宝塔+docker+jenkins部署vue项目(保姆级教程)

1.使用宝塔安装docker 在软件商城安装Docker管理器 2.使用docker下载jenkins镜像 使用命令行 docker pull jenkins/jenkins:lts //lts表示支持版本较长3.创建并且挂载jenkins目录并赋值 jenkins_home为我创建的目录 可以修改任意目录 mkdir -p /jenkins_home cho…