Textured Billboard and transparency problem.

edited March 2014 in GLSL / Shaders

Hello,

Coming from this thread, I am trying to achieve drawing transparent point-based billlboards AND normal opaque geometry masking the billboards.

As you can see by running the sketch, none of the billboards are 'occluded' by solid objects (i.e. you can allways see them, even behind an opaque object). ;(

I'm sure this has to do with depth/depthMask or blending, but I don't know how to fix it.

I've tried method found on the first post of this page, and used gl.glDepthMask(false) before drawing billboards and gl.glDepthMask(true) afterwards but could'nt get it to work as expected.

My sketch is simple : draw a solid sphere in the center of the screen, calculate spherical coordinates for billboards, draw the sphere, and draw the billboards (which I'd like to be 'hidden' by the center sphere when they are 'behind' it)..

Code : (reduced as much as possible).

import javax.media.opengl.glu.*;
import javax.media.opengl.GL2;
import com.jogamp.opengl.util.gl2.GLUT;

PShader   pointShader;
PVector[] positions;
PVector[] positionsSphere;

color[]   colors;
int       billboardCount   = 200;
Sphere    mySphere;

GL2       gl;

void setup() 
{
    size(1280, 720, OPENGL);

    String[] vertSource = new String[] {
        "#define PROCESSING_POINT_SHADER",
        "uniform mat4 projection;",
        "uniform mat4 modelview;",
        "uniform float weight;",
        "attribute vec4 vertex;",
        "attribute vec4 color;",
        "attribute vec2 offset;",
        "varying vec4 vertColor;",
        "varying vec2 texCoord;",
        "void main() {",
            "vec4 pos = modelview * vertex;",
            "vec4 clip = projection * pos;",
            "gl_Position = clip + projection * vec4(offset, 0, 0);",
            "texCoord = (vec2(0.5) + offset / weight);",
            "vertColor = color;",
        "}"
    };
    String[] fragSource = new String[] {
        "#ifdef GL_ES",
        "precision mediump float;",
        "precision mediump int;",
        "#endif",
        "uniform sampler2D sprite;",
        "varying vec4 vertColor;",
        "varying vec2 texCoord;",
        "void main() {",
            "gl_FragColor = texture2D(sprite, texCoord) * vertColor;",
        "}"
    };

    mySphere = new Sphere(width / 2, height / 2, 0);
    for(int i = 0; i < billboardCount; i++)
    {
      mySphere.addSphereItem();
    } 

    pointShader = new PShader(this, vertSource, fragSource);
    pointShader.set("sprite", loadImage("https" + "://dl.dropboxusercontent.com/u/31893519/particle.png"));
    strokeCap(SQUARE);
}

void drawCenterSphere()
{  
    pushStyle();    
    sphereDetail(64);
    fill(255);
    stroke(0,20);
    strokeWeight(0.5f);    
    sphere(250);  
    popStyle();
}

void draw() 
{
    background(200);        
    lights();

    translate(mySphere.xPos, mySphere.yPos ,mySphere.zPos);
    rotate(frameCount * 0.01f, 1.0, 0.75, 0.5);

    drawCenterSphere();
    // render the billboards.    
    for (SphereItem item : mySphere.items)
    {      
      item.render();
    }
}

class Sphere {

  float xPos = 0;                   //X Position of the Sphere
  float yPos = 0;                   //Y Position of the Sphere
  float zPos = 0;                   //Z Position of the Sphere

  float radius = 250;                  //Radius of the Sphere
  ArrayList<SphereItem> items = new ArrayList();  //List of all of the items contained in the Sphere

  public Sphere(float posX, float posY, float posZ)
  {
    xPos = posX;
    yPos = posY;
    zPos = posZ;  
  }

  public void addSphereItem() 
  {
    SphereItem si = new SphereItem(this, random(1.3f ), random(TWO_PI * 2)); 
    items.add(items.size(), si);
  };

  public void render() 
  { 
    for (SphereItem item : items) 
    {      
      item.render();
    }       
  }
}

public class SphereItem 
{

  Sphere parentSphere;

  //Spherical Coordinates
  float radius; 
  float theta;
  float phi;

  //Speed properties
  float thetaSpeed   = 0;
  float phiSpeed     = 0;

  //Size
  float itemSize     = 5;

  int itemColor;

  public SphereItem(Sphere parent, float theta, float phi) 
  {
    parentSphere  = parent;

    this.theta   = theta;
    this.phi     = phi;

    itemSize     = random(5f);
    thetaSpeed   = random(-0.01, 0.01);
    phiSpeed     = random(-0.01, 0.01);
    itemColor    = color(random(255),random(255),random(255));   
  }

  public void update() 
  {
        theta  += thetaSpeed;
        phi    += phiSpeed;
  }

  public void render() 
  {
    float r = parentSphere.radius ;

    //Convert spherical coordinates into Cartesian coordinates
    float x = cos(theta) * sin(phi) * r;
    float y = sin(theta) * sin(phi) * r;
    float z = cos(phi) * r;

    update();
    renderAsBillboards (x, y, z); 

  }

  public void renderAsBillboards(float x, float y, float z)
  {
    float size = itemSize *20; 

    shader(pointShader, POINTS);    
    pointShader.set("weight", size);

    blendMode(BLEND);
    hint(DISABLE_DEPTH_TEST);    

    stroke(itemColor);
    strokeWeight(size);  

    point(x,y,z);

    hint(ENABLE_DEPTH_TEST);
    resetShader();

    blendMode(BLEND);

  }

}

The billboard rendering code is located in the SphereItem class at line 173.

As you can notice, we can see the billboards through the center sphere, as if the sphere was transparent (and it is not !)..

So my question is :

How to modify this sketch in order to 'normally' occlude billboards so that they don't appear when drawn behind an opaque object ?

@poersch, @codeanticode, an idea ?

I'm really stuck on this one.

Answers

  • Use DISABLE_DEPTH_MASK/ENABLE_DEPTH_MASK instead of DISABLE_DEPTH_TEST/ENABLE_DEPTH_TEST, and the sphere will occlude the billboards, but they won't interfere with each other (because their depth values are not being written to the depth buffer, but the depth test is still being performed).

  • edited March 2014

    @codeanticode, thanks again for your patience ;-)

    But, whichblendMode() value should I define (at line 178) ?

    I have tried all values (from blendMode() documentation, and the only one where I don't see the quads is : blendMode(ADD)..

    Very nice, but this is additive blending..

    How can I at the same time :

         a) not see the quads
    
         b) be in 'normal' blending mode (or any other blend mode).. 
    

    ?

    (Note: I have 'rejected' your answer though it was part of the solution, because the forum keeps telling me about accept / reject, and I wanted to ask this additional question. No offense here ;) )

  • edited March 2014 Answer ✓

    Just add a little offset to the SphereItem position in order to avoid (visually) SphereItem/CenterSphere-intersections (line 127 in the following code). Additionally I optimized your code (a little) and commented most of the changes:

    import javax.media.opengl.GL2;
    
    PShader pointShader;
    PVector[] positions;
    PVector[] positionsSphere;
    color[] colors;
    int billboardCount = 200;
    Sphere mySphere;
    GL2 gl;
    
    void setup() {
        size(1280, 720, P3D);
        String[] vertSource = new String[] {
            "#define PROCESSING_POINT_SHADER", 
            "uniform mat4 projection;", 
            "uniform mat4 modelview;", 
            "uniform float weight;", 
            "attribute vec4 vertex;", 
            "attribute vec4 color;", 
            "attribute vec2 offset;", 
            "varying vec4 vertColor;", 
            "varying vec2 texCoord;", 
            "void main() {", 
            "vec4 pos = modelview * vertex;", 
            "vec4 clip = projection * pos;", 
            "gl_Position = clip + projection * vec4(offset, 0, 0);", 
            "texCoord = (vec2(0.5) + offset / weight);", 
            "vertColor = color;", 
            "}"
        };
        String[] fragSource = new String[] {
            "#ifdef GL_ES", 
            "precision mediump float;", 
            "precision mediump int;", 
            "#endif", 
            "uniform sampler2D sprite;", 
            "varying vec4 vertColor;", 
            "varying vec2 texCoord;", 
            "void main() {", 
            "gl_FragColor = texture2D(sprite, texCoord) * vertColor;", 
            "}"
        };
        mySphere = new Sphere(width / 2, height / 2, 0);
        for(int i = 0; i < billboardCount; i++)
            mySphere.addSphereItem();
        pointShader = new PShader(this, vertSource, fragSource);
        pointShader.set("sprite", loadImage("https" + "://dl.dropboxusercontent.com/u/31893519/particle.png"));
        strokeCap(SQUARE);
    }
    
    void drawCenterSphere() {  
        pushStyle();    
        sphereDetail(64);
        fill(255);
        stroke(0, 20);
        strokeWeight(0.5f);    
        sphere(250);  
        popStyle();
    }
    
    void draw() {
        background(200);
        translate(mySphere.xPos, mySphere.yPos, mySphere.zPos);
        rotate(frameCount * 0.01f, 1.0, 0.75, 0.5);
        drawCenterSphere();
        shader(pointShader, POINTS); // Don't do this for every SphereItem
        hint(DISABLE_DEPTH_MASK); // Don't do this for every SphereItem
        for(SphereItem item : mySphere.items)
            item.render();
        hint(ENABLE_DEPTH_MASK); // Don't do this for every SphereItem
        resetShader(); // Don't do this for every SphereItem
    }
    
    class Sphere {
    
        float xPos = 0;
        float yPos = 0;
        float zPos = 0;
        float radius = 250;
        ArrayList<SphereItem> items = new ArrayList();
    
        public Sphere(float posX, float posY, float posZ) {
            xPos = posX;
            yPos = posY;
            zPos = posZ;
        }
    
        public void addSphereItem() {
            SphereItem si = new SphereItem(this, random(1.3), random(TWO_PI * 2)); 
            items.add(items.size(), si);
        }
    
        public void render() { 
            for(SphereItem item : items) 
                item.render();
        }
    
    }
    
    public class SphereItem {
    
        Sphere parentSphere;
        float radius; 
        float theta;
        float phi;
        float thetaSpeed = 0;
        float phiSpeed   = 0;
        float itemSize   = 5;
        int itemColor;
    
        public SphereItem(Sphere parent, float theta, float phi) {
            parentSphere = parent;
            this.theta   = theta;
            this.phi     = phi;
            itemSize     = random(2.5, 5.0); // random(5f) can also be zero
            thetaSpeed   = random(-0.01, 0.01);
            phiSpeed     = random(-0.01, 0.01);
            itemColor    = (int)random(0xffffff) | 0xff000000; // Does the same as color(random(255), random(255), random(255)) but is faster
        }
    
        public void update() {
            theta += thetaSpeed;
            phi += phiSpeed;
        }
    
        public void render() {
            float r = parentSphere.radius * 1.1; // Make sure the points are not touching the parent's surface
            float x = cos(theta) * sin(phi) * r;
            float y = sin(theta) * sin(phi) * r;
            float z = cos(phi) * r;
            update();
            renderAsBillboards(x, y, z);
        }
    
        public void renderAsBillboards(float x, float y, float z) {
            float size = itemSize * 20; 
            pointShader.set("weight", size);
            stroke(itemColor);
            strokeWeight(size);  
            point(x, y, z);
        }
    
    }
    

    Btw.: You'll notice that the particles aren't depth sorted correctly, that's because Processing doesn't depth sort geometry, you'll have to do it on your own.

Sign In or Register to comment.