2

Im trying to understand an issue i am facing with ThreeJS. I have a simple cube on a page and i have a PNG of a white question mark, with the rest of the image transparent. When i add the texture to the cube;

cubeGeometry = new THREE.BoxGeometry( 5, 5, 5 );
cubeMaterial = new THREE.MeshStandardMaterial( { color: this.cubeColor, flatShading: true, map:new THREE.TextureLoader().load( require("./question-mark.png") ) } );
cubeMesh = new THREE.Mesh( cubeGeometry, cubeMaterial );

The transparent pixels in the PNG appear black no matter what i do. Whats even stranger is the white pixels of the PNG appear the color of this.cubeColor - which is set to orange.

I would expect the cube color to be orange with the transparent pixels of the PNG taking that color and the white pixels in the PNG to remain white.

Anyone shed some light ?

what I see

what i see

the texture

texture

gman
  • 100,619
  • 31
  • 269
  • 393
rosey3
  • 23
  • 1
  • 4
  • [This SO answer](https://stackoverflow.com/a/49708915/1461008) describes how to do what you appear to want -- render a decal texture. – WestLangley Dec 23 '19 at 03:08
  • Why not use the [alphaMap property](https://threejs.org/docs/#api/en/materials/MeshStandardMaterial.alphaMap)? – 2pha Dec 23 '19 at 22:37

1 Answers1

5

You can apply 2 or more materials like this

  const geometry = new THREE.BoxBufferGeometry(boxWidth, boxHeight, boxDepth);

  // prepare geometry to use 2 materials
  geometry.clearGroups();
  geometry.addGroup( 0, Infinity, 0 );
  geometry.addGroup( 0, Infinity, 1 );

  const loader = new THREE.TextureLoader();

  // create 2 materials
  const material0 = new THREE.MeshBasicMaterial({
    color: 'orange',
  });
  const material1 = new THREE.MeshBasicMaterial({
    map: loader.load('https://i.imgur.com/iFom4eT.png'),
    transparent: true,
  });

  // apply 2 materials to geometry
  const materials = [material0, material1];
  const cube = new THREE.Mesh(geometry, materials);

Example, note it uses BoxBufferGeometry not BoxGeometry

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const scene = new THREE.Scene();
  scene.background = new THREE.Color('purple');

  const boxWidth = 1;
  const boxHeight = 1;
  const boxDepth = 1;
  const geometry = new THREE.BoxBufferGeometry(boxWidth, boxHeight, boxDepth);
  
  // prepare geometry to use 2 materials
  geometry.clearGroups();
  geometry.addGroup( 0, Infinity, 0 );
  geometry.addGroup( 0, Infinity, 1 );

  const loader = new THREE.TextureLoader();
  
  // create 2 materials
  const material0 = new THREE.MeshBasicMaterial({
    color: 'orange',
  });
  const material1 = new THREE.MeshBasicMaterial({
    map: loader.load('https://i.imgur.com/hrzoDMj.png'),
    transparent: true,
  });
  
  // apply 2 materials to geometry
  const materials = [material0, material1];
  const cube = new THREE.Mesh(geometry, materials);
  scene.add(cube);

  function render(time) {
    time *= 0.001;  // convert time to seconds
    
    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();    
    }

    cube.rotation.x = time;
    cube.rotation.y = time;

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);

}

function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    renderer.setSize(width, height, false);
  }
  return needResize;
}

main();
body { margin: 0; }
#c { width: 100vw; height: 100vh; display: block; }
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.min.js"></script>
<canvas id="c"></canvas>

note that if you put a color and a texture (map) in a single material the 2 colors, (1) the color you specified and (2) the color from the texture, are multiplied with each other. So, wherever the texture has zeros, RGBA = 0,0,0,0 then the result is 0,0,0,0 as anything multiplied by zero is zero. This is why you got the results you got originally where outside the question mark is transparent and inside is orange.

Knowing this you can use your white question mark and change its color by setting the color on the material that is using the texture.

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const scene = new THREE.Scene();
  scene.background = new THREE.Color('purple');

  const boxWidth = 1;
  const boxHeight = 1;
  const boxDepth = 1;
  const geometry = new THREE.BoxBufferGeometry(boxWidth, boxHeight, boxDepth);
  
  // prepare geometry to use 2 materials
  geometry.clearGroups();
  geometry.addGroup( 0, Infinity, 0 );
  geometry.addGroup( 0, Infinity, 1 );

  const loader = new THREE.TextureLoader();
  
  // create 2 materials
  const material0 = new THREE.MeshBasicMaterial({
    color: 'orange',
  });
  const material1 = new THREE.MeshBasicMaterial({
    map: loader.load('https://i.imgur.com/hrzoDMj.png'),
    transparent: true,
  });
  
  // apply 2 materials to geometry
  const materials = [material0, material1];
  const cube = new THREE.Mesh(geometry, materials);
  scene.add(cube);

  function render(time) {
    time *= 0.001;  // convert time to seconds
    
    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();    
    }
    
    material1.color.setHSL(time, 1, 0.5);

    cube.rotation.x = time;
    cube.rotation.y = time;

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);

}

function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    renderer.setSize(width, height, false);
  }
  return needResize;
}

main();
body { margin: 0; }
#c { width: 100vw; height: 100vh; display: block; }
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.min.js"></script>
<canvas id="c"></canvas>
gman
  • 100,619
  • 31
  • 269
  • 393
  • Thank you for your response. Unforunately not what i am after. I have attached and image to the OP off what i am seeing after implementing transparent:true, alphaTest:0.01 and side:THREE.DoubleSide, and also attached the texture im trying to implement. In the "what i see" image you will notice the question mark is an orange color. I want the overall cube to be that orange color. You will notice that the question mark png is transparent background with a white question mark. I would like a cube the orange color and a white question mark on it. Thanks again – rosey3 Dec 23 '19 at 20:42
  • 1
    Perfecto! exactly what i wanted thank you very much ! i did not know about this groups property on the geometry this'l be extremly useful in the future! – rosey3 Dec 24 '19 at 11:46