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
相关产品推荐
相关产品推荐

