尝试在 React Native 中使用 Three.js 加载 obj 和 mtl 文件



主要目标:将从Maya导出的动画模型加载到React Native应用程序中导出文件:obj,mtl和png文件

我已经在我的 React Native 项目中设置了 https://github.com/react-community/react-native-webgl,它工作正常。

现在,当我尝试使用 MTLLoader 加载 MTL 文件时,出现以下错误:

Can't find variable: document

显然,MTLLoader 正在调用 TextureLoader,它在内部调用一些具有"文档"引用的加载函数。那么解决这个问题的方法是什么呢?

这是我正在使用的两个文件:

三.js

const THREE = require("three");
global.THREE = THREE;
if (!window.addEventListener)
window.addEventListener = () => { };
// require("three/examples/js/renderers/Projector");
require("three/examples/js/loaders/MTLLoader");
require("three/examples/js/loaders/OBJLoader");
export default THREE;

三观.js

import React, { Component } from "react";
import { StyleSheet, View } from "react-native";
import { WebGLView } from "react-native-webgl";
import THREE from "./three";
import { image } from "src/res/image";
export default class ThreeView extends Component {
requestId: *;
componentWillUnmount() {
cancelAnimationFrame(this.requestId);
}
onContextCreate = (gl: WebGLRenderingContext) => {
const rngl = gl.getExtension("RN");
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
const renderer = new THREE.WebGLRenderer({
canvas: {
width,
height,
style: {},
addEventListener: () => { },
removeEventListener: () => { },
clientHeight: height
},
context: gl
});
renderer.setSize(width, height);
renderer.setClearColor(0xffffff, 1);
let camera, scene;
let cube;
function init() {
camera = new THREE.PerspectiveCamera(75, width / height, 1, 1100);
camera.position.y = 150;
camera.position.z = 500;
scene = new THREE.Scene();
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load('female-croupier-2013-03-26.mtl', function (materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.load('female-croupier-2013-03-26.obj', function (object) {
scene.add(object);
}, onLoading, onErrorLoading);
}, onLoading, onErrorLoading);
}
const onLoading = (xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
};
const onErrorLoading = (error) => {
console.log('An error happened', error);
};
const animate = () => {
this.requestId = requestAnimationFrame(animate);
renderer.render(scene, camera);
// cube.rotation.y += 0.05;
gl.flush();
rngl.endFrame();
};
init();
animate();
};
render() {
return (
<View style={styles.container}>
<WebGLView
style={styles.webglView}
onContextCreate={this.onContextCreate}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
},
webglView: {
width: 300,
height: 300
}
});

正如其他人所说,此错误是由 threejs 尝试使用来自浏览器的功能引起的,而 react-native 没有这些功能。

到目前为止,我已经能够通过猴子修补纹理加载器以在 react-native-webgl 中使用加载器来加载纹理(这是您收到错误的阶段)。将其添加到您的 init 函数中(最好在顶部附近)。

//make sure you have defined renderer and rngl
/*
const renderer = new THREE.WebGLRenderer(...)
const rngl = gl.getExtension("RN");
*/
const loadTexture = async function(url, onLoad, onProgress, onError) {
let textureObject = new THREE.Texture();
console.log("loading",url,'with fancy texture loader');
let properties = renderer.properties.get(textureObject);
var texture = await rngl.loadTexture({yflip: false, image: url});
/*
rngl.loadTexture({ image: url })
.then(({ texture }) => {
*/
console.log("Texture [" + url + "] Loaded!")
texture.needsUpdate = true;
properties.__webglTexture = texture;
properties.__webglInit = true;
console.log(texture);

if (onLoad !== undefined) {
//console.warn('loaded tex', texture);
onLoad(textureObject);
}


//});

return textureObject;

}

THREE.TextureLoader.prototype.load = loadTexture;

这解决了加载纹理的问题,我可以看到它们在 Charles 中加载,但它们仍然无法在模型上渲染,所以我被困在了这一点。从技术上讲,这是一个正确的答案,但一旦你实施它,你就会被卡住。我希望你能发表评论,告诉我你已经走得更远了。

我有一个类似的设置并遇到了同样的问题。我的选择是切换到 JSONLoader,它不需要文档以 react-native 呈现。所以,我只是在Blender中加载了一个三js插件,然后将其导出为json。只需查看向Blender添加三js插件的过程

https://www.youtube.com/watch?v=mqjwgTAGQRY

万事如意

这可能会让你更接近:

GLTF 格式支持嵌入纹理图像(作为 base64)。 如果你的资产管道允许,你可以转换为GLTF,然后加载到three/react-native。

我必须为"decodeUriComponent"和"atob"提供一些"窗口"polyfill,因为GLTFLoader使用FileLoader来解析base64:

我已经成功加载了嵌入的缓冲区,但您将需要更多的填充物来加载纹理。 TextureLoader使用ImageLoader,它使用document.createElementNS

您正在使用使用 TextureLoader的 MTLLoader,而 TextureLoader 使用 ImageLoader。 图像加载器使用 document.createElementNS() 函数。

我所做的解决这个问题是直接调用 THREEjs TextureLoader:

let texture = new THREE.Texture(
url //URL = a base 64 JPEG string in this case     
);

(有关纹理的使用,请查看纹理文档)

然后我使用了来自 React native 的 Image 类(而不是需要构造 DOM 的 THREEjs Image)将其作为属性提供给 Texture:

import { Image } from 'react-native';
var img = new Image(128, 128);
img.src = url;
texture.normal = img;

最后将纹理映射到目标材质上:

const mat = new THREE.MeshPhongMaterial();
mat.map = texture;

在 react 原生文档中,它将解释如何使用 react 原生 Image 元素,它支持 base64 编码的 JPEG。

也许有一种方法可以挑出它需要 TextureLoader 的部分,并用这个答案替换该部分。让我知道它是如何工作的。

旁注,我还没有尝试在我的 webGLView 中显示它,但在日志中它看起来像普通的 threejs 对象,值得一试

使用expo-three中的TextureLoader

import { TextureLoader } from "expo-three";
export function loadTexture(resource) {
if (textureCache[resource]) {
return textureCache[resource].clone();
}
const texture = new TextureLoader().load(resource);
texture.magFilter = NearestFilter;
texture.minFilter = NearestFilter;
textureCache[resource] = texture;
return texture;
}

资料来源:https://github.com/EvanBacon/Expo-Crossy-Road/blob/master/src/Node/Generic.js

最新更新