JavaScript系列(46)-- WebGL图形编程详解
JavaScript WebGL图形编程详解 🎨
今天,让我们深入探讨JavaScript的WebGL图形编程。WebGL是一种基于OpenGL ES的JavaScript API,它允许我们在浏览器中渲染高性能的2D和3D图形。
WebGL基础概念 🌟
💡 小知识:WebGL直接与GPU通信,使用GLSL着色器语言编写顶点和片段着色器。它提供了底层的图形API,让我们能够充分利用硬件加速进行图形渲染。
基本实现 📊
// 1. WebGL上下文初始化
class WebGLContext {
constructor(canvas) {
this.canvas = canvas;
this.gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
if (!this.gl) {
throw new Error('WebGL not supported');
}
// 初始化基本设置
this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
this.gl.enable(this.gl.DEPTH_TEST);
this.gl.enable(this.gl.CULL_FACE);
this.gl.viewport(0, 0, canvas.width, canvas.height);
}
// 清除画布
clear() {
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
}
// 设置视口
setViewport(width, height) {
this.canvas.width = width;
this.canvas.height = height;
this.gl.viewport(0, 0, width, height);
}
}
// 2. 着色器程序管理
class ShaderProgram {
constructor(gl, vertexSource, fragmentSource) {
this.gl = gl;
this.program = this.createProgram(vertexSource, fragmentSource);
this.attributes = this.getAttributes();
this.uniforms = this.getUniforms();
}
// 创建着色器程序
createProgram(vertexSource, fragmentSource) {
const vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
const program = this.gl.createProgram();
this.gl.attachShader(program, vertexShader);
this.gl.attachShader(program, fragmentShader);
this.gl.linkProgram(program);
if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
throw new Error('Failed to link shader program');
}
return program;
}
// 创建着色器
createShader(type, source) {
const shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
throw new Error(
`Failed to compile shader: ${this.gl.getShaderInfoLog(shader)}`
);
}
return shader;
}
// 获取所有属性位置
getAttributes() {
const attributes = {};
const count = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_ATTRIBUTES);
for (let i = 0; i < count; i++) {
const info = this.gl.getActiveAttrib(this.program, i);
attributes[info.name] = this.gl.getAttribLocation(this.program, info.name);
}
return attributes;
}
// 获取所有统一变量位置
getUniforms() {
const uniforms = {};
const count = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_UNIFORMS);
for (let i = 0; i < count; i++) {
const info = this.gl.getActiveUniform(this.program, i);
uniforms[info.name] = this.gl.getUniformLocation(this.program, info.name);
}
return uniforms;
}
// 使用程序
use() {
this.gl.useProgram(this.program);
}
}
// 3. 几何体管理
class Geometry {
constructor(gl) {
this.gl = gl;
this.vao = this.gl.createVertexArray();
this.buffers = new Map();
}
// 创建缓冲区
createBuffer(name, data, target = this.gl.ARRAY_BUFFER) {
const buffer = this.gl.createBuffer();
this.gl.bindBuffer(target, buffer);
this.gl.bufferData(target, data, this.gl.STATIC_DRAW);
this.buffers.set(name, { buffer, target });
}
// 设置顶点属性
setAttribute(location, size, type, normalized = false, stride = 0, offset = 0) {
this.gl.vertexAttribPointer(
location, size, type, normalized, stride, offset
);
this.gl.enableVertexAttribArray(location);
}
// 绑定几何体
bind() {
this.gl.bindVertexArray(this.vao);
}
// 解绑几何体
unbind() {
this.gl.bindVertexArray(null);
}
}
高级功能实现 🚀
// 1. 矩阵变换
class Transform {
constructor() {
this.position = vec3.create();
this.rotation = quat.create();
this.scale = vec3.fromValues(1, 1, 1);
this.matrix = mat4.create();
}
// 更新变换矩阵
updateMatrix() {
mat4.fromRotationTranslationScale(
this.matrix,
this.rotation,
this.position,
this.scale
);
return this.matrix;
}
// 设置位置
setPosition(x, y, z) {
vec3.set(this.position, x, y, z);
return this;
}
// 设置旋转
setRotation(x, y, z) {
quat.fromEuler(this.rotation, x, y, z);
return this;
}
// 设置缩放
setScale(x, y, z) {
vec3.set(this.scale, x, y, z);
return this;
}
}
// 2. 相机系统
class Camera {
constructor() {
this.position = vec3.create();
this.target = vec3.create();
this.up = vec3.fromValues(0, 1, 0);
this.viewMatrix = mat4.create();
this.projectionMatrix = mat4.create();
}
// 更新视图矩阵
updateViewMatrix() {
mat4.lookAt(this.viewMatrix, this.position, this.target, this.up);
return this.viewMatrix;
}
// 设置透视投影
setPerspective(fov, aspect, near, far) {
mat4.perspective(this.projectionMatrix, fov, aspect, near, far);
return this;
}
// 设置正交投影
setOrthographic(left, right, bottom, top, near, far) {
mat4.ortho(this.projectionMatrix, left, right, bottom, top, near, far);
return this;
}
}
// 3. 材质系统
class Material {
constructor(gl, shader) {
this.gl = gl;
this.shader = shader;
this.uniforms = new Map();
}
// 设置统一变量
setUniform(name, value) {
this.uniforms.set(name, value);
return this;
}
// 应用材质
apply() {
this.shader.use();
for (const [name, value] of this.uniforms) {
const location = this.shader.uniforms[name];
if (location) {
this.setUniformValue(location, value);
}
}
}
// 设置统一变量值
setUniformValue(location, value) {
if (Array.isArray(value)) {
switch (value.length) {
case 2:
this.gl.uniform2fv(location, value);
break;
case 3:
this.gl.uniform3fv(location, value);
break;
case 4:
this.gl.uniform4fv(location, value);
break;
case 16:
this.gl.uniformMatrix4fv(location, false, value);
break;
}
} else if (typeof value === 'number') {
this.gl.uniform1f(location, value);
} else if (value instanceof WebGLTexture) {
this.gl.uniform1i(location, 0);
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, value);
}
}
}
实际应用场景 💼
// 1. 3D场景渲染
class Scene {
constructor(gl) {
this.gl = gl;
this.objects = new Set();
this.camera = new Camera();
}
// 添加对象
add(object) {
this.objects.add(object);
}
// 移除对象
remove(object) {
this.objects.delete(object);
}
// 渲染场景
render() {
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
const viewProjection = mat4.create();
mat4.multiply(
viewProjection,
this.camera.projectionMatrix,
this.camera.viewMatrix
);
for (const object of this.objects) {
object.render(viewProjection);
}
}
}
// 2. 粒子系统
class ParticleSystem {
constructor(gl, maxParticles) {
this.gl = gl;
this.maxParticles = maxParticles;
this.particles = [];
this.geometry = this.createParticleGeometry();
this.shader = this.createParticleShader();
}
// 创建粒子几何体
createParticleGeometry() {
const positions = new Float32Array(this.maxParticles * 3);
const colors = new Float32Array(this.maxParticles * 4);
const sizes = new Float32Array(this.maxParticles);
const geometry = new Geometry(this.gl);
geometry.createBuffer('position', positions);
geometry.createBuffer('color', colors);
geometry.createBuffer('size', sizes);
return geometry;
}
// 更新粒子
update(deltaTime) {
for (const particle of this.particles) {
particle.life -= deltaTime;
if (particle.life <= 0) {
this.resetParticle(particle);
} else {
particle.position[0] += particle.velocity[0] * deltaTime;
particle.position[1] += particle.velocity[1] * deltaTime;
particle.position[2] += particle.velocity[2] * deltaTime;
}
}
this.updateGeometry();
}
// 渲染粒子
render(viewProjection) {
this.shader.use();
this.shader.setUniform('viewProjection', viewProjection);
this.geometry.bind();
this.gl.drawArrays(this.gl.POINTS, 0, this.particles.length);
this.geometry.unbind();
}
}
// 3. 后处理效果
class PostProcessor {
constructor(gl) {
this.gl = gl;
this.framebuffer = this.createFramebuffer();
this.shader = this.createPostProcessShader();
this.quad = this.createScreenQuad();
}
// 创建帧缓冲区
createFramebuffer() {
const framebuffer = this.gl.createFramebuffer();
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer);
// 创建纹理附件
const texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
this.gl.texImage2D(
this.gl.TEXTURE_2D, 0, this.gl.RGBA,
this.gl.canvas.width, this.gl.canvas.height,
0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, null
);
this.gl.texParameteri(
this.gl.TEXTURE_2D,
this.gl.TEXTURE_MIN_FILTER,
this.gl.LINEAR
);
this.gl.texParameteri(
this.gl.TEXTURE_2D,
this.gl.TEXTURE_MAG_FILTER,
this.gl.LINEAR
);
// 附加纹理
this.gl.framebufferTexture2D(
this.gl.FRAMEBUFFER,
this.gl.COLOR_ATTACHMENT0,
this.gl.TEXTURE_2D,
texture,
0
);
return {
framebuffer,
texture
};
}
// 应用后处理效果
apply(scene) {
// 渲染场景到帧缓冲区
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer.framebuffer);
scene.render();
// 渲染后处理效果到屏幕
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
this.shader.use();
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, this.framebuffer.texture);
this.quad.bind();
this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
this.quad.unbind();
}
}
性能优化技巧 ⚡
// 1. 实例化渲染
class InstancedRenderer {
constructor(gl, geometry, maxInstances) {
this.gl = gl;
this.geometry = geometry;
this.maxInstances = maxInstances;
this.setupInstancedBuffers();
}
// 设置实例化缓冲区
setupInstancedBuffers() {
const matrices = new Float32Array(this.maxInstances * 16);
const colors = new Float32Array(this.maxInstances * 4);
// 创建矩阵缓冲区
const matrixBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, matrixBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, matrices, this.gl.DYNAMIC_DRAW);
// 设置矩阵属性
for (let i = 0; i < 4; i++) {
const location = this.geometry.shader.attributes[`instanceMatrix${i}`];
this.gl.enableVertexAttribArray(location);
this.gl.vertexAttribPointer(
location, 4, this.gl.FLOAT, false,
64, i * 16
);
this.gl.vertexAttribDivisor(location, 1);
}
// 创建颜色缓冲区
const colorBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, colorBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, colors, this.gl.DYNAMIC_DRAW);
const colorLocation = this.geometry.shader.attributes.instanceColor;
this.gl.enableVertexAttribArray(colorLocation);
this.gl.vertexAttribPointer(
colorLocation, 4, this.gl.FLOAT, false,
0, 0
);
this.gl.vertexAttribDivisor(colorLocation, 1);
}
// 更新实例数据
updateInstances(matrices, colors) {
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.matrixBuffer);
this.gl.bufferSubData(this.gl.ARRAY_BUFFER, 0, matrices);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.colorBuffer);
this.gl.bufferSubData(this.gl.ARRAY_BUFFER, 0, colors);
}
// 渲染实例
render(instanceCount) {
this.geometry.bind();
this.gl.drawArraysInstanced(
this.gl.TRIANGLES,
0,
this.geometry.vertexCount,
instanceCount
);
this.geometry.unbind();
}
}
// 2. 批处理渲染
class BatchRenderer {
constructor(gl, maxBatchSize) {
this.gl = gl;
this.maxBatchSize = maxBatchSize;
this.batch = [];
}
// 添加到批处理
add(object) {
if (this.batch.length >= this.maxBatchSize) {
this.flush();
}
this.batch.push(object);
}
// 刷新批处理
flush() {
if (this.batch.length === 0) return;
// 合并几何体数据
const vertices = [];
const indices = [];
let indexOffset = 0;
for (const object of this.batch) {
vertices.push(...object.vertices);
indices.push(...object.indices.map(i => i + indexOffset));
indexOffset += object.vertices.length / 3;
}
// 更新缓冲区
this.updateBuffers(vertices, indices);
// 渲染批处理
this.render();
// 清空批处理
this.batch.length = 0;
}
}
// 3. 视锥体剔除
class Frustum {
constructor() {
this.planes = new Array(6);
for (let i = 0; i < 6; i++) {
this.planes[i] = vec4.create();
}
}
// 从投影视图矩阵更新视锥体
updateFromMatrix(matrix) {
// 提取平面
for (let i = 0; i < 6; i++) {
const plane = this.planes[i];
const row = Math.floor(i / 2);
const sign = i % 2 === 0 ? 1 : -1;
vec4.set(
plane,
matrix[3] + sign * matrix[row],
matrix[7] + sign * matrix[row + 4],
matrix[11] + sign * matrix[row + 8],
matrix[15] + sign * matrix[row + 12]
);
vec4.normalize(plane, plane);
}
}
// 检查点是否在视锥体内
containsPoint(point) {
for (const plane of this.planes) {
if (vec4.dot(plane, [...point, 1]) < 0) {
return false;
}
}
return true;
}
// 检查包围球是否在视锥体内
containsSphere(center, radius) {
for (const plane of this.planes) {
if (vec4.dot(plane, [...center, 1]) < -radius) {
return false;
}
}
return true;
}
}
最佳实践建议 💡
- 性能优化模式
// 1. 状态管理
class GLState {
constructor(gl) {
this.gl = gl;
this.currentProgram = null;
this.currentTexture = null;
this.currentVAO = null;
}
// 使用着色器程序
useProgram(program) {
if (this.currentProgram !== program) {
this.gl.useProgram(program);
this.currentProgram = program;
}
}
// 绑定纹理
bindTexture(texture) {
if (this.currentTexture !== texture) {
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
this.currentTexture = texture;
}
}
// 绑定VAO
bindVAO(vao) {
if (this.currentVAO !== vao) {
this.gl.bindVertexArray(vao);
this.currentVAO = vao;
}
}
}
// 2. 资源管理
class ResourceManager {
constructor() {
this.resources = new Map();
this.loading = new Set();
}
// 加载资源
async load(url, type) {
if (this.resources.has(url)) {
return this.resources.get(url);
}
if (this.loading.has(url)) {
return new Promise(resolve => {
const check = () => {
if (this.resources.has(url)) {
resolve(this.resources.get(url));
} else {
requestAnimationFrame(check);
}
};
check();
});
}
this.loading.add(url);
try {
const resource = await this.loadResource(url, type);
this.resources.set(url, resource);
this.loading.delete(url);
return resource;
} catch (error) {
this.loading.delete(url);
throw error;
}
}
// 释放资源
unload(url) {
const resource = this.resources.get(url);
if (resource) {
if (resource.dispose) {
resource.dispose();
}
this.resources.delete(url);
}
}
}
// 3. 渲染队列
class RenderQueue {
constructor() {
this.opaque = [];
this.transparent = [];
}
// 添加渲染对象
add(object) {
if (object.material.transparent) {
this.transparent.push(object);
} else {
this.opaque.push(object);
}
}
// 排序渲染队列
sort(cameraPosition) {
// 不透明物体从前往后排序
this.opaque.sort((a, b) => {
return a.material.renderOrder - b.material.renderOrder;
});
// 透明物体从后往前排序
this.transparent.sort((a, b) => {
const distA = vec3.distance(a.position, cameraPosition);
const distB = vec3.distance(b.position, cameraPosition);
return distB - distA;
});
}
// 执行渲染
render(scene) {
// 渲染不透明物体
for (const object of this.opaque) {
object.render(scene);
}
// 渲染透明物体
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
for (const object of this.transparent) {
object.render(scene);
}
this.gl.disable(this.gl.BLEND);
}
}
结语 📝
WebGL为JavaScript提供了强大的图形渲染能力。通过本文,我们学习了:
- WebGL的基本概念和初始化
- 着色器程序和几何体管理
- 高级渲染技术
- 性能优化策略
- 最佳实践和设计模式
💡 学习建议:在使用WebGL时,要注意性能优化和内存管理。合理使用批处理和实例化渲染,避免频繁的状态切换。同时,要考虑跨平台兼容性和降级处理。
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