Three.js中实现SKY标记纹理始终面向屏幕的方案问询
解决方案:屏幕空间UV映射实现静态SKY纹理
核心思路
让SKY表面的纹理采样直接绑定到屏幕坐标,而非3D空间坐标,这样无论相机怎么移动旋转,纹理始终和屏幕对齐,效果就像透过3D模型的“洞”看到网页背景。
具体实现步骤
1. 自定义ShaderMaterial实现屏幕UV采样
给所有标记为SKY的网格单独用自定义ShaderMaterial,通过顶点着色器传递屏幕空间位置,片元着色器直接用屏幕UV采样纹理:
顶点着色器代码
varying vec2 vScreenUV; void main() { vec4 clipSpacePosition = projectionMatrix * modelViewMatrix * vec4(position, 1.0); // 把NDC坐标转成0-1范围的屏幕UV vScreenUV = clipSpacePosition.xy / clipSpacePosition.w * 0.5 + 0.5; gl_Position = clipSpacePosition; }
片元着色器代码
uniform sampler2D skyTexture; uniform vec2 textureRepeat; // 控制纹理平铺次数,对应网页background-repeat varying vec2 vScreenUV; void main() { // 应用平铺,模拟网页背景的repeat效果 vec2 tiledUV = vScreenUV * textureRepeat; gl_FragColor = texture2D(skyTexture, tiledUV); }
2. 处理多纹理场景
如果有多个不同的SKY纹理,不用为每个纹理写单独的Shader,靠uniform数组和索引就能搞定:
修改片元着色器支持多纹理
uniform sampler2D skyTextures[5]; // 根据实际数量调整 uniform int textureIndex; // 当前网格用的纹理索引 uniform vec2 textureRepeat; varying vec2 vScreenUV; void main() { vec2 tiledUV = vScreenUV * textureRepeat; gl_FragColor = texture2D(skyTextures[textureIndex], tiledUV); }
JS中配置材质
// 加载所有SKY纹理 const skyTextures = [ new THREE.TextureLoader().load('sky1.png'), new THREE.TextureLoader().load('sky2.png'), // ...更多纹理 ]; // 给每个SKY网格设置材质 skyMesh.material = new THREE.ShaderMaterial({ uniforms: { skyTextures: { value: skyTextures }, textureIndex: { value: 0 }, // 对应第一个纹理 textureRepeat: { value: new THREE.Vector2(2, 2) } // 平铺2x2 }, vertexShader: 顶点着色器代码, fragmentShader: 片元着色器代码, transparent: true, // 要透明洞效果就开这个 depthWrite: false // 避免遮挡后面的物体 });
3. 优化细节
- 要模拟“洞”的效果,确保SKY纹理的透明区域正确,同时材质开启
transparent: true和depthWrite: false,防止遮挡场景里的其他物体。 - 要是想让纹理和网页背景完全对齐,把
textureRepeat设成和网页背景一致的重复系数,同时监听窗口resize事件,同步更新相机和渲染器的尺寸。
为什么之前的方案不行?
- RenderTargets + envMap:这类方案是基于3D空间的环境映射,纹理会跟着相机视角做空间变换,自然会出现滑动和缩放,不符合屏幕静态的需求。
- 多纹理实例问题:通过Shader的uniform数组和索引,不用为每个纹理创建单独的Shader实例,只要在每个网格的材质里指定对应的索引就行。
内容的提问来源于stack exchange,提问作者Edward
相关产品推荐
相关产品推荐

