mcpField
W
webgl-expert

webgl-expert

github.com/ronnycoding开发20182 次安装19 周前更新
代码仓库

关于 webgl-expert

WebGL Expert 是面向浏览器图形开发者的参考技能,涵盖 WebGL 1.0 和 2.0。内容包括渲染概念、性能实践、调试方法及常见实现问题。

主要功能

  1. 01WebGL 1.0 与 2.0 指南
  2. 02着色器、缓冲区和纹理流程
  3. 03渲染与矩阵概念
  4. 04性能优化实践
  5. 05上下文管理与调试
  6. 06API 参考和示例

适用场景

  1. 01实现浏览器中的 2D 或 3D 渲染
  2. 02选择 WebGL 版本及扩展
  3. 03排查着色器或运行时错误
  4. 04改善渲染性能

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ronnycoding/.claude --skill webgl-expert

安装后在 Claude Code 中输入/webgl-expert确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

reference.md
examples/
basic-triangle.md
textured-cube.md
templates/
shader-template.glsl

详细介绍

WebGL 专家指南

WebGL(Web Graphics Library)API 开发专家指南,涵盖 WebGL 1.0 和 WebGL 2.0,适用于在 Web 浏览器中进行高性能 2D 和 3D 图形渲染。

概述

WebGL 是一种 JavaScript API,无需插件即可在 HTML canvas 元素中实现硬件加速的 3D 图形渲染。它与 OpenGL ES 2.0(WebGL 1.0)和 OpenGL ES 3.0(WebGL 2.0)标准高度一致。

主要功能:

  • 硬件加速的 2D 和 3D 渲染
  • 可编程着色器管线(GLSL)
  • 纹理映射和高级材质
  • 光照和变换系统
  • 用于游戏和可视化的高性能图形
  • 跨平台兼容性(所有现代浏览器)

核心接口

WebGLRenderingContext(WebGL 1.0)

WebGL 操作的基础接口,可通过 canvas 上下文获取:

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

if (!gl) {
    console.error('WebGL not supported');
}
WebGL2RenderingContext(WebGL 2.0)

包含高级功能的增强接口:

const gl = canvas.getContext('webgl2');

if (!gl) {
    console.log('WebGL 2 not supported, falling back to WebGL 1');
    gl = canvas.getContext('webgl');
}

WebGL 2 独有功能:

  • 3D 纹理
  • 采样器对象
  • 统一缓冲区对象(UBO)
  • 变换反馈
  • 顶点数组对象(VAO)- 核心功能
  • 实例化渲染
  • 多重渲染目标
  • 整数纹理和属性
  • 查询对象
  • 遮挡查询

渲染管线

1. 着色器的创建和编译

着色器是使用 GLSL(OpenGL 着色语言)编写并在 GPU 上运行的程序:

顶点着色器 - 处理每个顶点:

attribute vec3 aPosition;
attribute vec2 aTexCoord;
uniform mat4 uModelViewProjection;
varying vec2 vTexCoord;

void main() {
    gl_Position = uModelViewProjection * vec4(aPosition, 1.0);
    vTexCoord = aTexCoord;
}

片段着色器 - 确定像素颜色:

precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D uTexture;

void main() {
    gl_FragColor = texture2D(uTexture, vTexCoord);
}

JavaScript 着色器设置:

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, vertexShader, fragmentShader) {
    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;
}
2. 缓冲区管理

缓冲区用于存储顶点数据(位置、颜色、法线、纹理坐标):

// Create buffer
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

// Upload data
const positions = new Float32Array([
    -1.0, -1.0, 0.0,
     1.0, -1.0, 0.0,
     0.0,  1.0, 0.0
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);

// Set up attribute pointer
const positionLocation = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);

缓冲区使用模式:

  • gl.STATIC_DRAW - 数据不会变化
  • gl.DYNAMIC_DRAW - 数据偶尔变化
  • gl.STREAM_DRAW - 数据每帧都会变化
3. 纹理处理
function loadTexture(gl, url) {
    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // Placeholder until image loads
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
                  new Uint8Array([255, 0, 255, 255]));

    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);

        // Generate mipmaps if power of 2
        if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
            gl.generateMipmap(gl.TEXTURE_2D);
        } else {
            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);
        }
    };
    image.src = url;
    return texture;
}

