Skip to content

WebGL - 缩放

本章演示了如何使用 WebGL 变换来缩放(调整大小)对象,特别是三角形。

缩放涉及通过将对象的顶点坐标乘以缩放因子来改变对象的大小。例如,将顶点 [x, y, z] 乘以因子 2 会得到 [2x, 2y, 2z],从而使对象的大小加倍。缩放可以是均匀的(x、y、z 使用相同的因子)或非均匀的(使用不同的因子)。

在 WebGL 中,缩放通常使用变换矩阵实现。我们创建一个 4x4 矩阵,其对角线上包含所需的缩放因子,并在顶点着色器中将对象的顶点位置乘以该矩阵。此操作会修改每个顶点的最终位置(gl_Position)。

典型的缩放矩阵如下所示:

Sx 0 0 0
0 Sy 0 0
0 0 Sz 0
0 0 0 1

其中 Sx、Sy 和 Sz 分别是沿 X、Y 和 Z 轴的缩放因子。

在 WebGL 中实现缩放涉及以下关键步骤:

步骤 1:设置 Canvas 和 WebGL 上下文

获取 HTML canvas 元素,并使用 canvas.getContext('webgl') 获取 WebGL 渲染上下文。添加检查以确保成功获取上下文。

步骤 2:定义几何体和缓冲区

使用 JavaScript 数组定义对象的顶点坐标(例如,三角形)。创建 WebGL 缓冲区对象(VBO),并使用 gl.bufferData 和 Float32Array 将顶点数据加载到其中。

const vertices = new Float32Array([
-0.5, 0.5, 0.0, // Top-left
-0.5, -0.5, 0.0, // Bottom-left
0.5, -0.5, 0.0 // Bottom-right
]);

步骤 3:创建和编译着色器

编写、编译并将顶点着色器和片段着色器链接到 WebGL 程序中。包含编译和链接的错误检查。

  • 顶点着色器(Vertex Shader):为顶点坐标定义一个属性(例如,a_position),并为缩放矩阵定义一个统一变量(uniform)(例如,u_scaleMatrix)。将位置乘以矩阵:gl_Position = u_scaleMatrix * a_position;。记住顶点位置通常是 vec3,但对于矩阵乘法需要是 vec4(使用 vec4(a_position, 1.0))。
// Vertex Shader (GLSL ES 1.00)
attribute vec3 a_position;
uniform mat4 u_scaleMatrix;
void main() {
gl_Position = u_scaleMatrix * vec4(a_position, 1.0);
}
  • 片段着色器(Fragment Shader):使用 gl_FragColor 设置输出颜色。使用 precision mediump float; 定义精度。
// Fragment Shader (GLSL ES 1.00)
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.5, 0.0, 1.0); // Orange color
}

步骤 4:链接着色器和缓冲区

获取属性(a_position)和统一变量(u_scaleMatrix)的位置。启用顶点属性数组,并使用 gl.vertexAttribPointer 配置它以从绑定的 VBO 读取数据。

步骤 5:定义和应用缩放矩阵

定义缩放因子(例如,scaleX、scaleY、scaleZ)。以列主序(column-major order)将 4x4 缩放矩阵创建为 Float32Array。使用 gl.uniformMatrix4fv 将此矩阵传递给顶点着色器。

步骤 6:绘制对象

清除 canvas,设置视口(viewport),并使用 gl.drawArrays(gl.TRIANGLES, 0, 3) 渲染缩放后的三角形。

此示例演示将一个三角形沿 Y 轴缩放至原高的 1.5 倍。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebGL Scaling Example</title>
<style>
body { margin: 0; overflow: hidden; background-color: #555; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="webgl-canvas" width="400" height="400"></canvas>
<script>
// --- Shader Sources ---
const vsSource = `
attribute vec3 a_position;
uniform mat4 u_scaleMatrix;
void main() {
gl_Position = u_scaleMatrix * vec4(a_position, 1.0);
}
`;
const fsSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(0.0, 0.7, 0.2, 1.0); // Green color
}
`;
// --- Main WebGL Initialization ---
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('WebGL is not supported by your browser.');
throw new Error('WebGL not supported');
}
// --- Shader Compilation & Linking ---
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Shader compilation error:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, vsSource, fsSource) {
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
if (!vertexShader || !fragmentShader) return null;
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Program linking error:', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
const shaderProgram = createProgram(gl, vsSource, fsSource);
if (!shaderProgram) throw new Error('Failed to create shader program.');
// --- Geometry and Buffers ---
const vertices = new Float32Array([
-0.5, 0.5, 0.0, // Top-left
-0.5, -0.5, 0.0, // Bottom-left
0.5, -0.5, 0.0 // Bottom-right
]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// --- Attribute Location and Configuration ---
const positionAttribLocation = gl.getAttribLocation(shaderProgram, 'a_position');
if (positionAttribLocation < 0) {
console.error("Attribute 'a_position' not found");
}
gl.enableVertexAttribArray(positionAttribLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // Re-bind necessary before vertexAttribPointer
gl.vertexAttribPointer(
positionAttribLocation, // Attribute location
3, // Number of components per attribute (x, y, z)
gl.FLOAT, // Type of data
false, // Normalized?
0, // Stride (0 = auto)
0 // Offset (0 = start)
);
// --- Uniform Location and Scaling Matrix ---
const scaleMatrixLocation = gl.getUniformLocation(shaderProgram, 'u_scaleMatrix');
if (!scaleMatrixLocation) {
console.error("Uniform 'u_scaleMatrix' not found");
}
const scaleX = 1.0;
const scaleY = 1.5; // Scale Y by 1.5
const scaleZ = 1.0;
// Scaling Matrix (Column Major Order for WebGL)
const scaleMatrix = new Float32Array([
scaleX, 0.0, 0.0, 0.0,
0.0, scaleY, 0.0, 0.0,
0.0, 0.0, scaleZ, 0.0,
0.0, 0.0, 0.0, 1.0
]);
gl.useProgram(shaderProgram); // Activate the program *before* setting uniforms
gl.uniformMatrix44fv(scaleMatrixLocation, false, scaleMatrix);
// --- Drawing ---
gl.clearColor(0.1, 0.1, 0.1, 1.0); // Dark gray background
gl.enable(gl.DEPTH_TEST); // Enable depth testing for 3D
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.drawArrays(
gl.TRIANGLES, // Primitive type
0, // Starting index
3 // Number of vertices to draw
);
</script>
</body>
</html>

预期输出:

此代码将渲染一个水平居中的绿色三角形。与未缩放的三角形(大致位于 y=-0.5 到 y=0.5 的正方形内)相比,由于 scaleY = 1.5 因子,此三角形将沿垂直方向拉伸,从 y = -0.75 延伸到 y = 0.75。

  • 尝试改变 scaleX、scaleY 和 scaleZ 的值,查看不同的缩放效果。
  • 通过乘以各自的矩阵,将缩放与平移和旋转结合起来。记住矩阵乘法顺序很重要!(通常是 缩放 -> 旋转 -> 平移)。
  • 对于复杂的变换,考虑使用数学库,例如 gl-matrix (https://github.com/toji/gl-matrix),以简化矩阵操作。