0

I am trying to put different images on the top and bottom side both using texture in Threejs. But I am getting the same image at the bottom as well as the top. below is the code that I am using. My requirement is to display different images on both sides using texture in ThreeJs.

Note: 1. In my code, I have used the ThreeJs version(r125) which is necessary I Cannot use an older version.

<html lang="en">

    <head>
        <title>three.js webgl - geometry - shapes</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <link type="text/css" rel="stylesheet" href="main.css">
        <style>
            body {
                background-color: #f0f0f0;
                color: #444;
            }
        </style>
    </head>

    <body>

        <div id="info">texture on shapes</div>

        <script type="module">
            import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r125/build/three.module.js';
            import { OrbitControls } from "https://threejsfundamentals.org/threejs/resources/threejs/r125/examples/jsm/controls/OrbitControls.js";

            let container;
            let camera, scene, renderer;
            let group;
            let windowHalfX = window.innerWidth / 2;

            init();
            animate();

            function init() {

                container = document.createElement('div');
                document.body.appendChild(container);

                scene = new THREE.Scene();
                scene.background = new THREE.Color(0xbbbbbb);

                camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
                camera.position.set(0, -150, -300);
                scene.add(camera);

                const light = new THREE.PointLight(0xffffff, 0.8);
                camera.add(light);

                group = new THREE.Group();
                group.rotation.y = Math.PI;
                scene.add(group);

                const helper = new THREE.GridHelper(500, 10);
                helper.rotation.x = Math.PI / 2;
                group.add(helper);

                const rectWidth = 120;
                const rectHeight = 200;

                const rectangleShape = new THREE.Shape()
                    .moveTo(0, 0)
                    .lineTo(0, rectHeight)
                    .lineTo(rectWidth, rectHeight)
                    .lineTo(rectWidth, 0)
                    .lineTo(0, 0);

                const extrudeSettings = { depth: 10, bevelEnabled: true, bevelSegments: 2, steps: 1, bevelSize: 1, bevelThickness: 1 };

                const geometry = new THREE.ExtrudeGeometry(rectangleShape, extrudeSettings);

                var textureLoader1 = new THREE.TextureLoader();
                var topTexture = textureLoader1.load("https://threejsfundamentals.org/threejs/resources/images/tree-01.png"); // Top side Image
                topTexture.repeat.set(1 / rectWidth, 1 / rectHeight);

                var textureLoader2 = new THREE.TextureLoader();
                var bottomTexture = textureLoader2.load("https://threejsfundamentals.org/threejs/resources/images/tree-02.png"); // Bottom side Image
                bottomTexture.repeat.set(1 / rectWidth, 1 / rectHeight);

                var frontMaterial = new THREE.MeshPhongMaterial({ map: topTexture, side: THREE.FrontSide });
                var backMaterial = new THREE.MeshPhongMaterial({ map: bottomTexture, side: THREE.BackSide });
                var sideMaterial = new THREE.MeshPhongMaterial({ color: 0xD9D934 });

                var materials = [frontMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial, backMaterial];
                var material = new THREE.MeshFaceMaterial(materials);

                let mesh = new THREE.Mesh(geometry, material);
                mesh.position.set(-60, -100, 0);
                // Edited but faces not found
                for (var face in mesh.geometry.faces) {
                    if (mesh.geometry.faces[face].normal.z < -0.9) {
                        mesh.geometry.faces[face].materialIndex = 5;
                    }
                }
                group.add(mesh);
            
                renderer = new THREE.WebGLRenderer({ antialias: true });
                renderer.setPixelRatio(window.devicePixelRatio);
                renderer.setSize(window.innerWidth, window.innerHeight);
                container.appendChild(renderer.domElement);

                const controls = new OrbitControls(camera, renderer.domElement);
                controls.target.set(0, 0, 0);
                controls.update();

                window.addEventListener('resize', onWindowResize);

            }

            function onWindowResize() {

                windowHalfX = window.innerWidth / 2;

                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();

                renderer.setSize(window.innerWidth, window.innerHeight);

            }

            function animate() {

                requestAnimationFrame(animate);

                render();

            }

            function render() {
                renderer.render(scene, camera);
            }

        </script>

    </body>
