6

I've recreated a bag model for my application and exported it into ThreeJs as an .obj:

enter image description here

I've assigned a different colour to every face found in the models geometry like this:

var geometry = new THREE.Geometry().fromBufferGeometry( bagMesh.children[0].geometry );

for (var i = 0; i < geometry.faces.length; i ++ ) {
  var face = geometry.faces[i];
  // 7 & 8 = front side
  // can we flip its normal?
  if(i === 7 || i === 8) { 
    face.color.setHex( 0xff0000 );
  } else {
    face.color.setHex( Math.random() * 0xffffff );
  }
}
geometry.translate( 0, -1, 0.75);
mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, side: THREE.DoubleSide }) );
scene.add(mesh);

I've identified the faces of the front-side at indices 7 and 8 of the faces array and turned them red.

The problem is that this colour can be seen when I look inside of the bag too:

enter image description here

I realize that this is because I've set the object to THREE.DoubleSide but if I change it to THREE.FrontSide then the sides only partially visible.

So my question is how do I assign a different unique colour to each side (all 11 of them, counting the inside too) without that colour appearing on that sides respective opposite?

I'm trying to keep things simple here by only using colours as opposed to mapping images onto it, which is what I'll want to eventually get to.

Note - My previous model solved this problem by treating each side as a seperate mesh but this caused other issues like z-hiding and flickering problems.

Thanks

EDIT

@WestLangley I've setup a fiddle to demonstrate what you added in your comment. Assuming that I got it right it didn't have the desired affect:

