乐闻世界logo
搜索文章和话题

What is Cubemap in WebGL? What are its application scenarios?

3月7日 12:04

WebGL Cubemap Overview

A cubemap is a special type of texture composed of 6 independent 2D textures, corresponding to the 6 faces of a cube. It uses 3D direction vectors for sampling and is commonly used to implement skyboxes, environment reflections, and refractions.

Cubemap Structure

A cubemap consists of 6 faces:

shell
┌─────────┐ │ +Y │ (Top) ┌──────┼─────────┼──────┬─────────┐ │ -X │ +Z │ +X │ -Z │ │ Left │ Front │ Right│ Back │ └──────┴─────────┴──────┴─────────┘ │ -Y │ (Bottom) └─────────┘

Creating a Cubemap

Basic Creation Process

javascript
// Create cubemap const cubemap = gl.createTexture(); gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubemap); // 6 face image URLs const faceImages = [ 'px.jpg', // +X (Right) 'nx.jpg', // -X (Left) 'py.jpg', // +Y (Top) 'ny.jpg', // -Y (Bottom) 'pz.jpg', // +Z (Front) 'nz.jpg' // -Z (Back) ]; // Load 6 face images const targets = [ gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_X, gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, gl.TEXTURE_CUBE_MAP_POSITIVE_Z, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ]; let loadedCount = 0; faceImages.forEach((src, index) => { const image = new Image(); image.onload = () => { gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubemap); gl.texImage2D( targets[index], 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image ); loadedCount++; if (loadedCount === 6) { // All faces loaded, generate mipmap gl.generateMipmap(gl.TEXTURE_CUBE_MAP); } }; image.src = src; }); // Set texture parameters gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);

Programmatically Generate Cubemap

javascript
// Create solid color cubemap function createSolidColorCubemap(gl, color) { const cubemap = gl.createTexture(); gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubemap); const targets = [ gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_X, gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, gl.TEXTURE_CUBE_MAP_POSITIVE_Z, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ]; const size = 1; const data = new Uint8Array([ color[0] * 255, color[1] * 255, color[2] * 255, (color[3] || 1) * 255 ]); targets.forEach(target => { gl.texImage2D( target, 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); }); return cubemap; }

Using Cubemaps in Shaders

Vertex Shader

glsl
attribute vec3 a_position; uniform mat4 u_modelMatrix; uniform mat4 u_viewMatrix; uniform mat4 u_projectionMatrix; varying vec3 v_worldPos; void main() { vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0); v_worldPos = worldPos.xyz; gl_Position = u_projectionMatrix * u_viewMatrix * worldPos; }

Fragment Shader

