stonehank
stonehank

Reputation: 23

Threejs merge multiply materials, but render incorrectly

I want to merge two mesh with different materials.

This is what i 'v done, but render incorrect, it just like render one side of the object.

Here is my code on codesandbox:

https://codesandbox.io/s/summer-dawn-5nq05?fontsize=14

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <script src="https://threejs.org/build/three.js"></script>
    <script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
  </head>

  <body>
    <script>
      let renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      // Scene

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

      // Camera

      let camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        1,
        1000
      );
      camera.position.set(30, 0, 100);

      let controls = new THREE.OrbitControls(camera);

      for (let i = 0; i < 1; i++) {
        // Geometry

        let cube1Geometry = new THREE.PlaneGeometry(10, 10, 10);
        let cube2Geometry = new THREE.PlaneGeometry(10, 15, 5);

        // Material

        let cube1Material = new THREE.MeshStandardMaterial({
          color: 0x7c3c3c,
          roughness: 1
        });
        let cube2Material = new THREE.MeshStandardMaterial({
          color: 0x01b8ba,
          roughness: 0.1
        });

        // Mesh

        let cube1 = new THREE.Mesh(cube1Geometry, cube1Material);
        let cube2 = new THREE.Mesh(cube2Geometry, cube2Material);

        // Combine

        let singleGeometry = combine([cube1, cube2]);

        let single = new THREE.Mesh(singleGeometry, [
          cube1Material,
          cube2Material
        ]);
        // scene.add(cube1);
        // scene.add(cube2);
        scene.add(single);

        single.position.set(
          Math.random() * 5,
          Math.random() * 10,
          Math.random() * 20
        );
      }

      function combine(meshes) {
        let mergeGeometry = new THREE.Geometry();

        for (let i = 0; i < meshes.length; i++) {
          meshes[i].updateMatrix();
          // update materialIndex
          for (let j = 0; j < meshes[i].geometry.faces.length; j++) {
            meshes[i].geometry.faces[j].materialIndex = 0;
          }
          mergeGeometry.merge(meshes[i].geometry, meshes[i].matrix, i);
        }

        return mergeGeometry;
      }

      // LIGHT
      let light1 = new THREE.AmbientLight(0x666666);
      scene.add(light1);
      let light = new THREE.SpotLight(0xdfebff, 1);
      light.position.set(50, 200, 100);
      scene.add(light);

      requestAnimationFrame(function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
      });
    </script>
  </body>
</html>

Upvotes: 0

Views: 144

Answers (1)

ScieCode
ScieCode

Reputation: 1735

ANSWER OUTDATED

Threejs' API has changed and no longer support the old Geometry interface, now you need to deal directly with BufferGeometries and their attributes. You can find more merging utilities on DOCS: BufferGeometryUtils.


This behavior happens because BoxGeometry (source) stipulates a different MaterialIndex for each of its faces. This, in turn, controls which material will be used for that face, when utilizing an array of Materials.

To make sure that every geometry only uses the specified material in the array, you need to reset MaterialIndex to 0 for each face of the geometry. That way, geometry.merge() will correctly adjust each geometry to use the correct material.

I've modified your combine function to do this on the fly. I'm not sure if there's a better way of resetting MaterialIndex, at least I couldn't find any.

function combine(meshes) {
  
    let mergeGeometry = new THREE.Geometry();
    
    for (let i = 0; i < meshes.length; i++) {
    
      meshes[i].updateMatrix();
      
      // update materialIndex
      for ( let j = 0; j < meshes[i].geometry.faces.length; j++ ) {
      
        meshes[i].geometry.faces[j].materialIndex = 0;
        
      }
      
      mergeGeometry.merge( meshes[i].geometry, meshes[i].matrix, i );
      
    }
    
    return mergeGeometry;
    
}

JSFiddle Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <style>
      body {
        margin: 0;
        position: fixed;
      }
      canvas {
        width: 100%;
        height: 100%;
        display: block;
      }
    </style>
    <script src="https://threejs.org/build/three.js"></script>
    <script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
  </head>

  <body>
    <script>
      let renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);


      // Scene

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

      // Camera

      let camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        1,
        1000
      );
      camera.position.set(30, 0, 50);

      let controls = new THREE.OrbitControls(camera);

      // Geometry

      let cube1Geometry = new THREE.BoxGeometry(10, 10, 10);
      let cube2Geometry = new THREE.BoxGeometry(10, 15, 5);
      

      // Material

      let cube1Material = new THREE.MeshStandardMaterial({
        color: 0x7c3c3c,
        roughness: 1
      });
      let cube2Material = new THREE.MeshStandardMaterial({
        color: 0x01b8ba,
        roughness: 0.1
      });

      // Mesh

      let cube1 = new THREE.Mesh(cube1Geometry, cube1Material);
      let cube2 = new THREE.Mesh(cube2Geometry, cube2Material);

      // Combine

      let singleGeometry = combine([cube1, cube2]);

      let single = new THREE.Mesh(singleGeometry, [
        cube1Material,
        cube2Material
      ]);

      scene.add(single);

      function combine(meshes) {
      
        let mergeGeometry = new THREE.Geometry();
        
        for (let i = 0; i < meshes.length; i++) {
        
          meshes[i].updateMatrix();
          
          // update materialIndex
          for ( let j = 0; j < meshes[i].geometry.faces.length; j++ ) {
          
            meshes[i].geometry.faces[j].materialIndex = 0;
            
          }
          
          mergeGeometry.merge( meshes[i].geometry, meshes[i].matrix, i );
          
        }
        
        return mergeGeometry;
        
      }

      // LIGHT
      let light1 = new THREE.AmbientLight(0x666666);
      scene.add(light1);
      let light = new THREE.SpotLight(0xdfebff, 1);
      light.position.set(50, 200, 100);
      scene.add(light);

      requestAnimationFrame(function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
      });
    </script>
  </body>
</html>

Upvotes: 1

Related Questions