(function onLoad() {
  var canvasElement;
  var width, height;
  var scene, camera;
  var renderer;
  var controls;

  var pivot;
  var bagMesh;
  var planeMesh;
  
  const objLoader = new THREE.OBJLoader2();
  const fileLoader = new THREE.FileLoader();
 
  init();

  function init() {
    container = document.getElementById('container');
    initScene();
    addGridHelper();
    addCamera();
    addLighting();
    addRenderer();
    addOrbitControls();

    loadPlaneObj();
    
    // Logic
  var update = function() {};

  // Draw scene
  var render = function() {
   renderer.render(scene, camera);
  };

  // Run game logic (update, render, repeat)
  var gameLoop = function() {
   requestAnimationFrame(gameLoop);
   update();
   render();
  };
  gameLoop();
  }

  /**** Basic Scene Setup ****/
  function initScene() {
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xd3d3d3);
    var axis = new THREE.AxesHelper();
    scene.add(axis);
  }

  function addCamera() {
    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.set(3,3,3);
    scene.add(camera);
  }

  function addGridHelper() {
    var planeGeometry = new THREE.PlaneGeometry(2000, 2000);
    planeGeometry.rotateX(-Math.PI / 2);

    var planeMaterial = new THREE.ShadowMaterial({
      opacity: 0.2
    });
    var plane = new THREE.Mesh(planeGeometry, planeMaterial);
    plane.position.y = -200;
    plane.receiveShadow = true;
    scene.add(plane);

    var helper = new THREE.GridHelper(2000, 100);
    helper.material.opacity = 0.25;
    helper.material.transparent = true;
    scene.add(helper);

    var axis = new THREE.AxesHelper();
    scene.add(axis);
  }

  // *********** Lighting settings **********************
  function addLighting() {
    var light = new THREE.HemisphereLight(0xffffff, 0xffffff, 1);
    scene.add(light);
  }

  // ************** Material settings **************
  function setMaterial(materialName) {
    // get the object from the scene
    var bagMesh = scene.getObjectByName('bag');
    var material;

    if (!materialName) {
      materialName = materials.material;
    }

    if (bagMesh) {
      var colour = parseInt(materials.colour);
      switch (materialName) {
        case 'MeshBasicMaterial':
          material = new THREE.MeshBasicMaterial({
            color: colour
          });
          break;
        case 'MeshDepthMaterial':
          material = new THREE.MeshDepthMaterial();
          break;
        case 'MeshLambertMaterial':
          material = new THREE.MeshLambertMaterial({
            color: colour
          });
          break;
        case 'MeshNormalMaterial':
          material = new THREE.MeshNormalMaterial();
          break;
        case 'MeshPhongMaterial':
          material = new THREE.MeshPhongMaterial({
            color: colour
          });
          break;
        case 'MeshPhysicalMaterial':
          material = new THREE.MeshPhysicalMaterial({
            color: colour
          });
          break;
        case 'MeshStandardMaterial':
          material = new THREE.MeshStandardMaterial({
            color: colour
          });
          break;
        case 'MeshToonMaterial':
          material = new THREE.MeshToonMaterial({
            color: colour
          });
          break;
      }
      bagMesh.children.forEach(function(c) {
        c.material = material;
      });
    }
  }

  function setMaterialColour(colour) {
    materials.colour = colour;
    setMaterial(null);
  }
  // ************** End of materials ***************

  function addRenderer() {
    renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    container.appendChild(renderer.domElement);
  }

  function addOrbitControls() {
    var controls = new THREE.OrbitControls(camera, renderer.domElement);
  }

  function addPivot() {
    var cubeGeo = new THREE.BoxBufferGeometry(5, 5, 5);
    var cubeMat = new THREE.MeshBasicMaterial();
    pivot = new THREE.Mesh(cubeGeo, cubeMat);
    bagMesh.position.x -= 15;
    bagMesh.position.z -= 55;

    pivot.add(bagMesh);
    pivot.add(handle);
    scene.add(pivot);
  }
  
  function loadPlaneObj() {
  loadObj('Plane', 'https://rawgit.com/Katana24/threejs-experimentation/master/models/Plane.obj', 'https://rawgit.com/Katana24/threejs-experimentation/master/models/Plane.mtl', addPlaneToSceneSOAnswer);
 }
  
  function loadObj(objName, objUrl, mtlUrl, onLoadFunc) {
  var onLoadMtl = function(materials) {
     objLoader.setModelName(objName);
     objLoader.setMaterials(materials);

     fileLoader.setPath('');
     fileLoader.setResponseType('arraybuffer');
     fileLoader.load(objUrl,
       function(onLoadContent) {
         var mesh = objLoader.parse(onLoadContent);
         onLoadFunc(mesh);
       },
       function(inProgress) {},
       function(error) {
         throw new Error('Couldnt load the model: ', error);
       });
   };
   objLoader.loadMtl(mtlUrl, objName+'.mtl', onLoadMtl);
 }
  
  function addPlaneToSceneSOAnswer(mesh) {
  var frontMaterial = new THREE.MeshBasicMaterial( { color : 0xff0000, side: THREE.FrontSide } );
  var backMaterial  = new THREE.MeshBasicMaterial( { color : 0x00ff00, side: THREE.BackSide } );
  
  var geometry = new THREE.Geometry().fromBufferGeometry( mesh.children[0].geometry );
  var length = geometry.faces.length;
  geometry.faces.splice(14, 1);

  for (var i = 0; i < geometry.faces.length; i ++ ) {
   var face = geometry.faces[i];
   face.color.setHex(Math.random() * 0xffffff);
  }
  mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, side: THREE.DoubleSide }) );
  mesh.material.side = THREE.FrontSide; 
  
  var mesh2 = new THREE.Mesh( geometry, mesh.material.clone() ); 
  mesh2.material.side = THREE.BackSide; 
  // mesh2.material.vertexColors = THREE.NoColors; 
  mesh2.material.vertexColors = [new THREE.Color(0xff0000), new THREE.Color(0x00ff00), new THREE.Color(0x0000ff)];

  mesh.add( mesh2 );
  scene.add(mesh);
 }
})();
body {
  background: transparent;
  padding: 0;
  margin: 0;
  font-family: sans-serif;
}

#canvas {
  margin: 10px auto;
  width: 800px;
  height: 350px;
  margin-top: -44px;
}
<body>
  <div id="container"></div>
  <script src="https://threejs.org/build/three.js"></script>
  <script src="https://threejs.org/examples/js/libs/dat.gui.min.js"></script>
  <script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
  <script src="https://threejs.org/examples/js/loaders/MTLLoader.js"></script>
  <script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/loaders/LoaderSupport.js"></script>
  <script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/loaders/OBJLoader2.js"></script>
</body>

What am I missing here?

