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

React Native+Expo+Three.js模型纹理未包裹且渲染黑屏问题排查

Expo + Three.js React Native 3D模型黑屏&纹理失效问题解决

问题描述

使用Expo和Three.js在React Native应用中渲染3D模型时,模型已加载但完全黑屏,纹理无法正确包裹。已尝试以下操作但未解决:

  • 提升环境光与平行光强度
  • 更换材质类型(MeshBasicMaterial、MeshStandardMaterial)
  • 检查纹理加载状态
  • 验证.obj和.mtl文件格式

相关代码:

import React, { useState, useRef } from "react";
import { View } from "react-native";
import { GLView } from "expo-gl";
import { Renderer } from "expo-three";
import * as THREE from "three";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader";
import { Asset } from "expo-asset";

const ThreeJSModelViewer: React.FC = () => {
  const [modelLoaded, setModelLoaded] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const objectRef = useRef<THREE.Object3D | null>(null);

  const handleContextCreate = async (gl: any) => {
    const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;

    const renderer = new Renderer({ gl });
    renderer.setSize(width, height);
    renderer.setClearColor("#ffffff", 0);

    const scene = new THREE.Scene();

    const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
    camera.position.z = 7;

    const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
    scene.add(ambientLight);

    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
    directionalLight.position.set(1, 1, 1).normalize();
    scene.add(directionalLight);

    try {
      const objAsset = Asset.fromModule(require("../../assets/temp-obj/ibraheem.obj"));
      const mtlAsset = Asset.fromModule(require("../../assets/temp-obj/ibraheem.mtl"));
      const textureAsset = Asset.fromModule(require("../../assets/temp-obj/ibraheem.png"));

      await Promise.all([objAsset.downloadAsync(), mtlAsset.downloadAsync(), textureAsset.downloadAsync()]);

      const mtlLoader = new MTLLoader();
      const materials = await mtlLoader.loadAsync(mtlAsset.uri);
      materials.preload();

      const objLoader = new OBJLoader();
      objLoader.setMaterials(materials);

      const object = await objLoader.loadAsync(objAsset.uri);

      const textureLoader = new THREE.TextureLoader();
      const texture: THREE.Texture = await new Promise((resolve, reject) => {
        textureLoader.load(
          textureAsset.uri,
          (loadedTexture) => resolve(loadedTexture),
          undefined,
          (error) => reject(error)
        );
      });

      object.traverse((child) => {
        if (child instanceof THREE.Mesh) {
          child.material = new THREE.MeshPhongMaterial({
            map: texture,
            specular: new THREE.Color(0x111111),
            shininess: 200,
          });
        }
      });

      // Resize and position the object
      const resizeObject = (obj: THREE.Object3D) => {
        const box = new THREE.Box3().setFromObject(obj);
        const size = box.getSize(new THREE.Vector3());
        const maxDim = Math.max(size.x, size.y, size.z);
        const scale = 5 / maxDim;
        obj.scale.multiplyScalar(scale);

        box.setFromObject(obj);
        box.getCenter(obj.position);
        obj.position.multiplyScalar(-1);
      };

      resizeObject(object);
      scene.add(object);
      setModelLoaded(true);

      objectRef.current = object;

      const animate = () => {
        requestAnimationFrame(animate);
        if (objectRef.current) {
          objectRef.current.rotation.y += 0.01;
        }
        renderer.render(scene, camera);
        gl.endFrameEXP();
      };
      animate();
    } catch (error) {
      console.error("Error loading 3D model:", error);
      setError("Failed to load 3D model.");
    }
  };

  return (
    <View style={{ flex: 1 }}>
      <GLView style={{ flex: 1 }} onContextCreate={handleContextCreate} />
      {/* ... (other UI components) */}
    </View>
  );
};

export default ThreeJSModelViewer;

.mtl文件内容:

newmtl FaceTexture
map_Kd ibraheem.png

解决方案

以下是几个关键修复点,逐一尝试:

1. 修复纹理加载设置

iOS/Android平台下,Three.js纹理默认的flipY属性可能导致纹理显示异常,同时需要确保纹理的UV映射模式正确:

const texture: THREE.Texture = await new Promise((resolve, reject) => {
  textureLoader.load(
    textureAsset.uri,
    (loadedTexture) => {
      // 修复纹理翻转问题
      loadedTexture.flipY = false;
      // 设置纹理包裹模式(适配UV坐标)
      loadedTexture.wrapS = THREE.RepeatWrapping;
      loadedTexture.wrapT = THREE.RepeatWrapping;
      resolve(loadedTexture);
    },
    undefined,
    (error) => reject(error)
  );
});

2. 禁用背面剔除或设置双面渲染

如果模型的面法线方向错误,默认的背面剔除会导致模型不可见,添加双面渲染:

object.traverse((child) => {
  if (child instanceof THREE.Mesh) {
    child.material = new THREE.MeshPhongMaterial({
      map: texture,
      specular: new THREE.Color(0x111111),
      shininess: 200,
      // 开启双面渲染
      side: THREE.DoubleSide
    });
  }
});

3. 确保相机正确看向模型中心

调整模型位置后,需要让相机聚焦到模型中心:

resizeObject(object);
scene.add(object);
// 让相机看向模型中心
camera.lookAt(object.position);
setModelLoaded(true);

4. 修正渲染器清屏设置

当前设置renderer.setClearColor("#ffffff", 0)会让背景完全透明,若模型仍黑屏,可临时将alpha值改为1,确认是否是模型本身不可见:

renderer.setClearColor("#ffffff", 1);

5. 处理MTLLoader的纹理路径问题

MTLLoader默认会从.mtl文件所在路径加载纹理,但Expo Asset的URI是独立的,可手动为材质指定纹理路径:

const mtlLoader = new MTLLoader();
// 设置纹理加载器的资源路径
mtlLoader.setTexturePath(textureAsset.uri.replace('ibraheem.png', ''));
const materials = await mtlLoader.loadAsync(mtlAsset.uri);
materials.preload();

6. 添加像素比适配

为Expo Renderer添加像素比适配,避免渲染模糊或异常:

import { Platform } from 'react-native';

// ...

const renderer = new Renderer({ gl });
renderer.setSize(width, height);
renderer.setPixelRatio(Platform.OS === 'ios' ? 2 : 1);
renderer.setClearColor("#ffffff", 1);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 10:54:49