WebGL着色器与效果开发技能Skill webgl

该技能专注于使用 WebGL 技术为 JARVIS AI 助手 3D HUD 开发自定义着色器和视觉特效,强调 GPU 加速渲染、安全性和性能优化,适用于全息效果、后处理、粒子系统等应用场景。关键词:WebGL, 着色器, 视觉效果, GPU渲染, 安全, JARVIS, HUD。

前端开发 0 次安装 0 次浏览 更新于 3/15/2026

名称: webgl 描述: 用于JARVIS 3D HUD的WebGL着色器和效果 模型: sonnet 风险级别: 中 版本: 1.0.0

WebGL 开发技能

文件组织: 该技能采用拆分结构。请参阅 references/ 文件夹获取高级模式和安全性示例。

1. 概述

该技能提供WebGL专业知识,用于在JARVIS AI助手HUD中创建自定义着色器和视觉特效。它专注于GPU加速渲染,并考虑安全性。

风险级别: 中 - 直接GPU访问,可能导致资源耗尽、驱动程序漏洞

主要用例:

  • 全息效果的自定义着色器
  • 后处理效果(泛光、故障效果)
  • 使用计算着色器的粒子系统
  • 实时数据可视化

2. 核心职责

2.1 基本原则

  1. 测试驱动开发优先: 在实现前编写测试 - 测试着色器、上下文和资源
  2. 性能意识: 优化GPU使用 - 批量绘制、重用缓冲区、压缩纹理
  3. GPU安全: 实现超时机制和资源限制
  4. 着色器验证: 在编译前验证所有着色器输入
  5. 上下文管理: 优雅处理上下文丢失
  6. 性能预算: 设置严格的绘制调用和三角形限制
  7. 回退策略: 提供非WebGL回退方案
  8. 内存管理: 跟踪和限制纹理/缓冲区使用

3. 技术栈和版本

3.1 浏览器支持

浏览器 WebGL 2.0 说明
Chrome 56+ 完全支持
Firefox 51+ 完全支持
Safari 15+ WebGL 2.0支持
Edge 79+ 基于Chromium

3.2 安全性考虑

// 检查WebGL支持和功能
function getWebGLContext(canvas: HTMLCanvasElement): WebGL2RenderingContext | null {
  const gl = canvas.getContext('webgl2', {
    alpha: true,
    antialias: true,
    powerPreference: 'high-performance',
    failIfMajorPerformanceCaveat: true  // 如果软件渲染则失败
  })

  if (!gl) {
    console.warn('WebGL 2.0 不支持')
    return null
  }

  return gl
}

4. 实现模式

4.1 安全着色器编译

// utils/shaderUtils.ts

// ✅ 安全的着色器编译,带错误处理
export function compileShader(
  gl: WebGL2RenderingContext,
  source: string,
  type: number
): WebGLShader | null {
  const shader = gl.createShader(type)
  if (!shader) return null

  gl.shaderSource(shader, source)
  gl.compileShader(shader)

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    const error = gl.getShaderInfoLog(shader)
    console.error('着色器编译错误:', error)
    gl.deleteShader(shader)
    return null
  }

  return shader
}

// ✅ 安全的程序链接
export function createProgram(
  gl: WebGL2RenderingContext,
  vertexShader: WebGLShader,
  fragmentShader: WebGLShader
): WebGLProgram | null {
  const program = gl.createProgram()
  if (!program) return null

  gl.attachShader(program, vertexShader)
  gl.attachShader(program, fragmentShader)
  gl.linkProgram(program)

  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    const error = gl.getProgramInfoLog(program)
    console.error('程序链接错误:', error)
    gl.deleteProgram(program)
    return null
  }

  return program
}

4.2 上下文丢失处理

// composables/useWebGL.ts
export function useWebGL(canvas: Ref<HTMLCanvasElement | null>) {
  const gl = ref<WebGL2RenderingContext | null>(null)
  const contextLost = ref(false)

  onMounted(() => {
    if (!canvas.value) return

    // ✅ 处理上下文丢失
    canvas.value.addEventListener('webglcontextlost', (e) => {
      e.preventDefault()
      contextLost.value = true
      console.warn('WebGL 上下文丢失')
    })

    canvas.value.addEventListener('webglcontextrestored', () => {
      contextLost.value = false
      initializeGL()
      console.info('WebGL 上下文恢复')
    })

    initializeGL()
  })

  function initializeGL() {
    gl.value = getWebGLContext(canvas.value!)
    // 重新初始化所有资源
  }

  return { gl, contextLost }
}

4.3 全息着色器

// shaders/holographic.frag
#version 300 es
precision highp float;

uniform float uTime;
uniform vec3 uColor;
uniform float uScanlineIntensity;

