1

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> 

8
  • Nothing is translucent here (the correct term is transparent, not translucent). Moreover, there is no such thing as transparency. Transparency is the instruction to the rendering system on how the effective pixel colors should be rendered. In case of not 100% opacity property of some element prescribed in CSS, the color should combine the element color with the color of elements supposed to be underneath. And what you see is not that case. This is a perceptional illusion. Such illusions are so well-known... and this one cannot confuse me... Commented Jan 17 at 18:53
  • I'm confused, are you saying the red on the outside is expected of an opaque material? Like this is what would happen in the physical world too? Commented Jan 17 at 20:06
  • Opaque is what you say. How without opaqueness property something can look transparent? No, the coloring model is not at all like in the physical world. We can only simulate something that looks alike to the human eye. Opaque element means that pixel colors are not affected by the colors underneath. In your example, you have something similar to opaqueness, but is totally unrelated. This is lighting. The computational model combines the properties of the surface with the properties of the modeled light beams and the properties of the camera/lens. Commented Jan 17 at 20:33
  • 1
    this is not an illusion, it's a rendering bug. that reddish glow shouldn't show up on any outer faces. don't just believe someone's assertion. people assert all kinds of things. doesn't mean they're right, or the assertion is true. and don't get pulled into a debate on the semantics of words. your video makes the situation abundantly clear. Commented Jan 18 at 0:59
  • 2
    Take a look at this thread. Commented Jan 18 at 16:40

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.