3D Float/Reflection Effect
My second 3D application where I try out a glassy texture as well as floating effects.


Initializing the project
First I started with creating a next14.2.16 application using:
npx create-next-app@14.2.16
npm i three @react-three/fiber
npm i @react-three/dreiReason for not using @latest is that currently the latest version of Next15 does not support R3F. So instead I'm using the latest stable version of Next.js which is 14.2.16.
Making a model
Inside Blender, I created a 3D model of the jinx smiley from the arcane series. To make this I took an image of the series and turned it into an svg in Adobe Illustrator.

Setting up a scene
First started off with creating a scene by importing the component I'm about to make into page.tsx. The ssr: false option on the dynamic import will let the component be redendered strictly on client-side. This will save serversources and optimize performance since pre-rendering the animation won't provide much value.
import styles from "./page.module.css";
import dynamic from "next/dynamic";
const Scene = dynamic(() => import("@/components/Models"), {
ssr: false,
});
export default function Home() {
return (
<main className={styles.main}>
<Scene />
</main>
);
}
Creating our models
I started off with creating a canvas in index.jsx where I will put in my Jinx and text models.
"use client";
import { Canvas } from "@react-three/fiber";
import Model from "./Model";
import TextModels from "./TextModels";
import { Environment } from "@react-three/drei";
export default function Index() {
return (
<Canvas style={{ background: "#000000" }}>
<Model />
<TextModels />
<directionalLight intensity={2} position={[0, 2, 3]} />
<Environment preset="city" />
</Canvas>
);
}
Text component
I created a simpel Text component to use for the background of the application. The 3D models should reflect this text later on.
"use client";
import React from "react";
import { Text } from "@react-three/drei";
import { useThree } from "@react-three/fiber";
export default function TextModel() {
const { viewport } = useThree();
return (
<group scale={viewport.width}>
{["Media Design", "Semester 3", "Expo"].map((text, index, array) => (
<Text
key={index}
font={"/fonts/PPNeueMontreal-Bold.otf"}
position={[0, (-index + (array.length - 1) / 2) * 0.3, -1]}
fontSize={0.1}
color="white"
anchorX="center"
anchorY="middle"
>
{text}
</Text>
))}
</group>
);
}Creating a model
I imported the .glb file into the component and gave the mesh a basic material.
import React, { useRef } from "react";
import { useGLTF, Text } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
export default function Model() {
const { nodes } = useGLTF("/medias/jinx.glb");
const { viewport } = useThree();
const jinx = useRef(null);
useFrame(() => {
if (jinx.current) {
jinx.current.rotation.z += 0.005;
}
});
return (
<group scale={viewport.width / 50}>
<mesh ref={jinx} {...nodes.Jinx} position={[0, 0, 0]}>
<meshBasicMaterial />
</mesh>
</group>
);
}Result

Transmission material
To create the glassy effect with a bit of distortion, I've used the MeshTransmissionMaterial in the React Three Drei package. This already supports transmission but adds multiple other properties like thickness, distortion and chromaticAberration on top of it. With this I can create a more glassy/prismatic look to the model.

"use client";
import React, { useRef } from "react";
import { MeshTransmissionMaterial, useGLTF, Text } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
import { useControls } from "leva";
export default function Model() {
const { nodes } = useGLTF("/medias/jinx.glb");
const { viewport } = useThree();
const jinx = useRef();
useFrame(() => {
if (jinx.current) {
jinx.current.rotation.z += 0.002;
}
});
const materialProps = useControls({
thickness: { value: 0.2, min: 0, max: 3, step: 0.05 },
roughness: { value: 0, min: 0, max: 1, step: 0.1 },
transmission: { value: 1, min: 0, max: 1, step: 0.1 },
ior: { value: 1.2, min: 0, max: 3, step: 0.1 },
chromaticAberration: { value: 0.02, min: 0, max: 1 },
backside: { value: true },
});
return (
<group scale={viewport.width / 50}>
<mesh ref={jinx} {...nodes.Jinx} position={[0, 0, 0]}>
<MeshTransmissionMaterial {...materialProps} />
</mesh>
</group>
);
}Result

Adding multiple models
I've been looking around for some inspiration in the meantime and found something on Behance which I thought would be nice in this project.