glsl
precision mediump float; varying vec3 v_worldPos; uniform vec3 u_cameraPos; uniform samplerCube u_cubemap; void main() { // Calculate direction vector from camera to fragment vec3 direction = normalize(v_worldPos - u_cameraPos); // Sample cubemap using direction vector vec4 color = textureCube(u_cubemap, direction); gl_FragColor = color; }

Main Application Scenarios

1. Skybox

A skybox is used to render distant environmental backgrounds, giving the impression of infinite space.

glsl
// Skybox vertex shader attribute vec3 a_position; uniform mat4 u_viewMatrix; uniform mat4 u_projectionMatrix; varying vec3 v_texCoord; void main() { // Remove translation component, keep only rotation mat4 viewRotation = mat4(mat3(u_viewMatrix)); vec4 pos = u_projectionMatrix * viewRotation * vec4(a_position, 1.0); // Ensure skybox is at far clipping plane gl_Position = pos.xyww; // Use position as texture coordinate v_texCoord = a_position; } // Skybox fragment shader precision mediump float; varying vec3 v_texCoord; uniform samplerCube u_skybox; void main() { gl_FragColor = textureCube(u_skybox, v_texCoord); }
javascript
// Render skybox function drawSkybox(gl, skyboxProgram, cubemap) { // Disable depth writing gl.depthMask(false); gl.useProgram(skyboxProgram); // Bind cubemap gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubemap); gl.uniform1i(gl.getUniformLocation(skyboxProgram, 'u_skybox'), 0); // Draw cube drawCube(gl); // Restore depth writing gl.depthMask(true); }

2. Environment Reflection

Use cubemaps to simulate reflection effects on smooth surfaces.

glsl
// Reflection vertex shader attribute vec3 a_position; attribute vec3 a_normal; uniform mat4 u_modelMatrix; uniform mat4 u_viewMatrix; uniform mat4 u_projectionMatrix; varying vec3 v_worldPos; varying vec3 v_normal; void main() { vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0); v_worldPos = worldPos.xyz; v_normal = mat3(u_modelMatrix) * a_normal; gl_Position = u_projectionMatrix * u_viewMatrix * worldPos; } // Reflection fragment shader precision mediump float; varying vec3 v_worldPos; varying vec3 v_normal; uniform vec3 u_cameraPos; uniform samplerCube u_environmentMap; uniform float u_reflectivity; void main() { vec3 normal = normalize(v_normal); vec3 viewDir = normalize(u_cameraPos - v_worldPos); // Calculate reflection vector vec3 reflectDir = reflect(-viewDir, normal); // Sample environment map vec4 reflectionColor = textureCube(u_environmentMap, reflectDir); // Can combine base color and reflection vec3 baseColor = vec3(0.8, 0.8, 0.8); vec3 finalColor = mix(baseColor, reflectionColor.rgb, u_reflectivity); gl_FragColor = vec4(finalColor, 1.0); }

3. Environment Refraction

Simulate refraction effects of light passing through transparent objects.

glsl
// Refraction fragment shader precision mediump float; varying vec3 v_worldPos; varying vec3 v_normal; uniform vec3 u_cameraPos; uniform samplerCube u_environmentMap; uniform float u_refractiveIndex; // Refractive index, e.g., 1.52 for glass void main() { vec3 normal = normalize(v_normal); vec3 viewDir = normalize(u_cameraPos - v_worldPos); // Calculate refraction vector // refract(I, N, eta) where eta = incident medium RI / refractive medium RI vec3 refractDir = refract(-viewDir, normal, 1.0 / u_refractiveIndex); // Sample environment map vec4 refractionColor = textureCube(u_environmentMap, refractDir); gl_FragColor = refractionColor; }

4. Fresnel Reflection

Simulate the real-world effect where reflection intensity changes with viewing angle.

glsl
// Fresnel reflection fragment shader precision mediump float; varying vec3 v_worldPos; varying vec3 v_normal; uniform vec3 u_cameraPos; uniform samplerCube u_environmentMap; uniform float u_fresnelPower; void main() { vec3 normal = normalize(v_normal); vec3 viewDir = normalize(u_cameraPos - v_worldPos); // Calculate reflection vector vec3 reflectDir = reflect(-viewDir, normal); vec4 reflectionColor = textureCube(u_environmentMap, reflectDir); // Calculate Fresnel factor // Larger angle between view and normal, stronger reflection float fresnel = pow(1.0 - max(dot(viewDir, normal), 0.0), u_fresnelPower); // Base color vec3 baseColor = vec3(0.1, 0.2, 0.3); // Mix base color and reflection vec3 finalColor = mix(baseColor, reflectionColor.rgb, fresnel); gl_FragColor = vec4(finalColor, 1.0); }

5. Dynamic Environment Mapping

Generate cubemaps in real-time for reflections.

javascript
// Dynamically generate environment map function generateEnvironmentMap(gl, scene, centerPos, size = 256) { // Create cubemap const cubemap = gl.createTexture(); gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubemap); // Create framebuffer const framebuffer = gl.createFramebuffer(); // 6 face camera directions const directions = [ { target: gl.TEXTURE_CUBE_MAP_POSITIVE_X, eye: [1, 0, 0], up: [0, -1, 0] }, { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X, eye: [-1, 0, 0], up: [0, -1, 0] }, { target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y, eye: [0, 1, 0], up: [0, 0, 1] }, { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, eye: [0, -1, 0], up: [0, 0, -1] }, { target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z, eye: [0, 0, 1], up: [0, -1, 0] }, { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, eye: [0, 0, -1], up: [0, -1, 0] } ]; // Set projection matrix (90 degree FOV) const projectionMatrix = mat4.create(); mat4.perspective(projectionMatrix, Math.PI / 2, 1, 0.1, 1000); // Render 6 faces directions.forEach(dir => { // Set view matrix const viewMatrix = mat4.create(); mat4.lookAt(viewMatrix, centerPos, [centerPos[0] + dir.eye[0], centerPos[1] + dir.eye[1], centerPos[2] + dir.eye[2]], dir.up ); // Render scene to framebuffer renderSceneToCubemapFace(gl, scene, framebuffer, cubemap, dir.target, projectionMatrix, viewMatrix, size); }); return cubemap; }

Cubemap Sampling Principle

Cubemaps use 3D direction vectors (x, y, z) for sampling. Which face is selected depends on which component has the largest absolute value:

shell
If |x| is largest: x > 0: Use +X face, coordinates ( -z/x, -y/x ) x < 0: Use -X face, coordinates ( z/x, -y/x ) If |y| is largest: y > 0: Use +Y face, coordinates ( x/y, z/y ) y < 0: Use -Y face, coordinates ( x/y, -z/y ) If |z| is largest: z > 0: Use +Z face, coordinates ( x/z, -y/z ) z < 0: Use -Z face, coordinates ( -x/z, -y/z )

Performance Optimization Suggestions

  1. Prefilter Environment Map:

    • Precompute blurred cubemaps for different roughness levels
    • Use mipmap levels to store reflections for different roughness
  2. Cubemap Compression:

    • Use compressed texture formats to reduce memory usage
    • DXT, ETC, PVRTC formats
  3. Dynamic Environment Map Optimization:

    • Reduce resolution (e.g., 128x128)
    • Reduce update frequency (e.g., update every 10 frames)
    • Only update reflections for visible objects
  4. LOD System:

    • Use low-resolution cubemaps for distant objects
    • Use high-resolution cubemaps for nearby objects

Common Issues

Seam Issues

Seams may appear between cubemap faces.

Solution:

javascript
// Use CLAMP_TO_EDGE wrap mode gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);

Image Orientation Issues

Different face images may need to be flipped.

Solution:

  • Use image editing software to adjust beforehand
  • Or flip texture coordinates in shader
glsl
// Flip Y coordinate vec2 flippedCoord = vec2(texCoord.x, 1.0 - texCoord.y);
标签:WebGL