Zakalwe
Zakalwe

Reputation: 21

Black unreflective faces using MeshStandardMaterial in three.js

I'm a beginner to three.js and 3d graphics in general. This is primarily a learning exercise for me.

I'm trying to display a dodecahedron with metallic appearance, however about half of the faces are black and do not reflect. The other faces look correct.

I am aware there is a built in function to generate a dodecahedron, and also to calculate normals, but I want to do it manually to check my understanding.

At first I thought there could be some problem with the vertex normals, however using the VertexNormalsHelper, it appears that the normals are all pointing in the right directions.

Can anyone advise me as to where I'm going wrong? Many thanks!

Code:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>shiny dodecahedron</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
            body {
                background:#000;
                color:#fff;
                padding:0;
                margin:0;
                font-weight: bold;
                overflow:hidden;
            }
            a { color: #ffffff; }
            #info {
                position: absolute;
                top: 0px; width: 100%;
                color: #ffffff;
                padding: 5px;
                font-family:Monospace;
                font-size:13px;
                text-align:center;
            }
            #vt { display:none }
            #vt, #vt a { color:orange; }
        </style>
    </head>

    <body>

        <div id="info">
            <span id="description">Dodecahedron standard material demo.</span> 
        </div>

        <script src="js/three.js"></script>
        <script src="js/controls/OrbitControls.js"></script>

        <script src="js/Detector.js"></script>
        <script src="js/libs/stats.min.js"></script>
        <script src='js/libs/dat.gui.min.js'></script>

        <script>
            if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
            var stats;
            var camera, scene, renderer;
            var settings = {
                metalness: 1.0,
                roughness: 0.4,
                ambientIntensity: 0.2
            };
            var mesh, material;
            var directionalLight, ambientLight;
            var mouseX = 0;
            var mouseY = 0;
            var windowHalfX = window.innerWidth / 2;
            var windowHalfY = window.innerHeight / 2;
            var height = 500; // of camera frustum
            var r = 0.0;

            var GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;

            var scaleFactor = 1/(1+GOLDEN_RATIO);

            var explicitDodecahedronVertices = new Float32Array([
                (2*Math.cos(2*0*Math.PI/5)), (2*Math.sin(2*0*Math.PI/5)), GOLDEN_RATIO+1, // 0
                (2*Math.cos(2*1*Math.PI/5)), (2*Math.sin(2*1*Math.PI/5)), GOLDEN_RATIO+1, // 1
                (2*Math.cos(2*2*Math.PI/5)), (2*Math.sin(2*2*Math.PI/5)), GOLDEN_RATIO+1, // 2
                (2*Math.cos(2*3*Math.PI/5)), (2*Math.sin(2*3*Math.PI/5)), GOLDEN_RATIO+1, // 3
                (2*Math.cos(2*4*Math.PI/5)), (2*Math.sin(2*4*Math.PI/5)), GOLDEN_RATIO+1, // 4

                (2*GOLDEN_RATIO*Math.cos(2*0*Math.PI/5)), (2*GOLDEN_RATIO*Math.sin(2*0*Math.PI/5)), GOLDEN_RATIO-1, // 5
                (2*GOLDEN_RATIO*Math.cos(2*1*Math.PI/5)), (2*GOLDEN_RATIO*Math.sin(2*1*Math.PI/5)), GOLDEN_RATIO-1, // 6
                (2*GOLDEN_RATIO*Math.cos(2*2*Math.PI/5)), (2*GOLDEN_RATIO*Math.sin(2*2*Math.PI/5)), GOLDEN_RATIO-1, // 7
                (2*GOLDEN_RATIO*Math.cos(2*3*Math.PI/5)), (2*GOLDEN_RATIO*Math.sin(2*3*Math.PI/5)), GOLDEN_RATIO-1, // 8
                (2*GOLDEN_RATIO*Math.cos(2*4*Math.PI/5)), (2*GOLDEN_RATIO*Math.sin(2*4*Math.PI/5)), GOLDEN_RATIO-1, // 9

                (-2*GOLDEN_RATIO*Math.cos(2*0*Math.PI/5)), (-2*GOLDEN_RATIO*Math.sin(2*0*Math.PI/5)), -(GOLDEN_RATIO-1), // 10
                (-2*GOLDEN_RATIO*Math.cos(2*1*Math.PI/5)), (-2*GOLDEN_RATIO*Math.sin(2*1*Math.PI/5)), -(GOLDEN_RATIO-1), // 11
                (-2*GOLDEN_RATIO*Math.cos(2*2*Math.PI/5)), (-2*GOLDEN_RATIO*Math.sin(2*2*Math.PI/5)), -(GOLDEN_RATIO-1), // 12
                (-2*GOLDEN_RATIO*Math.cos(2*3*Math.PI/5)), (-2*GOLDEN_RATIO*Math.sin(2*3*Math.PI/5)), -(GOLDEN_RATIO-1), // 13
                (-2*GOLDEN_RATIO*Math.cos(2*4*Math.PI/5)), (-2*GOLDEN_RATIO*Math.sin(2*4*Math.PI/5)), -(GOLDEN_RATIO-1), // 14

                (-2*Math.cos(2*0*Math.PI/5)), (-2*Math.sin(2*0*Math.PI/5)), -(GOLDEN_RATIO+1), // 15
                (-2*Math.cos(2*1*Math.PI/5)), (-2*Math.sin(2*1*Math.PI/5)), -(GOLDEN_RATIO+1), // 16
                (-2*Math.cos(2*2*Math.PI/5)), (-2*Math.sin(2*2*Math.PI/5)), -(GOLDEN_RATIO+1), // 17
                (-2*Math.cos(2*3*Math.PI/5)), (-2*Math.sin(2*3*Math.PI/5)), -(GOLDEN_RATIO+1), // 18
                (-2*Math.cos(2*4*Math.PI/5)), (-2*Math.sin(2*4*Math.PI/5)), -(GOLDEN_RATIO+1), // 19
            ]);

            var dodecahedronFaceIndexes = new Uint8Array([
                // one of pair of opposite pentagons parallel to xy plane
                // 0, 1, 2, 3, 4
                1, 3, 0, 
                1, 2, 3,
                0, 3, 4,

                // 0, 4, 5, 9, 12
                4, 12, 0,
                12, 5, 0,
                12, 4, 9,

                // 3, 4, 8, 9, 11
                3, 11, 4,
                3, 8, 11,
                4, 11, 9,

                // 2, 3, 7, 8, 10
                2, 10, 3,
                2, 7, 10,
                3, 10, 8,

                // 1, 2, 6, 7, 14
                1, 14, 2,
                1, 6, 14,
                2, 14, 7,

                // 0, 1, 5, 6, 13
                0, 13, 1,
                0, 5, 13,
                1, 13, 6,


                // 15, 16, 17, 18, 19
                15, 18, 16,
                15, 19, 18,
                16, 18, 17,

                // 15, 19, 10, 14, 7
                15, 7, 19,
                15, 10, 7,
                19, 7, 14,

                // 18, 19, 13, 14, 6
                19, 6, 18,
                6, 13, 18,
                6, 19, 14, 

                // 17, 18, 12, 13, 5
                18, 5, 17,
                5, 12, 17,
                5, 18, 13, 

                // 16, 17, 11, 12, 9
                17, 9, 16,
                9, 11, 16,
                9, 17, 12, 

                // 15, 16, 10, 11, 8
                16, 8, 15,
                8, 10, 15,
                8, 16, 11
            ]);



            var vertices;
            var normals;

            function ComputeVerticesNormals(){
                localVertices = [];
                localNormals = [];

                for (var i = 0; i < dodecahedronFaceIndexes.length; i += 3)
                {
                    var i1 = dodecahedronFaceIndexes[i] * 3;
                    var i2 = dodecahedronFaceIndexes[i + 1] * 3;
                    var i3 = dodecahedronFaceIndexes[i + 2] * 3;

                    // get the vertices for this triangular pentagon segement
                    var ax = explicitDodecahedronVertices[i1];
                    var ay = explicitDodecahedronVertices[i1 + 1];
                    var az = explicitDodecahedronVertices[i1 + 2];

                    var bx = explicitDodecahedronVertices[i2];
                    var by = explicitDodecahedronVertices[i2 + 1];
                    var bz = explicitDodecahedronVertices[i2 + 2];

                    var cx = explicitDodecahedronVertices[i3];
                    var cy = explicitDodecahedronVertices[i3 + 1];
                    var cz = explicitDodecahedronVertices[i3 + 2];

                    // calc normals
                    var ux = bx - ax;
                    var uy = by - ay;
                    var uz = bz - az;

                    var vx = bx - cx;
                    var vy = by - cy;
                    var vz = bz - cz;

                    var nx = (uz * vy) - (uy * vz);
                    var ny = (ux * vz) - (uz * vx);
                    var nz = (uy * vx) - (ux * vy);

                    // append vertices, normals to list
                    localVertices.push(ax, ay, az, bx, by, bz, cx, cy, cz);
                    localNormals.push(nx, ny, nz, nx, ny, nz, nx, ny, nz)
                }

                vertices = Float32Array.from(localVertices);
                normals = Float32Array.from(localNormals);
            }



            init();
            animate();

            function init() {
                var container = document.createElement( 'div' );
                document.body.appendChild( container );
                renderer = new THREE.WebGLRenderer();
                renderer.setPixelRatio( window.devicePixelRatio );
                renderer.setSize( window.innerWidth, window.innerHeight );
                container.appendChild( renderer.domElement );
                renderer.gammaInput = true;
                renderer.gammaOutput = true;
                //
                scene = new THREE.Scene();
                var aspect = window.innerWidth / window.innerHeight;
                camera = new THREE.OrthographicCamera( - height * aspect, height * aspect, height, - height, 1, 10000 );
                camera.position.z = 400;
                scene.add( camera );
                controls = new THREE.OrbitControls( camera, renderer.domElement );
                controls.enableZoom = false;
                controls.enableDamping = true;

                // lights
                ambientLight = new THREE.AmbientLight( 0xffffff, settings.ambientIntensity );
                scene.add( ambientLight );

                directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
                directionalLight.position.set(-50*32,0,0);

                scene.add( directionalLight );

                var path = "https://threejs.org/examples/textures/cube/SwedishRoyalCastle/";
                var format = '.jpg';
                var urls = [
                        path + 'px' + format, path + 'nx' + format,
                        path + 'py' + format, path + 'ny' + format,
                        path + 'pz' + format, path + 'nz' + format
                    ];


                var loader = new THREE.CubeTextureLoader();
                loader.setCrossOrigin( 'anonymous' );
                var reflectionCube = loader.load( urls );
                reflectionCube.format = THREE.RGBFormat;

                scene.background = reflectionCube;


                material = new THREE.MeshStandardMaterial( {
                    color: 0x888888,
                    roughness: settings.roughness,
                    metalness: settings.metalness,
                    side: THREE.FrontSide,
                    envMap: reflectionCube
                } );

                var geometry = new THREE.BufferGeometry();
                ComputeVerticesNormals();

                geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
                geometry.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );

                geometry.center();
                mesh = new THREE.Mesh( geometry, material );
                mesh.scale.multiplyScalar( 100 );

                scene.add( mesh );

                var vnh = new THREE.VertexNormalsHelper( mesh, 100, 0x00ff00, 1 ); 

                scene.add (vnh);


                stats = new Stats();
                container.appendChild( stats.dom );
                //
                window.addEventListener( 'resize', onWindowResize, false );
            }
            function onWindowResize() {
                var aspect = window.innerWidth / window.innerHeight;
                camera.left = - height * aspect;
                camera.right = height * aspect;
                camera.top = height;
                camera.bottom = - height;
                camera.updateProjectionMatrix();
                renderer.setSize( window.innerWidth, window.innerHeight );
            }
            //
            function animate() {
                requestAnimationFrame( animate );
                controls.update();
                stats.begin();
                render();
                stats.end();
            }
            function render() {
                renderer.render( scene, camera );
            }
        </script>

    </body>

</html>

Upvotes: 1

Views: 1434

Answers (1)

Zakalwe
Zakalwe

Reputation: 21

Thanks to WestLangley's comment, I realised the error was in my understanding of THREE.OrbitControls. I mistakenly assumed that the camera was static, and the model was rotating, whereas in reality it was the reverse - the model was static, and the camera was moving around the model.

Upvotes: 1

Related Questions