</html>
Java-Dev
  • 438
  • 4
  • 20

1 Answers1

0

Materials on ExtrudeGeometry don't work the way you're expecting them to. Quoting the documentation for ExtrudeGeometry

When creating a Mesh with this geometry, if you'd like to have a separate material used for its face and its extruded sides, you can use an array of materials. The first material will be applied to the face; the second material will be applied to the sides.

You can add a third material if you want and then set it on one of the faces by manually changing that face's material index. But there's no way to do this automagically via the constructor as far as I know.

Edit: Correcting myself, I believe that you can no longer do this (change the material index of a single face) because of changes to the way ExtrudeGeometry is implemented. Instead I believe you now have to use a single texture and create a UV map.

Here's your code updated to use UV map which uses the top half of one image as the texture for the top face of the ExtrudeGeometry and the bottom half for bottom face. This is done by defining a custom UV generator and adding it to the config passed to ExtrudeGeometry. The UV generator supplies two methods: generateTopUV() and generateSideWallUV(). The latter is the same as the default one supplied by THREE. The other is the one that does the UV mapping for the top and bottom of the geometry.

Note that this is a very non-portable kludge that's hard-coded for the geometry of this particular problem.

<html lang="en">

<head>
    <title>three.js webgl - geometry - shapes</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <link type="text/css" rel="stylesheet" href="main.css">
    <style>
        body {
            background-color: #f0f0f0;
            color: #444;
        }
    </style>
</head>

