2

I am trying to create a simple map with flat, paper-like trees sticking out of it in WebGL with THREE.js. I cannot grasp how the library handles z-buffering. I've tried playing with renderer.sortObjects parameter as well as with material.depthWrite and object.renderDepth, but no combination seems to be working - I either get the library to display the trees in proper order (those further from the camera are obstructed by those closer to the camera), but with weird transparency glitches, OR I manage to get the transparency right, but the trees further from the screen appear on top on those that are closer. After hours of trying to get this right, this is what I ended up with: screenshot

As you can see, the trees that are more to the right are rendered on top on those to the left.

Please help me before I go completely nuts :)

Here's my code: http://jsfiddle.net/UgZxc/

var map_size = 80;

var MapModel = {};

var types_amount = 2;
var floorMap = [];

for(var i=1; i<=map_size; i++){
    floorMap[i]=[];
    for(var j=1; j<=map_size; j++){
        var ran = Math.ceil(Math.random()*types_amount);
        switch(ran){
            case 1:
                floorMap[i][j]='grass';
                break;
            case 2: 
                floorMap[i][j]='water';
                break;
        }
    }
}

MapModel.floorMap = floorMap;

var objectMap = [];
for(var i = map_size; i>=1; i--){
    objectMap[i] = [];
    for(var j=1; j<=map_size; j++){
        objectMap[i][j] = [];
        var rand = Math.ceil(Math.random()*2);
        switch(rand){
            case 1:
                objectMap[i][j].push('tree');
                break;
        }
    }
}

MapModel.objectMap = objectMap;




block_size=100;


// Constructor
MapApp = function()
{
    Sim.App.call(this);
}


// Subclass Sim.App
MapApp.prototype = new Sim.App();

// Our custom initializer
MapApp.prototype.init = function(param)
{
    Sim.App.prototype.init.call(this, param);

    // Create the Earth and add it to our sim
    for(var i=1; i<=map_size; i++){
        for(var j=map_size; j>=1; j--){
            var square = new Square();
            square.init(i, j);
            this.addObject(square);                    
            var arr = MapModel.objectMap[i][j];
            for(var k in arr){
                var obj = new MapObject();
                obj.init(i, j, arr[k]);
                this.addObject(obj);
            }
        }
    }

    // Let there be light!
    var sun = new Sun();
    sun.init();
    this.addObject(sun);

    this.camera.position.x = 3*block_size;
    this.camera.position.y = 3*block_size;
    this.camera.position.z=5*block_size;
    this.camera.rotation.x = Math.round(45* 100* Math.PI/180)/100;

    this.selfUpdate = function(){
        this.camera.position.x += 0.125 * block_size/10;
        this.camera.position.x += 0.050 * block_size/10;
    }

}

Square = function()
{
    Sim.Object.call(this);
    this.size = block_size;
}

Square.prototype = new Sim.Object();

wrote2 = false;
Square.prototype.init = function(x, y){   
    var type=MapModel.floorMap[x][y];
    var reflectivity = 0;    
    switch(type){
        case "grass":
            var earthmap = "http://dl.dropboxusercontent.com/u/1142760/static/html/webgl/tiles/samatrawa.png";
            break;
        case "water":
            var earthmap = "http://dl.dropboxusercontent.com/u/1142760/static/html/webgl/tiles/samawoda.png";
            reflectivity = 1;
            break;

    }
    //console.log(earthmap);
    var geometry = new THREE.CubeGeometry(this.size, this.size, this.size );
    var texture = THREE.ImageUtils.loadTexture(earthmap);
    var material = new THREE.MeshPhongMaterial( { map: texture, color: 0xffffff, reflectivity: reflectivity } );
    material.depthWrite = true;
    var mesh = new THREE.Mesh( geometry, material ); 

    mesh.translateX(x*this.size);
    mesh.translateY(y*this.size);
    mesh.renderDepth = y*block_size;

    if(!wrote2){
        wrote2=true;
        console.log('square renderDepth:', mesh.renderDepth, 'square mesh.position.y:', mesh.position.y);
        console.log('square material.depthWrite', material.depthWrite);
    }

    this.setObject3D(mesh); 
}


Square.prototype.update = function()
{
    // "I feel the Earth move..."
    //this.object3D.rotation.y += 0.1;

    //Sim.Object.prototype.update.call(this);
}

