几个简单的小例子手把手带你入门webgl(二)

简介: 实战——绘制个三角形在进行实战之前,我们先给你看一张图,让你能大概了解,用原生webgl生成一个三角形需要那些步骤:draw我们就跟着这个流程图一步一步去操作:初始化canvas新建一个webgl画布<canvas id="webgl" width="500" height="500"></canvas>创建webgl 上下文:const gl = document.getElementById('webgl').getContext('webgl')创建着色器程序着色器的程序这些代码,其实是重复的,我们还是先看下图,看下我们到底需要哪些步骤:shader那我们就跟着这

实战——绘制个三角形



在进行实战之前,我们先给你看一张图,让你能大概了解,用原生webgl生成一个三角形需要那些步骤:


image.png


我们就跟着这个流程图一步一步去操作:


初始化canvas


新建一个webgl画布


<canvas id="webgl" width="500" height="500"></canvas>


创建webgl 上下文:


const gl = document.getElementById('webgl').getContext('webgl')


创建着色器程序


着色器的程序这些代码,其实是重复的,我们还是先看下图,看下我们到底需要哪些步骤:


image.png

shader


那我们就跟着这个流程图:一步一步来好吧。


创建着色器


const vertexShader = gl.createShader(gl.VERTEX_SHADER)
 const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)


gl.VERTEX_SHADER  和 gl.FRAGMENT_SHADER  这两个是全局变量 分别表示「顶点着色器」「片元着色器」


绑定数据源


顾名思义:数据源,也就是我们的着色器 代码。


编写着色器代码有很多种方式:


  1. 用 script 标签  type  notjs 这样去写


  1. 模板字符串 (比较喜欢推荐这种)


我们先写顶点着色器:


const vertexShaderSource = `
    attribute vec4 a_position;
    void main() {
        gl_Position = a_position;
    }
 `


顶点着色器 必须要有 main 函数 ,他是强类型语言, 「记得加分号哇」 不是js 兄弟们。我这段着色器代码非常简单   定义一个vec4 的顶点位置, 然后传给 gl_Position

这里有小伙伴会问 ?这里「a_position」一定要这么搞??


这里其实是这样的哇, 就是我们一般进行变量命名的时候  都会增加带有关键词的前缀 用来区分每个变量的名字 他是属性 还是 全局变量 还是纹理   比如这样:


uniform mat4 u_mat;


表示个矩阵,如果不这样也可以哈。但是要专业呗,防止bug 影响。


我们接着写片元着色器:


const fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
`


这个其实理解起来非常简单哈, 每个像素点的颜色 是红色 , gl_FragColor 其实对应的是 「rgba」  也就是颜色的表示。


有了数据源之后开始绑定:


// 创建着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
//绑定数据源
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)


是不是很简单哈哈哈哈,我觉得你应该会了。


后面着色器的一些操作


其实后面「编译着色器」「绑定着色器」「连接着色器程序」「使用着色器程序」  都是一个api 搞定的事不多说了 直接看代码:


// 编译着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建着色器程序
const program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接 并使用着色器
gl.linkProgram(program)
gl.useProgram(program)


这样我们就创建好了一个着色器程序了。


这里又有人问,我怎么知道我创建的着色器是对的还是错的呢?我就是很粗心的人呢???好的他来了 如何调试:


const success = gl.getProgramParameter(program, gl.LINK_STATUS)
if (success) {
  gl.useProgram(program)
  return program
}
console.error(gl.getProgramInfoLog(program), 'test---')
gl.deleteProgram(program)


「getProgramParameter」  这个方法用来判断 我们着色器 「glsl」 语言写的是不是对的, 然后你可以通过 「getProgramInfoLog」这个方法 类似于打 日志 去发现❌了。


数据存入缓冲区


有了着色器,现在我们差的就是数据了对吧。


上文在写顶点着色器的时候用到了Attributes属性,说明是「这个变量要从缓冲中读取数据」,下面我们就来把数据存入缓冲中。


首先创建一个顶点缓冲区对象(Vertex Buffer Object, VBO)


const buffer = gl.createBuffer()


gl.createBuffer()函数创建缓冲区并返回一个标识符,接下来需要为WebGL绑定这个buffer


gl.bindBuffer(gl.ARRAY_BUFFER, buffer)


gl.bindBuffer()函数把标识符buffer设置为「当前缓冲区」,后面的所有的数据都会都会被放入当前缓冲区,「直到bindBuffer绑定另一个当前缓冲区」


我们新建一个数组 然后并把数据存入到缓冲区中。


const data = new Float32Array([0.0, 0.0, -0.3, -0.3, 0.3, -0.3])
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)


因为「JavaScript与WebGL通信必须是二进制的」,不能是传统的文本格式,所以这里使用了ArrayBuffer对象将数据转化为二进制,因为顶点数据是浮点数,精度不需要太高,所以使用Float32Array就可以了,这是JavaScript与GPU之间大量实时交换数据的有效方法。


「gl.STATIC_DRAW」  指定数据存储区的使用方法:缓存区的内容可能会经常使用,但是不会更改


「gl.DYNAMIC_DRAW」 表示 缓存区的内容经常使用,也会经常更改。


「gl.STREAM_DRAW」 表示缓冲区的内容可能不会经常使用


从缓冲中读取数据


「GLSL」着色程序的唯一输入是一个属性值「a_position」。我们要做的第一件事就是从刚才创建的GLSL着色程序中找到这个属性值所在的位置。


const aposlocation = gl.getAttribLocation(program, 'a_position')


接下来我们需要告诉「WebGL」怎么从我们之前准备的缓冲中获取数据给着色器中的属性。首先我们需要启用对应属性


gl.enableVertexAttribArray(aposlocation)


最后是从缓冲中读取数据绑定给被激活的「aposlocation」的位置


gl.vertexAttribPointer(aposlocation, 2, gl.FLOAT, false, 0, 0)


gl.vertexAttribPointer()函数有六个参数:


  1. 读取的数据要绑定到哪


  1. 表示每次从缓存取几个数据,也可以表示每个顶点有几个单位的数据,取值范围是1-4。这里每次取2个数据,之前vertices声明的6个数据,正好是3个顶点的二维坐标。


  1. 表示数据类型,可选参数有gl.BYTE有符号的8位整数,gl.SHORT有符号的16位整数,gl.UNSIGNED_BYTE无符号的8位整数,gl.UNSIGNED_SHORT无符号的16位整数,gl.FLOAT32位IEEE标准的浮点数。


  1. 表示是否应该将整数数值归一化到特定的范围,对于类型gl.FLOAT此参数无效。


  1. 表示每次取数据与上次隔了多少位,0表示每次取数据连续紧挨上次数据的位置,WebGL会自己计算之间的间隔。


  1. 表示首次取数据时的偏移量,必须是字节大小的倍数。0表示从头开始取。


渲染


现在着色器程序 和数据都已经ready 了, 现在就差渲染了。渲染之前和2d canvas 一样做一个清除画布的动作:


// 清除canvas
gl.clearColor(0, 0, 0, 0)
gl.clear(gl.COLOR_BUFFER_BIT)


我们用「0、0、0、0」清空画布,分别对应 「r, g, b, alpha (红,绿,蓝,阿尔法」)值, 所以在这个例子中我们让画布变透明了。


开启绘制三角形:


gl.drawArrays(gl.TRIANGLES, 0, 3)


  1. 「第一个参数表示绘制的类型」


  1. 「第二个参数表示从第几个顶点开始绘制」


  1. 「第三个参数表示绘制多少个点,缓冲中一共6个数据,每次取2个,共3个点」

「绘制类型共有下列几种」「看图:」


image.png


drawtype


这里我们看下画面是不是一个红色的三角形 :


image.png


三角形截图


我们创建的数据是这样的:


「画布的宽度是 500 * 500 转换出来的实际数据其实是这样的」


0,0  ====>  0,0 
-0.3, -0.3 ====> 175, 325
0.3, -0.3 ====>  325, 325


矩阵的使用


有了静态的图形我们开始着色器,对三角形做一个缩放。


改写顶点着色器:其实在顶点着色器上加一个全局变量  这就用到了 着色器的第二个属性  uniform


const vertexShaderSource = `
  attribute vec4 a_position;
  // 添加矩阵代码
  uniform mat4 u_mat;
  void main() {
      gl_Position = u_mat * a_position;
  }
