how to draw millions of points efficiently in Processing

oatoat
edited January 2015 in How To...

The following code can draw a 3D matrix of 256x256x256 points. However, it gets very slow even if the matrix is set as just 1x256x256 points.

May I ask how to efficiently draw millions of points in Processing, especially using the peasyCam and controlP5 libraries?

Thanks!

//////////////////////////////////////////////////////////
import processing.opengl.*;

import toxi.processing.*;
import toxi.geom.*;
import toxi.geom.mesh.*;
import toxi.math.*;
ToxiclibsSupport gfx;

import peasy.*; 
PeasyCam cam;
Boolean camOrtho = false;

import controlP5.*;
ControlP5 cp5;

ArrayList<Pt> pts = new ArrayList<Pt>();

//////////////////////////////////////////////////////////
void setup(){
  size(1000, 1000, OPENGL);
  smooth(8);
  frameRate(60);

  gfx=new ToxiclibsSupport(this);

  setupCam();

  for (int r=0; r<256; r++) {
    for (int g=0; g<256; g++) {
      for (int b=0; b<1; b++) {
        color _c = color(r,g,b);
        Vec3D _l = new Vec3D(r/5, g/5, b);
        pts.add(new Pt(_l, _c));
      }
    }
  }

  println("num of points: ", pts.size()); 

}

//////////////////////////////////////////////////////////
void draw(){
  background(120);

  // draw stuff here ...
  //drawPt();
  for (int i=pts.size()-1; i>=0; i--) {
    Pt p = pts.get(i);
    p.display();
  }

  // draw gizmo and bounding box
  drawGizmo();
  drawBbox();

}

class Pt {
  Vec3D loc;
  color c;

  Pt (Vec3D _l, color _c) {
    loc = _l;
    c = _c;
  }

  void display () {
    strokeWeight(1);
    stroke(loc.x*5, loc.y*5, loc.z*5);
    //point(loc.x, loc.y, loc.z);
    gfx.point(loc);
  }

}

//////////////////////////////////////////////////////////
void setupCam () {
  //use the following to avoid pt been shown in fixed size (P2.0b8)
  hint(ENABLE_STROKE_PERSPECTIVE);

  //----- perspective -----
  float fov      = PI/3;  // field of view
  float nearClip = 1;
  float farClip  = 100000;
  float aspect   = float(width)/float(height);  
  perspective(fov, aspect, nearClip, farClip);

  // looking at [0,0,0] from [0,0,150]
  cam = new PeasyCam(this, 0, 0, 0, 150); 

  cam.setWheelScale(0.1); // set mouse wheel sensitivity
}

//////////////////////////////////////////////////////////
void drawGizmo () {
  // draw coordinates gizmo at (0,0,0) 
  strokeWeight(0.5);
  stroke(255,0,0);
  line(0,0,0, 10,0,0);
  stroke(0,255,0);
  line(0,0,0, 0,-10,0);
  stroke(0,0,255);
  line(0,0,0, 0,0,10);
}

//////////////////////////////////////////////////////////
void drawBbox () {
  // draw bounding box of 100x100x100
  // centered at (0,0,0)  
  stroke(0);
  noFill();
  box(100);
}