function isPowerOf2(value) {
    return (value & (value - 1)) === 0;
}
4. 渲染循环
function render(gl, program) {
    // Clear canvas
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // Enable depth testing
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);

    // Use program
    gl.useProgram(program);

    // Set uniforms
    const projectionMatrix = mat4.create();
    mat4.perspective(projectionMatrix, Math.PI / 4, canvas.width / canvas.height, 0.1, 100.0);

    const uniformLocation = gl.getUniformLocation(program, 'uModelViewProjection');
    gl.uniformMatrix4fv(uniformLocation, false, projectionMatrix);

    // Draw
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    // Animation loop
    requestAnimationFrame(() => render(gl, program));
}

矩阵数学

WebGL 使用列主序矩阵进行变换。推荐使用以下库:

  • glMatrix - 快速的矩阵/向量运算
  • three.js - 内置数学功能的高级 3D 库

常见变换:

// Model matrix (object transform)
const modelMatrix = mat4.create();
mat4.translate(modelMatrix, modelMatrix, [x, y, z]);
mat4.rotate(modelMatrix, modelMatrix, angle, [0, 1, 0]);
mat4.scale(modelMatrix, modelMatrix, [sx, sy, sz]);

// View matrix (camera)
const viewMatrix = mat4.create();
mat4.lookAt(viewMatrix, eyePosition, targetPosition, upVector);

// Projection matrix
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, fov, aspect, near, far);

// Combined MVP matrix
const mvpMatrix = mat4.create();
mat4.multiply(mvpMatrix, projectionMatrix, viewMatrix);
mat4.multiply(mvpMatrix, mvpMatrix, modelMatrix);

性能优化

最佳实践
  1. 尽量减少状态变更 - 将状态相似的绘制调用进行批处理
  2. 使用顶点数组对象(VAO) - 减少属性设置开销
  3. 纹理图集 - 将多张纹理合并为一张
  4. 实例化渲染 - 高效绘制大量相似对象
  5. 视锥体剔除 - 不渲染视野外的对象
  6. 细节层次(LOD) - 远距离使用更简单的模型
  7. 纹理压缩 - 使用压缩纹理格式(DXT、ETC、ASTC)
  8. 尽量降低着色器复杂度 - 保持片段着色器简单
  9. 使用统一缓冲区(WebGL 2)- 高效共享统一数据
  10. 避免 CPU-GPU 同步 - 不要频繁回读数据
实例化渲染(WebGL 2)
const ext = gl.getExtension('ANGLE_instanced_arrays'); // WebGL 1
// or use gl.drawArraysInstanced directly in WebGL 2

// Set up per-instance attribute
const instanceOffsetBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, instanceOffsetBuffer);
gl.bufferData(gl.ARRAY_BUFFER, offsetData, gl.STATIC_DRAW);

const offsetLocation = gl.getAttribLocation(program, 'aInstanceOffset');
gl.enableVertexAttribArray(offsetLocation);
gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(offsetLocation, 1); // Advance per instance

// Draw multiple instances
gl.drawArraysInstanced(gl.TRIANGLES, 0, vertexCount, instanceCount);

扩展系统

检查并使用扩展以启用高级功能:

function getExtension(gl, name) {
    const ext = gl.getExtension(name);
    if (!ext) {
        console.warn(`Extension ${name} not supported`);
    }
    return ext;
}

// Common extensions
const anisotropic = getExtension(gl, 'EXT_texture_filter_anisotropic');
const floatTextures = getExtension(gl, 'OES_texture_float');
const depthTexture = getExtension(gl, 'WEBGL_depth_texture');
const drawBuffers = getExtension(gl, 'WEBGL_draw_buffers');
const loseContext = getExtension(gl, 'WEBGL_lose_context'); // for testing

重要扩展类别:

  • 纹理格式: WEBGL_compressed_texture_s3tc、WEBGL_compressed_texture_etc
  • 渲染: WEBGL_draw_buffers、EXT_blend_minmax、EXT_frag_depth
  • 精度: OES_texture_float、OES_texture_half_float
  • 实例化: ANGLE_instanced_arrays(WebGL 1)
  • 调试: WEBGL_debug_renderer_info、WEBGL_debug_shaders

上下文管理

上下文丢失处理
canvas.addEventListener('webglcontextlost', (event) => {
    event.preventDefault();
    console.log('WebGL context lost');
    cancelAnimationFrame(animationId);
}, false);

canvas.addEventListener('webglcontextrestored', () => {
    console.log('WebGL context restored');
    initWebGL(); // Recreate all resources
    render();
}, false);
上下文创建选项
const gl = canvas.getContext('webgl2', {
    alpha: false,                    // No alpha channel (better performance)
    antialias: true,                 // Antialiasing (performance cost)
    depth: true,                     // Depth buffer
    stencil: false,                  // Stencil buffer
    premultipliedAlpha: true,        // Alpha premultiplication
    preserveDrawingBuffer: false,    // Keep buffer after render
    powerPreference: 'high-performance', // GPU preference
    failIfMajorPerformanceCaveat: false  // Fallback to software
});

