0

I'm using a custom shader material in my application. It's an extended (and renamed) copy of THREE.ShaderLib['phong'], and it works great, except for transparency, which is hit-or-miss. Sometimes objects behind a transparent object are visible, but often get culled. To ensure I didn't break something, I created the fiddle below.

When the scene initializes, everything looks as it should--three transparent planes, all transparency working correctly. Even moving the scene around appears to work correctly at first glance. But suddenly you might notice some popping. Slowing down the rotation shows that at certain angles the planes simply cease being transparent with respect to each other. (I've added a convenient "BREAK IT" button to move the camera into one such position.)

So what's going on? Am I still/actually breaking something? I've tried initializing the materials individually, but the clone seems to do the job of cloning the materials as well. Or is this just a gotcha in how this material works, and I have to find another way to approach it?

Fiddle: http://jsfiddle.net/TheJim01/rwt64fgd/

JS:

// BufferGeometry Tester

var hostDiv, scene, renderer, camera, root, controls, light;

var WIDTH = 500;//window.innerWidth,
    HEIGHT = 500;//window.innerHeight,
    FOV = 35,
    NEAR = 1,
    FAR = 1000;

function breakIt(){
    camera.position.set(-25.759449580212017, -17.239852126859287, 39.2331270225625);
    camera.lookAt(scene.position);
    camera.up.set(0.07701484672816412, 0.8931831414880415, 0.44304919494903006);
}

function createBufferGeometryMesh(){
    var geo = new THREE.BufferGeometry();

    var tl = new THREE.Vector3(-10.,  10., 0.),
        tr = new THREE.Vector3(10.,  10., 0.),
        bl = new THREE.Vector3(-10.,  -10., 0.),
        br = new THREE.Vector3(10.,  -10., 0.);

    var n0 = new THREE.Vector3(0., 0., 1.);

    var vertices = 
        [
            tl.x, tl.y, tl.z,
            tr.x, tr.y, tr.z,
            br.x, br.y, br.z,
            bl.x, bl.y, bl.z
        ],
    normals =
        [
            n0.x, n0.y, n0.z,
            n0.x, n0.y, n0.z,
            n0.x, n0.y, n0.z,
            n0.x, n0.y, n0.z
        ],
    indices = [ 0, 2, 1, 0, 3, 2 ];

    geo.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( vertices ), 3 ) );
    geo.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( normals ), 3 ) );
    geo.addAttribute( 'index', new THREE.BufferAttribute( new Uint32Array( indices ), 1 ) );

    /*/
    var mat = new THREE.MeshPhongMaterial( {
                    color: 0xffffff,
                    ambient: 0xffffff,
                    specular: 0xffffff,
                    shininess: 50,
                    side: THREE.DoubleSide
                } );
    /*/
    var shader = THREE.ShaderLib['phong'];

    var uniforms = null,
        parameters = null;

    uniforms = THREE.UniformsUtils.clone(shader.uniforms);
    uniforms['diffuse'].value.setHex(0xcccccc);
    uniforms['ambient'].value.setHex(0x0);
    uniforms['emissive'].value.setHex(0x0);
    uniforms['specular'].value.setHex(0xffffff);
    uniforms['shininess'].value = 1;
    uniforms['opacity'].value = 0.5;
    uniforms['ambient'].value.convertGammaToLinear();

    parameters = {
        fragmentShader: shader.fragmentShader,
        vertexShader: shader.vertexShader,
        uniforms: uniforms,
        lights: true,
        fog: false,
        side: THREE.DoubleSide,
        blending: THREE.NormalBlending,
        transparent: (uniforms['opacity'].value < 1.0)
    };
    var mat = new THREE.ShaderMaterial(parameters);

    var msh = new THREE.Mesh(geo, mat);

    return msh;
}

