薪火相传

距离有点久远了,唉可惜自己比较懒,没能在做完作业后立马补一篇博客。现在差不多都忘啦,结果pj3还要用webgl,只好先复习一下pj2自己学的东西。也许会稍微记录一下重要步骤。。

有需要的同学可以拉到最后直接看完整js代码。

项目说明

用webgl画一个颜色渐变多边形,需要实现顶点拖动,旋转动画,绘制网格等功能

完整代码(javascript)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
// 顶点着色器
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' + // attribute 变量
'uniform mat4 u_ModelMatrix;\n' + //变换矩阵
'attribute vec4 a_Color;\n' +
'varying vec4 v_Color;\n' + // varying 变量
'uniform int u_RenderMode;\n' + // 新增 uniform 变量,用于区分绘制模式(图形or边框线)
'void main() {\n' +
' gl_Position = u_ModelMatrix * a_Position;\n' + // 设置顶点坐标
' if(u_RenderMode == 0) {\n' +
' v_Color = a_Color;\n' + // 传递给片元着色器
' }\n' +
' else {\n'+
' v_Color = vec4(1.0, 0.0, 0.0, 1.0);\n' +
' }\n' +
'}\n';

// 片元着色器
var FSHADER_SOURCE =
'precision mediump float;\n' + // 精度
'varying vec4 v_Color;\n' + // 接收varying变量
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';

// 旋转速度(度/秒)
var ANGLE_STEP = 45.0;
// 缩放速度
var SCALE_SPEED = 0.2;
// 缩放方向(变大or变小)
var SCALE_DIRECTON = -1;
// 是否开启动画
var ANIMATION = false;
// 避免动画开始的瞬移
var init = false;
// 判断是否正拖动点
var isDragging = false;
// 当前正在拖动的点
var DraggingVertex = -1;
// 是否显示边框
var frame = true;

// 三角形当前的旋转角度
var currentAngle = 0.0;
var currentScale = 1.0;

function main() {
var canvas = document.getElementById("webgl");
if(!canvas) { console.log("Fail to load canvas"); return false;}

// 设置画布大小
canvas.setAttribute("width", canvasSize.maxX);
canvas.setAttribute("height", canvasSize.maxY);

// 将canvas坐标转换为webgl坐标
var rect = canvas.getBoundingClientRect();
for(var i=0, len = vertex_pos.length; i<len; i++) {
vertex_pos[i][0] = ((vertex_pos[i][0] - rect.left) - canvas.width / 2) / (canvas.width / 2);
vertex_pos[i][1] = (canvas.height / 2 - (vertex_pos[i][1] - rect.top)) / (canvas.height / 2);
}

// 获取webgl对象
var gl = getWebGLContext(canvas);
if(!gl) { console.log("Fail to get the rendering context for WebGL"); return false;}

// 初始化着色器
var shader_main = initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);
if (!shader_main) {
console.log('Failed to intialize shaders.');
return;
}

// 创建缓冲区对象
var n = initVertexBuffers(gl, canvas, rect);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}

// 设置背景色
gl.clearColor(0, 0, 0, 1);

// 获取渲染模式uniform变量
var u_RenderMode = gl.getUniformLocation(gl.program, 'u_RenderMode');
if (!u_RenderMode) {
console.log('Failed to get the storage location of u_RenderMode');
return;
}

// 获取旋转矩阵uniform变量
var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
if (!u_ModelMatrix) {
console.log('Failed to get the storage location of u_ModelMatrix');
return;
}

// 创建旋转矩阵
var modelMatrix = new Matrix4();

// 动画效果
var tick = function() {
var currentStage = animate(currentAngle, currentScale);
currentAngle = currentStage[0]; // 更新旋转角度
currentScale = currentStage[1]; // 更新缩放大小

draw(gl, n, currentAngle, currentScale, modelMatrix, u_ModelMatrix, u_RenderMode); // 渲染图形
if(ANIMATION)
requestAnimationFrame(tick, canvas); // 请求再次调用tick
};
draw(gl, n, currentAngle, currentScale, modelMatrix, u_ModelMatrix, u_RenderMode);

// 注册键盘点击事件
document.addEventListener("keydown", function(event) {
if (event.key === "t" || event.key === "T") { // 开始/暂停动画
ANIMATION = !ANIMATION;
if(ANIMATION) {
init = true;
tick(); // 调用 tick 函数
}
}
else if (event.key === "e" || event.key === "E") { // 复位
ANIMATION = false;
currentAngle = 0.0;
currentScale = 1.0;
init = true;
tick();
}
else if (event.key === "b" || event.key === "B") { // 显示边框
frame = !frame;
init = true;
tick();
}
});

// 注册鼠标点击事件
canvas.onmousedown = function(ev){
isDragging = true;
click(ev, gl, canvas,
currentAngle, currentScale, // 当前的状态,为了确定当前的点的位置
modelMatrix, u_ModelMatrix,
rect);
};

