WebGL学习(三)图形变化

182 阅读3分钟

1. 平移

顶点着色器

attribute vec4 pos;
// 新增变量
attribute float offset;
void main() {
  // 为主使用pos分量加上offset改变
  gl_Position = vec4(pos.x + offset, pos.y, pos.z, 1);
}
const offset = gl.getAttribLocation(program, 'offset')
let x = 0
setInterval(() => {
  if(x > 1) x = -1
  gl.vertexAttrib1f(offset, x)
  x += 0.05
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3)
}, 60)

效果

firefox_2DnE8BZgh7.gif

2. 缩放

和平移一样的原理,只不过是倍数移动坐标

顶点着色器

attribute vec4 pos;
attribute float scale;
void main() {
  // 乘以坐标值
  gl_Position = vec4(pos.x * scale, pos.y * scale, pos.z, 1);
}
const scale = gl.getAttribLocation(program, 'scale')
setInterval(() => {
  if(x > 2) x = 1
  gl.vertexAttrib1f(scale, x)
  x += 0.1
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3)
}, 60)

firefox_4ZNGQrqmkK.gif

3.旋转

顶点着色器

attribute vec4 pos;
attribute float deg;
void main() {
  // 利用公式绕z旋转
  // 其实画个图用就明白了
  gl_Position.x = pos.x * cos(deg) - pos.y * sin(deg);
  gl_Position.y = pos.x * sin(deg) + pos.y * cos(deg);
  gl_Position.z = pos.z;
  gl_Position.w = pos.w;
}
let x = 0
const rotate = () => {
  x += 0.05
  gl.vertexAttrib1f(deg, x)
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3)
  // 使用了requestAnimationFrame,让动画流畅些
  requestAnimationFrame(rotate)
}
rotate()

firefox_8tmCSzPy2r.gif

4.平移矩阵

上面的平移旋转无非就是一些线性变换,或者说解方程。所以天然和线性代数契合,如果把变换转化成矩阵计算,只剩下加、乘,计算机计算也会变得快速简单。

