site infoHacknerd | Tech Blog
blog cover

🗳️ [WebGL编程指南] 3. 绘制和变换三角形

WebGL

不管三维模型的型状多么负责,其组成的基本单位都是三角形。通过创建更细小和更大量的三角形,就可以创建更复杂和更逼真的三维模型。

绘制多个点

上文响应鼠标点击事件的实现中,points 储存鼠标的位置,每次遍历向着色器传入一个点,调用drawArrays 方法将这个点绘制出来。但这个方法只能绘制一个点,对于多个顶点的图形,需要一次性地将顶点传入着色器。

javascriptCopy
  for(let i = 0; i < points.length; i+=2 ) {
	  // 传递变量
	  gl.vertexAttrib3f(a_Position, points[i], points[i+1], 0.0);
	  // 绘制
	  gl.drawArrays(gl.POINTS, 0, 1);
  }

WebGL提供了缓冲区对象,支持一次性向着色器传入多个顶点。

javascriptCopy
// 入口函数
function drawPoint() {
	// ...
	
	const n = 3;
	// 创建三个顶点
	const vertices = new Float32Array([
		0.0,  0.5,
	 -0.5, -0.5,
		0.5, -0.5
	])
	const vertextBuffer = gl.createBuffer();
	
	// 将缓冲区对象绑定到目标
	gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
	// 向缓冲区写入数据
	gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
  // 获取 a_Position 储存位置
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  
  // ...
  
  // 缓冲区对象分配给 a_Position
  gl.vertexArribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
  // 连接a_Position与缓冲区对象
  gl.enableVertextAttribArray(a_Position)
  
  // 绘制三个点
  gl.drawArray(gl.POINTS, 0, n)
}

  • gl.vertexArrib[1234]f系列函数只能一次给attribute 分配一个值,gl.vertexArribPointer 方法可以将数组的所有值赋值给attribute变量
  • image

    类型化数组

    为了绘制三维图形,WebGL通常要处理大量类型相同的数据,如顶点坐标和颜色。为了优化性能,引入了特殊的数组。浏览器事先知道数据类型,处理起来更有效率

    image

    绘制三角形

    javascriptCopy
    // 入口函数
    function drawPoint() {
      // 顶点着色器
      const VSHADER_SOURCE =
    	  'attribute vec4 a_Position;\n' + // 使用attribute 变量
    	  'void main() {\n' +
    	  '  gl_Position = a_Position;\n' + // 去掉了顶点大小
    	  '}\n'; 
    	  
    	// ...
    	
    	const n = 3;
    	// 创建三个顶点
    	const vertices = new Float32Array([
    		0.0,  0.5,
    	 -0.5, -0.5,
    		0.5, -0.5
    	])
    	const vertextBuffer = gl.createBuffer();
    	
    	// 将缓冲区对象绑定到目标
    	gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
    	// 向缓冲区写入数据
    	gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
      // 获取 a_Position 储存位置
      var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
      
      // ...
      
      // 缓冲区对象分配给 a_Position
      gl.vertexArribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
      // 连接a_Position与缓冲区对象
      gl.enableVertextAttribArray(a_Position)
      
      
      // 绘制三个点
      gl.drawArray(gl.TRIANGLES, 0, n)
    }

  • 去除了顶点着色器 gl_PointSize = 10.0, 该语句只在绘制单个点才起作用
  • 修改gl.drawArray() 第一个参数为gl.TRIANGLES (表示从一个顶点开始,执行三次顶点着色器,通过三个顶点绘制三角形)
  • image
    image
    image

    绘制矩形

    javascriptCopy
    // 入口函数
    function drawPoint() {
    	const n = 4;
    	// 创建四个顶点
    	const vertices = new Float32Array([
    	 -0.5,  0.5,
    	 -0.5, -0.5,
     	  0.5,  0.5,
    		0.5, -0.5,
    	])
    	
    	// ...
    	gl.drawArrays(gl.TRIANGLE_STRIP, 0, n)
    }

    平移、旋转、缩放

    平移旋转缩放,被称为变换(trasnformations)或者仿射变换(affine transformations)

  • 平移
  • image

    给顶点着色器每个分量,都平移TX,TY,TZ,可以实现平移。

    javascriptCopy
    // 入口函数
    function drawPoint() {
      // 顶点着色器
      const VSHADER_SOURCE =
    	  'attribute vec4 a_Position;\n' + // 使用attribute 变量
    	  'uniform vec4 u_Translation;\n' + // 平移分量
    	  'void main() {\n' +
    	  '  gl_Position = a_Position + u_Translation;\n' + // 平移分量
    	  '}\n'; 
    	  
    	 // ...
    }

  • 旋转(使用变换矩阵)
  • 1.假设点p(x, y ,z)绕z轴旋转了B角度
  • 2.可得 x = r * cos(a) y = r * sin (a)
  • 3.x’ = r * cos (a + b) y’ = r * sin( a + b)
  • 4.由三角函数可得
  • image
    image
  • 1.x’ = r * (cos(a) * cos(b) - sin(a) * sin(b)) y’ = r * ( sin(a) * cos(b) + cos(a) * sin(b))
  • 2.带入2. 可得x’ = x*cos(b) - y*sin(b) y’ = x*sin(b) + y*cos(b)
  • 3.将x’ y’带入矩阵乘法
  • 4.绕x y轴旋转,同理可以得到变换矩阵
  • image
    image
    image
    image
    javascriptCopy
    // 入口函数
    function drawPoint() {
      // 顶点着色器
      const VSHADER_SOURCE =
    	  'attribute vec4 a_Position;\n' + // 使用attribute 变量
    	  'uniform vec4 u_xformMatrix;\n' + // 变换矩阵
    	  'void main() {\n' +
    	  '  gl_Position = a_Position * u_xformMatrix;\n' +
    	  '}\n'; 
    	
    	// ...
    	const ANGLE = 90.0
    	const radian = Math.PI * ANGLE / 180 // 角度转弧度
    	const cosB = Math.cos(radian)
    	const sinB = Math.sin(radian)
    	
    	const xformMatrix = new Float32Array([
    		cosB, sinB, 0.0, 0.0,
    		-sinB, cosB, 0,0, 0.0,
    		0.0, 0.0, 1.0, 0.0,
    		0.0, 0.0, 0.0, 1.0
    	])
    	
    	const u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix')
    	gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix)
    	
    	// ...
    }

  • 平移矩阵
  • image
    javascriptCopy
    // 入口函数
    function drawPoint() {
      // 顶点着色器
      const VSHADER_SOURCE =
    	  'attribute vec4 a_Position;\n' + // 使用attribute 变量
    	  'uniform vec4 u_xformMatrix;\n' + // 变换矩阵
    	  'void main() {\n' +
    	  '  gl_Position = a_Position * u_xformMatrix;\n' +
    	  '}\n'; 
    	
    	// ...
    	const Tx = 0.5
    	const Ty = 0.5
    	const Tz = 0.0
    	
    	const xformMatrix = new Float32Array([
    		1.0, 0.0, 0.0, 0.0,
    		0.0, 1.0, 0,0, 0.0,
    		0.0, 0.0, 1.0, 0.0,
    		Tx, Ty, Tz, 1.0
    	])
    	
    	const u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix')
    	gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix)
    	
    	// ...
    }

  • 缩放
  • image
    image
    javascriptCopy
    // 入口函数
    function drawPoint() {
      // 顶点着色器
      const VSHADER_SOURCE =
    	  'attribute vec4 a_Position;\n' + // 使用attribute 变量
    	  'uniform vec4 u_xformMatrix;\n' + // 变换矩阵
    	  'void main() {\n' +
    	  '  gl_Position = a_Position * u_xformMatrix;\n' +
    	  '}\n'; 
    	
    	// ...
    	const Sx = 0.5
    	const Sy = 0.5
    	const Sz = 0.0
    	
    	const xformMatrix = new Float32Array([
    		Sx, 0.0, 0.0, 0.0,
    		0.0, Sy, 0,0, 0.0,
    		0.0, 0.0, Sz, 0.0,
    		0.0, 0.0, 0.0, 1.0
    	])
    	
    	const u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix')
    	gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix)
    	
    	// ...
    }

  • 注意:WebGL和OpenGL的矩阵是按列主序排序的
  • image

    比如上图在Float32数组里面的顺序应该是[a, e, i, m, b, f, j ,n ….]

    Contents

    • 绘制多个点
    • 类型化数组
    • 绘制三角形
    • 绘制矩形
    • 平移、旋转、缩放

    2024/10/14 06:46