WebGL创建3D对象
目录
- 准备WebGL上下文
- 定义几何体
- 创建顶点缓冲对象
- 设置顶点属性
- 定义几何体的面
- 创建变换矩阵
- 处理光照
- 绑定纹理
- 绘制3D对象
- 性能优化
- 添加动画
- 复杂的光照模型
- 纹理映射
- 混合模式
- 高级技术
准备WebGL上下文
首先,需要获取HTML5 <canvas>
元素的WebGL上下文:
<canvas id="webgl-canvas"></canvas>
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');
定义几何体
创建3D对象的第一步是定义几何体的顶点数据。这里以一个简单的立方体为例:
const vertices = [
// 面1 (前)
-1, -1, 1, // 0
1, -1, 1, // 1
1, 1, 1, // 2
-1, 1, 1, // 3
// 面2 (后)
-1, -1, -1, // 4
-1, 1, -1, // 5
1, 1, -1, // 6
1, -1, -1, // 7
// ...其他面...
];
创建顶点缓冲对象
将顶点数据存储在顶点缓冲对象中,以便WebGL高效地处理:
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
设置顶点属性
在顶点着色器中声明顶点属性,并在主程序中关联顶点缓冲对象:
const vertexShaderSource = `
attribute vec3 a_position;
void main() {
gl_Position = vec4(a_position, 1.0);
}
`;
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
定义几何体的面
为每个面分配一组顶点,使用索引缓冲对象(Element Array Buffer):
const indices = [
// 面1
0, 1, 2, 2, 3, 0,
// 面2
4, 5, 6, 6, 7, 4,
// ...其他面...
];
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
创建变换矩阵
创建模型、视图和投影矩阵,用于将3D空间的几何体转换为2D屏幕空间:
const modelMatrix = mat4.create();
const viewMatrix = mat4.create();
const projectionMatrix = mat4.create();
// 示例:旋转立方体
mat4.rotate(modelMatrix, modelMatrix, Math.PI / 2, [1, 0, 0]); // 90度绕X轴旋转
// 示例:设置相机位置
mat4.translate(viewMatrix, viewMatrix, [-3, 0, -5]);
处理光照
在片段着色器中计算光照效果。这里使用简单的一点光源:
const fragmentShaderSource = `
precision mediump float;
uniform vec3 u_lightPosition;
uniform vec3 u_color;
varying vec3 v_normal;
void main() {
vec3 lightDirection = normalize(u_lightPosition - v_worldPosition);
float intensity = max(dot(v_normal, lightDirection), 0.0);
vec3 litColor = u_color * intensity;
gl_FragColor = vec4(litColor, 1.0);
}`
绑定纹理
加载和应用纹理到3D对象:
const texture = loadTexture(gl, 'texture.jpg');
function loadTexture(gl, url) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
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);
const image = new Image();
image.onload = () => {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
};
image.src = url;
return texture;
}
绘制3D对象
在主循环中,绘制3D对象:
function drawScene() {
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// 计算模型视图投影矩阵
const mvpMatrix = mat4.create();
mat4.multiply(mvpMatrix, projectionMatrix, viewMatrix);
mat4.multiply(mvpMatrix, mvpMatrix, modelMatrix);
// 绑定纹理
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
// 绘制立方体
gl.useProgram(shaderProgram);
gl.uniformMatrix4fv(shaderProgram.uniforms.u_mvpMatrix, false, mvpMatrix);
gl.uniform3fv(shaderProgram.uniforms.u_lightPosition, [0, 10, 0]);
gl.uniform3fv(shaderProgram.uniforms.u_color, [1, 0, 0]); // 红色
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
}
setInterval(drawScene, 1000 / 60);
性能优化
使用VBO和EBO减少内存拷贝和提高效率。
使用视锥体剔除和背面剔除减少不必要的渲染。
使用多线程(Web Workers)处理复杂的计算任务。
使用分批渲染(Batch Rendering)减少渲染调用次数。
使用LOD(Level of Detail)根据距离动态调整细节级别。
使用实例化(Instancing)绘制多个相似的物体。
添加动画
为了使3D对象动态变化,我们可以修改模型矩阵(modelMatrix)中的旋转、平移或缩放值。例如,我们可以实现一个旋转动画:
let rotationAngle = 0;
function animate() {
requestAnimationFrame(animate);
// 旋转立方体
rotationAngle += 0.01;
mat4.fromRotation(modelMatrix, rotationAngle, [0, 1, 0]);
drawScene();
}
animate();
复杂的光照模型
简单的点光源不足以模拟真实世界的光照。可以实现更复杂的光照模型,如环境光、漫射光、镜面光和菲涅尔效应:
const fragmentShaderSource = `
precision mediump float;
uniform vec3 u_ambientLight;
uniform vec3 u_diffuseLight;
uniform vec3 u_specularLight;
uniform vec3 u_eyePosition;
uniform vec3 u_shininess;
varying vec3 v_normal;
varying vec3 v_worldPosition;
void main() {
vec3 lightDirection;
vec3 viewDirection;
vec3 ambient, diffuse, specular;
// 环境光
ambient = u_ambientLight;
// 漫射光
lightDirection = normalize(u_lightPosition - v_worldPosition);
diffuse = u_diffuseLight * max(dot(v_normal, lightDirection), 0.0);
// 镜面光
viewDirection = normalize(u_eyePosition - v_worldPosition);
vec3 halfVector = normalize(lightDirection + viewDirection);
specular = u_specularLight * pow(max(dot(v_normal, halfVector), 0.0), u_shininess);
vec3 litColor = ambient + diffuse + specular;
gl_FragColor = vec4(litColor, 1.0);
}`
纹理映射
除了颜色,我们还可以使用纹理来增加3D对象的细节。在片段着色器中,可以使用纹理坐标采样纹理:
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
vec4 texColor = texture2D(u_texture, v_texCoord);
vec3 litColor = ... // 计算光照
gl_FragColor = vec4(litColor * texColor.rgb, texColor.a);
}
在主程序中,设置纹理坐标属性,并在绘制时传入纹理:
const texCoordAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_texCoord');
gl.enableVertexAttribArray(texCoordAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.uniform1i(shaderProgram.uniforms.u_texture, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
混合模式
WebGL支持多种混合模式,可以通过gl.blendFuncSeparate()
和gl.blendEquationSeparate()
设置。例如,实现半透明混合:
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
高级技术
- 阴影映射:使用额外的渲染通道和深度贴图模拟物体在其他物体上的阴影。
- 法线映射:使用法线贴图模拟高光和凹凸感,无需额外的几何信息。
- 环境映射:使用环境贴图模拟物体反射周围环境的外观。
- GPU粒子系统:利用GPU的并行处理能力创建粒子效果,如火花、烟雾、水波等。
- 屏幕空间后处理:在所有3D渲染完成后,对整个屏幕应用额外的效果,如模糊、色彩校正、抗锯齿等。