You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Three.js:向ShaderMaterial传递纹理数组的实现方法

解决Three.js中向自定义着色器传递纹理列表并动态访问的问题

嘿,这个需求我之前在做批量图集渲染的时候刚好踩过坑,咱们一步步拆解来解决:

1. 在Three.js中传递纹理数组到着色器

首先,Three.js支持通过uniform数组传递多个纹理。你需要先在着色器里声明这个数组,再在JS层面把纹理列表赋值给对应的uniform:

着色器中的uniform声明

如果用WebGL2(推荐,支持动态数组索引),可以直接写:

#define MAX_TEXTURE_COUNT 16 // 预先定义最大纹理数量,按需调整
uniform sampler2D textures[MAX_TEXTURE_COUNT];

如果要兼容WebGL1,数组长度必须是编译期常量,得固定一个足够覆盖你需求的数值(比如16、32)。

JS层面赋值纹理数组

创建ShaderMaterial时,在uniforms里定义数组并传入你的纹理实例:

const material = new THREE.ShaderMaterial({
  uniforms: {
    textures: { value: [texture1, texture2, texture3] }, // 你的纹理列表
    // 后面还要加图集UV参数,咱们后续说
  },
  vertexShader: /* 顶点着色器代码 */,
  fragmentShader: /* 片元着色器代码 */,
  // 启用WebGL2的话要加这行
  glslVersion: THREE.GLSL3,
});

2. 从顶点着色器传递纹理索引n到片元着色器

你需要给每个顶点(或每个面)绑定对应的纹理索引,通过attribute传递,再用varying变量传到片元着色器:

顶点着色器代码

attribute float textureIndex; // 从JS传入的每个顶点的纹理索引
varying float vTextureIndex; // 传递到片元着色器的索引
varying vec2 vUv; // 传递基础UV,用于图集采样

void main() {
  vTextureIndex = textureIndex; // 直接传递,注意同面顶点要共用同一个索引
  vUv = uv;
  // 常规顶点变换逻辑
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

JS层面设置attribute数据

用BufferGeometry给每个顶点添加textureIndex属性:

const geometry = new THREE.PlaneGeometry(1, 1); // 示例用平面展示图片
// 假设每个平面4个顶点对应同一个纹理索引(比如索引0)
const textureIndices = new Float32Array([0, 0, 0, 0]); 
geometry.setAttribute('textureIndex', new THREE.BufferAttribute(textureIndices, 1));

3. 片元着色器中动态访问第n个纹理(结合图集)

这里分WebGL2和WebGL1两种场景,同时处理图集的UV偏移与缩放:

情况1:WebGL2(代码更简洁)

WebGL2支持动态数组索引,直接把varying转成整数即可访问,再结合图集UV参数:
首先在uniform里添加图集UV数组:

uniform vec4 atlasUVData[MAX_TEXTURE_COUNT]; // 每个元素格式:[offsetX, offsetY, scaleX, scaleY]
varying vec2 vUv;
varying float vTextureIndex;

void main() {
  int index = int(vTextureIndex + 0.5); // 加0.5避免插值带来的小数误差
  vec4 atlasData = atlasUVData[index];
  // 计算图集实际UV:基础UV缩放后加上偏移
  vec2 atlasUv = vUv * atlasData.zw + atlasData.xy;
  // 采样对应纹理
  vec4 color = texture(textures[index], atlasUv);
  gl_FragColor = color;
}

JS层面给atlasUVData赋值:

// 假设每个缩略图在图集里的位置和大小已预先计算好
material.uniforms.atlasUVData.value = [
  new THREE.Vector4(0, 0, 0.25, 0.25), // 第一个纹理的图集UV参数
  new THREE.Vector4(0.25, 0, 0.25, 0.25), // 第二个纹理的图集UV参数
  // ... 其他纹理的参数
];

情况2:WebGL1(兼容旧设备)

WebGL1不支持动态数组索引,得用条件判断或switch来选择纹理:

uniform sampler2D textures[4]; // 固定最大4个纹理,按需调整
uniform vec4 atlasUVData[4];
varying vec2 vUv;
varying float vTextureIndex;

vec4 getAtlasColor(float index) {
  vec2 atlasUv;
  vec4 color;
  if (index == 0.0) {
    atlasUv = vUv * atlasUVData[0].zw + atlasUVData[0].xy;
    color = texture2D(textures[0], atlasUv);
  } else if (index == 1.0) {
    atlasUv = vUv * atlasUVData[1].zw + atlasUVData[1].xy;
    color = texture2D(textures[1], atlasUv);
  } else if (index == 2.0) {
    atlasUv = vUv * atlasUVData[2].zw + atlasUVData[2].xy;
    color = texture2D(textures[2], atlasUv);
  } else {
    atlasUv = vUv * atlasUVData[3].zw + atlasUVData[3].xy;
    color = texture2D(textures[3], atlasUv);
  }
  return color;
}

void main() {
  gl_FragColor = getAtlasColor(vTextureIndex);
}

关键注意事项

  • 纹理数量限制:WebGL1中uniform纹理数组的长度一般最多32个(不同设备有差异),WebGL2限制宽松很多。
  • 索引插值问题:varying变量会被光栅化插值,所以同一面的所有顶点必须用相同的textureIndex,避免片元拿到非整数索引。
  • 图集UV校验:确保offset是缩略图在图集里的左上角坐标(0~1范围),scale是缩略图占图集的比例(比如4x4图集,每个缩略图scale为0.25)。

内容的提问来源于stack exchange,提问作者duhaime

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:37:03