平移为例(x,y,z,w)(x,y,z,w)  1.将平移的线性变换转化为方程组{x=ax+by+cz+dwy=ax+by+cz+dwz=ax+by+cz+dww=ax+by+cz+dw[xyz1]=[abcdabcdabcdabcd]×[xyz1]  注意webgl里的矩阵是列排序的,w通常为1  2.对比方程组  {x=ax+dy=by+dz=by+d1=d=1  所以平移矩阵就是:  [a00d0b0d00cd0001]  一般来说平移只需要x+Δx一次项系数为1,所以最终结果是  [100d010d001d0001]  其中dddxyz的平移距离\boldsymbol{平移为例}\\\qquad\\ (x, y, z, w)\rightarrow(x', y', z', w')\\\;\\ \mathbf{\small 1. 将平移的线性变换转化为方程组}\\\qquad\\ \begin{cases} x'=ax+by+cz+dw\\ y'=a'x+b'y+c'z+d'w\\ z'=a''x+b''y+c''z+d''w\\ w'=a'''x+b'''y+c'''z+d'''w\\ \end{cases}\\ \qquad\\ \begin{bmatrix} x'\\y'\\z'\\1 \end{bmatrix}= \begin{bmatrix} a&b&c&d\\ a'&b'&c'&d'\\ a''&b''&c''&d''\\ a'''&b'''&c'''&d'''\\ \end{bmatrix} \times \begin{bmatrix} x\\y\\z\\1 \end{bmatrix} \qquad\\\;\\ \mathbf{\small 注意webgl里的矩阵是列排序的,w通常为1}\\\;\\ \mathbf{\small 2. 对比方程组}\\\;\\ \begin{cases} x'=ax+d\\ y'=b'y+d'\\ z'=b''y+d''\\ 1=d'''=1\\ \end{cases}\\\;\\ 所以平移矩阵就是:\\\;\\ \begin{bmatrix} a&0&0&d\\ 0&b'&0&d'\\ 0&0&c''&d''\\ 0&0&0&1\\ \end{bmatrix}\\ \;\\ 一般来说平移只需要x+\Delta x一次项系数为1,所以最终结果是\\ \;\\ \begin{bmatrix} 1&0&0&d\\ 0&1&0&d'\\ 0&0&1&d''\\ 0&0&0&1\\ \end{bmatrix}\\ \;\\ 其中d、d''、d'''是x、y、z的平移距离\\

参考1

参考2

5.旋转矩阵

图片.png

参考这个图:从p -> p'

对于p点来说  {x=rcosαy=rsinα  可以推断出p  {x=rcos(α+β)y=rsin(α+β)  由三角公式可得{x=rcos(α+β)=r(cosαcosβsinαsinβ)y=rsin(α+β)=r(sinαcosβ+cosαsinβ)  带入p点方程可得{x=xcosβysinβy=ycosβ+xsinβz=zz轴角度没有发生变化)  和平移一样我们对比方程{x=ax+by+czy=dx+ey+fzz=gx+hg++iz[xyz]=[abcdefghi]×[xyz]得到{a=cosβb=sinβc=0d=sinβe=cosβf=0g=0h=0i=1[xyz]=[cosβsinβ0sinβcosβ0001]×[xyz]对于p点来说\\\;\\ \begin{cases} x=rcos{\alpha}\\ y=rsin{\alpha}\\ \end{cases}\\\;\\ 可以推断出p'点\\\;\\ \begin{cases} x'=rcos({\alpha}+{\beta})\\ y'=rsin({\alpha}+{\beta})\\ \end{cases}\\\;\\ 由三角公式可得\\ \begin{cases} x'=rcos({\alpha}+{\beta})=r(cos{\alpha}\cos{\beta}-sin{\alpha}\sin{\beta})\\ y'=rsin({\alpha}+{\beta})=r(sin{\alpha}\cos{\beta}+cos{\alpha}\sin{\beta}) \end{cases}\\\;\\ 带入p点方程可得\\ \begin{cases} x'=xcos{\beta}-ysin{\beta}\\ y'=ycos{\beta}+xsin{\beta}\\ z'=z(z轴角度没有发生变化) \end{cases}\\\;\\ 和平移一样我们对比方程\\ \begin{cases} x'=ax+by+cz\\ y'=dx+ey+fz\\ z'=gx+hg++iz\\ \end{cases} \Longrightarrow \begin{bmatrix} x'\\y'\\z' \end{bmatrix} = \begin{bmatrix} a&b&c\\ d&e&f\\ g&h&i \end{bmatrix}\times \begin{bmatrix} x\\ y\\ z \end{bmatrix}\\ 得到\\ \begin{cases} a=cos\beta\\ b=-sin\beta\\ c=0\\ d=sin\beta\\ e=cos\beta\\ f=0\\ g=0\\ h=0\\ i=1\\ \end{cases}\Longrightarrow \begin{bmatrix} x'\\ y'\\ z' \end{bmatrix}= \begin{bmatrix} cos\beta&-sin\beta&0\\ sin\beta&cos\beta&0\\ 0&0&1 \end{bmatrix} \times \begin{bmatrix} x\\ y\\ z \end{bmatrix}\\

上面的推导中,旋转矩阵是3X3,而平移矩阵为4X4,阶数不同不能做计算组合起来。所以加一维。

{x=ax+by+cz+dy=ex+fy+gz+hz=ix+jg+kz+lw=1=mx+nx+oz+p[xyzw]=[abcdefghijklmnop]×[xyzw]  同样的对比方程可得旋转矩阵[cosβsinβ00sinβcosβ0000100001]\begin{cases} x'=ax+by+cz+d\\ y'=ex+fy+gz+h\\ z'=ix+jg+kz+l\\ w'=1=mx+nx+oz+p \end{cases} \Longrightarrow \begin{bmatrix} x'\\y'\\z'\\w' \end{bmatrix} = \begin{bmatrix} a&b&c&d\\ e&f&g&h\\ i&j&k&l\\ m&n&o&p \end{bmatrix}\times \begin{bmatrix} x\\ y\\ z\\ w\\ \end{bmatrix}\\\;\\ 同样的对比方程可得旋转矩阵\\ \begin{bmatrix} cos\beta&-sin\beta&0&0\\ sin\beta&cos\beta&0&0\\ 0&0&1&0\\ 0&0&0&1\\ \end{bmatrix}

6. 缩放矩阵

图片.png

{x=Sx×xy=Sy×yz=Sz×zw=1与之前一样对比方程组可得:  [Sx0000Sy0000Sz00001]\begin{cases} x'=S_x\times x\\ y'=S_y\times y\\ z'=S_z\times z\\ w'=1 \end{cases}\\ 与之前一样对比方程组可得:\\\;\\ \begin{bmatrix} S_x&0&0&0\\ 0&S_y&0&0\\ 0&0&S_z&0\\ 0&0&0&1 \end{bmatrix}

7.矩阵变换使用

我们以旋转为例。

顶点着色器:

attribute vec4 pos;
// 矩阵只能是uniform
// 用attribute无法赋值
uniform mat4 routeMatrix;
void main(){
  // 左乘旋转矩阵
  gl_Position = routeMatrix * pos;
  gl_PointSize = 30.0;
}

片段着色器:

precision mediump float;
void main(){
  gl_FragColor = vec4(1, 0, 0, 1);
}

主程序

import vertexCode from './vertex.vert'
import fragmentCode from './fragment.frag'
////////////////////////// 初始化 ///////////////////////////////////
const canvas = document.getElementById('webgl') as HTMLCanvasElement
const gl = canvas.getContext('webgl')

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

gl.shaderSource(vertexShader, vertexCode)
gl.shaderSource(fragmentShader, fragmentCode)
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 pos = gl.getAttribLocation(program, 'pos')
const buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(
  gl.ARRAY_BUFFER,
  new Float32Array([
    -0.5, 0.5,
    -0.5, -0.5,
    0.5, 0.5,
    0.5, -0.5,
  ]),
  gl.STATIC_DRAW
)

gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(pos)
////////////////////////// 初始化end ///////////////////////////////////

/**************************** 旋转部分代码 ***********************/
const routeMatrix = gl.getUniformLocation(program, 'routeMatrix')
const angle = 45
const b = Math.PI * angle / 180

/**
 * 原始旋转矩阵是
 * cos -sin 0 0
 * sin cos  0 0
 *  0   0   1 0
 *  0   0   0 1
 *
 * 由于webgl里面是按列主序存储所以应该是下面这样
 */
const routeMatrixValue = new Float32Array([
  Math.cos(b), Math.sin(b), 0, 0,
  -Math.sin(b), Math.cos(b), 0, 0,
  0, 0, 1, 0,
  0, 0, 0, 1
])

/**
 * 矩阵赋值
 * @param location 变量地址
 * @param transpose 是否转置,在webgl里面没有转置方法所以只能false
 * @param value 值
 */
gl.uniformMatrix4fv(routeMatrix, false, routeMatrixValue)

//////////////// 绘制 ////////////////////
gl.clearColor(0, 0, 0, 1)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)

红色方块被旋转了45度。 图片.png

8.组合使用

假如现在需要平移并旋转:<变换之后的坐标>=<旋转矩阵>×<平移矩阵>×<原始坐标>  其中我们可以把旋转、平移矩阵合并,也就是先乘起来当做一个整体,称之为模型矩阵  <模型矩阵>=<平移矩阵>×<原始坐标>假如现在需要平移并旋转:\\ <变换之后的坐标>=<旋转矩阵>\times<平移矩阵>\times<原始坐标>\\\;\\ 其中我们可以把旋转、平移矩阵合并,也就是先乘起来\\当做一个整体,称之为模型矩阵\\\;\\ <模型矩阵>=<平移矩阵>\times<原始坐标>

这里开始我们使用工具库来做矩阵操作,我使用的是gl-matrix

// 使用例子
import { mat4 } from 'gl-matrix'
// 创建一个旋转45度矩阵
const modelMatrix = mat4.create()
mat4.fromZRotation(modelMatrix, 45)

下面我们来创建一个模型矩阵

// 还是上面的代码
const routeMatrix = gl.getUniformLocation(program, 'routeMatrix')

// 创建4阶矩阵变量
const modelMatrix = mat4.create()
const transM = mat4.create()
const rotatM = mat4.create()
// 创建平移矩阵
mat4.fromTranslation(transM, [0.7, 0, 0])
// 创建旋转矩阵
mat4.fromZRotation(rotatM, 45)
// 叉乘矩阵形成模型矩阵
// 注意先平移和后平移效果是不一样的
mat4.multiply(modelMatrix, transM, rotatM)

gl.uniformMatrix4fv(routeMatrix, false, modelMatrix)
gl.clearColor(0, 0, 0, 1)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)

代码效果 图片.png 工具库里有直接生成旋转平移矩阵的函数:fromRotationTranslation

const routateQuat = quat.create()
quat.rotateZ(routateQuat, routateQuat, 30)
// 注意第二个参数是一个四元数,什么是四元数有点复杂,有时间再研究
mat4.fromRotationTranslation(modelMatrix, routateQuat, [0.3, 0, 0])
gl.uniformMatrix4fv(routeMatrix, false, modelMatrix)