`


然后和属性一样,我们需要找到 uniform 对应的位置:


const matlocation = gl.getUniformLocation(program, 'u_mat')


然后初始化一个缩放举证:


// 初始化一个旋转矩阵。
  const mat = new Float32Array([
    Tx,  0.0, 0.0, 0.0,
    0.0,  Ty, 0.0, 0.0,
    0.0, 0.0,  Tz, 0.0,
    0.0, 0.0, 0.0, 1.0,
  ]);


Tx, Ty, Tz 对应的其实就是 x y z 轴缩放的比例。


最后一步, 将矩阵应用到着色器上, 在画之前, 这样每个点 就可以✖️ 这个缩放矩阵了 ,所以整体图形 也就进行了缩放。


gl.uniformMatrix4fv(matlocation, false, mat)


三个参数分别代表什么意思:


  1. 全局变量的位置


  1. 是否为转置矩阵


  1. 矩阵数据


OK 我写了三角形缩放的动画:


let Tx = 0.1 //x坐标的位置
  let Ty = 0.1 //y坐标的位置
  let Tz = 1.0 //z坐标的位置
  let Tw = 1.0 //差值
  let isOver = true
  let step = 0.08
  function run() {
    if (Tx >= 3) {
      isOver = false
    }
    if (Tx <= 0) {
      isOver = true
    }
    if (isOver) {
      Tx += step
      Ty += step
    } else {
      Tx -= step
      Ty -= step
    }
    const mat = new Float32Array([
      Tx,  0.0, 0.0, 0.0,
      0.0,  Ty, 0.0, 0.0,
      0.0, 0.0,  Tz, 0.0,
      0.0, 0.0, 0.0, 1.0,
    ]);
    gl.uniformMatrix4fv(matlocation, false, mat)
    gl.drawArrays(gl.TRIANGLES, 0, 3)
    // 使用此方法实现一个动画
    requestAnimationFrame(run)
  }


效果图如下:最后 给大家看一下webgl 内部是怎么搞的 一张gif 动画 :


640 (4).gif


原始的数据通过 顶点着色器  生成一系列 新的点。


变量的使用


说完矩阵了下面👇,我们开始说下着色器中的varying 这个变量 是如何和片元着色器进行联动的。


我们还是继续改造顶点着色器:


const vertexShaderSource = `
  attribute vec4 a_position;
  uniform mat4 u_mat;
  // 变量
  varying vec4 v_color;
  void main() {
      gl_Position = u_mat * a_position;
      v_color =  gl_Position * 0.5 + 0.5;
  }
`


这里有一个小知识 , gl_Position  他的值范围是  「-1 -1」 但是片元着色 他是颜色 他的范围是 「0 - 1」 , 所以呢这时候呢,我们就要 做一个范围转换  所以为什么要 乘 0.5  在加上 0.5 了, 希望你们明白。


改造下片元着色器:


const fragmentShaderSource = `
    precision lowp float;
    varying vec4 v_color;
    void main() {
        gl_FragColor = v_color;
    }
`


只要没一个像素点 改为由顶点着色器传过来的就好了。


我们看下这时候的三角形 变成啥样子了。


640 (5).gif

彩色三角形


是不是变成彩色三角形了, 这里很多人就会问, 这到底是怎么形成呢, 本质是在三角形的三个顶点, 做线性插值的过程:


image.png

相关文章
|
7月前
|
API Python
免费网络北京时间API接口
本文介绍如何通过接口盒子的免费API获取当前北京时间,支持多种格式及POST/GET请求方式。需注册账号获取ID和KEY,适用于服务器时间同步、日志记录等场景。
2944 6
|
5月前
|
前端开发 安全 测试技术
Postman Mac 版安装终极指南:从下载到流畅运行,一步到位
Postman 是 API 开发与测试的高效工具,支持各类 HTTP 请求调试与团队协作。本文详解 Mac 版下载、安装步骤,助你快速上手。同时推荐一体化 API 协作平台 Apifox,集文档、调试、测试于一体,提升开发效率与团队协同能力。
|
JavaScript 前端开发
JavaScript如何判断变量undefined
JavaScript如何判断变量undefined
|
JSON 前端开发 安全
CORS 是什么?它是如何解决跨域问题的?
【10月更文挑战第20天】CORS 是一种通过服务器端配置和浏览器端协商来解决跨域问题的机制。它为跨域资源共享提供了一种规范和有效的方法,使得前端开发人员能够更加方便地进行跨域数据交互。
|
数据采集 缓存 前端开发
服务器端渲染(SSR)
服务器端渲染(SSR)
|
前端开发
10分钟弄懂微应用框架——乾坤,真香!
10分钟弄懂微应用框架——乾坤,真香!
680 2
|
算法 定位技术
路径规划算法 - 求解最短路径 - A*(A-Star)算法
路径规划算法 - 求解最短路径 - A*(A-Star)算法
3563 1
|
存储 JavaScript 前端开发
基于SpringBoot+vue的校园招聘系统
基于SpringBoot+vue的校园招聘系统
基于SpringBoot+vue的校园招聘系统
|
缓存 JSON 安全
【Uniapp 专栏】Uniapp 与后端接口对接的实战要点
【5月更文挑战第12天】在 Uniapp 项目开发中,成功对接后端接口至关重要。要点包括:深入理解后端提供的接口文档,确保数据格式(如 JSON)正确处理,选择合适的请求方式(如 GET、POST),设置正确的请求头,做好错误处理和数据缓存策略,确保安全性(如使用 HTTPS 和令牌验证)并进行全面测试。同时,进行版本管理和团队协作,与后端开发人员保持良好沟通,以实现高效、稳定的接口对接。
1244 5
|
前端开发 容器
Bootstrap 5 保姆级教程(一):容器 & 网格系统
Bootstrap 5 保姆级教程(一):容器 & 网格系统