in vec2 vUv;
out vec4 fragColor;

void main() {
  // 扫描线效果
  float scanline = sin(vUv.y * 200.0 + uTime * 2.0) * 0.5 + 0.5;
  scanline = mix(1.0, scanline, uScanlineIntensity);

  // 边缘发光
  float edge = smoothstep(0.0, 0.1, vUv.x) *
               smoothstep(1.0, 0.9, vUv.x) *
               smoothstep(0.0, 0.1, vUv.y) *
               smoothstep(1.0, 0.9, vUv.y);

  vec3 color = uColor * scanline * edge;
  float alpha = edge * 0.8;

  fragColor = vec4(color, alpha);
}

4.4 资源管理

// utils/resourceManager.ts
export class WebGLResourceManager {
  private textures: Set<WebGLTexture> = new Set()
  private buffers: Set<WebGLBuffer> = new Set()
  private programs: Set<WebGLProgram> = new Set()

  private textureMemory = 0
  private readonly MAX_TEXTURE_MEMORY = 256 * 1024 * 1024  // 256MB

  constructor(private gl: WebGL2RenderingContext) {}

  createTexture(width: number, height: number): WebGLTexture | null {
    const size = width * height * 4  // RGBA

    // ✅ 强制内存限制
    if (this.textureMemory + size > this.MAX_TEXTURE_MEMORY) {
      console.error('纹理内存限制超出')
      return null
    }

    const texture = this.gl.createTexture()
    if (texture) {
      this.textures.add(texture)
      this.textureMemory += size
    }
    return texture
  }

  dispose(): void {
    this.textures.forEach(t => this.gl.deleteTexture(t))
    this.buffers.forEach(b => this.gl.deleteBuffer(b))
    this.programs.forEach(p => this.gl.deleteProgram(p))
    this.textureMemory = 0
  }
}

4.5 统一变量验证

// ✅ 类型安全的统一变量设置
export function setUniforms(
  gl: WebGL2RenderingContext,
  program: WebGLProgram,
  uniforms: Record<string, number | number[] | Float32Array>
): void {
  for (const [name, value] of Object.entries(uniforms)) {
    const location = gl.getUniformLocation(program, name)
    if (!location) {
      console.warn(`统一变量 '${name}' 未找到`)
      continue
    }

    if (typeof value === 'number') {
      gl.uniform1f(location, value)
    } else if (Array.isArray(value)) {
      switch (value.length) {
        case 2: gl.uniform2fv(location, value); break
        case 3: gl.uniform3fv(location, value); break
        case 4: gl.uniform4fv(location, value); break
        case 16: gl.uniformMatrix4fv(location, false, value); break
      }
    }
  }
}

5. 实现工作流(TDD)

5.1 逐步过程

  1. 编写失败测试 -> 2. 实现最小功能 -> 3. 重构 -> 4. 验证
// 步骤 1: tests/webgl/shaderCompilation.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { compileShader } from '@/utils/shaderUtils'

describe('WebGL 着色器编译', () => {
  let gl: WebGL2RenderingContext

  beforeEach(() => {
    gl = document.createElement('canvas').getContext('webgl2')!
  })

  it('应编译有效着色器', () => {
    const source = `#version 300 es
      in vec4 aPosition;
      void main() { gl_Position = aPosition; }`
    expect(compileShader(gl, source, gl.VERTEX_SHADER)).not.toBeNull()
  })

  it('对无效着色器应返回 null', () => {
    expect(compileShader(gl, '无效', gl.FRAGMENT_SHADER)).toBeNull()
  })
})

// 步骤 2-3: 实现和重构(见第4.1节)
// 步骤 4: npm test && npm run typecheck && npm run build

5.2 测试上下文和资源

describe('WebGL 上下文', () => {
  it('应处理上下文丢失', async () => {
    const { gl, contextLost } = useWebGL(ref(canvas))
    gl.value?.getExtension('WEBGL_lose_context')?.loseContext()
    await nextTick()
    expect(contextLost.value).toBe(true)
  })
})

describe('资源管理器', () => {
  it('应强制内存限制', () => {
    const manager = new WebGLResourceManager(gl)
    expect(manager.createTexture(1024, 1024)).not.toBeNull()
    expect(manager.createTexture(16384, 16384)).toBeNull() // 超出限制
  })
})

6. 性能模式

6.1 缓冲区重用

// 不好 - 每帧创建新缓冲区
const buffer = gl.createBuffer()
gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW)
gl.deleteBuffer(buffer)

// 好 - 重用缓冲区,仅更新数据
gl.bufferSubData(gl.ARRAY_BUFFER, 0, data)  // 更新现有缓冲区

6.2 绘制调用批处理

