webgl——绘制一个多点(三)

前言

前面我们将了绘制一个点,并通过 JavaScript 向 webgl 系统传递变量进行绘制。但是如果我们绘制一个复杂图形,比如我们常见的三维世界。其实不管三维模型的形状多么复杂,其基本组成部分都是一个个三角形。为什么是三角形呢,因为三角形构成是三个点,三个点可以唯一确定一个面;这样就可以简化图形绘图时的一些不必要的计算。接下来我们将绘制一个 ‘F’,效果如下:

在这里插入图片描述

绘制一个 ‘F’

首先还是贴代码:

import React, { useRef, useEffect } from 'react'
import { VSHADERR_SOURCE, FSHADER_SOURCE } from './glsl'
import { initShader } from '../../utils/webglFunc'
import { d2_f as shape } from './df2'
import './index.css'

const DrawFShape = () => {
  const canvasDom = useRef<HTMLCanvasElement | null>(null)

  const initVertexBuffers = (gl: WebGLRenderingContext) => {
    // 定义顶点
    const verticies = shape(100, 100, 100, 150, 30)
    // 创建缓冲区对象
    const vertexBuffer = gl.createBuffer()
    // 将缓冲区对象绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
    // 向缓冲区对象中写入数据
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticies), gl.STATIC_DRAW)
    // 在顶点着色器中获取 a_Position 的地址
    // gl.program 为包含顶点着色器和片元着色器的着色器程序对象
    const a_Position = gl.getAttribLocation((gl as any).program, 'a_Position')
    // 获取存储位置其实是从 0 开始根据定义的位置依次计数的结果
    if (a_Position < 0) {
      console.log('Failed to get the storage location of a_Position')
      return
    }
    // 将缓冲区对象分配给 a_Position 变量
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
    // 连接 a_Position 变量与分配给他的缓冲区对象
    gl.enableVertexAttribArray(a_Position)
    return verticies.length
  }

  const main = () => {
    if (!canvasDom.current) return
    const gl = canvasDom.current.getContext('webgl')

    if (!gl) {
      console.log('Failed to get the rendering context for WebGL')
      return
    }
    gl.canvas.width = (gl as any).canvas.clientWidth
    gl.canvas.height = (gl as any).canvas.clientHeight
    
    // 初始化着色器
    if (!initShader(gl, VSHADERR_SOURCE, FSHADER_SOURCE)) {
      console.log('Failed to intialize shaders.')
      return
    }

    const n = initVertexBuffers(gl)
    const u_Color = gl.getUniformLocation((gl as any).program, 'u_color')
    const u_Resolution = gl.getUniformLocation((gl as any).program, 'u_resolution')
    gl.uniform4fv(u_Color, [Math.random(), Math.random(), Math.random(), 1.0])
    gl.uniform2fv(u_Resolution, [gl.canvas.width, gl.canvas.height])

    // 指定清空 canavs 的颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0)

    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
    // 清空 canvas
    gl.clear(gl.COLOR_BUFFER_BIT)

    // 第一个参数为绘制的类型,第二个参数是指定从哪个顶点开始绘制(整数型,从第一个点开始就是 0),
    // 第三个参数是指定绘制需要用到多少个顶点(整数型)
    // 当程序调用 l.drawArrays 方法时,顶点着色器将被执行 n 次,每次处理一个顶点。执行顶点着色器代码中的 main 函数。
    // 一旦顶点着色器执行完之后,片元着色器开始执行。
    if (!n) return
    gl.drawArrays(gl.TRIANGLES, 0, n / 2)
  }

  useEffect(() => {
    main()
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return (
    <canvas ref={canvasDom}></canvas>
  )
}

export default DrawFShape

着色器代码:

// ./glsl
export const VSHADERR_SOURCE = `
  attribute vec2 a_Position;
  uniform vec2 u_resolution;
  void main() {
    //[0, 1]
    vec2 zeroToOne = a_Position / u_resolution;
    //[0, 2]
    vec2 zeroToTwo = zeroToOne * 2.0;
    //[-1, 1]
    vec2 clipSpace = zeroToTwo - 1.0;
    gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
  }
`

export const FSHADER_SOURCE = `
  precision mediump float;
  uniform vec4 u_color;
  void main() {
    gl_FragColor = u_color;
  }
`

构建坐标点代码:
注意第一个点是 “F” 的左边的点。

// ./df2
/**
 * 
 * @param {*} x 横坐标位置 表示 F 的左下角的位置
 * @param {*} y 纵坐标位置
 * @param {*} width 宽度
 * @param {*} height 高度
 * @param {*} thickness 厚度
 */
export const d2_f = (x: number, y: number, width: number, height: number, thickness: number) => {

  const data = [
    // 左边
    x, y,
    x + thickness, y,
    x, y + height,
    x, y + height,
    x + thickness, y,
    x + thickness, y + height,
    // 第一个横杠
    x + thickness, y,
    x + width, y,
    x + thickness, y + thickness,
    x + thickness, y + thickness,
    x + width, y,
    x + width, y + thickness,
    // 第二个横杠
    x + thickness, y + thickness * 2,
    x + width * 2 / 3, y + thickness * 2,
    x + thickness, y + thickness * 3,
    x + thickness, y + thickness * 3,
    x + width * 2 / 3, y + thickness * 2,
    x + width * 2 / 3, y + thickness * 3,
  ]
  return data
}

缓冲区对象

当我们只需要绘制一个点的时候调用 gl.drawArrays(gl.POINTS, 0, 1) 进行每次绘制一个点,但是如果我们需要绘制多个点,就需要用到缓冲区对象,他可以一次性向着色器传入多个顶点的数据,缓冲区对象是 webgl 系统中的一块内存区域,我们可以一次性地向缓冲区对象中填充大量的顶点数据,然后将这些数据保存在其中,供顶点着色器使用。

使用缓冲区对象向顶点着色器传入多个顶点的数据,需要遵循下面五个步骤:

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

总的来说使用缓冲区传递变量就上面几个步骤,在代码里;也是很详细的注释。

这里还需要注意的是,我们给顶点着色器传入了一个 u_resolution 的数值,是因为需要进行坐标的转换,因为在我们定义图形的宽高时,都是基于 canvas 页面的款到定义;但是在表达到 webgl 的坐标系统中,需要进行归一化处理。那么讲到 webgl 坐标系统,到底是什么样呢,来看下图:
在这里插入图片描述

图出自《WebGL 编程指南》

因为我们的世界是三维的,也不难理解 webgl 的坐标轴包含 x、y、z 三个轴。在 webgl 中,当你面向计算机屏幕时,z 轴是水平的(正方向为右),y 轴是垂直的(正方向为下),而 z 轴垂直于屏幕(正方向为外)。观察者的眼睛位于原点(0.0, 0.0, 0.0)处,视线是沿着 z 轴的的负方向。这套坐标系也叫右手坐标系。
在这里插入图片描述
如下图,webgl 的坐标系和 canvas 绘图区的坐标系不同,需要将前者映射到后者。默认情况下,webgl 坐标与 canvas 坐标的对应关系如下:

  • canvas 的中心点: (0.0, 0.0, 0.0)
  • canvas 的上边缘和下边缘:(-1.0, 0.0, 0.0) 和 (1.0, 0.0, 0.0)
  • canvas 的左边缘和右边缘:(0.0, -1.0, 0.0) 和 (0.0, 1.0, 0.0)
    在这里插入图片描述
    上面的坐标只是将 canvas 的坐标转换到 webgl 的坐标系统;在 webgl 坐标系统中,我们将每个物体的坐标称为本地坐标,但是,在一个三维场景中,会有很多物体的;每个物体都相对于自身坐标存在 webgl 系统中,肯定不行的。我们会将本地坐标系通过模型矩阵转换为世界坐标。也就是不在相对于模型自身的坐标。然后再通过视图矩阵转换为视图坐标系,最终通过正射投影矩阵或者透视投影矩阵转换为裁剪坐标。最终显示在 webgl 系统中。后面我们会写模型矩阵、视图矩阵等。

还有一个需要注意的是,我们在顶点着色器设置 gl_Position 的时候乘了一个 vec2(1, -1):

gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

是因为我们前面说过 webgl 系统的 y 轴跟我们设置的点的数据轴的方向是不一样的;乘以一个二维矢量,第一项跟 1 相乘,第二项跟 -1 相乘;对 y 进行了取反。

还有就是不知道大家有没有发现 gl_Position 为啥是四维的矢量呢,,是因为还有一个近大远小的一个系数。在后面透视投影会说到。

还有一个注意的问题就是,我们在片元着色器中,有这样一句话:

precision mediump float;

这句话是声明浮点数的精度的。因为我们在 u_Color 是传入的浮点数,这里不设定精度会报错。

接下来我们看看 bufferData 相关:
他是向缓冲区对象中写入数据,也就是描述 buffer 被绑定在哪里。 第一个参数有两个类型:gl.ARRAY_BUFFER 存顶点属性,gl.ELEMENT_ARRAY_BUFFER 存储顶点索引的类型;
最后一个参数是提示 webgl 数据将如何被使用 gl.STATIC_DRAW 表示数据通常不会发生变化,表示 gl.DYNAMIC_DRAW 会发生变化的数据

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticies), gl.STATIC_DRAW)