canvas.onmousemove = function(ev) {
if (!isDragging) return;
if (ANIMATION) return;
var x = toW(ev.clientX, 0, canvas, rect); // 鼠标x坐标
var y = toW(ev.clientY, 1, canvas, rect); // 鼠标y坐标

// 得到旋转矩阵
modelMatrix.setRotate(currentAngle, 0, 0, 1);
modelMatrix.scale(currentScale, currentScale, currentScale);
// 求逆
modelMatrix = modelMatrix.invert();
// 更新点(注意需要更新旋转前的坐标)
if(DraggingVertex!=-1){
vertex_pos[DraggingVertex][0] = x * modelMatrix.elements[0] + y * modelMatrix.elements[4] + modelMatrix.elements[12];
vertex_pos[DraggingVertex][1] = x * modelMatrix.elements[1] + y * modelMatrix.elements[5] + modelMatrix.elements[13];
}
// 重新创建缓冲区对象
var n = initVertexBuffers(gl, canvas, rect);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
draw(gl, n, currentAngle, currentScale, modelMatrix, u_ModelMatrix, u_RenderMode);
}

canvas.onmouseup = function() {
isDragging = false;
};
}

function initVertexBuffers(gl, canvas, rect) {
// 创建缓冲区对象
var vertexBuffer = gl.createBuffer();
if(!vertexBuffer) {
console.log('Failed to create the buffer object');
return -1;
}

// 将缓冲区对象绑定到目标
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

// 向缓冲区对象中写入数据
var n = 4 * polygon.length; // 点的个数
var vertices = new Float32Array(n * 5);

var offset = 0; // 偏移用于将每个四边形的顶点数据写入数组

for(var i=0, len = polygon.length; i<len; i++) {
for(var j=0; j<4; j++) {
vertices[offset++] = vertex_pos[polygon[i][j]][0];
vertices[offset++] = vertex_pos[polygon[i][j]][1];
vertices[offset++] = vertex_color[polygon[i][j]][0]/255;
vertices[offset++] = vertex_color[polygon[i][j]][1]/255;
vertices[offset++] = vertex_color[polygon[i][j]][2]/255;
}
}

// 向缓冲区写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

var FSIZE = vertices.BYTES_PER_ELEMENT;

// 获取坐标attribute变量
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
// 将缓冲区对象分配给a_position变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 5, 0);
// 连接a_Position变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);

// 获取颜色attribute变量
var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
if(a_Color < 0) {
console.log('Failed to get the storage location of a_Color');
return -1;
}
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
gl.enableVertexAttribArray(a_Color);

// 解绑缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, null);

return n;
}

function draw(gl, n, currentAngle, currentScale, modelMatrix, u_ModelMatrix, u_RenderMode) {
// 计算旋转矩阵
modelMatrix.setRotate(currentAngle, 0, 0, 1);
modelMatrix.scale(currentScale, currentScale, currentScale);
// 把旋转矩阵传给渲染器
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

// 清除canvas
gl.clear(gl.COLOR_BUFFER_BIT);

// 画出图形
gl.uniform1i(u_RenderMode, 0);
for(var i=0; i<16; i+=4) {
gl.drawArrays(gl.TRIANGLE_FAN, i, 4);
}
if(frame) {
gl.uniform1i(u_RenderMode, 1);
for(var i=0; i<16; i+=4) {
gl.drawArrays(gl.LINE_LOOP, i, 3);
gl.drawArrays(gl.LINE_LOOP, i, 4);
}
}
}

// 上次调用tick函数的时间
var g_last = Date.now();
function animate(angle, scale) {
// 计算时间差距
var now = Date.now();
var elapsed;
if(!init) {
elapsed = now - g_last;
}
else {
elapsed = 0;
init = false;
}
g_last = now;
// 根据时间间隔调整新角度
var newAngle = (angle + (ANGLE_STEP * elapsed) / 1000.0) % 360;
// 根据时间间隔调整新缩放比例
var update = scale + SCALE_DIRECTON * (SCALE_SPEED * elapsed) / 1000.0;
var newSize;
if(update < 0.2) {
SCALE_DIRECTON *= -1;
newSize = 0.4 - update;
}
else if(update > 1) {
SCALE_DIRECTON *= -1;
newSize = 2 - update;
}
else {
newSize = update;
}

return [newAngle, newSize];
}

function click(ev, gl, canvas, currentAngle, currentScale, modelMatrix, u_ModelMatrix, rect) {
var x = toW(ev.clientX, 0, canvas, rect); // 鼠标x坐标
var y = toW(ev.clientY, 1, canvas, rect); // 鼠标y坐标
// 计算旋转矩阵
modelMatrix.setRotate(currentAngle, 0, 0, 1);
modelMatrix.scale(currentScale, currentScale, currentScale);

var vertex_X;
var vertex_Y;

var min_length = 0.001;
var min_vertex = -1;

for(var i=0, len = vertex_pos.length; i<len; i++) {
vertex_X = vertex_pos[i][0] * modelMatrix.elements[0] + vertex_pos[i][1] * modelMatrix.elements[4] + modelMatrix.elements[12];
vertex_Y = vertex_pos[i][0] * modelMatrix.elements[1] + vertex_pos[i][1] * modelMatrix.elements[5] + modelMatrix.elements[13];
if((x - vertex_X)**2 + (y - vertex_Y)**2 < min_length) {
min_vertex = i;
min_length = (x - vertex_X)**2 + (y - vertex_Y)**2;
}
}
DraggingVertex = min_vertex;
}

function toW(co, XorY, canvas, rect){ // 变换至Webgl坐标
if(!XorY) {
return ((co - rect.left) - canvas.width / 2) / (canvas.width / 2);
}
else {
return (canvas.height / 2 - (co - rect.top)) / (canvas.height / 2);
}
}