// 不好 - 每个对象一个绘制调用
objects.forEach(obj => {
  gl.useProgram(obj.program)
  gl.drawElements(...)
})

// 好 - 按材质/着色器批处理
const batches = groupByMaterial(objects)
batches.forEach(batch => {
  gl.useProgram(batch.program)
  batch.objects.forEach(obj => gl.drawElements(...))
})

6.3 纹理压缩

// 不好 - 总是未压缩 RGBA
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)

// 好 - 可用时使用压缩格式
const ext = gl.getExtension('WEBGL_compressed_texture_s3tc')
if (ext) gl.compressedTexImage2D(gl.TEXTURE_2D, 0, ext.COMPRESSED_RGBA_S3TC_DXT5_EXT, ...)

6.4 实例化渲染

// 不好 - 粒子的单独绘制调用
particles.forEach(p => {
  gl.uniform3fv(uPosition, p.position)
  gl.drawArrays(gl.TRIANGLES, 0, 6)
})

// 好 - 单个实例化绘制调用
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, particles.length)

6.5 VAO 使用

// 不好 - 每帧重新绑定属性
gl.enableVertexAttribArray(0)
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0)

// 好 - 使用 VAO 存储属性状态
const vao = gl.createVertexArray()
gl.bindVertexArray(vao)
// 设置一次,然后仅绑定 VAO 进行渲染

7. 安全标准

7.1 已知漏洞

CVE 严重性 描述 缓解措施
CVE-2024-11691 Apple M 系列内存损坏 更新浏览器、操作系统补丁
CVE-2023-1531 Chrome use-after-free 更新 Chrome

7.2 OWASP Top 10 覆盖

OWASP 类别 风险 缓解措施
A06 易受攻击组件 保持浏览器更新
A10 SSRF 浏览器上下文隔离

7.3 GPU 资源保护

// ✅ 实现资源限制
const LIMITS = {
  maxDrawCalls: 100,
  maxTriangles: 1_000_000,
  maxTextures: 32,
  maxTextureSize: 4096
}

function checkLimits(stats: RenderStats): boolean {
  if (stats.drawCalls > LIMITS.maxDrawCalls) {
    console.error('绘制调用限制超出')
    return false
  }
  if (stats.triangles > LIMITS.maxTriangles) {
    console.error('三角形限制超出')
    return false
  }
  return true
}

8. 常见错误和反模式

8.1 关键安全反模式

永远不要: 跳过上下文丢失处理

// ❌ 危险 - 上下文丢失时应用崩溃
const gl = canvas.getContext('webgl2')
// 无上下文丢失处理程序!

// ✅ 安全 - 优雅处理
canvas.addEventListener('webglcontextlost', handleLoss)
canvas.addEventListener('webglcontextrestored', handleRestore)

永远不要: 无限资源分配

// ❌ 危险 - GPU 内存耗尽
for (let i = 0; i < userCount; i++) {
  textures.push(gl.createTexture())
}

// ✅ 安全 - 强制限制
if (textureCount < MAX_TEXTURES) {
  textures.push(gl.createTexture())
}

8.2 性能反模式

避免: 过多状态更改

// ❌ 不好 - 未批处理的绘制调用
objects.forEach(obj => {
  gl.useProgram(obj.program)
  gl.bindTexture(gl.TEXTURE_2D, obj.texture)
  gl.drawElements(...)
})

// ✅ 好 - 按材质批处理
batches.forEach(batch => {
  gl.useProgram(batch.program)
  gl.bindTexture(gl.TEXTURE_2D, batch.texture)
  batch.objects.forEach(obj => gl.drawElements(...))
})

9. 预实施检查清单

阶段 1: 编写代码前

  • [ ] 为着色器、上下文和资源编写失败测试
  • [ ] 定义性能预算(绘制调用 <100, 内存 <256MB)
  • [ ] 确定所需 WebGL 扩展

阶段 2: 实施期间

  • [ ] 上下文丢失处理与恢复
  • [ ] 资源限制和内存跟踪
  • [ ] 编译前着色器验证
  • [ ] 使用 VAO、批处理绘制、重用缓冲区
  • [ ] 粒子实例化渲染

阶段 3: 提交前

  • [ ] 测试通过: npm test -- --run tests/webgl/
  • [ ] 类型检查: npm run typecheck
  • [ ] 构建: npm run build
  • [ ] 性能验证(绘制、内存)
  • [ ] 无 WebGL 回退测试

10. 总结

WebGL 为 JARVIS HUD 提供 GPU 加速图形。关键原则:处理上下文丢失,强制资源限制,验证着色器,跟踪内存,批处理绘制调用,最小化状态更改。

记住: WebGL 绕过浏览器沙盒 - 始终防范资源耗尽。 参考文献: references/advanced-patterns.md, references/security-examples.md