WebGL是基于OpenGL ES的Web 3D图形API,可以在浏览器中渲染高性能的2D和3D图形。本章将介绍WebGL的基本概念和使用方法。
const webglOverview = {
definition: 'WebGL是一种JavaScript API,用于在浏览器中渲染交互式2D和3D图形',
features: [
'基于OpenGL ES 2.0/3.0',
'使用GPU加速渲染',
'跨平台支持',
'无需插件',
'与HTML元素集成'
],
applications: [
'3D游戏',
'数据可视化',
'虚拟现实',
'产品展示',
'科学模拟'
]
};
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) {
alert('您的浏览器不支持WebGL');
}
const gl2 = canvas.getContext('webgl2');
console.log('WebGL版本:', gl.getParameter(gl.VERSION));
console.log('GLSL版本:', gl.getParameter(gl.SHADING_LANGUAGE_VERSION));
console.log('渲染器:', gl.getParameter(gl.RENDERER));
console.log('厂商:', gl.getParameter(gl.VENDOR));
const renderPipeline = {
stages: [
{
name: '顶点数据',
description: '顶点坐标、颜色、纹理坐标等'
},
{
name: '顶点着色器',
description: '处理每个顶点,计算位置'
},
{
name: '图元装配',
description: '将顶点组装成三角形、线段等'
},
{
name: '光栅化',
description: '将图元转换为片段'
},
{
name: '片段着色器',
description: '计算每个像素的颜色'
},
{
name: '深度测试/模板测试',
description: '决定哪些片段可见'
},
{
name: '帧缓冲',
description: '最终输出到屏幕'
}
]
};
attribute vec3 aPosition;
attribute vec4 aColor;
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;
varying vec4 vColor;
void main() {
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aPosition, 1.0);
vColor = aColor;
}
precision mediump float;
varying vec4 vColor;
uniform vec3 uLightPosition;
uniform vec3 uAmbientColor;
void main() {
gl_FragColor = vColor;
}
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('着色器编译错误:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, vertexSource, fragmentSource) {
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
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('程序链接错误:', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
return program;
}
const vertexShaderSource = `
attribute vec3 aPosition;
attribute vec4 aColor;
uniform mat4 uModelViewProjection;
varying vec4 vColor;
void main() {
gl_Position = uModelViewProjection * vec4(aPosition, 1.0);
vColor = aColor;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`;
const program = createProgram(gl, vertexShaderSource, fragmentShaderSource);
const vertices = new Float32Array([
0.0, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0
]);
const colors = new Float32Array([
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0
]);
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
gl.useProgram(program);
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aColor = gl.getAttribLocation(program, 'aColor');
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.enableVertexAttribArray(aColor);
gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, 0, 0);
function render() {
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.drawArrays(gl.TRIANGLES, 0, 3);
requestAnimationFrame(render);
}
render();
const mat4 = {
create() {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
},
identity(out) {
out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0;
out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0;
out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0;
out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1;
return out;
},
perspective(out, fovy, aspect, near, far) {
const f = 1.0 / Math.tan(fovy / 2);
out[0] = f / aspect;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = f;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = (far + near) / (near - far);
out[11] = -1;
out[12] = 0;
out[13] = 0;
out[14] = (2 * far * near) / (near - far);
out[15] = 0;
return out;
},
lookAt(out, eye, center, up) {
let x0, x1, x2, y0, y1, y2, z0, z1, z2, len;
z0 = eye[0] - center[0];
z1 = eye[1] - center[1];
z2 = eye[2] - center[2];
len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
z0 *= len; z1 *= len; z2 *= len;
x0 = up[1] * z2 - up[2] * z1;
x1 = up[2] * z0 - up[0] * z2;
x2 = up[0] * z1 - up[1] * z0;
len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
if (len) {
len = 1 / len;
x0 *= len; x1 *= len; x2 *= len;
}
y0 = z1 * x2 - z2 * x1;
y1 = z2 * x0 - z0 * x2;
y2 = z0 * x1 - z1 * x0;
out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0;
out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0;
out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0;
out[12] = -(x0 * eye[0] + x1 * eye[1] + x2 * eye[2]);
out[13] = -(y0 * eye[0] + y1 * eye[1] + y2 * eye[2]);
out[14] = -(z0 * eye[0] + z1 * eye[1] + z2 * eye[2]);
out[15] = 1;
return out;
},
translate(out, a, v) {
const x = v[0], y = v[1], z = v[2];
out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
return out;
},
rotateY(out, a, rad) {
const s = Math.sin(rad);
const c = Math.cos(rad);
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
out[0] = a00 * c - a20 * s;
out[1] = a01 * c - a21 * s;
out[2] = a02 * c - a22 * s;
out[3] = a03 * c - a23 * s;
out[8] = a00 * s + a20 * c;
out[9] = a01 * s + a21 * c;
out[10] = a02 * s + a22 * c;
out[11] = a03 * s + a23 * c;
return out;
},
multiply(out, a, b) {
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
return out;
}
};
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix,
45 * Math.PI / 180,
canvas.width / canvas.height,
0.1,
100.0
);
const viewMatrix = mat4.create();
mat4.lookAt(viewMatrix,
[0, 0, 5],
[0, 0, 0],
[0, 1, 0]
);
const modelMatrix = mat4.create();
mat4.translate(modelMatrix, modelMatrix, [0, 0, 0]);
mat4.rotateY(modelMatrix, modelMatrix, 0);
const mvpMatrix = mat4.create();
mat4.multiply(mvpMatrix, viewMatrix, modelMatrix);
mat4.multiply(mvpMatrix, projectionMatrix, mvpMatrix);
const uMVP = gl.getUniformLocation(program, 'uModelViewProjection');
gl.uniformMatrix4fv(uMVP, false, mvpMatrix);
function loadTexture(gl, url) {
return new Promise((resolve) => {
const texture = gl.createTexture();
const image = new Image();
image.onload = function() {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
resolve(texture);
};
image.src = url;
});
}
const texture = await loadTexture(gl, 'texture.png');
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
const uTexture = gl.getUniformLocation(program, 'uTexture');
gl.uniform1i(uTexture, 0);
const textureCoords = new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0
]);
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, textureCoords, gl.STATIC_DRAW);
const aTexCoord = gl.getAttribLocation(program, 'aTexCoord');
gl.enableVertexAttribArray(aTexCoord);
gl.vertexAttribPointer(aTexCoord, 2, gl.FLOAT, false, 0, 0);
const lightingVertexShader = `
attribute vec3 aPosition;
attribute vec3 aNormal;
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat3 uNormalMatrix;
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
vec4 worldPosition = uModelMatrix * vec4(aPosition, 1.0);
vPosition = worldPosition.xyz;
vNormal = uNormalMatrix * aNormal;
gl_Position = uProjectionMatrix * uViewMatrix * worldPosition;
}
`;
const lightingFragmentShader = `
precision mediump float;
varying vec3 vNormal;
varying vec3 vPosition;
uniform vec3 uLightPosition;
uniform vec3 uLightColor;
uniform vec3 uAmbientColor;
uniform vec3 uMaterialColor;
void main() {
vec3 normal = normalize(vNormal);
vec3 lightDir = normalize(uLightPosition - vPosition);
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * uLightColor * uMaterialColor;
vec3 ambient = uAmbientColor * uMaterialColor;
vec3 viewDir = normalize(-vPosition);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
vec3 specular = spec * uLightColor;
vec3 result = ambient + diffuse + specular;
gl_FragColor = vec4(result, 1.0);
}
`;
🎮 WebGL开发建议
- 使用库:Three.js、Babylon.js等库简化开发
- 调试工具:使用浏览器开发者工具和WebGL Inspector
- 性能优化:减少绘制调用、使用实例化渲染
- 错误处理:检查着色器编译和程序链接状态
📚 学习路径
- 基础:理解渲染管线、着色器
- 进阶:矩阵变换、光照模型
- 高级:阴影、后处理、计算着色器
下一章将探讨 SVG图形,学习可缩放矢量图形的使用方法。