常见模式

帧缓冲区渲染(渲染到纹理)
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

const targetTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetTexture, 0);

// Render to framebuffer
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.viewport(0, 0, width, height);
// ... render scene ...

// Render to canvas
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, canvas.width, canvas.height);
多重渲染目标(WebGL 2)
const ext = gl.getExtension('WEBGL_draw_buffers'); // WebGL 1
// Fragment shader outputs to multiple targets
gl.drawBuffers([
    gl.COLOR_ATTACHMENT0,
    gl.COLOR_ATTACHMENT1,
    gl.COLOR_ATTACHMENT2
]);

常见陷阱

  1. 未检查编译/链接错误 - 始终检查着色器状态
  2. 忘记启用属性 - 调用 gl.enableVertexAttribArray()
  3. 数据类型不正确 - 使用 Float32Array、Uint16Array 等
  4. 未处理上下文丢失 - 添加事件监听器
  5. 混用 WebGL 1 和 2 API - 检查版本兼容性
  6. 假设纹理尺寸为 2 的幂 - 正确处理非 POT 纹理
  7. Z 冲突 - 深度缓冲区精度不足
  8. 坐标系混淆 - WebGL 使用裁剪空间 [-1, 1]
  9. 过早优化 - 先进行性能分析,再优化
  10. 未清除缓冲区 - 每帧调用 gl.clear()

调试工具

  1. 浏览器开发者工具 - 检查控制台中的 WebGL 错误
  2. WebGL Inspector - 用于捕获帧的浏览器扩展
  3. Spector.js - WebGL 调试库
  4. gl.getError() - 检查运行时错误
  5. WEBGL_debug_shaders - 获取转换后的着色器源码
// Error checking
const error = gl.getError();
if (error !== gl.NO_ERROR) {
    console.error('WebGL error:', error);
}

常用库和框架

  • three.js - 功能全面的 3D 库,带有场景图
  • Babylon.js - 支持物理和 VR 的游戏引擎
  • PlayCanvas - 基于云的游戏引擎
  • Pixi.js - 快速的 2D WebGL 渲染器
  • Phaser - 2D 游戏框架
  • regl - 函数式 WebGL 封装
  • twgl - 轻量级 WebGL 辅助库
  • glMatrix - 高性能矩阵/向量库

学习资源

快速参考

请参阅 reference.md,了解:

  • 完整的常量参考
  • 所有 WebGL 方法
  • GLSL 内置函数
  • 扩展兼容性矩阵

请参阅 examples,了解:

  • 基本三角形渲染
  • 纹理映射
  • 光照模型
  • 高级技术

版本兼容性

同时支持 WebGL 1 和 2 时:

function initWebGL(canvas) {
    const gl = canvas.getContext('webgl2');
    let version = 2;

    if (!gl) {
        gl = canvas.getContext('webgl');
        version = 1;
        console.log('Using WebGL 1');
    }

    // Feature detection
    const hasVAO = version === 2 || gl.getExtension('OES_vertex_array_object');
    const hasInstancing = version === 2 || gl.getExtension('ANGLE_instanced_arrays');

    return { gl, version, hasVAO, hasInstancing };
}

安全注意事项

  • 跨源纹理 - 正确使用 CORS
  • 着色器验证 - 验证用户提供的着色器代码
  • 资源限制 - 不要信任客户端报告的功能
  • 计时攻击 - 注意着色器编译计时
  • 上下文指纹识别 - 用户可能会出于隐私考虑屏蔽 WebGL

为用户提供 WebGL 帮助时:

  1. 确定版本 - 检查需要 WebGL 1 还是 2
  2. 检查要求 - 浏览器支持情况及所需扩展
  3. 从简单开始 - 先实现基本渲染,再添加高级功能
  4. 系统地调试 - 按顺序检查着色器、缓冲区和状态
  5. 分析性能 - 使用浏览器工具找出瓶颈
  6. 考虑使用库 - 对于复杂项目,推荐 three.js/Babylon.js
  7. 验证输入 - 检查上下文是否为 null,以及是否存在编译错误
  8. 处理上下文丢失 - 始终实现恢复机制
  9. 适当优化 - 不要过早进行过度优化
  10. 在不同设备上测试 - GPU 功能差异可能很大

评论

支持 Markdown