Why is my three.js cube looking translucent when I change the metalness, roughness, and color even though opacity stays at 1.
The translucence only appears with a rectangle light, not a point light.
Watch the demo video and you’ll see the light appear on the outside as if the box was translucent.
<!DOCTYPE html>
<html>
<head>
<title>Simple Rectangle Light</title>
<style>
body { margin: 0; }
canvas { display: block; }
#controls {
position: fixed;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
padding: 10px;
border-radius: 5px;
color: white;
font-family: Arial;
}
</style>
</head>
<body>
<div id="controls">
<label><input type="checkbox" id="rectLight" checked> Rectangle Light</label><br>
<label><input type="checkbox" id="pointLight" checked> Point Light</label><br>
<div>
Rectangle Light Position:<br>
X: <input type="range" id="rectX" min="-1" max="1" step="0.1" value="0"><br>
Y: <input type="range" id="rectY" min="-1" max="0" step="0.1" value="-0.9"><br>
Z: <input type="range" id="rectZ" min="-1" max="1" step="0.1" value="0"><br>
Rectangle Light Rotation:<br>
X: <input type="range" id="rectRotX" min="0" max="6.28" step="0.1" value="1.57"><br>
Y: <input type="range" id="rectRotY" min="0" max="6.28" step="0.1" value="0"><br>
Z: <input type="range" id="rectRotZ" min="0" max="6.28" step="0.1" value="0"><br>
Rectangle Light Size:<br>
Width: <input type="range" id="rectWidth" min="0.1" max="1.6" step="0.1" value="1.0"><br>
Height: <input type="range" id="rectHeight" min="0.1" max="1.6" step="0.1" value="1.0">
</div>
<div>
Material Properties:<br>
Color: <input type="range" id="matColor" min="0" max="255" step="1" value="51"><br>
Roughness: <input type="range" id="matRough" min="0" max="1" step="0.1" value="0.9"><br>
Metalness: <input type="range" id="matMetal" min="0" max="1" step="0.1" value="0.0">
</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/examples/jsm/controls/OrbitControls.js": "https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js",
"three/examples/jsm/lights/RectAreaLightUniformsLib.js": "https://unpkg.com/[email protected]/examples/jsm/lights/RectAreaLightUniformsLib.js",
"three/examples/jsm/helpers/RectAreaLightHelper.js": "https://unpkg.com/[email protected]/examples/jsm/helpers/RectAreaLightHelper.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { RectAreaLightUniformsLib } from 'three/examples/jsm/lights/RectAreaLightUniformsLib.js';
import { RectAreaLightHelper } from 'three/examples/jsm/helpers/RectAreaLightHelper.js';
// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
// Camera setup
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// Renderer setup
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Initialize rect area light uniforms
RectAreaLightUniformsLib.init();
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
// Comment out texture loading
// const textureLoader = new THREE.TextureLoader();
// const skinTexture = textureLoader.load('skin-image.png');
// Add material definition before box creation
const material = new THREE.MeshStandardMaterial({
color: 0x333333,
roughness: 0.9,
metalness: 0.0,
opacity: 1.0,
transparent: false
});
// Create box
const outerSize = 2;
const thickness = 0.2; // Back to original thickness
const box = new THREE.Group();
// Bottom
const bottom = new THREE.Mesh(
new THREE.BoxGeometry(outerSize, thickness, outerSize),
material
);
bottom.position.y = -outerSize/2;
box.add(bottom);
// Front wall
const front = new THREE.Mesh(
new THREE.BoxGeometry(outerSize, outerSize, thickness),
material
);
front.position.z = outerSize/2 - thickness/2;
front.position.y = -thickness/2;
box.add(front);
// Back wall
const back = new THREE.Mesh(
new THREE.BoxGeometry(outerSize, outerSize, thickness),
material
);
back.position.z = -outerSize/2 + thickness/2;
back.position.y = -thickness/2;
box.add(back);
// Left wall
const left = new THREE.Mesh(
new THREE.BoxGeometry(thickness, outerSize, outerSize),
material
);
left.position.x = -outerSize/2 + thickness/2;
left.position.y = -thickness/2;
box.add(left);
// Right wall
const right = new THREE.Mesh(
new THREE.BoxGeometry(thickness, outerSize, outerSize),
material
);
right.position.x = outerSize/2 - thickness/2;
right.position.y = -thickness/2;
box.add(right);
scene.add(box);
// Rectangle light at bottom
const width = 1.0; // Smaller than inner width (was 1.8)
const height = 1.0; // Make it square like the box (was 1.8)
const intensity = 10;
const rectLight = new THREE.RectAreaLight(0xff0000, intensity, width, height);
rectLight.position.set(0, -0.9 + 0.01, 0); // Keep it just above bottom
rectLight.rotation.x = Math.PI / 2; // Face up
scene.add(rectLight);
// Add visible helper for the light
const helper = new RectAreaLightHelper(rectLight);
rectLight.add(helper);
// Move ambient light before PMREM setup
const ambientLight = new THREE.AmbientLight(0xffffff, 2.0);
scene.add(ambientLight);
// Add point light inside box
const pointLight = new THREE.PointLight(0xff0000, 10.0);
pointLight.position.set(0, -0.5, 0);
pointLight.distance = 3; // How far the light reaches
pointLight.decay = 1; // How quickly it fades with distance
scene.add(pointLight);
// Make the visualization sphere bigger
const sphereGeometry = new THREE.SphereGeometry(0.2, 16, 16); // Increase from 0.05 to 0.2
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const lightSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
lightSphere.position.copy(pointLight.position);
scene.add(lightSphere);
// Animation
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);
});
document.getElementById('rectLight').addEventListener('change', (e) => {
rectLight.visible = e.target.checked;
helper.visible = e.target.checked;
});
document.getElementById('pointLight').addEventListener('change', (e) => {
pointLight.visible = e.target.checked;
lightSphere.visible = e.target.checked;
});
document.getElementById('rectX').addEventListener('input', (e) => {
rectLight.position.x = parseFloat(e.target.value);
updateURL();
});
document.getElementById('rectY').addEventListener('input', (e) => {
rectLight.position.y = parseFloat(e.target.value) + 0.01; // Keep slight offset from bottom
});
document.getElementById('rectZ').addEventListener('input', (e) => {
rectLight.position.z = parseFloat(e.target.value);
});
document.getElementById('rectRotX').addEventListener('input', (e) => {
rectLight.rotation.x = parseFloat(e.target.value);
});
document.getElementById('rectRotY').addEventListener('input', (e) => {
rectLight.rotation.y = parseFloat(e.target.value);
});
document.getElementById('rectRotZ').addEventListener('input', (e) => {
rectLight.rotation.z = parseFloat(e.target.value);
});
document.getElementById('rectWidth').addEventListener('input', (e) => {
rectLight.width = parseFloat(e.target.value);
helper.update(); // Update the helper to show new size
});
document.getElementById('rectHeight').addEventListener('input', (e) => {
rectLight.height = parseFloat(e.target.value);
helper.update(); // Update the helper to show new size
});
document.getElementById('matColor').addEventListener('input', (e) => {
const value = parseInt(e.target.value);
material.color.setRGB(value/255, value/255, value/255);
updateURL();
});
document.getElementById('matRough').addEventListener('input', (e) => {
material.roughness = parseFloat(e.target.value);
});
document.getElementById('matMetal').addEventListener('input', (e) => {
material.metalness = parseFloat(e.target.value);
});
// Add function to update URL with current settings
function updateURL() {
const params = new URLSearchParams();
// Material settings
params.set('color', document.getElementById('matColor').value);
params.set('rough', document.getElementById('matRough').value);
params.set('metal', document.getElementById('matMetal').value);
// Light visibility
params.set('rectVisible', document.getElementById('rectLight').checked);
params.set('pointVisible', document.getElementById('pointLight').checked);
// Rectangle light settings
params.set('rectX', document.getElementById('rectX').value);
params.set('rectY', document.getElementById('rectY').value);
params.set('rectZ', document.getElementById('rectZ').value);
params.set('rectRotX', document.getElementById('rectRotX').value);
params.set('rectRotY', document.getElementById('rectRotY').value);
params.set('rectRotZ', document.getElementById('rectRotZ').value);
params.set('rectWidth', document.getElementById('rectWidth').value);
params.set('rectHeight', document.getElementById('rectHeight').value);
window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`);
}
// Also add function to load settings from URL on page load
function loadFromURL() {
const params = new URLSearchParams(window.location.search);
// Helper function to load a parameter
function loadParam(id, prop, obj, converter = parseFloat) {
if(params.has(id)) {
const value = converter(params.get(id));
document.getElementById(id).value = value;
if(obj && prop) obj[prop] = value;
}
}
// Load all parameters
loadParam('color', null, null, value => {
const v = parseInt(value);
material.color.setRGB(v/255, v/255, v/255);
return v;
});
loadParam('rough', 'roughness', material);
loadParam('metal', 'metalness', material);
loadParam('rectX', 'x', rectLight.position);
// ... etc for all parameters
}
// Call on page load
loadFromURL();
</script>
</body>
</html>