I have a 3D model from Mixamo and used npx gltfjsx to make it into a component. Now I would like to render this model multiple times, but when I try to do so, I only get one model on Canvas.
Is there a way to do this? Here is my file:
const { actions } = useAnimations(animations, heroRef);
return (
<>
<group ref={heroRef} dispose={null}>
<group rotation={[Math.PI / 2, 0, 0]} scale={0.01}>
<primitive object={nodes.mixamorig1Hips} />
<skinnedMesh
geometry={nodes.Ch36.geometry}
material={materials.Ch36_Body}
skeleton={nodes.Ch36.skeleton}
/>
</group>
{/* <gridHelper args={[25, 25]}/> */}
</group>
</>
);
}
useGLTF.preload("/Mannequin.glb"); ```
Any help/advice is appreciated.
The model needs to be cloned, like this:
const scene = React.useMemo(() => {
return model.scene.clone();
}, [model]);
The above clones the scene, but you can do the same to nodes, materials, etc. inside the object returned from useGLTF
Here's an example:
https://codesandbox.io/s/heuristic-clarke-g58yp?file=/src/App.js
Related
I started react three fiber not long ago, and I try to move my camera to my mouse position when I click on my plane.
Here is my code:
import { OrbitControls, PerspectiveCamera, useGLTF } from '#react-three/drei'
import { Perf } from 'r3f-perf'
import { useRef, useEffect } from 'react'
import { useFrame, useThree } from '#react-three/fiber'
import * as THREE from 'three'
export default function Experience() {
const cameraRef = useRef()
const cubeRef = useRef()
const camera = useThree( state => state.camera )
// Move camera to the mouse
function cameraMovement( event ) {
const x = event.point.x
const y = event.point.y
const z = event.point.z
console.log( camera.position )
cameraRef.current.position.lerp( new THREE.Vector3( x, 2, z ), 1 )
console.log( camera.position )
}
return <>
<Perf position="top-left" />
<OrbitControls makeDefault />
<PerspectiveCamera ref={cameraRef} makeDefault position={[40, 40, 40]} />
<ambientLight intensity={0.5} />
<mesh receiveShadow onClick={cameraMovement}>
<boxGeometry args={[20, 0.5, 20]} />
<meshStandardMaterial color="greenyellow" />
</mesh>
<mesh receiveShadow position={[0, 3, 0]} ref={cubeRef}>
<boxGeometry args={[3, 3, 3]} />
<meshPhongMaterial color='red' />
</mesh>
</>
}
The camera moves, but the movement is quite strange, and also the position seems to be wrong.
But when I try to move an object for example it follows my click.
I can't understand why it doesn't work, if you have any ideas I'm interested !
Thanks!
I've used a gltf to jsx converter on Github (https://github.com/pmndrs/gltfjsx) to create JSX components of my model. However, I'm struggling to understand how to adjust my model.js so that the model automatically spins on its axis. Could anyone help me with this?
import React, { useRef } from 'react'
import { useGLTF } from '#react-three/drei'
export default function Model({ ...props }) {
const group = useRef()
const { nodes, materials } = useGLTF('/model.glb')
return (
<group ref={group} {...props} dispose={null}>
<group position={[-0.0, -0.3, -1]} rotation={[-Math.PI / 2, 0, 2.7]} scale={0.58}>
<mesh geometry={nodes.boot_0.geometry} material={nodes.boot_0.material} />
<mesh geometry={nodes.boot001_0.geometry} material={nodes.boot001_0.material} />
<mesh geometry={nodes.boot002_0.geometry} material={nodes.boot002_0.material} />
<mesh geometry={nodes.boot003_0.geometry}
</group>
</group>
<group position={[-0.02, -0.01, 0.06]} rotation={[-Math.PI / 2, 0, 0]} scale={0.58}/>
</group>
)
}
useGLTF.preload('/model.glb')
Simply import the useFrame hook from '#react-three/fiber', and set the x-axis adjustment accordingly. In your case, it would look like this:
import React, { useRef } from 'react'
import { useGLTF } from '#react-three/drei'
import { useFrame } from '#react-three/fiber' // Import the useFrame hook
export default function Model({ ...props }) {
const group = useRef()
const { nodes, materials } = useGLTF('/model.glb')
// We utilize the useFrame hook here to rotate on the x-axis.
useFrame(() => {
group.current.rotation.x += .02;
})
return (
<group ref={group} {...props} dispose={null}>
<group position={[-0.0, -0.3, -1]} rotation={[-Math.PI / 2, 0, 2.7]} scale={0.58}>
<mesh geometry={nodes.boot_0.geometry} material={nodes.boot_0.material} />
<mesh geometry={nodes.boot001_0.geometry} material={nodes.boot001_0.material} />
<mesh geometry={nodes.boot002_0.geometry} material={nodes.boot002_0.material} />
<mesh geometry={nodes.boot003_0.geometry}
</group>
</group>
<group position={[-0.02, -0.01, 0.06]} rotation={[-Math.PI / 2, 0, 0]} scale={0.58}/>
</group>
)
}
useGLTF.preload('/model.glb')
I'm currently new and learning about three.js. And i'm using react-three-fiber to make it happen with React, but i stumbled upon a problem. The model however, cannot receive any shadow from another model. I've tried to use obj.castShadow = true and obj.receiveShadow = true to one of the receiving shadow model object on the parent and the children as well but it shows no difference. Is there any way to cast a shadow to another model?
And the shadow.. it seems very rough. Is there any way to smoothen it?
Here's my sandbox:
https://codesandbox.io/s/modest-newton-np1sw
Code:
import React, { Suspense, useMemo, useState } from "react";
import { Canvas } from "react-three-fiber";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import { OrbitControls} from "drei";
import { Mesh } from "three";
import billboard from "../assets/models/billboard.obj";
import bridge from "../assets/models/bridge.obj";
const Model = ({ modelPath }) => {
const [obj, setObj] = useState();
useMemo(() => new OBJLoader().load(modelPath, setObj), [modelPath]);
if (obj) {
obj.castShadow = true;
obj.traverse((children) => {
if (children instanceof Mesh) {
children.castShadow = true;
}
});
}
return obj ? <primitive object={obj} /> : null;
};
const ShadowedModel = ({ modelPath }) => {
const [obj, setObj] = useState();
useMemo(() => new OBJLoader().load(modelPath, setObj), [modelPath]);
if (obj) {
obj.castShadow = true;
obj.receiveShadow = true;
obj.traverse((children) => {
if (children instanceof Mesh) {
children.castShadow = true;
children.receiveShadow = true;
}
});
}
return obj ? <primitive object={obj} /> : null;
};
const Lights = () => {
return (
<>
<ambientLight intensity={0.1} />
<spotLight
castShadow
position={[-50, 50, 20]}
intensity={0.5}
shadow-mapSize-shadowMapWidth={2048}
shadow-mapSize-shadowMapHeight={2048}
shadow-camera-left={-50}
shadow-camera-right={50}
shadow-camera-top={-50}
shadow-camera-bottom={50}
/>
<pointLight position={[10, -10, -20]} intensity={0.3} />
<pointLight position={[0, 10, 5]} intensity={0.3} />
<spotLight intensity={1} position={[0, 1000, 0]} />
</>
);
};
const Billboard = () => {
return (
<mesh
castShadow
position={[-15, 5, -35]}
scale={[0.05, 0.05, 0.05]}
rotation={[0, 20, 0]}
>
<Model modelPath={billboard} />
</mesh>
);
};
const Bridge = () => {
return (
<mesh
castShadow
receiveShadow
position={[10, -40, -80]}
// position={[-80, -40, -150]}
scale={[0.15, 0.15, 0.15]}
rotation={[0, 10.2, 0]}
>
<ShadowedModel modelPath={bridge} />
</mesh>
);
};
const Shadow = () => {
return (
<group>
<mesh
receiveShadow
rotation={[-Math.PI / 2, 0, 0]}
position={[-20, -32, -40]}
>
<planeBufferGeometry attach="geometry" args={[500, 500]} />
<meshLambertMaterial attach="material" color={"lightblue"} />
</mesh>
</group>
);
};
const MegatronModel = () => {
return (
<>
<Canvas
shadowMap
colorManagement
camera={{ position: [0, 0, 5], fov: 60 }}
>
<OrbitControls
enablePan={Boolean("Pan", true)}
enableZoom={Boolean("Zoom", true)}
enableRotate={Boolean("Rotate", true)}
/>
<Shadow />
<Suspense fallback={null}>
<Bridge />
</Suspense>
<Billboard />
<Lights />
</Canvas>
</>
);
};
export default MegatronModel;
Any help would be appreciated.
Thank you very much.
Your model is indeed receiving shadows. The problem is that you have several PointLights at full intensity that are washing everything out into white. Even shaded areas are blown out to white. See here, I've disabled all your PointLights and only kept the spotlight and ambient light so you can see the shadows again:
I recommend you use as few lights as possible to make rendering less resource-intensive. Adding so many lights could lower your framerate, overheat laptops, and drain more battery on cell-phones. For a pretty realistic lighting setup, I like using a single hemisphere light with a directional light like in this demo. Hemisphere makes it feel like there's atmospheric light coming from the top, with reflected ground light from the bottom.
As far as pixelated shadows go, you're using a large 2048x2048 map, but it's spread out across too large an area. I see you're trying to set left, right, top, bottom distances but those only apply to DirectionalLight, which uses an orthographic camera. Left, right, top, & bottom properties don't apply to SpotLight, which uses a perspective camera. If you want to make the Spotlight shadowmap narrower, use the .angle property.
I want to build an album app with react native, the main idea is to use a sectionList and show the data in an array like this:
class CustomImage extends Component{
render(){
return(
<View>
<Image style={styles.Img} source={this.props.imageName} />
<Text style={styles.text}>{this.props.name}</Text>
</View>
);
}
}
export default class DisplayAnImage extends Component {
render() {
return (
<View>
<SectionList
sections={[
{titile: 'small soldier', data: ["./gifs/2.gif", "./gifs/3.gif", "./gifs/4.gif"]}
]}
renderItem={({item}) => <CustomImage name={item} fromWeb={false} imageName={require(item)} />}
renderSectionHeader={({section}) => <Text style={styles.sectionHeader}>{section.title}</Text>}
/>
</View>
);
}
}
Above code was indented to make it work inspirited by this answer
I have already put the gifs folder under the project folder, and if I using a static string as source={require(url)} in the Image component, things will work, but when the url came out of an array items iteration, it will not work.
How can I make this work with react native?
EDIT
Don't know if I could edit to make it more specificly, the really thing I want to do is to use a generated array like this:
function numberRange(start, end){
return new Array(end-start).fill().map((d,i) => {
var url = "./gifs/" + ( i+start) + ".gif";
return require(url)
});
}
export default class DisplayAnImage extends Component {
render() {
return (
<View>
<SectionList
sections={[
{title: 'small soldier', data: numberRange(2,30)},
{title: 'small soldier', data: numberRange(31,60)}
]}
renderItem={({item}) => <CustomImage name={item} fromWeb={false} imageName={item} />}
renderSectionHeader={({section}) => <Text style={styles.sectionHeader}>{section.title}</Text>}
/>
</View>
);
}
}
Don't know if have a way to use this array generator to make an array or I have to enter the path one by one on bare hand :-(
require is a javascript keyword with a preload nature.
And, images will not related to path at runtime --- provide a path to it will not get anything. it becomes bundle resources, so with console.log you can only see resource token related to bundle ex: _1 _2.
It will not work even change require("./gifs/1.gif") to require("./gifs/"+"1.gif").
Try this:
data: [require("./gifs/2.gif"), require("./gifs/3.gif"), require("./gifs/4.gif")]
In React, it seems like an animation is always bound to a property in this.state. But what if I have multiple objects in a view that need to be animated? For example what if I have a ListView with multiple Images and I want to animate the opacity of those images as they load into the ListView?
render() {
//var _scrollView: ScrollView;
return (
<View style={styles.container}>
<ScrollView
style={styles.scrollView}>
<ListView
initialListSize={1}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
style={styles.postList}
/>
</ScrollView>
</View>
);
}
renderRow(post) {
//var postItemHeight = windowSize / 2
return (
<View style={styles.postItem}>
<Image
style={styles.postImage}
source={{uri: post.cover_image}}
onLoad={(e) => this.imageLoaded()}>
</Image>
</View>
);
}
imageLoaded() {
// now animate the image opacity
// for every image that is loaded into the listview
}
There is no reason the Animated value needs to live in component state - that's just how the examples show how to do it. If you wanted, you can keep an array of Animated values in the state, put them in your Flux store, or however you want to do it.
In your particular case, however, the easiest solution would be create a component that represents a single image row of your ListView. You can then use that component's individual state to manage its animation.
For example:
const FadeImage = React.createClass({
displayName: 'FadeImage',
propTypes: Image.propTypes,
getInitialState() {
return {
opacity: new Animated.Value(0)
};
},
setNativeProps(nativeProps) {
this._image.setNativeProps(nativeProps);
},
fadeIn() {
Animated.spring(this.state.opacity, {
toValue: 1,
friction: 10,
tension: 60
}).start();
},
render() {
return (
<Animated.View style={{opacity: this.state.opacity}}>
<Image {...this.props} onLoad={this.fadeIn} ref={component => this._image = component} />
</Animated.View>
);
}
});
It's a drop-in replacement for Image, so you can use it just as you would a regular Image component.