My initial thought was to make it like this, but then I did a small user test during making this. From these tests, there were some people who suggested making it more Fontys-style (the uni that I'm in right now). So I did, and it came out like the following. Adding a Float component for all the models and the consts:
models, I've created new models in Blender, like how I did in an earlier step
positions
materialProps
"use client";
import React, { useRef } from "react";
import { MeshTransmissionMaterial, useGLTF, Float } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
import { useControls } from "leva";
export default function Model() {
const ionicModel = useGLTF("/medias/ionic.glb");
console.log("Ionic model nodes:", ionicModel.nodes);
console.log("Ionic model scene:", ionicModel);
const models = [
{ nodes: useGLTF("/medias/v0.glb").nodes, key: "v0" },
{ nodes: useGLTF("/medias/tailwind.glb").nodes, key: "Curve" },
{ nodes: useGLTF("/medias/next.glb").nodes, key: "Curve" },
{ nodes: useGLTF("/medias/figma.glb").nodes, key: "Figma" },
{ nodes: useGLTF("/medias/illustrator.glb").nodes, key: "Illustrator" },
{ nodes: useGLTF("/medias/react.glb").nodes, key: "React" },
{ nodes: useGLTF("/medias/ionic.glb").nodes, key: "Iconic" },
{ nodes: useGLTF("/medias/bolt.glb").nodes, key: "Bolt" },
];
const { viewport } = useThree();
const Reference = useRef([]);
const positions = [
[-27, -12, 3],
[3, 15, -6],
[-21, 3, -3],
[24, 18, -6],
[-42, 27, -21],
[-9, -24, -12],
[24, -12, -6],
[24, 0, 3],
];
const rotationDirection = useRef(1);
const maxRotation = Math.PI / 4;
useFrame(() => {
Reference.current.forEach((ref) => {
if (ref) {
if (ref.rotation.z >= maxRotation) {
rotationDirection.current = -1;
} else if (ref.rotation.z <= -maxRotation) {
rotationDirection.current = 1;
}
ref.rotation.z += 0.0005 * rotationDirection.current;
}
});
});
const materialProps = useControls({
thickness: { value: 0.4, min: 0, max: 3, step: 0.05 },
roughness: { value: 0.1, min: 0, max: 1, step: 0.1 },
transmission: { value: 0.95, min: 0, max: 1, step: 0.1 },
ior: { value: 1.5, min: 0, max: 3, step: 0.1 },
chromaticAberration: { value: 0.04, min: 0, max: 1 },
backside: { value: true },
clearcoat: { value: 1 },
clearcoatRoughness: { value: 0.1 },
color: { value: "#FFC0CB" },
});
return (
<group scale={viewport.width / 80}>
{positions.map((position, index) => (
<Float
key={index}
speed={0.5}
rotationIntensity={1}
floatIntensity={1}
floatingRange={[0, 0.01]}
>
<mesh
key={index}
ref={(el) => (Reference.current[index] = el)}
{...models[index % models.length].nodes[
models[index % models.length].key
]}
position={position}
>
<MeshTransmissionMaterial {...materialProps} />
</mesh>
</Float>
))}
</group>
);
}Adjusting the Text component
To match the NABA- and Fontys-style a bit more, I went for the Box component of react-three/drei and added the Float in here as well to match the other models.
"use client";
import React from "react";
import { Text, Box, Float } from "@react-three/drei";
import { useThree } from "@react-three/fiber";
export default function TextModel() {
const { viewport } = useThree();
const texts = [
{ text: "Media Design", style: "black-bg" },
{ text: "Semester 3", style: "border-bg" },
{ text: "Expo", style: "black-bg" },
];
return (
<group scale={viewport.width}>
{texts.map((item, index, array) => {
const isBlackBg = item.style === "black-bg";
return (
<group position={[0, (-index + (array.length - 1) / 2) * 0.25, -1]}>
<Float
key={index}
speed={1}
rotationIntensity={2}
floatIntensity={1}
floatingRange={[0, 0.01]}
>
<Box args={[0.8, 0.2, 0.1]} position={[0, 0, 0]}>
<meshStandardMaterial
color={isBlackBg ? "#653365" : "#e5017e"}
roughness={0.5}
metalness={0.1}
/>
</Box>
<Text
font={"/fonts/PPNeueMontreal-Bold.otf"}
fontSize={0.1}
color="white"
anchorX="center"
anchorY="middle"
position={[0, 0, 0.09]}
>
{item.text}
</Text>
</Float>
</group>
);
})}
</group>
);
}Final Result
So here's the final result of this blog. I've been looking around, asking people for their thoughts about this. Everyone I asked thought it looked cool, and I myself like it too. What do you think? Did you like the tutorial? Comment something below and share your thoughts about this! 🙂
