react-three/drei/native远程URL加载纹理失败材质渲染为黑色
问题描述
在React Native应用中尝试使用useTexture加载图像纹理,并将其应用到meshStandardMaterial材质上。当从本地assets文件夹导入图片时,纹理渲染完全正常;但将图片源替换为远程URL时,纹理无法生效,材质渲染为纯黑色。
相关实现代码如下:
import React, { Suspense, useRef } from 'react'; import { View } from 'react-native'; import { Canvas } from '@react-three/fiber/native'; import { useTexture } from '@react-three/drei/native'; import useWindowDimensions from 'react-native/Libraries/Utilities/useWindowDimensions'; import localTexturePath from './assets/duck_texture.png' function Plane() { const ref = useRef(); // 远程URL加载失效 const textureUrl = 'https://cdntest.metatube.studio/public/Texture.png' // 本地资源加载正常 // const textureUrl = localTexturePath useTexture(textureUrl, (t) => { ref.current.map = t ref.current.needsUpdate = true }) return( <mesh> <planeBufferGeometry attach="geometry" args={[3, 3]} /> <meshStandardMaterial ref={ref} attach="material" color="orange" /> </mesh> ) } export default function App() { const { width, height } = useWindowDimensions(); return ( <View style={{ height, width, backgroundColor: '#FFA000', }} > <Canvas onCreated={(s) => { s.setSize(width, height); }} gl={{ physicallyCorrectLights: true }} camera={{ position: [-6, 0, 16], fov: 36 }} > <color attach="background" args={[0xe2f4df]} /> <ambientLight /> <directionalLight intensity={1.1} position={[0.5, 0, 0.866]} /> <directionalLight intensity={0.8} position={[-6, 2, 2]} /> <Suspense> <Plane /> </Suspense> </Canvas> </View> ); }
已知Three.js本身在Web环境支持通过远程URL加载纹理,需要确认React Native环境下drei的该表现是否为预期行为,以及移动端该如何将远程URL对应的纹理应用到材质上。
项目相关依赖及版本如下:
"@react-three/drei": "^9.11.3", "@react-three/fiber": "^8.0.21", "expo": "~45.0.0", "expo-gl": "~11.3.0", "react": "18.1.0", "react-native": "0.69.0-rc.3", "three": "0.141.0"
问题结论
这个表现是React Native环境下的预期行为,不属于bug。
React Native环境没有浏览器内置的Image元素和网络资源自动加载能力,@react-three/drei/native导出的useTexture默认仅适配了Metro打包的本地静态资源加载逻辑,没有内置远程图片的下载、解码适配,直接传入远程HTTP/HTTPS URL时,底层纹理加载器无法获取到可解析的图像二进制数据,最终会渲染出纯黑默认材质。
解决方案
核心逻辑:不要直接把远程URL传给useTexture,先将远程图片下载到应用本地缓存目录,拿到本地文件路径后再传入加载即可。
- Expo项目方案(最简便)
- 安装expo-asset依赖:
expo install expo-asset - 先通过expo-asset下载远程图片到本地缓存,拿到本地URI后再传给
useTexture,参考实现:
- 安装expo-asset依赖:
import React, { Suspense, useRef, useState, useEffect } from 'react'; import { View } from 'react-native'; import { Canvas } from '@react-three/fiber/native'; import { useTexture } from '@react-three/drei/native'; import useWindowDimensions from 'react-native/Libraries/Utilities/useWindowDimensions'; import { Asset } from 'expo-asset'; function Plane() { const ref = useRef(); const [localTexturePath, setLocalTexturePath] = useState(null); // 组件挂载时下载远程纹理到本地 useEffect(() => { async function fetchRemoteTexture() { const remoteAsset = Asset.fromURI('https://cdntest.metatube.studio/public/Texture.png'); await remoteAsset.downloadAsync(); setLocalTexturePath(remoteAsset.localUri); } fetchRemoteTexture(); }, []); // 本地路径存在后再加载纹理 const texture = useTexture(localTexturePath, (t) => { if (ref.current) { ref.current.map = t; ref.current.needsUpdate = true; } }); if (!localTexturePath) return null; return( <mesh> <planeBufferGeometry attach="geometry" args={[3, 3]} /> <meshStandardMaterial ref={ref} attach="material" color="orange" /> </mesh> ) } // 其余App组件代码保持不变即可
- 裸React Native项目方案
- 安装文件读写依赖:
npm install react-native-fs --save - 实现逻辑和Expo方案一致:先通过
react-native-fs的下载接口把远程图片存到应用缓存目录,拿到file://开头的本地文件路径后,再传给useTexture加载。
- 安装文件读写依赖:
注意事项
- RN环境不存在浏览器端的CORS限制,不需要服务端配置跨域头,但要保证远程图片的HTTPS证书有效,否则iOS/Android原生网络层会直接拦截请求
- 如果纹理加载后仍然显示异常,可手动配置纹理参数:
t.flipY = false、t.colorSpace = THREE.SRGBColorSpace,适配移动端纹理坐标和颜色空间规范 - 远程纹理加载过程记得搭配Suspense或者加载状态占位,避免纹理未加载完成时组件提前渲染导致报错
内容的提问来源于stack exchange,提问作者Ketan Bhokray
相关产品推荐
相关产品推荐

