WebGL入门概念

119 阅读4分钟

Canvas

引入<canvas>标签,允许JavaScript动态地绘制图形。

三步走: 获取<canvas>元素、获取绘图上下文、开始绘图

const test1 = () => {
  const canvas: any = document.getElementById('canvas')
  const webgl = canvas?.getContext("webgl");
  if (!webgl) {
    console.log("获取WebGL上下文失败")
    return
  }
  webgl.clearColor(1.0, 0.0, 1.0, 0.5)
  webgl.clear(webgl.COLOR_BUFFER_BIT)
}

useEffect(() => {
  test1()
}, [])

return <canvas width={300} height={300} id='canvas'></canvas>

clear()清空

 颜色缓冲区、深度缓冲区、模版缓冲区

着色器

顶点着色器(Vertex shader)

用来描述顶点特性的程序

void main() {
  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
  gl_PointSize = 10.0;
}

片元着色器(Fragment shader)

进行逐片元处理过程

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

初始化着色器

在初始化着色器之前,顶点着色器和片元着色器都是空白的,然后需要将字符串形式的代码从JavaScript传给WebGL系统,并建立着色器。

gl_Position = vec4(0.0, 0.0, 0.0, 1.0);

由4个分量组成的矢量被称为齐次坐标

齐次坐标

齐次坐标使用如下的符号描述:(x,y,z,w)。齐次坐标(x,y,z,w)等价于三维坐标(x/w,y/w,z/w)。如果齐次坐标的第四个分量为1,则可以当成三维坐标。w的值必须大于或等于0,如果w接近0,那么它表示的点趋于无穷远。

WebGL坐标系统

WebGL使用的是三维坐标,具有X轴、Y轴、Z轴。 WebGL的坐标系和<canvas>绘图区的坐标系不同,需要将前者映射到后者。

  • <canvas>的中心点:(0.0, 0.0, 0.0)
  • <canvas>的上边缘和下边缘:(0.0, 1.0, 0.0)和(0.0, -1.0, 0.0)
  • <canvas>的左边缘和右边缘:(-1.0, 0.0, 0.0)和(1.0, 0.0, 0.0)

JavaScript和着色器之间的数据传输

使用attribute变量

使用步骤:

  • 在顶点着色器中,声明attribute变量;
  • 在顶点着色器中,声明attribute变量;
  • attribute变量传输数据;
const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;
    gl_PointSize = 10.0;
  }
`

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

function main() {
  const canvas = document.getElementById('canvas')
  const webgl = getWebGLContext(canvas, true)
  if (!initShaders(webgl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    return
  }
    
  const a_Position = webgl.getAttribLocation(webgl.program, 'a_Position')
  if (a_Position < 0) {
    return
  }
  webgl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0)

  webgl.clearColor(1.0, 0.0, 1.0, 0.5)
  webgl.clear(webgl.COLOR_BUFFER_BIT)
  webgl.drawArrays(webgl.POINTS, 0, 1)
}

解析 attribute vec4 a_Position;
attribute被称为存储限定符vec4表示类型,a_Position表示变量名;
可以解释为:定义vec4类型的存储限定符变量a_Position

这样就完成了着色器部分,表示可以从外部接收顶点坐标了

initShaders()是WebGL中的辅助函数,从而建立了顶点着色器,然后WebGL会对着色器进行解析,识别出attribute变量,并且是需要存储的,这样在需要获取该存储地址的时候

const a_Position = webgl.getAttribLocation(webgl.program, 'a_Position')

解析:获取atteibute变量的存储位置,第一个参数是包含了顶点着色器和片元着色器的对象,第二个参数是想要获取的存储地址变量的名称。

小的应用示例

效果

cursor-point.gif

部分代码解析

const VSHADER_SOURCE = `
  attribute vec3 a_position;
  uniform mat4 proj;
  attribute vec3 a_color;
  varying vec3 inColor;
  void main(void){
    gl_Position = vec4(a_position,1.0)  ;
    gl_PointSize=60.0;
    inColor = a_color;
  }
`
const FSHADER_SOURCE = `
  precision mediump float;
  varying vec3 inColor;
  void main(void){
    gl_FragColor = vec4(inColor,1.0);
  }
`

解析:precision mediump float 使用的是精度限定符来指定变量的范围和精度,这儿的为中等精度

在顶点着色器中定义两个对象,分别是位置信息和颜色信息,而在之后的代码中改变着色器的数据时,对于顶点着色器,可以直接在其中进行修改,而颜色的修改就需要在片元着色器中,但在外面的代码中,获取attribute的只能从顶点着色器,那么在片元着色器中需要通过varying把两个属性绑定起来进行修改。

const initShader = () => {
  let vsshader = webgl.createShader(webgl.VERTEX_SHADER)
  let fsshader = webgl.createShader(webgl.FRAGMENT_SHADER)

  webgl.shaderSource(vsshader, VSHADER_SOURCE)
  webgl.shaderSource(fsshader, FSHADER_SOURCE)

  webgl.compileShader(vsshader)
  webgl.compileShader(fsshader)
  if (!webgl.getShaderParameter(vsshader, webgl.COMPILE_STATUS)) {
    var err = webgl.getShaderInfoLog(vsshader)
    alert(err)
    return
  }
  if (!webgl.getShaderParameter(fsshader, webgl.COMPILE_STATUS)) {
    var err = webgl.getShaderInfoLog(fsshader)
    alert(err)
    return
  }
  let program = webgl.createProgram()
  webgl.attachShader(program, vsshader)
  webgl.attachShader(program, fsshader)

  webgl.linkProgram(program)
  webgl.useProgram(program)

  webgl.program = program
}

可以看到webgl和顶点着色器、片元着色器进行了关联,方便之后的获取。 当用户点击区域的时候

let aPsotion = webgl.getAttribLocation(webgl.program, "a_position")
let aColor = webgl.getAttribLocation(webgl.program, "a_color")

获取了属性后,如果需要改变着色器的信息时

const addAttribute = (array: number[], shader: any) => {
  let pointPosition = new Float32Array(array)
  let pointBuffer = webgl.createBuffer()
  webgl.bindBuffer(webgl.ARRAY_BUFFER, pointBuffer)
  webgl.bufferData(webgl.ARRAY_BUFFER, pointPosition, webgl.STATIC_DRAW)
  webgl.enableVertexAttribArray(shader)
  webgl.vertexAttribPointer(shader, 3, webgl.FLOAT, false, 0, 0)
}

主要是在方法bufferData中将着色器属性和信息数据进行绑定,之后就是刷新绘制操作了。

webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT)
webgl.drawArrays(webgl.POINTS, 0, points.length / 3)

使用uniform变量

上面的例子中,如果是用uniform变量,和上面的attribute变量使用类似:首先获取变量的存储地址,然后在程序中按照这个地址把数据传递过去。

let aColor = webgl.getUniformLocation(webgl.program, "a_color")
webgl.uniform4f(aColor, array[0], array[1], array[2], array[3])

通过getUniformLocation方式获取片元着色器中的对象,之后通过uniform4f中地址的方式改变数据。

在这里先抛出一个问题,如果把需要更改color的属性变量放在片元着色器中,然后通过getUniformLocation获取对象,之后再修改属性的数据信息,这样是不是可以不用在顶点着色器中通过关联的方式修改片元着色器的属性数据信息,这样是不是可以达到同样的效果?