纹理
纹理是存储在 GPU 上的图像或数据网格。通常作为资源加载并由材质使用,也可在代码中创建、填入数据、在自定义着色器中采样,或通过渲染目标向其渲染。
纹理类型
| 类型 | 创建选项 | 典型用途 |
|---|---|---|
| 二维纹理 | 默认 | 图像、数据贴图、渲染目标输出 |
| 立方体贴图 | cubemap: true | 环境贴图、反射、点光源阴影,包含六个方形面 |
| 二维纹理数组 | arrayLength: n | 相同尺寸的二维层堆栈,通过层索引采样,用于地形层、替身和精灵集 |
| 体纹理(三维) | volume: true 和 depth: n | 以三维坐标采样的纹素网格,用于体积雾、云、三维查找表和体素数据 |
创建纹理
const texture = new Texture(app.graphicsDevice, {
name: 'MyTexture',
width: 256,
height: 256,
format: PIXELFORMAT_RGBA8,
mipmaps: true,
minFilter: FILTER_LINEAR_MIPMAP_LINEAR,
magFilter: FILTER_LINEAR,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE
});
其他纹理类型使用相同构造函数,通过选项选择:
// a cubemap with six 128x128 faces
const cubemap = new Texture(app.graphicsDevice, { width: 128, height: 128, cubemap: true });
// a texture array with 16 layers of 256x256
const textureArray = new Texture(app.graphicsDevice, { width: 256, height: 256, arrayLength: 16 });
// a volume texture of 64x64x32 texels
const volume = new Texture(app.graphicsDevice, { width: 64, height: 64, depth: 32, volume: true });
像素格式
format 决定各纹素如何存储。Texture API 参考列出了所有格式及 WebGL2、WebGPU 支持细节。主要分类为:
- 8 位格式:默认
PIXELFORMAT_RGBA8,以及PIXELFORMAT_RG8、PIXELFORMAT_R8,用于颜色和一般数据。 - sRGB 格式:多数颜色图像等在 sRGB 中制作的纹理,应使用
PIXELFORMAT_SRGBA8等 sRGB 格式,或用srgb: true选择格式的 sRGB 变体。采样时转换到线性空间,保证光照符合物理规律。参见线性工作流和 sRGB 纹理处理。 - HDR 格式:半浮点
PIXELFORMAT_RGBA16F、浮点PIXELFORMAT_RGBA32F、小浮点PIXELFORMAT_RG11B10F和紧凑共享指数PIXELFORMAT_RGB9E5,用于 0–1 之外的值。参见 HDR 渲染。 - 整数格式:例如
PIXELFORMAT_R8U、PIXELFORMAT_RGBA32U,保存整数,不能滤波,需在着色器中按纹素坐标读取。 - 压缩格式:DXT、ETC、PVRTC、ASTC 等系列,大幅节省内存,通常由纹理资源生成。参见纹理压缩。
- 深度格式:
PIXELFORMAT_DEPTH、PIXELFORMAT_DEPTH16和PIXELFORMAT_DEPTHSTENCIL,作为目标的深度缓冲区。
并非所有格式都可用于渲染输出,参见渲染目标页面的选择格式。
上传数据
创建时可通过 levels 提供各 mip 层级的数据,从最高分辨率层开始:
- 二维纹理:各层为类型化数组,或图像、画布、ImageBitmap、视频等浏览器图像源。
- 立方体贴图:各层为六个面的数组,顺序为 +X、-X、+Y、-Y、+Z、-Z。
- 纹理数组:各 mip 层级为数组,每个数组条目对应一个纹理层。
- 体纹理:各 mip 层级为单个类型化数组,依次保存全部深度切片。
// a volume texture, filled with data for all its depth slices
const data = new Uint8Array(64 * 64 * 32 * 4);
// ... fill the data, slice after slice, row after row
const volume = new Texture(app.graphicsDevice, {
width: 64,
height: 64,
depth: 32,
volume: true,
format: PIXELFORMAT_RGBA8,
levels: [data]
});
之后也可更新:
const pixels = texture.lock();
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = 255; // red
pixels[i + 1] = 0; // green
pixels[i + 2] = 0; // blue
pixels[i + 3] = 255; // alpha
}
texture.unlock();
采样选项
以下选项控制滤波和环绕,之后也可通过同名属性更改:
minFilter和magFilter:缩小与放大时的滤波,例如三线性的FILTER_LINEAR_MIPMAP_LINEAR,或不滤波的FILTER_NEAREST。anisotropy:各向异性滤波级别,保持掠射角纹理清晰。addressU、addressV、addressW:各方向的环绕,例如ADDRESS_REPEAT或ADDRESS_CLAMP_TO_EDGE;W 用于体纹理。
Mipmap
Mipmap 是经过滤波的较小纹理副本,让远处采样平滑且高效。mipmaps 默认启用,层数由最大维度决定,体纹理也考虑深度。可用 numLevels 显式指定层数。
如果只提供最高分辨率数据,上传时会自动生成其余层级,适用于包括体纹理在内的所有类型。压缩和整数纹理不支持自动生成,需提供全部层级。
向纹理渲染后也会生成 mipmap,参见渲染目标的 Mipmap。
在着色器中采样
StandardMaterial 等材质采样二维纹理和立方体贴图。纹理数组和体纹理需通过自定义着色器,按匹配类型声明:
| 纹理类型 | GLSL | WGSL |
|---|---|---|
| 二维纹理 | sampler2D | texture_2d<f32> |
| 立方体贴图 | samplerCube | texture_cube<f32> |
| 二维纹理数组 | sampler2DArray | texture_2d_array<f32> |
| 体纹理 | sampler3D | texture_3d<f32> |
例如纹理数组和体纹理:
- GLSL
- WGSL
uniform mediump sampler2DArray uLayers;
uniform mediump sampler3D uVolume;
// in the fragment shader
vec4 layerColor = texture(uLayers, vec3(uv, layerIndex));
vec4 volumeColor = texture(uVolume, uvw);
var uLayers: texture_2d_array<f32>;
var uLayersSampler: sampler;
var uVolume: texture_3d<f32>;
var uVolumeSampler: sampler;
// in the fragment shader
let layerColor = textureSample(uLayers, uLayersSampler, uv, layerIndex);
let volumeColor = textureSample(uVolume, uVolumeSampler, uvw);
material.setParameter('uLayers', textureArray);
material.setParameter('uVolume', volume);
整数纹理使用整数采样类型,例如 GLSL 的 usampler2D、WGSL 的 texture_2d<u32>,通过 texelFetch / textureLoad 按纹素坐标读取。
WebGPU 可通过 Texture#getView 创建 TextureView,只绑定部分 mip 层级或数组层。WebGL 中视图绑定整张纹理。
回读和复制
Texture#read 从 GPU 下载矩形像素区域,返回解析为这些数据的 Promise。face 选择立方体面,layer 选择数组层,slice 选择体纹理深度切片。省略 slice 时,一次操作依次读取全部切片:
const pixels = await texture.read(0, 0, texture.width, texture.height, { mipLevel: 0 });
// a single depth slice of a volume texture, or all of them
const slicePixels = await volume.read(0, 0, volume.width, volume.height, { slice: 5 });
const volumePixels = await volume.read(0, 0, volume.width, volume.height);
Texture#copy 在 GPU 上将纹理区域复制到相同格式的另一纹理。支持相同的 face、layer、slice,省略 slice 时复制体纹理全部切片:
// copy all depth slices of a volume texture
destination.copy(volume);
// copy one layer of a texture array
destinationArray.copy(textureArray, { layer: 3 });
渲染到纹理
可以向纹理渲染,包括立方体面、数组层和体纹理深度切片。参见渲染目标。
纹理内存
纹理销毁前会持续占用 GPU 内存。大小取决于尺寸、格式和 mip 层级;立方体全部面、数组全部层和体纹理全部切片均计入。观察内存可使用:
代码创建的纹理不再使用时,应销毁:
texture.destroy();