至此,绘制一个“F”图形已完成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
使用 WebGL 绘制正方形需要进行以下步骤: 1. 获取 WebGL 上下文对象 ```javascript const canvas = document.querySelector('canvas'); const gl = canvas.getContext('webgl'); ``` 2. 设置顶点着色器和片元着色器 ```javascript const vertexShaderSource = ` attribute vec2 a_position; void main() { gl_Position = vec4(a_position, 0, 1); } `; const fragmentShaderSource = ` precision mediump float; void main() { gl_FragColor = vec4(1, 0, 0, 1); } `; const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.compileShader(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(fragmentShader); ``` 3. 创建着色器程序并链接顶点着色器和片元着色器 ```javascript const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); ``` 4. 创建缓冲区并绑定数据 ```javascript const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); const positions = [ -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); ``` 5. 设置顶点属性指针并启用 ```javascript const positionAttributeLocation = gl.getAttribLocation(program, 'a_position'); gl.enableVertexAttribArray(positionAttributeLocation); gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0); ``` 6. 清空画布并绘制正方形 ```javascript gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); ``` 完整的 WebGL 绘制正方形的代码如下: ```javascript const canvas = document.querySelector('canvas'); const gl = canvas.getContext('webgl'); const vertexShaderSource = ` attribute vec2 a_position; void main() { gl_Position = vec4(a_position, 0, 1); } `; const fragmentShaderSource = ` precision mediump float; void main() { gl_FragColor = vec4(1, 0, 0, 1); } `; const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.compileShader(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(fragmentShader); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); const positions = [ -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); const positionAttributeLocation = gl.getAttribLocation(program, 'a_position'); gl.enableVertexAttribArray(positionAttributeLocation); gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0); gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); ``` 这样就可以在 canvas 中绘制一个红色的正方形了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiegiser#

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值