# 用 WebGL 做一个齿轮动画

https://aralroca.com/blog/how...

## 识别要绘制的数据

1. 圆心（xy
2. 半径

• 齿数
• 笔触颜色（边框的颜色）*
• 填充色
• 子级（更多具有相同数据结构的齿轮）
• 旋转方向（仅对父级有效）

``````const x1 = 0.1
const y1 = -0.2

const x2 = -0.42
const y2 = 0.41

const x3 = 0.56
const y3 = 0.28

export const gears = [
{
center: [x1, y1],
direction: 'counterclockwise',
numberOfTeeth: 20,
fillColor: [0.878, 0.878, 0.878],
children: [
{
center: [x1, y1],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x1, y1],
fillColor: [0.741, 0.741, 0.741],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x1 - 0.23, y1],
fillColor: [1, 1, 1],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x1, y1 - 0.23],
fillColor: [1, 1, 1],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x1 + 0.23, y1],
fillColor: [1, 1, 1],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x1, y1 + 0.23],
fillColor: [1, 1, 1],
strokeColor: [0.682, 0.682, 0.682],
},
],
},
{
center: [x2, y2],
direction: 'clockwise',
numberOfTeeth: 12,
fillColor: [0.741, 0.741, 0.741],
children: [
{
center: [x2, y2],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x2, y2],
fillColor: [0.682, 0.682, 0.682],
strokeColor: [0.6, 0.6, 0.6],
},
],
},
{
center: [x3, y3],
direction: 'clockwise',
numberOfTeeth: 6,
fillColor: [0.741, 0.741, 0.741],
children: [
{
center: [x3, y3],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x3, y3],
fillColor: [0.682, 0.682, 0.682],
strokeColor: [0.6, 0.6, 0.6],
},
],
},
]``````

## 怎样实现旋转

（视频4）

``````function rotation(angleInRadians = 0) {

return [
c, -s, 0,
s, c, 0,
0, 0, 1
]
}``````

``rotationMatrix * positionMatrix // 这不是我们想要的``

``````function translation(tx, ty) {
return [
1, 0, 0,
0, 1, 0,
tx, ty, 1
]
}``````

``````// 现在它们会围绕自己的轴心旋转
translationMatrix * rotationMatrix * translationToOriginMatrix * positionMatrix``````

``(Speed A * Number of teeth A) = (Speed B * Number of teeth B)``

## 实现

• 应该画什么，怎样画。
• 我们有每个齿轮及其零件的坐标。
• 怎样旋转每个齿轮。

### 用着色器初始化程序

``````const vertexShader = `#version 300 es
precision mediump float;
in vec2 position;
uniform mat3 u_rotation;
uniform mat3 u_translation;
uniform mat3 u_moveOrigin;

void main () {
vec2 movedPosition = (u_translation * u_rotation * u_moveOrigin * vec3(position, 1)).xy;
gl_Position = vec4(movedPosition, 0.0, 1.0);
gl_PointSize = 1.0;
}
```````

``````const fragmentShader = `#version 300 es
precision mediump float;
out vec4 color;
uniform vec3 inputColor;

void main () {
color = vec4(inputColor, 1.0);
}
```````

``````const gl = getGLContext(canvas)
const program = getProgram(gl, vs, fs)
const rotationLocation = gl.getUniformLocation(program, 'u_rotation')
const translationLocation = gl.getUniformLocation(program, 'u_translation')
const moveOriginLocation = gl.getUniformLocation(program, 'u_moveOrigin')

run() // 下一节解释这个函数``````

`getGLContext``getShader``getProgram` 完成了我们在上一篇文章中的操作。我把它们放在这里：

``````function getGLContext(canvas, bgColor) {
const gl = canvas.getContext('webgl2')
const defaultBgColor = [1, 1, 1, 1]

gl.clearColor(...(bgColor || defaultBgColor))
gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT)

return gl
}

}

}

function getProgram(gl, vs, fs) {
const program = gl.createProgram()

gl.useProgram(program)

console.error(gl.getProgramInfoLog(program))
}

return program
}``````

### 绘制每帧 + 计算旋转角度

``````// 1 个齿的齿轮步长，
// 齿数更多的步长将用以下公式计算：
// realRotationStep = rotationStep / numberOfTeeth
const rotationStep = 0.2

// 角度都初始化为0
const angles = Array.from({ length: gears.length }).map((v) => 0)

function run() {
// 为每个齿轮计算在该帧的角度
gears.forEach((gear, index) => {
const direction = gear.direction === 'clockwise' ? 1 : -1
const step = direction * (rotationStep / gear.numberOfTeeth)

angles[index] = (angles[index] + step) % 360
})

drawGears() // 下一节解释这个函数

// Render next frame
window.requestAnimationFrame(run)
}``````

### 绘制齿轮

``````function drawGears() {
gears.forEach((gear, index) => {
const [centerX, centerY] = gear.center

// u_translation
gl.uniformMatrix3fv(
translationLocation,
false,
translation(centerX, centerY)
)

// u_rotation
gl.uniformMatrix3fv(rotationLocation, false, rotation(angles[index]))

// u_moveOrigin
gl.uniformMatrix3fv(
moveOriginLocation,
false,
translation(-centerX, -centerY)
)

// 渲染齿轮
renderGearPiece(gear)
if (gear.children) gear.children.forEach(renderGearPiece)
})
}``````

``````function renderGearPiece({
center,
fillColor,
strokeColor,
numberOfTeeth,
}) {
const { TRIANGLE_STRIP, POINTS, TRIANGLES } = gl
const coords = getCoords(gl, center, radius)

if (fillColor) drawShape(coords, fillColor, TRIANGLE_STRIP)
if (strokeColor) drawShape(coords, strokeColor, POINTS)
if (numberOfTeeth) {
drawShape(
fillColor,
TRIANGLES
)
}
}``````
• 如果是带边界的圆 --> 使用 `POINTS`
• 如果是彩色圆 --> 使用 `TRIANGLE_STRIP`
• 如果是一个有齿的圆 --> 使用 `TRIANGLES`

``````export default function getCoords(gl, center, radiusX, teeth = 0) {
const toothSize = teeth ? 0.05 : 0
const step = teeth ? 360 / (teeth * 3) : 1
const [centerX, centerY] = center
const positions = []

for (let i = 0; i <= 360; i += step) {
positions.push(
centerX,
centerY,
centerX + (radiusX + toothSize) * Math.cos(2 * Math.PI * (i / 360)),
centerY + (radiusY + toothSize) * Math.sin(2 * Math.PI * (i / 360))
)
}

return positions
}``````

`drawShape` 的代码与上一篇文章中看到的代码相同：它将坐标和颜色传递给 GPU，然后调用 `drawArrays` 函数来指示模式。

``````function drawShape(coords, color, drawingMode) {
const data = new Float32Array(coords)
const buffer = createAndBindBuffer(gl, gl.ARRAY_BUFFER, gl.STATIC_DRAW, data)

gl.useProgram(program)
linkGPUAndCPU(gl, { program, buffer, gpuVariable: 'position' })

const inputColor = gl.getUniformLocation(program, 'inputColor')
gl.uniform3fv(inputColor, color)
gl.drawArrays(drawingMode, 0, coords.length / 2)
}``````

27194 人关注
459 篇文章