How to obtain 3D textured Sphere

edited March 2018 in Library Questions

Hello everybody, I'd like to ask what kind of technique this render is using:

http://cpetry.github.io/NormalMap-Online/

I man, I don't know how that is called, 3D texture? How can I obtain something like this? Even a simple example :)

Any reference or link will be appreciated, thank you

Answers

  • So you mean, specifically, you want to model height distortion on the surface of a 3D sphere, like this?

    Screen Shot 2018-03-19 at 9.36.29 AM

  • Yes, nothing too complex, just have the possibility to increase or decrease the texture level.

    Here is another example but with MaxMSP: https://www.facebook.com/185345661820182/videos/530908980597180/

  • in the Menu File | there are examples

    Here it is Topics | Textures

    there is one for cube and one for sphere

  • Thank you Chrisir, but I know how to apply a play texture to a 3D shape :)

    Have you seen the links I provided? There's more than a plain texture.. the shape is 3D transformed based on texture!

  • edited March 2018

    This is more of a proof of concept than anything else. Basically you can alter the position of a vertex based on the color of the texture at the point where the vertex would be otherwise.

    int cols, rows;
    int scl = 20;
    int w = 2000;
    int h = 1600;
    
    float flying = 0;
    
    float[][] terrain;
    
    PImage img;
    
    void setup() {
      size(600, 600, P3D);
      cols = w / scl;
      rows = h/ scl;
      terrain = new float[cols][rows];
      String http = "http://";
      img = loadImage( http + "www.tfguy44.com/MyIcon1.PNG");
      texture(img);
      noStroke();
    }
    
    float ax,ay;
    
    void draw() {
     lights();
    //  flying -= 0.1;
    
    //  float yoff = flying;
    //  for (int y = 0; y < rows; y++) {
    //    float xoff = 0;
    //    for (int x = 0; x < cols; x++) {
    //      terrain[x][y] = map(noise(xoff, yoff), 0, 1, -100, 100);
    //      xoff += 0.2;
    //    }
    //    yoff += 0.2;
    //  }
    
    
    
      background(64);
      //stroke(255);
      //noFill();
    
      translate(width/2, height/2);
      rotateY(map(mouseX,0,width,-PI,PI));
      scale(0.2);
      translate(-w/2, -h/2);
      for (int y = 0; y < rows-1; y++) {
        beginShape(TRIANGLE_STRIP);
        texture(img);
        for (int x = 0; x < cols; x++) {
          ax = map(x,0,cols,0,img.width);
          ay = map(y,0,rows,0,img.height);
    
          vertex(x*scl, y*scl, map(brightness(img.get(int(ax),int(ay))),0,255,0,200), ax, ay  );
    
          ax = map(x,0,cols,0,img.width);
          ay = map(y+1,0,rows,0,img.height);
    
          vertex(x*scl, (y+1)*scl, map(brightness(img.get(int(ax),int(ay))),0,255,0,200), ax, ay );
          //rect(x*scl, y*scl, scl, scl);
        }
        endShape();
      }
    }
    
  • Thank you TfGuy44, this is great! :D So you think that the "extrusion" is performed with taking the brightness of the img?

    How can I add it to a Sphere instead of a plain 2d rectangle?

    import peasy.*;
    PImage img;
    float x, y, z;
    PeasyCam cam;
    PVector [][] globe;
    int total = 50;
    void setup() {
      size(600, 500, P3D);
    
    
      globe = new PVector[total+1][total+1];
      cam = new PeasyCam(this, 300);
      img = loadImage("12973286_807261599407608_6176856687055456330_o.jpg");
    }
    float ax, ay, az;
    
    void draw() {
      background(50);
      noStroke();
      //lights();
      //fill(255,255,0);
      float r = 200;
    
      for (int i = 0; i < total+1; i++) {
        float lat = map(i, 0, total, 0, PI);
        for (int j = 0; j < total+1; j++) {
          float lon = map(j, 0, total, 0, TWO_PI);
    
          x = r * sin(lat)* cos(lon);
          y = r * sin(lat)* sin(lon);
          z = r * cos(lat);
    
          globe[i][j] = new PVector(x, y, z);
        }
      }
    
      for (int i = 0; i < total; i++) {
    
        beginShape(TRIANGLE_STRIP);
        texture(img);
        for (int j = 0; j < total+1; j++) {
          PVector v = globe[i][j];
          ax = map(j, 0, total, 0, img.width);
          ay = map(j, 0, total, 0, img.height);
          az = map(i, 0, total, 0, img.height);
          vertex(v.x, v.y,  v.z + map(brightness(img.get(int(ax), int(ay))), 0, 255, 0, 100), ax, ay  );
          //point(x, y, z);
          PVector v2 = globe[i+1][j];
          vertex(v2.x, v2.y, v.z + map(brightness(img.get(int(ax), int(ay))), 0, 255, 0, 100), ax, ay );
        }
        endShape();
      }
    }
    

    I tried, but this is soooo wrong ahah

  • edited March 2018 Answer ✓

    I modified the textured sphere code from here to do what you want,

    edit:

    This is 99% of the way there, you just have to fix it so it gets the radius for every 3rd vertex from the next row band of the strip. The triangle strip goes around in bands, it currently leaves gaps between the bands of the strip because the 3rd point of the triangle is not getting the radius from the next band.

    Fixed it

    lowerTween and upperTween used to adjust the bump mapping offset

    The "map.png" I used

    int ptsW, ptsH;
    
    PGraphics pgBump;
    PImage img;
    
    int numPointsW;
    int numPointsH_2pi; 
    int numPointsH;
    
    float[] coorX;
    float[] coorY;
    float[] coorZ;
    float[] multXZ;
    
    float rot = 0, rotX = 0, rotY = 0;
    float drawRad = 10;
    int drawOp  = 20;
    
    boolean keyDown = false;
    
      float lTween = 2.16;
      float uTween = 2.5;
    
    void setup() {
      size(640, 360, P3D);
    
      noStroke();
    
      loadBump();
    
      ptsW=200;
      ptsH=200;
      // Parameters below are the number of vertices around the width and height
      initializeSphere(ptsW, ptsH);
    }
    
    void loadBump() {
      try {
        img=loadImage("moon.png");
      }
      finally {
        if (img == null) {
          img = createImage(width, height, RGB);
          for (int i = 0; i < img.pixels.length; i++) img.pixels[i] = color(130);
        }
      }
      pgBump = createGraphics(img.width, img.height, P2D);
      pgBump.beginDraw();
      pgBump.noStroke();
      pgBump.image(img, 0, 0);
      pgBump.endDraw();
    }
    
    void keyPressed() {
      keyDown = true;
    }
    void keyReleased() {
      keyDown = false;
    }
    
    void editBump() {
      pgBump.beginDraw();
      pgBump.image(img, 0, 0);
      if (mousePressed) {
        if (mouseButton == LEFT)  pgBump.fill(255, drawOp);
        else pgBump.fill(0, drawOp);
        pgBump.ellipse(mouseX/(float)width * img.width, mouseY/(float)height * img.height, drawRad, drawRad);
      }
      pgBump.endDraw();
      img = pgBump.get();
      pgBump.beginDraw();
    
      pgBump.fill(255, 0, 0);
      pgBump.ellipse(mouseX/(float)width * img.width, mouseY/(float)height * img.height, drawRad, drawRad);
      pgBump.endDraw();
    }
    
    void hud() {
      camera(width/2.0, height/2.0, (height/2.0) / tan(PI*30.0 / 180.0), width/2.0, height/2.0, 0, 0, 1, 0);
      fill(255);
      int y = -5, yS = 15;
      text("brush radius(q/e):"+ drawRad + " draw alpha(1/2):" + drawOp, 10, y+=yS);
      text("lowerTween(3/4):"+ nf(lTween,0,2) + " upperTween(5/6):" + nf(uTween,0,2), 10, y+=yS);
    
      text("rotate wasd", 10, y+=yS);
      text("fps " + (int)frameRate, 10, y+=yS);
    }
    
    void keyHandle() {
      if (keyDown) {
        if (key == 'a') rotX += 0.01;
        if (key == 'w') rotY += 0.01;
        if (key == 'd') rotX -= 0.01;
        if (key == 's') rotY -= 0.01;
        if (key == 'q') drawRad -= 0.2;
        if (key == 'e') drawRad += 0.2;
        if (key == '1') drawOp -= 1;
        if (key == '2') drawOp += 1;
        if (key == '3') lTween -= 0.01;
        if (key == '4') lTween += 0.01;
        if (key == '5') uTween -= 0.01;
        if (key == '6') uTween += 0.01;
        drawOp = constrain(drawOp, 1, 255);
      }
    }
    
    void draw() {
      keyHandle();
      editBump();
    
      background(0);
      hud();
      lights();
      camera(
        width/2+map(0, 0, width, -2*width, 2*width), 
        height/2+map(0, 0, height, -height, height), 
        height/2/tan(PI*30.0 / 180.0), 
        width, height/2.0, 0, 
        0, 1, 0);
      pushMatrix();
      translate(width/2, height/2, 0); 
      rotateX(rotX);
      rotateZ(rotY);
    
    
      textureBumpSphere(200, img, pgBump.get(), lTween, uTween);
      popMatrix();
    }
    
    void initializeSphere(int numPtsW, int numPtsH_2pi) {
      // The number of points around the width and height
      numPointsW=numPtsW+1;
      numPointsH_2pi=numPtsH_2pi;  // How many actual pts around the sphere (not just from top to bottom)
      numPointsH=ceil((float)numPointsH_2pi/2)+1;  // How many pts from top to bottom (abs(....) b/c of the possibility of an odd numPointsH_2pi)
    
      coorX=new float[numPointsW];   // All the x-coor in a horizontal circle radius 1
      coorY=new float[numPointsH];   // All the y-coor in a vertical circle radius 1
      coorZ=new float[numPointsW];   // All the z-coor in a horizontal circle radius 1
      multXZ=new float[numPointsH];  // The radius of each horizontal circle (that you will multiply with coorX and coorZ)
    
      for (int i=0; i<numPointsW; i++) {  // For all the points around the width
        float thetaW=i*2*PI/(numPointsW-1);
        coorX[i]=sin(thetaW);
        coorZ[i]=cos(thetaW);
      }
      for (int i=0; i<numPointsH; i++) {  // For all points from top to bottom
        if (int(numPointsH_2pi/2) != (float)numPointsH_2pi/2 && i==numPointsH-1) {  // If the numPointsH_2pi is odd and it is at the last pt
          float thetaH=(i-1)*2*PI/(numPointsH_2pi);
          coorY[i]=cos(PI+thetaH); 
          multXZ[i]=0;
        } else {
          //The numPointsH_2pi and 2 below allows there to be a flat bottom if the numPointsH is odd
          float thetaH=i*2*PI/(numPointsH_2pi);
    
          //PI+ below makes the top always the point instead of the bottom.
          coorY[i]=cos(PI+thetaH); 
          multXZ[i]=sin(thetaH);
        }
      }
    }
    
    void textureBumpSphere(float baseRadius, PImage bump, PImage tex, float lowerTween, float upperTween) { 
      // These are so we can map certain parts of the image on to the shape 
      float changeU=tex.width/(float)(numPointsW); //-1
      float changeV=tex.height/(float)(numPointsH); //-1
      float u=0;  // Width variable for the texture
      float v=0;  // Height variable for the texture
    
    
      beginShape(TRIANGLE_STRIP);
      texture(tex);
      for (int i=0; i<(numPointsH-1); i++) {  // For all the rings but top and bottom
        // Goes into the array here instead of loop to save time
        float coory=coorY[i];
        float cooryPlus=coorY[i+1];
        float multxz=multXZ[i];
        float multxzPlus=multXZ[i+1];
        float radius;
        for (int j=0; j<numPointsW; j++) { // For all the pts in the ring
    
          float pixelBrightness = brightness(bump.get((int)u, (int)v)); // Get the brightnesss from the pixel of bump texture
          radius = baseRadius *  map( pixelBrightness, 0, 255, lowerTween, upperTween); // map the brightness between upper and lower tween scaling to a bumped radius 
          normal(-coorX[j]*multxz, -coory, -coorZ[j]*multxz);
          vertex(coorX[j]*multxz*radius, coory*radius, coorZ[j]*multxz*radius, u, v); // use new bumped radius for placing the vertex
    
          // repeat for the vertex that is 1 triangle_strip band above the last vertex
          pixelBrightness = brightness(bump.get((int)u, (int)(v+changeV)));
          radius = baseRadius *  map(pixelBrightness , 0, 255, lowerTween, upperTween);
          normal(-coorX[j]*multxzPlus, -cooryPlus, -coorZ[j]*multxzPlus);
          vertex(coorX[j]*multxzPlus*radius, cooryPlus*radius, coorZ[j]*multxzPlus*radius, u, v+changeV);
          u+=changeU;
        }
        v+=changeV;
        u=0;
      }
      endShape();
    }
    

    result

    Unfortunately to get results like the video you linked your probably going to want to do this inside of a shader, it would be hundreds-thousands times more efficient than doing it on CPU, or do it half-way on the GPU with mesh tweening.

    You should check out the MeshTweening example in Examples>Demos>Graphics

    edit2: I did a little more on it and added the ability to draw on the bump map in processing. I wish I knew the math to be able to convert from screen coordinates to the surface of the sphere. https://pastebin.com/Dfc4PXyZ

  • Thank you lmccandless this is soooo beautiful!! I will need to study trigonometry to understand this (I can't comprehend anything ahah) but, I'm really impressed, thank you!!!

    I'm out of borders if I ask you to add some comments that describe some functions? It would help me a lot :)

    Than, I'll move to study shader.. so I have a LOT of work to do :D

    Thank you again

  • edited March 2018 Answer ✓

    I added some comments to my previous code post.

    I also made a version that uses textures and elevation maps from NASA of the moon. The previous version recalculated the bumping of the sphere every frame, this version stores the bumped sphere in a PShape and only recalculates it when needed.

    https://github.com/lmccandless/moonBumpMap

  • edited March 2018

    WOW =D> this is absolutely outstanding and astonishing, I will never say enough thank you for this!

    I only hope someday I'll can do something like this and share some of my knowledge with other people too.

    Inspirational

Sign In or Register to comment.