<body>

    <div id="info">texture on shapes</div>

    <script type="module">
        import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r125/build/three.module.js';
        import { OrbitControls } from "https://threejsfundamentals.org/threejs/resources/threejs/r125/examples/jsm/controls/OrbitControls.js";

        let container;
        let camera, scene, renderer;
        let group;
        let windowHalfX = window.innerWidth / 2;

        init();
        animate();

        function init() {

            container = document.createElement('div');
            document.body.appendChild(container);

            scene = new THREE.Scene();
            scene.background = new THREE.Color(0xbbbbbb);

            camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
            camera.position.set(0, -150, -300);
            scene.add(camera);
            const light = new THREE.PointLight(0xffffff, 0.8);
            camera.add(light);

            group = new THREE.Group();
            group.rotation.y = Math.PI;
            scene.add(group);

            const helper = new THREE.GridHelper(500, 10);
            helper.rotation.x = Math.PI / 2;
            group.add(helper);

            const rectWidth = 120;
            const rectHeight = 200;

            const rectangleShape = new THREE.Shape()
                .moveTo(0, 0)
                .lineTo(0, rectHeight)
                .lineTo(rectWidth, rectHeight)
                .lineTo(rectWidth, 0)
                .lineTo(0, 0);

            const uvGenerator = {
                    generateTopUV:  function(geometry, vertices, indexA, indexB, indexC) {
                            const ax = vertices[indexA * 3];
                            const ay = vertices[indexA * 3 + 1];
                            const az = vertices[indexA * 3 + 2];
                            const bx = vertices[indexB * 3];
                            const by = vertices[indexB * 3 + 1];
                            const bz = vertices[indexB * 3 + 2];
                            const cx = vertices[indexC * 3];
                            const cy = vertices[indexC * 3 + 1];
                            const cz = vertices[indexC * 3 + 2];
                            if(indexA > 3) {
                                    return([
                                            new THREE.Vector2(ax, ay / 2),
                                            new THREE.Vector2(bx, by / 2),
                                            new THREE.Vector2(cx, cy / 2),
                                    ]);
                            } else {
                                    return([
                                            new THREE.Vector2(ax, (ay / 2) + rectHeight / 2),
                                            new THREE.Vector2(bx, (by / 2) + rectHeight / 2),

                                            new THREE.Vector2(cx, (cy / 2) + rectHeight / 2),
                                    ]);
                            }
                    },
                    generateSideWallUV: function(geometry, vertices, indexA, indexB, indexC, indexD) {
                            const ax = vertices[indexA * 3];
                            const ay = vertices[indexA * 3 + 1];
                            const az = vertices[indexA * 3 + 2];
                            const bx = vertices[indexB * 3];
                            const by = vertices[indexB * 3 + 1];
                            const bz = vertices[indexB * 3 + 2];
                            const cx = vertices[indexC * 3];
                            const cy = vertices[indexC * 3 + 1];
                            const cz = vertices[indexC * 3 + 2];
                            const dx = vertices[indexD * 3];
                            const dy = vertices[indexD * 3 + 1];
                            const dz = vertices[indexD * 3 + 2];
                            if(Math.abs(ay - by) < 0.01) {
                                    return([
                                            new THREE.Vector2(ax, 1 - az),
                                            new THREE.Vector2(bx, 1 - bz),
                                            new THREE.Vector2(cx, 1 - cz),
                                            new THREE.Vector2(dx, 1 - dz),
                                    ]);
                            } else {
                                    return([
                                            new THREE.Vector2(ay, 1 - az),
                                            new THREE.Vector2(by, 1 - bz),
                                            new THREE.Vector2(cy, 1 - cz),
                                            new THREE.Vector2(dy, 1 - dz),
                                    ]);
                            }
                    },
            };

            const extrudeSettings = { depth: 10, bevelEnabled: true, bevelSegments: 2, steps: 1, bevelSize: 1, bevelThickness: 1, UVGenerator: uvGenerator };

            const geometry = new THREE.ExtrudeGeometry(rectangleShape, extrudeSettings);

            var textureLoader1 = new THREE.TextureLoader();
            var topTexture = textureLoader1.load("https://threejsfundamentals.org/threejs/resources/images/tree-01.png"); // Top side Image
            topTexture.repeat.set(1 / rectWidth, 1 / rectHeight);

            var frontMaterial = new THREE.MeshPhongMaterial({ map: topTexture, side: THREE.FrontSide });
            var sideMaterial = new THREE.MeshPhongMaterial({ color: 0xD9D934 });

            var materials = [frontMaterial, sideMaterial];
            var material = new THREE.MeshFaceMaterial(materials);

            let mesh = new THREE.Mesh(geometry, material);
            mesh.position.set(-60, -100, 0);
            group.add(mesh);
        
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(window.innerWidth, window.innerHeight);
            container.appendChild(renderer.domElement);

            const controls = new OrbitControls(camera, renderer.domElement);
            controls.target.set(0, 0, 0);
            controls.update();
            window.addEventListener('resize', onWindowResize);

        }

        function onWindowResize() {

            windowHalfX = window.innerWidth / 2;

            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();

            renderer.setSize(window.innerWidth, window.innerHeight);

        }

        function animate() {

            requestAnimationFrame(animate);

            render();

        }
        function render() {
            renderer.render(scene, camera);
        }

    </script>

</body>
jbg
  • 208
  • 1
  • 8
  • I found a similar solution that you suggest from https://stackoverflow.com/questions/40023065/threejs-texture-issue-while-adding-hole-into-the-shape?noredirect=1&lq=1 from the user(Priyank) but here I am getting mesh.geometry.faces is undefined so, won't execute for-loop. – Java-Dev Mar 15 '21 at 10:02
  • Yes, that was the point of my edit. That's how it worked in previous versions, but now I believe the only way to accomplish the same thing is with a UV map. – jbg Mar 15 '21 at 13:34
  • Updated my answer with an edited version of your example. – jbg Mar 15 '21 at 15:15