Katana24
  • 8,706
  • 19
  • 76
  • 118
  • 3
    With your previous method of treating each side as a separate mesh, were you using THREE.FrontSide on one and THREE.BackSide on the other? I don't think face colors, like you're using here, will work without duplicating the mesh... Using images (with two materials on one mesh) is another option. – Don McCurdy Jan 10 '18 at 00:31
  • Can you elaborate on what you mean by using two materials for the one mesh and how I'd differentiate between the sides? – Katana24 Jan 10 '18 at 09:18
  • 1
    `mesh.material.side = THREE.FrontSide; var mesh2 = new THREE.Mesh( geometry, material.clone() ); mesh2.material.side = THREE.BackSide; mesh2.material.vertexColors = THREE.NoColors; mesh.add( mesh2 );` – WestLangley Jan 10 '18 at 17:48
  • @WestLangley I've edited my answer with your comment. Could you elaborate from it? – Katana24 Jan 11 '18 at 11:34
  • 1
    If you want the inside to be a solid color, what I posted will work. If you want the inside to have vertex colors, then you need clone the geometry, too, (`geometry.clone()`), and set the desired interior vertex colors on the cloned geometry. – WestLangley Jan 11 '18 at 16:33

2 Answers2

2

I followed along with Don's suggestion about the different materials but didn't know entirely what he meant.

I examined this question which details setting the materialIndex. I investigated what this means and what it means is that when you pass a geometry and an array of materials to a mesh like this:

mesh = new THREE.Mesh( geometry, [frontMaterial, backMaterial, otherMaterial] );

then that face will get the material (frontMaterial because it's at position 0) assigned to it.

Coming back to my original question, I decided to simplify (for the moment) and see if I could apply what I want to just a Plane mesh exported from Blender.

The Plane has two Faces when added into 3JS. I found I could flip each face or assign a different material to each but I needed to duplicate the faces in order to achieve this:

function addMeshTwoToScene() {
    var frontMaterial = new THREE.MeshBasicMaterial( { color : 0xff0000, side: THREE.FrontSide } );
    var backMaterial    = new THREE.MeshBasicMaterial( { color : 0x00ff00, side: THREE.BackSide } );
    var geometry = new THREE.Geometry().fromBufferGeometry( planeMesh.children[0].geometry );

    // Duplicates the face
    var length = geometry.faces.length;

    for (var i = 0; i < length; i++ ) {
      var face = geometry.faces[i];
      var newFace = Object.assign({}, face);
      geometry.faces.push(newFace);
    }

    for (var i = 0; i < geometry.faces.length; i ++ ) {
      var face = geometry.faces[i];
      if(i === 0 || i === 3) {
        face.materialIndex = 0;
      } else {
        face.materialIndex = 1;
      }
    }
    var mesh = new THREE.Mesh( geometry, [frontMaterial, backMaterial] );
    scene.add(mesh);
}

This results in the following:

top enter image description here

I'm not going to mark this as the accepted answer yet as I still need to apply it to the more complex model in the question plus I think there could still be a better way to do this, like flipping a particular vertex to some other value.

Katana24
  • 8,706
  • 19
  • 76
  • 118
0

One solution would be to use a ShaderMaterial and define the colors based on whether the face is front or back facing.

Let me walk you through this simple example

Hold left click to rotate the mesh. If you're not familiar with ShaderFrog, click "Edit Source" on the right and scroll down the bottom of the fragment shader.

if (!gl_FrontFacing) gl_FragColor = vec4(vec3(0.0, 0.0, 1.0) * brightness, 1.0);

gl_FrontFacing is a boolean. Quite self explanatory, it'll return true if a face is front, and false otherwise. The line reads "if the face is not front facing, render it blue at with alpha = 1.0.

Hoping that helps.

Console-buche
  • 405
  • 3
  • 12
  • I had considered using a ShaderMaterial for achieving the desired colours but would this still work with adding images attache to materials? – Katana24 Jan 11 '18 at 11:37
  • Or course, passing textures as uniforms will do the trick, provided your model's uvs are set. From here, you'll be able to add textures, blend multiple textures... sky's the limit – Console-buche Jan 11 '18 at 11:42
  • Ok - I'll examine this further – Katana24 Jan 11 '18 at 13:20