// Custom Sun class
Sun = function()
{
    Sim.Object.call(this);
}

Sun.prototype = new Sim.Object();

Sun.prototype.init = function()
{
    // Create a point light to show off the earth - set the light out back and to left a bit
    var light = new THREE.DirectionalLight( 0xC5BC98, 2);
    light.position.set(-10, 0, 20);

    // Tell the framework about our object
    this.setObject3D(light);    
}


MapObject = function(){
    Sim.Object.call(this);
}

MapObject.prototype = new Sim.Object();
wrote=false
MapObject.prototype.init = function(x, y, type){
    switch(type){
        case "tree":
            var textureURL = "http://dl.dropboxusercontent.com/u/1142760/static/html/webgl/tiles/samodrzewo.png";
            break;
        case "water":
            var textureURL = "http://dl.dropboxusercontent.com/u/1142760/static/html/webgl/tiles/samawoda.png";
            break;

    }
    //console.log(textureURL);
    var geometry = new THREE.PlaneGeometry(1*block_size, 2*block_size);
    var texture = THREE.ImageUtils.loadTexture(textureURL);
    var material = new THREE.MeshPhongMaterial( { map: texture, transparent:true } );
    material.depthWrite = false;

    var mesh = new THREE.Mesh( geometry, material ); 

    mesh.position.x=x*block_size;
    mesh.position.y=y*block_size;
    mesh.translateZ(2*block_size);

    mesh.rotation.x = Math.round(45 * 100 * Math.PI /180)/100;

    //mesh.renderDepth = y*block_size;
    mesh.renderDepth = -y*1000 ;
    if(!wrote){
        console.log('object renderDepth:', mesh.renderDepth, 'object mesh.position.y:', mesh.position.y);
        console.log('object material.depthWrite', material.depthWrite);
        wrote = true;
    }
    //console.log(mesh.rotation.x);

    this.setObject3D(mesh); 
}

var renderer = null;
            var scene = null;
            var camera = null;
            var mesh = null;

            $(document).ready(
                function() {
                    var container = document.getElementById("container");
                    var app = new MapApp();
                    app.init({ container: container });
                    app.run();
                }
            );
Kuba Orlik
  • 3,360
  • 6
  • 34
  • 49
  • 1
    [This answer](http://stackoverflow.com/questions/8763603/transparent-textures-behaviour-in-webgl) may help –  Dec 01 '13 at 05:27
  • I've already stumbled upon this answer. It doesn't say how do I affect the rendering order, though – Kuba Orlik Dec 01 '13 at 09:32
  • I'm afraid that if you play with render depth, you'll have to handle it every time you move/rotate the cam. Could you show us you it looks without render depth ? – GameAlchemist Dec 01 '13 at 10:33
  • @GameAlchemist, what do you mean? – Kuba Orlik Dec 01 '13 at 11:56
  • if you change render depth for trees, then you'll have to always care about that depth when rotating the camera (depth won't be any more along the y axis). At your place i would try a much lighter scene (with, say two trees) so i can inspect deeply and understand what's going on. Could show us how does look the render without renderDepth ? – GameAlchemist Dec 01 '13 at 12:56
  • Fortunately for me I don't plan on rotating the camera. Please tell me how do I turn off renderDepth. Do you mean setting material.renderDepth to false? – Kuba Orlik Dec 01 '13 at 16:13

1 Answers1

7

Generally, this problem cannot be solved by turning depth testing/writing on/off. This is well explained in this answer: Transparent textures behaviour in WebGL

Therefore it can only be solved by drawing the transparent objects in the correct order. The solution is (mostly the default three.js behavior!):

  • Keep depth testing/writing enabled (you rarely disable this anyway).
  • Enable sorting of objects: app.renderer.sortObjects = true; although i don't see where in your code it is disabled.
  • Set renderDepth manually only if you see artifacts.

However, in your case, it turns out that your three.js version does a bad job at reordering (maybe some unstable sorting, I won't dig into that) so you get seemingly random artifacts. Updating to the latest build fixes that.

Working fiddle: http://jsfiddle.net/UgZxc/12/

As a sidenote: Try reducing your code examples/fiddles next time, and also the number of dependencies. There's a lot of unrelated code in there.

Community
  • 1
  • 1
morris4
  • 1,977
  • 17
  • 14