Answers

  • maybe you can enter all (when they are constant) in a GROUP PShape in setup() and work with this. See tutorial on PShape please

  • Thanks, Chrisir!

    But, the PShape method doesn't seem to improve the performance much ...:-S

    //////////////////////////////////////////////////////////
    import processing.opengl.*;
    
    import toxi.processing.*;
    import toxi.geom.*;
    import toxi.geom.mesh.*;
    import toxi.math.*;
    ToxiclibsSupport gfx;
    
    import peasy.*;
    PeasyCam cam;
    Boolean camOrtho = false;
    
    import controlP5.*;
    ControlP5 cp5;
    
    ArrayList<Pt> pts = new ArrayList<Pt>();
    
    PShape pt, allPts;
    
    
    //////////////////////////////////////////////////////////
    void setup(){
      size(1000, 1000, OPENGL);
      smooth(8);
      frameRate(60);
    
      gfx=new ToxiclibsSupport(this);
    
      setupCam();
    
      // create a shape group
      allPts = createShape(GROUP);
    
      for (int r=0; r<256; r++) {
        for (int g=0; g<256; g++) {
          for (int b=0; b<5; b++) {
            color _c = color(r,g,b);
            Vec3D _l = new Vec3D(r/5, g/5, b/5);
            pts.add(new Pt(_l, _c));
    
           // create a point shape
            pt = createShape(POINT, r/5,g/5,b);
            pt.setStroke(color(r,g,b));
           // add the point shape into the shape group
            allPts.addChild(pt);
          }
        }
      }
    
      println("num of points: ", pts.size());
    
    }
    
    //////////////////////////////////////////////////////////
    void draw(){
      background(120);
    
      // draw stuff here ...
      //drawPt();
    //  for (int i=pts.size()-1; i>=0; i--) {
    //    Pt p = pts.get(i);
    //    p.display();
    //  }
    
      // draw the shape group with all points
      shape(allPts);
    
      // draw gizmo and bounding box
      drawGizmo();
      drawBbox();
    
    }
    
    class Pt {
      Vec3D loc;
      color c;
    
      Pt (Vec3D _l, color _c) {
        loc = _l;
        c = _c;
      }
    
      void display () {
        strokeWeight(1);
        stroke(loc.x*5, loc.y*5, loc.z*5);
        //point(loc.x, loc.y, loc.z);
        gfx.point(loc);
      }
    
    }
    
    //////////////////////////////////////////////////////////
    void setupCam () {
      //use the following to avoid pt been shown in fixed size (P2.0b8)
      hint(ENABLE_STROKE_PERSPECTIVE);
    
      //----- perspective -----
      float fov      = PI/3;  // field of view
      float nearClip = 1;
      float farClip  = 100000;
      float aspect   = float(width)/float(height); 
      perspective(fov, aspect, nearClip, farClip);
    
      // looking at [0,0,0] from [0,0,150]
      cam = new PeasyCam(this, 0, 0, 0, 150);
    
      cam.setWheelScale(0.1); // set mouse wheel sensitivity
    }
    
    //////////////////////////////////////////////////////////
    void drawGizmo () {
      // draw coordinates gizmo at (0,0,0)
      strokeWeight(0.5);
      stroke(255,0,0);
      line(0,0,0, 10,0,0);
      stroke(0,255,0);
      line(0,0,0, 0,-10,0);
      stroke(0,0,255);
      line(0,0,0, 0,0,10);
    }
    
    //////////////////////////////////////////////////////////
    void drawBbox () {
      // draw bounding box of 100x100x100
      // centered at (0,0,0) 
      stroke(0);
      noFill();
      box(100);
    } 
    
  • Hm.... I thought it would.... sry....

  • It's very inefficient to have a separate PShape for each point. Instead you can place all the points in single PShape using POINTS, where each vertex is a point.

      // create a shape
      int dim = 200;
      allPts = createShape();
      allPts.beginShape(POINTS);
      allPts.strokeCap(SQUARE);
      for (int r=0; r<256; r++) {
        for (int g=0; g<256; g++) {
          for (int b=0; b<5; b++) {
            color _c = color(r,g,b);
            Vec3D _l = new Vec3D(r/5, g/5, b/5);
            pts.add(new Pt(_l, _c));
           // add the point shape into the shape
            allPts.stroke(_c);
            allPts.vertex(random(-dim, dim), random(-dim, dim), random(-dim, dim));
          }
        }
      }
      allPts.endShape();
    
  • edited January 2015

    edit...

  • IF you ever got your code working, could you please post a copy of it so that others of us with similar issues could learn from your success?

  • edited April 2015

    Hello !

    I did an example with 128 x 128 x 128, it represents more than 2 millions triangles and I think it's enough.

    256 x 256 x 256 is too big and create an error ;

    200 x 200 x200 works but too slow...

    128 x128 x128 works at 60FPS :)

    The main code is located inside the vertex shader, not in java.

    Here is the code inside the pde :

    import peasy.*; 
    import java.nio.*;
    PeasyCam cam;
    Boolean camOrtho = false;   
    int vertLoc;
    FloatBuffer vertData;
    PShader shader;
    PGL pgl;
    int nbPoint = 128*128*128;
    
    void setup(){
      size(800,600,P3D);
      shader = loadShader("frag.glsl", "vert.glsl");
      setupCam();
    
      float[] vertices = new float[nbPoint * 2 * 3];
      int i,i2=0;
      for(i=0;i<nbPoint;i++){
        vertices[i2++] = 0; //triangle-vertex-id
        vertices[i2++] = i; //point id
    
        vertices[i2++] = 1; //triangle-vertex-id
        vertices[i2++] = i; //point id
    
        vertices[i2++] = 2; //triangle-vertex-id
        vertices[i2++] = i; //point id 
      }
    
      vertData = allocateDirectFloatBuffer(vertices.length);    
      vertData.rewind();
      vertData.put(vertices);
      vertData.position(0); 
    }
    
    void draw(){
       background(0);
    
       pgl = beginPGL();
       shader.bind();
       shader.set("width",parseFloat(width));
       shader.set("height",parseFloat(height));
       shader.set("triangleSize",0.5);
       vertLoc = pgl.getAttribLocation(shader.glProgram, "vertex");
       pgl.enableVertexAttribArray(vertLoc);
       pgl.vertexAttribPointer(vertLoc, 2, PGL.FLOAT, false, 0, vertData);
       pgl.drawArrays(PGL.TRIANGLES, 0, nbPoint * 3);
       pgl.disableVertexAttribArray(vertLoc);
       shader.unbind();  
       endPGL();  
    }
    
    void setupCam () {
      //use the following to avoid pt been shown in fixed size (P2.0b8)
      hint(ENABLE_STROKE_PERSPECTIVE);
    
      //----- perspective -----
      float fov      = PI/3;  // field of view
      float nearClip = 1;
      float farClip  = 100000;
      float aspect   = float(width)/float(height); 
      perspective(fov, aspect, nearClip, farClip);
    
      // looking at [0,0,0] from [0,0,150]
      cam = new PeasyCam(this, 0, 0, 0, 150);
      cam.setWheelScale(0.1); // set mouse wheel sensitivity
    } 
    
    FloatBuffer allocateDirectFloatBuffer(int n) {
      return ByteBuffer.allocateDirect(n * Float.SIZE/8).order(ByteOrder.nativeOrder()).asFloatBuffer();
    }
    

    The code of the vertex shader :

    uniform mat4 transform;
    
    attribute vec4 vertex;
    uniform float width;
    uniform float height;
    uniform float triangleSize;
    
    varying vec4 vertColor;
    
    const vec4 vertex0 = vec4(-0.5,+0.5,0.0,1.0); 
    const vec4 vertex1 = vec4(+0.5,+0.5,0.0,1.0); 
    const vec4 vertex2 = vec4(+0.0,-0.5,0.0,1.0); 
    
    void main() {
    
      float vertexId = vertex.x;
      float pointId = vertex.y;
    
      float n = 128.0;
    
      float offsetX = mod(pointId , n);
      float offsetY = mod((pointId / n) , n);
      float offsetZ = pointId / (n*n) ;
    
      offsetX /= n;
      offsetY /= n;
      offsetZ /= n ;
    
      vec4 color = vec4(offsetX,offsetY,offsetZ,1.0);
    
      offsetX *= width;
      offsetY *= height;
      offsetZ *= width;
    
      offsetX -= width/2f;
      offsetY -= height/2f;
      offsetZ -= width/2f;
    
      vec4 v;
      if(vertexId == 0.0) v = vertex0;
      else if(vertexId == 1.0) v = vertex1;
      else if(vertexId == 2.0) v = vertex2;
    
      v.xyz *= triangleSize;
      v.x += offsetX;
      v.y += offsetY;
      v.z += offsetZ;
    
    
      gl_Position = transform * v;    
      vertColor = color;
    }
    

    and the code of the fragment shader

    #ifdef GL_ES
    precision mediump float;
    precision mediump int;
    #endif
    
    varying vec4 vertColor;
    
    void main() {
      gl_FragColor = vertColor;
    }
    

  • edited April 2015

    If you want that all your point are front of the camera (not rotated), you can modify a bit the vertexShader like that :

    uniform mat4 transform;
    
    attribute vec4 vertex;
    uniform float width;
    uniform float height;
    uniform float triangleSize;
    
    varying vec4 vertColor;
    
    const vec4 vertex0 = vec4(-0.5,+0.5,0.0,1.0); 
    const vec4 vertex1 = vec4(+0.5,+0.5,0.0,1.0); 
    const vec4 vertex2 = vec4(+0.0,-0.5,0.0,1.0); 
    
    void main() {
    
      float vertexId = vertex.x;
      float pointId = vertex.y;
    
      float n = 128.0;
    
      vec4 offset = vec4(0.0,0.0,0.0,1.0);
      offset.x = mod(pointId , n);
      offset.y = mod((pointId / n) , n);
      offset.z = pointId / (n*n) ;
    
      offset.xyz /= n;
    
    
      vec4 color = vec4(offset.xyz,1.0);
    
      offset.x *= width;
      offset.y *= height;
      offset.z *= width;
    
      offset.x -= width/2.0;
      offset.y -= height/2.0;
      offset.z -= width/2.0;
    
      vec4 v;
      if(vertexId == 0.0) v = vertex0;
      else if(vertexId == 1.0) v = vertex1;
      else if(vertexId == 2.0) v = vertex2;
      v.xyz *= triangleSize;
    
      gl_Position = (transform * offset) + v;
    
      vertColor = color;
    } 
    

  • thanks, tlecoz! May I ask how to run your code? I don't know about those non-processing part...

  • edited April 2015 Answer ✓

    Hello ! 1) Copy/paste the pde part in a sketch.

    2) save the sketch somewhere on your disk

    3) in Processing, click on the "sketch" button (in the top-menu), then choose "show sketch folder", the place where you saved the pde will appear on the screen.

    4) create a folder called "data" at the root of your pde (at the same level) and go into it

    5) create a file called "vert.glsl" and copy-paste the vertex shader part.

    6) create a file called "frag.glsl" and copy-paste the fragment shader part.

    7) be sure that your pde and your glsl files are saved

    8) run the sketch

    You should read the shader tutorial to understand exactly what happened in the pde part, and the basic of fragment/vertex shader.

    Here some comments concerning the vertex shader part only (the part you will not find in a tutorial)

    //this uniform is created automaticly by Processing
    // it contains the Peasy cam transformation-matrix
    uniform mat4 transform;
    
    //"vertex" is our buffer created in the pde
    attribute vec4 vertex;
    
    //these variable are defined in the pde
    uniform float width;
    uniform float height;
    uniform float triangleSize;
    
    //I will compute the color in the vertexShader 
    //and share it to the fragment shader, 
    //then I define a varying
    varying vec4 vertColor;
    
    
    //I define 3 constants that represent
    //the normalized coordonnates of a single triangle 
    //
    //each constant is defined only one time for the whole shader,
    const vec4 vertex0 = vec4(-0.5,+0.5,0.0,1.0); 
    const vec4 vertex1 = vec4(+0.5,+0.5,0.0,1.0); 
    const vec4 vertex2 = vec4(+0.0,-0.5,0.0,1.0); 
    
    //it's better to define the triangle like that because it need 2x less memory
    //than if you use a 4 values by vertex (like Processing does by default)
    //and if it require much less memory, you can move much more triangles !
    
    
    
    void main() {
      //step1 : I get the 2 datas from my buffer and store them 
      //in a well-named float. 
      float vertexId = vertex.x;
      float pointId = vertex.y;
    
      //
      //It's easier to read, and actually, for some complex reasons, it's better 
      //for the glsl-compilator too.
    
      //I defined the size of the grid
      float n = 128.0;
    
      //and create a variable "offset" that will contains the position of 
      //the center of each triangle
      vec4 offset = vec4(0.0,0.0,0.0,1.0);
    
      //I compute the X,Y,Z position using modulo and the pointId
      offset.x = mod(pointId , n);
      offset.y = mod((pointId / n) , n);
      offset.z = pointId / (n*n) ;
    
      //I divide my position by the length of my grid,
      // to get a normalized position (between 0 and 1)
      offset.xyz /= n;
    
      //I compute a color from the position, with an alpha of 1.0 (100%) 
      vec4 color = vec4(offset.xyz,1.0);
    
     // I multiply my normalized position by the dimension of the screen
     //to get a correct repartition of my triangles in the space
      offset.x *= width;
      offset.y *= height;
      offset.z *= width;
    
      //I substract the half of the screen-dimension  to my position
      //then the camera will look at the center of my grid 
      //(instead of the top-left corner)
      offset.x -= width/2.0;
      offset.y -= height/2.0;
      offset.z -= width/2.0;
    
      //now that I now where is the center of my triangle,
      //I need to get each vertex position to create my triangle
      vec4 v;
    
      //to do that, I check the value of the vertexId and apply 
      //the matching constant to it
      if(vertexId == 0.0) v = vertex0;
      else if(vertexId == 1.0) v = vertex1;
      else if(vertexId == 2.0) v = vertex2;
    
      //because my vertex-coordonate is normalized, I can set a custom size easily
      v.xyz *= triangleSize;
    
      //finally, I apply the PeasyCam transformation to the position of the center
      //of the triangle, and I add my resized vertex to it
      gl_Position = (transform * offset) + v;
    
      //And I pass the color to the fragment shader
      vertColor = color;
    } 
    

    Good luck !

  • Dear tlecoz, thank you very much! It is very efficient using your approach. I need to learn more about shader coding for this type of heave display and animation task.

Sign In or Register to comment.