Search code examples
jsonthree.jstransparencyopacity

Transparent material of JSON Model looks weird in the Three.js scene


When I changed the opacity of my JSON model, I found the material looks weird.

Here is my code

var jsonLoader = new THREE.JSONLoader();

jsonLoader.load('model/body.json', addBodyToScn);
function addBodyToScn(geometry, material) {
    var Mtl = new THREE.MeshFaceMaterial(material);
    jsonMesh = new THREE.Mesh(geometry, Mtl);
    jsonMesh.scale.set(2, 2, 2);
    jsonMesh.material.materials.forEach(function(m){
        m.transparent = true;
        m.opacity = 1;
    });


jsonLoader.load('model/cow.json', addCowToScn);
function addCowToScn(geometry, material) {
    var Mtl2 = new THREE.MeshFaceMaterial(material);
    jsonMesh = new THREE.Mesh(geometry, Mtl2);
    jsonMesh.scale.set(2, 2, 2);
    jsonMesh.material.materials.forEach(function(m){
        m.transparent = true;
        m.opacity = 1;
    });

I tried to change the opacity of the cow and I want to see the part of human inside the cow.

opacity = 0.5
opacity = 0

I can not see the part of human inside the cow, no matter I used different numbers of the opacity.

I don't understand what happened.


Solution

  • The problem you are seeing has likely to do with the z-buffer and transparency. In realtime-rendering, proper transparency is way harder to do than it might seem at first, I'll try to explain why.

    What is happening: The transparent cow is rendered first. While rendering, a depth-value (=distance to camera) is written into the z-buffer for every fragment (pixel) of the cow. The pixel-color is blended with the value in the color-buffer (which is in your case black at this point).

    After that the human body is rendered, but for each rendered fragment a depth-test is performed first: if the rendered fragment of the human body is behind a fragment that has already been rendered (based on the value found in the z-buffer), that fragment is discarded and will not be written into the color-buffer.

    Maybe this youtube-video from the (excellent) udacity-course on three.js helps to illustrate the problem here.

    Three.js renders objects in two passes:

    • first all opaque objects are rendered, from front to back. This way, objects in the back that are hidden behind an object in the front don't need to get rendered* (thanks to the depth-test)
    • in the second pass, all transparent object are rendered, from the back to the front. This is to allow for transparent objects behind transparent objects to not be discarded by the depth-test (which is exactly what you want)

    Now, which object is in front and which is in the back is determined based on the distance between object.position and camera.position. This gets a bit tricky once objects start to intersect, as it is likely the case in your example.

    Unfortunately, there is currently no solution for this problem that will work in all situations.

    You can try modifying the object positions so that the cow is definitely in front of the human body, in that case the rendering should work ok.

    There are also two material-properties in three.js that control the z-buffer behaviour, named depthTest and depthWrite (see here). So you could disable depth-writing for the cow or disable the depth-test for the human body. Unfortunately, both are not really a solution, as disabling the depth-test will lead to the human-body being rendered on top of everything in the scene (even opaque objects in front of it) and disabling depth-writing leads to objects behind the cow being rendered as if they were in front.