function init() {
    hostDiv = document.createElement('div');
    document.body.appendChild(hostDiv);

    scene = new THREE.Scene();

    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(WIDTH, HEIGHT);
    renderer.setClearColor( 0x888888, 1 );
    hostDiv.appendChild(renderer.domElement);

    camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
    camera.position.z = 50;
    camera.lookAt(scene.position);

    controls = new THREE.TrackballControls(camera, renderer.domElement);

    light = new THREE.PointLight(0xffffff, 1, 1000);
    light.position.copy(camera.position);

    scene.add(camera);
    scene.add(light);

    var square = createBufferGeometryMesh();
    square.material.uniforms['diffuse'].value.setHex(0xff0000);
    square.material.needsUpdate = true;
    square.translateY(5);
    square.translateX(5);
    square.translateZ(5);
    scene.add(square);

    square = createBufferGeometryMesh();
    square.material.uniforms['diffuse'].value.setHex(0x00ff00);
    square.material.needsUpdate = true;
    square.translateY(-5);
    square.translateX(-5);
    square.translateZ(-5);
    scene.add(square);

    square = createBufferGeometryMesh();
    square.material.uniforms['diffuse'].value.setHex(0x0000ff);
    square.material.needsUpdate = true;
    square.scale.set(0.5, 0.5, 0.5);
    scene.add(square);

    animate();

    var button = document.createElement('input');
    button.setAttribute('type', 'button');
    button.setAttribute('value', 'BREAK IT');
    button.addEventListener('click', breakIt);
    document.body.appendChild(button);
}

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

function animate() {
    light.position.copy(camera.position);    

    requestAnimationFrame(animate);
    render();
    controls.update();
}

init();
TheJim01
  • 8,411
  • 1
  • 30
  • 54
  • 1. Congratulations on doing small experiments to lean. It is the only way you will really understand webgl and three.js. 2. Do you see the same problem when you use transparent `MeshPhongMaterial` instead of your custom material? – WestLangley Dec 17 '14 at 22:59
  • 1. I'm a do-er learner, so it suits me best, and helps illustrate my point when I need to ask for help. :) 2. Yes. Version 7 of my fiddle swaps the `ShaderMaterial` for a similarly-configured `MeshPhongMaterial`. – TheJim01 Dec 18 '14 at 14:46
  • 1
    Did more digging, trying, etc. I found http://stackoverflow.com/questions/15994944/transparent-objects-in-threejs which (although based on r57) states transparency is based on object position. Did some tests on this, and it's true. With some tweaks, I got the camera into a position where--with the planes stacked in the same manner--the blue plane was the closest, but the red plane was still in front of the blue plane. Freaky results. – TheJim01 Dec 18 '14 at 15:01
  • 1. Therefore, it is not caused by your shader. Simplify to `MeshBasicMaterial`. See if you can create a simple example with unexplained results. 2. You can also google "order independent transparency" -- which three.js does not do, BTW. – WestLangley Dec 18 '14 at 15:06
  • `MeshBasicMaterial` does the same. But I expected that--the results aren't all that unexplained. The alpha blending is done based on object positions, rather than fragment positions. This could force weird situations, like what I see in my examples. But since three.js doesn't support OIT (yet... https://github.com/mrdoob/three.js/issues/4814), I guess I'm out of luck for now. It's not a _terrrible_ problem, I was just hoping there was a workaround. – TheJim01 Dec 18 '14 at 16:34

1 Answers1

2

The transparency artifacts that you are seeing have nothing to do with your particular shader. The same problem will occur with MeshBasicMaterial.

Alpha blending of transparent objects in three.js is order-dependent.

three.js sorts objects based on their distance from the camera, and renders transparent objects in order from farthest to closest. See this related answer.

Consequently, you may see artifacts when the camera is moved, and the sort order changes.

There are some use-case-specific work-arounds that may be helpful, such as controlling the render order by setting renderer.sortObjects = false, or by changing material properties. You will have to experiment to see what works for you.

three.js r.69

Community
  • 1
  • 1
WestLangley
  • 102,557
  • 10
  • 276
  • 276