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.
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 have 3 .GLB files which I want to change based on the state (Sort of a gallery and one main image), but it loads weirdly when changing and it doesn't render some 3d models. Using three.js, ChakraUI, and nextJS. As I've only begun to test out 3d model I don't know if this is the correct way to re-render or conditionally show the model.
Series.js
const itemData = [
{
image: "/images/ssr-img1.jpg",
model: "zombie.glb",
},
{
image: "/images/ssr-img2.jpg",
model: "star-icon.glb",
},
{
image: "/images/ssr-img3.jpg",
model: "dog.glb",
},
{
image: "/images/ssr-img4.jpg",
model: "zombie.glb",
},
{
image: "/images/ssr-img5.jpg",
model: "star-icon.glb",
},
{
image: "/images/ssr-img6.jpg",
model: "dog.glb",
},
];
export default function Series() {
const [items, setItems] = useState(itemData);
const [model, setModel] = useState("star-icon.glb");
const [currentImage, setCurrentImage] = useState(itemData[0].image);
return (
<Box id="nfc" bg="#edf3f8" py="2rem">
<Suspense fallback={null}>
<Test model={model} />
</Suspense>
<Title color="black">Rarity List</Title>
<Stack>
<HStack align="center" justify="center" my="1rem" maxW="full">
{items.map((item, index) => (
<Image
maxW="5%"
key={index}
onClick={() => setModel(item.model)}
cursor="pointer"
src={item.image}
alt="test"
/>
))}
</HStack>
</Stack>
</Box>
);
}
Test.js (The 3d model)
import { Canvas, useFrame } from "#react-three/fiber";
import { useGLTF, Environment, OrbitControls } from "#react-three/drei";
import { Box } from "#chakra-ui/react";
function Dog(props) {
const { scene } = useGLTF(props.model);
return <primitive object={scene} {...props} />;
}
function Zoom() {
useFrame((state, delta) => {
state.camera.zoom += delta / 100;
state.camera.updateProjectionMatrix();
});
}
export default function Test({ model }) {
return (
<Box h="xl">
<Canvas camera={{ position: [2.5, 2.5, -2.5], fov: 35 }}>
<ambientLight intensity={0.5} />
<Dog
model={model}
position={[-0.1, -0.2, 0]}
rotation={[0, Math.PI / 2, 0]}
scale={0.2}
/>
<Environment preset="city" />
<OrbitControls
minPolarAngle={Math.PI / 2.5}
maxPolarAngle={Math.PI / 2.5}
/>
<Zoom />
</Canvas>
</Box>
);
}
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 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
Here is a cube of planes that I can click
The main issue with this so far is that clicking on a plane clicks through when I would only want to click the plane that my mouse is over. What am I missing on my Three.js Plane?
I have tried searching for something relating to collision in three.js but to no avail so far.
After further research, I think it has something to do with RayCasting?
import React, { useState, useRef } from "react";
import { OrbitControls, Plane } from "#react-three/drei";
import { Canvas, useFrame, useThree, extend } from "#react-three/fiber";
import styles from "../styles/game.module.css";
import { DoubleSide } from "three";
const Cell = (props) => {
const [hovered, hover] = useState(false);
const [checked, setChecked] = useState(false);
const colorStyle = () => {
if (hovered) return "hotpink";
if (checked) return "lightblue";
return "orange";
};
return (
<Plane
scale={1}
onClick={() => setChecked(!checked)}
onPointerEnter={() => hover(true)}
onPointerLeave={() => hover(false)}
position={props.position}
rotation={props.rotation}
>
<meshPhongMaterial side={DoubleSide} color={colorStyle()} />
</Plane>
);
};
const Cube = () => {
useFrame((state, delta) => {
});
return (
<>
{/* Back Face */}
<Cell position={[-1, 1, -1.5]} rotation={[0, 0, 0]} />
// other cells here
{/* Front Face */}
<Cell position={[-1, 1, 1.5]} rotation={[0, 0, 0]} />
// other cells here
{/* Left Face */}
<Cell position={[-1.5, 1, 1]} rotation={[0, Math.PI / 2, 0]} />
// other cells here
{/* Right Face */}
<Cell position={[1.5, 1, 1]} rotation={[0, Math.PI / 2, 0]} />
// other cells here
{/* Bottom Face */}
<Cell position={[1, -1.5, 1]} rotation={[Math.PI / 2, 0, 0]} />
// other cells here
{/* Top */}
<Cell position={[1, 1.5, 1]} rotation={[Math.PI / 2, 0, 0]} />
// other cells here
</>
);
};
const SceneItems = () => {
return (
<>
<OrbitControls minDistance={7.5} maxDistance={15} />
<ambientLight intensity={0.5} />
<spotLight position={[10, 15, 10]} angle={0.3} />
<Cube position={[1, 1, 1]} />
</>
);
};
const CompleteScene = () => {
return (
<div id={styles.scene}>
<Canvas>
<SceneItems />
</Canvas>
</div>
);
};
It appears that all I needed to prevent this click-through from happening was to add event.stopPropagation() to my event listeners on my Plane. Now I no longer click through the Plane
const Cell = (props) => {
const [hovered, hover] = useState(false);
const [checked, setChecked] = useState(false);
useCursor(hovered);
const colorStyle = () => {
if (hovered) return "hotpink";
if (checked) return "lightblue";
return "orange";
};
return (
<Plane
scale={1}
onClick={(e) => {
e.stopPropagation();
setChecked(!checked);
}}
onPointerEnter={(e) => {
e.stopPropagation();
hover(true);
}}
onPointerLeave={(e) => {
e.stopPropagation();
hover(false);
}}
position={props.position}
rotation={props.rotation}
>
<meshPhongMaterial side={DoubleSide} color={colorStyle()} />
</Plane>
);
};
More details here: https://docs.pmnd.rs/react-three-fiber/api/events#pointer-capture