import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); // Alpha for transparent background
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000, 0); // Transparent background
document.body.appendChild(renderer.domElement);
// Camera controls
const controls = new OrbitControls(camera, renderer.domElement);
camera.position.set(0, 2, 15); // Adjusted for face-on view
controls.update();
// Custom shader material
const discMaterial = new THREE.ShaderMaterial({
uniforms: {
baseColor: { value: new THREE.Color(0x000015) }, // Nearly black navy blue
edgeIntensity: { value: 5.0 } // Increased for vibrant edges
},
vertexShader: `
varying vec3 vNormal;
varying vec3 vViewPosition;
varying vec2 vUv;
void main() {
vNormal = normalize(normalMatrix * normal);
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewPosition = -mvPosition.xyz;
vUv = uv;
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform vec3 baseColor;
uniform float edgeIntensity;
varying vec3 vNormal;
varying vec3 vViewPosition;
varying vec2 vUv;
// Function to create a rainbow gradient
vec3 getRainbowColor(float t) {
vec3 magenta = vec3(1.0, 0.0, 1.0);
vec3 purple = vec3(0.5, 0.0, 1.0);
vec3 blue = vec3(0.0, 0.0, 1.0);
vec3 yellow = vec3(1.0, 1.0, 0.0);
vec3 teal = vec3(0.0, 1.0, 1.0);
if (t < 0.25) return mix(magenta, purple, t / 0.25);
else if (t < 0.5) return mix(purple, blue, (t - 0.25) / 0.25);
else if (t < 0.75) return mix(blue, yellow, (t - 0.5) / 0.25);
else return mix(yellow, teal, (t - 0.75) / 0.25);
}
void main() {
vec3 normal = normalize(vNormal);
vec3 viewDir = normalize(vViewPosition);
// Edge detection
float edgeFactor = 1.0 - abs(dot(normal, viewDir));
float edge = smoothstep(0.95, 0.98, edgeFactor); // Wider edge range
// Edge gradient based on distance from center
float dist = length(vUv - 0.5);
float edgeGradient = smoothstep(0.45, 0.5, dist); // Wider rim for visibility
// Apply rainbow gradient to the edge
float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (2.0 * 3.14159) + 0.5;
vec3 rainbowColor = getRainbowColor(angle);
vec3 finalEdge = rainbowColor * edgeGradient * edge * edgeIntensity;
// Mix base color with edge, prioritizing edge color
vec3 finalColor = mix(baseColor, finalEdge, edgeGradient * 2.0);
gl_FragColor = vec4(finalColor, 1.0);
}
`,
side: THREE.DoubleSide
});
// Create three discs in a triangular arrangement
function createDiscs() {
const discGroup = new THREE.Group();
const geometry = new THREE.CylinderGeometry(2, 2, 0.4, 128); // Increased segments for smoothness
const positions = [
[-4, 0, 0], // Bottom left
[4, 0, 0], // Bottom right
[0, 4, 0] // Top center
];
positions.forEach(pos => {
const disc = new THREE.Mesh(geometry, discMaterial);
disc.position.set(...pos);
disc.rotation.set(0.05, 0.05, 0.05); // Reduced rotation
discGroup.add(disc);
});
return discGroup;
}
const discs = createDiscs();
scene.add(discs);
// Lighting
const ambientLight = new THREE.AmbientLight(0x505050, 0.8); // Stronger ambient light
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 2.0); // Stronger directional light
directionalLight.position.set(0, 10, 10);
scene.add(directionalLight);
const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5); // Enhanced edge light
directionalLight2.position.set(10, 10, 0);
scene.add(directionalLight2);
// Animation loop
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
Iβm working on creating flat disc objects with an ultra-smooth dark surface and iridescent edge effects similar to the reference image below (imagine 3 floating discs with navy centers and vibrant rainbow-colored edges).
Current implementation details:
- Using flat circular geometries with minimal thickness
- Dark navy/black central surface using MeshStandardMaterial
- Attempting to create a chromatic edge effect that displays magenta, purple, blue, yellow, and teal gradients
- PBR rendering pipeline
Technical challenges:
- Getting the edge shader to properly display the holographic color gradient without affecting the dark center
- Maintaining the metallic reflective quality on the edges while keeping the center matte
- Optimizing the rim effect for different viewing angles
Has anyone successfully implemented similar edge-highlighting effects? Would a custom shader be more efficient than trying to fake this with material combinations? Any code examples or techniques would be greatly appreciated.
Looking specifically for implementations that maintain good performance while keeping the visual fidelity of the iridescent edges when the camera position changes.
Example attached