FDNavigate back to the homepage

Immersive Portals in Three and R3F - Unleashing the Power of Stencil Masks from Drei

Rick
May 21st, 2023 · 2 min read

The stencil masks provide a really powerful way of hiding parts of a scene behind mask. In this particular use case you have to fly through a circle to get to the other scene.

You can make the scenes as big or as small as you like and so long as you define certain properties on the materials then everything will work seamlessly.

The first part is to do with the actual circle geometry which will represent our portal.

1<Mask id={1} position={[0, 0, 2.2]}>
2 <circleGeometry args={[4.0, 128]} />
3 <meshBasicMaterial
4 colorWrite={true}
5 depthWrite={false}
6 stencilWrite={true}
7 stencilRef={1}
8 stencilFunc={THREE.AlwaysStencilFunc}
9 stencilFail={THREE.ReplaceStencilOp}
10 stencilZFail={THREE.ReplaceStencilOp}
11 stencilZPass={THREE.ReplaceStencilOp}
12 side={THREE.FrontSide}
13 color={new THREE.Color("red")}
14 />
15</Mask>

We use the mask component from @react-three/drei and wrap the geometry and the material in it. Its important you give it an id so we can determine what is masked and what is not, between worlds. The position is such that it is just infront of the atom.

Be careful or the cameras near option and it doesnt look weird cutting things off or glitching, so set near to something really small. Not too precise as this can cause issues of its own.

The matertial for the circle has colorwrite to true, I did this in particular as if I dont the background of the scene you are looking at through the portal isnt right, so if we are on the left and the left has a black bhackground, instead of the portal showing the red background it would be black as we set color write to false.

These properties:

1stencilRef={1}
2stencilFunc={THREE.AlwaysStencilFunc}
3stencilFail={THREE.ReplaceStencilOp}
4stencilZFail={THREE.ReplaceStencilOp}
5stencilZPass={THREE.ReplaceStencilOp}

I played around with until I found something which worked.

Then i set it to frontside only so the portal trip would be one way! and the color to be red as the side we are on is black and the side we are looking at through the portal is red, this way we can have the seamless transition of background colors.

You have to ensure the colors are the same of the circle material and the background of the opposite side for seamless transitions when we go through the circle.

The atom is what we see and is the main mesh of the scene.

1<Bounds fit clip observe>
2 <Float floatIntensity={4} rotationIntensity={0} speed={4}>
3 <Atom scale={1.5} />
4 </Float>
5</Bounds>

(FYI I forked this repo from another one which has these meshes on the codesandbox)

And inside the Atom component we define a function which runs every time the camera changes position or rotation or in general changes one of its properties.

This function controls how we invert the masks and scenes, such that it feels like we are entering two worlds.

Quite simple but very effective!

1const hasCameraPassedThroughCircle = () => {
2 const cameraPosition = camera.position.clone();
3 const circleCenter = new THREE.Vector3(0, 0, 2.2);
4 const cameraToCenter = circleCenter.clone().sub(cameraPosition);
5 const distanceToCenter = cameraToCenter.length();
6 const circleRadius = 4.0;
7 if (distanceToCenter < circleRadius) {
8 // Camera passes through the circle geometry
9 return false;
10 console.log("Camera passed through the portal.");
11 } else {
12 // Camera does not pass through the circle geometry
13 return true;
14 console.log("Camera does not pass through the portal.");
15 }
16};
17
18const handleCameraMove = (event) => {
19 // Create a vector representing the circle's normal
20 const circleNormal = new THREE.Vector3(0, 0, 1);
21
22 // Create a vector representing the target point
23 const targetPoint = camera.position;
24
25 // Create a vector representing the circle's position
26 const circlePosition = new THREE.Vector3(0, 0, 2.2);
27
28 // Create a vector representing the direction from the circle's position to the target point
29 const directionToTarget = targetPoint.clone().sub(circlePosition);
30
31 // Calculate the dot product of the direction vector and the circle normal
32 const dotProduct = directionToTarget.dot(circleNormal);
33
34 if (!hasCameraPassedThroughCircle()) {
35 if (dotProduct >= 0.0) {
36 console.log("Target point is in front of the circle.");
37 ref.current.stencilWrite = true;
38 ref.current.stencilRef = THREE.ReferenceStencilValue;
39 ref.current.stencilFunc = THREE.NotEqualStencilFunc;
40 ref.current.stencilFail = THREE.ReplaceStencilOp;
41 ref.current.stencilZFail = THREE.ReplaceStencilOp;
42 ref.current.stencilZPass = THREE.ReplaceStencilOp;
43 scene.background = new THREE.Color("black");
44 } else {
45 console.log("Target point is behind the circle.");
46 ref.current.stencilWrite = true;
47 ref.current.stencilRef = 2;
48 ref.current.stencilFunc = THREE.NotEqualStencilFunc;
49 ref.current.stencilFail = THREE.ReplaceStencilOp;
50 ref.current.stencilZFail = THREE.ReplaceStencilOp;
51 ref.current.stencilZPass = THREE.ReplaceStencilOp;
52 scene.background = new THREE.Color("red");
53 }
54 }
55};

The hasCameraPassedThroughCircle function checks if the camera has passed through the circle otherwise we dont change anything including the mask. If we have passed through the circle geometry we then check if we are infront or behind the circle mesh.

If we are behind we invert the settings and have a red background and if we are infront we keep the main background black and this works seamlessly as we color the circle red which is the same as the inverted background.

I havent properly inverted the effect so once you go through the other side will look red and as if the portal doesnt exist.

You can image what kind of cool effects you can make with this workflow! and using something like drei’s helpers makes it so much easier.

Not perfect but gives you the general idea :)

More articles from theFrontDev

Mastering Skybox Realism - Loading and Applying HDRI with Three and R3F

Embark on a journey towards breathtaking visual fidelity as we delve into the realm of HDRI-driven skybox shaders in Three.js. In this comprehensive article, we guide you through the process of loading and seamlessly applying High Dynamic Range Images (HDRI) to create stunningly realistic skybox environments. Discover the art of harnessing the true potential of HDRI to evoke immersive atmospheres and dynamic lighting conditions. Learn the intricacies of integrating HDRI seamlessly into Three.js skybox shaders, unlocking a world of photorealistic rendering and captivating visual experiences. Join me as we explore the techniques for achieving unmatched visual realism with HDRI in Three.js skybox shaders.

May 21st, 2023 · 1 min read

Sublime Soft Particle Fog Effect

Enhance your visual compositions with the Sublime Soft Particle Fog Effect. This powerful and versatile rendering technique adds an exquisite touch of realism and depth to your scenes. Immerse your viewers in a world where atmospheric conditions are brought to life with a gentle and realistic fog that gracefully envelops the environment. The Sublime Soft Particle Fog Effect creates a sense of scale, depth, and ambiance, making every detail feel tangible and captivating. With its smooth transitions, dynamic lighting interaction, and subtle movements, this effect elevates your visuals to a new level of sophistication. Whether you're creating architectural visualizations, cinematic masterpieces, or immersive gaming environments, the Sublime Soft Particle Fog Effect is an essential tool for crafting visually stunning and emotionally impactful experiences.

May 21st, 2023 · 1 min read
© 2021–2024 theFrontDev
Link to $https://twitter.com/TheFrontDevLink to $https://github.com/Richard-ThompsonLink to $https://www.linkedin.com/in/richard-thompson-248ba3111/