Texture on a deformed plane is "bent". How do I fix it?

edited August 2016 in Questions about Code

Here is a screenshot

The texture image is just a simple ellipse (link). As you can see as soon as I wrap it on the deformed plane it looks broken

Here's my code. pg is just an image with the ellipse.

  textureMode(NORMAL);
  beginShape(TRIANGLE_FAN);

  texture(pg);
  vertex(30,20, 0, 0);
  vertex(680,62, 1, 0);
  vertex(629,478, 1, 1);
  vertex(158,499, 0, 1);

  endShape();

IMHO I was expecting a result like my mockup (link). What am I doing wrong here?

Answers

  • Try rendering it as a QUAD instead of a TRIANGLE_FAN.

    Try Using OPENGL render instead of P3D.

    YMMV.

  • I changed it to beginShape(QUAD); and the renderer to OPENGL instead of P2D

    Still bent

  • known problem, and not just a processing problem: https://en.wikipedia.org/wiki/Texture_mapping#Perspective_correctness

    (i thought this was better in later versions and with opengl. which version are you using?)

    it also helps having more, smaller quads, if that's an option. and try using the 3d version of vertex() with z = 0

  • edited July 2015

    It's just as bad with z=0.

    I'm using processing 2.2.1

    My system has OpenGL 4.4 installed.

    This affine texture mapping problem seems like something from the 90s. I can't believe I ran into this bug like this. Is there really no fix in processing? Maybe there's a texture mode I can switch.

  • I tried all of the above and various hint () and smooth() settings with no luck.

    I'm sure I've fixed it before, maybe using raw opengl.

    Oddly, the textured cube example used to suffer from the same problem but now doesn't. I think that may be using javascript mode now though.

  • Going through your history I did find this thread: http://forum.processing.org/two/discussion/comment/32599/#Comment_32599

    The last post talks about fixing it by not using the processing libraries and instead using a java library for rendering. The fix is kind of headscratchingly odd. I'd much rather have a "native" solution that doesn't use external libraries.

  • Answer ✓

    The link you provide is not the solution to your problem because it distorts the image that you want to use as a texture.

    In 3D the quad is not 'distorted' rather a perspective transformation is applied to the quad so the texture is unaffected. In 2D the quad is deformed so you get distortion along the diagonal.

    The only way I have found to solve this problem is to slice the quad into smaller quads. I also use TRIANGLE_STRIP because it makes the code more succinct.

    This is the output from the program below

    compo

    The first image is using the OpenGL QUAD, the second uses OpenGL TRIANGLE_STRIP (2 triangles) and the last one uses a computed grid to give 200 triangles.

    If to want to animate the shape then you can call the setCorners method as many times as you like.

    The sketch has 2 tabs

    Main tab code

     // Press keys 1, 2 and 3 for the different renders
    QuadGrid qgrid;
    
    PGraphics img;
    int state = 1;
    float[] vx = { 
      30, 377, 343, 73
    };
    float[] vy = { 
      10, 23, 333, 389
    };
    
    public void setup() {
      size(400, 400, P2D);
      makeImage();
      // image, nbr slices accross, nbr slices down
      qgrid = new QuadGrid(img, 10, 10);
      //  Vertices must be in order TL, TR, BR, BL
      qgrid.setCorners(vx[0], vy[0], vx[1], vy[1], vx[2], vy[2], vx[3], vy[3] );
      noStroke();
    }
    
    public void makeImage() {
      img = createGraphics(400, 400, JAVA2D);
      img.beginDraw();
      img.background(255, 255, 200);
      img.fill(255, 200, 200);
      img.stroke(168, 0, 0);
      int c = 0;
      for (int x = 0; x < img.width; x += 40) {
        for (int y = 0; y < img.height; y += 40)
          if ( c++ % 2 == 0 ) img.rect(x, y, 40, 40);
        c++;
      }
      img.endDraw();
    }
    
    public void draw() {
      background(0);
      switch(state) {
      case 1:
        drawQuad();
        break;
      case 2:
        drawTriStrip();
        break;
      case 3:
        qgrid.drawGrid(this);
        break;
      }
    }
    
    // Uses OpenGL QUAD
    void drawQuad() {
      textureMode(NORMAL);
      beginShape(QUAD);
      texture(img);
      vertex(vx[0], vy[0], 0, 0);
      vertex(vx[1], vy[1], 1, 0);
      vertex(vx[2], vy[2], 1, 1);
      vertex(vx[3], vy[3], 0, 1);
      endShape();
    }
    
    // Uses OpenGL TRIANGLE_STRIP
    void drawTriStrip() {
      textureMode(NORMAL);
      beginShape(TRIANGLE_STRIP);
      texture(img);
      vertex(vx[0], vy[0], 0, 0);
      vertex(vx[1], vy[1], 1, 0);
      vertex(vx[3], vy[3], 0, 1);
      vertex(vx[2], vy[2], 1, 1);
      endShape();
    }
    
    public void keyReleased() {
      if (key >= '1' && key <= '3')
        state = (int)(key - '0');
      if (key == 's')
        saveFrame("grid_" + state + ".jpg");
    }
    

    QuadGrid.java tab

    // There is no need to modify the code in this tab.
    import processing.core.*;
    
    public final class QuadGrid {
    
      private final PImage img;
      private final int nbrCols, nbrRows;
      private final VPoint[][] vp;
    
      // Prevent use of default constructor
      private QuadGrid() {
        img = null;
        nbrCols = nbrRows = 0;
        vp = null;
      };
    
      /**
       * 
       * @param img the image must not be null
       * @param nbrXslices must be >= 1
       * @param nbrYslices must be >= 1
       */
      public QuadGrid(PImage img, int nbrXslices, int nbrYslices) {
        this.img = img;
        nbrCols = (nbrXslices >= 1) ? nbrXslices : 1;
        nbrRows = (nbrYslices >= 1) ? nbrYslices : 1;
        if (img != null) {
          vp = new VPoint[nbrCols+1][nbrRows+1];
          // Set corners so top-left is [0,0] and bottom-right is [image width, image height]
          float deltaU = 1.0f/nbrCols;
          float deltaV = 1.0f/nbrRows;
          for (int col = 0; col <= nbrCols; col++)
            for (int row = 0; row <= nbrRows; row++)
              vp[col][row] = new VPoint(col * deltaU, row * deltaV);
          setCorners(0, 0, img.width, 0, img.width, img.height, 0, img.height);
        } else
          vp = null;
      }
    
      /**
       * Calculate all the quad coordinates
       *  Vetices in order TL, TR, BR, BL
       */
      public void setCorners(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) {
        if (vp == null) return;
        // Do outer corners
        vp[0][0].setXY(x0, y0);
        vp[nbrCols][0].setXY(x1, y1);
        vp[nbrCols][nbrRows].setXY(x2, y2);
        vp[0][nbrRows].setXY(x3, y3);
        // Top row
        float deltaX = (x1 - x0) / nbrCols;
        float deltaY = (y1 - y0) / nbrRows;
        for (int col = 0; col <= nbrCols; col++)
          vp[col][0].setXY(x0 + col * deltaX, y0 + col * deltaY); 
        // Bottom row
        deltaX = (x2 - x3) / nbrCols;
        deltaY = (y2 - y3) / nbrRows;
        for (int col = 0; col <= nbrCols; col++)
          vp[col][nbrRows].setXY(x3 + col * deltaX, y3 + col * deltaY);
        // Fill each column in the grid in turn
        for (int col = 0; col <= nbrCols; col++) {
          for (int row = 1; row < nbrRows; row++) {
            VPoint vpF = vp[col][0];
            VPoint vpL = vp[col][nbrRows];
            deltaX = (vpL.x - vpF.x) / nbrRows;
            deltaY = (vpL.y - vpF.y) / nbrRows;
            vp[col][row].setXY(vpF.x + row * deltaX, vpF.y + row * deltaY);
          }
        }
      }
    
      public void drawGrid(PApplet app) {
        if (vp == null) return;
        app.textureMode(PApplet.NORMAL);
        app.noStroke(); // comment out this line to see triangles
        for (int row = 0; row < nbrRows; row++) {
          app.beginShape(PApplet.TRIANGLE_STRIP);
          app.texture(img);
          for (int col = 0; col <= nbrCols; col++) {
    
            VPoint p0 = vp[col][row];
            VPoint p1 = vp[col][row+1];
            app.vertex(p0.x, p0.y, p0.u, p0.v);
            app.vertex(p1.x, p1.y, p1.u, p1.v);
          }
          app.endShape();
        }
      }
    
      private class VPoint {
        public float x = 0;
        public float y = 0;
        public float u;
        public float v;
    
    
        public VPoint(float u, float v) {
          this.u = u;
          this.v = v;
        }
    
        public void setXY(float x, float y) {
          this.x = x;
          this.y = y;
        }
      }
    }
    
  • quark that is a thing of beauty.

    Thank you very much for this. It fixed everything and gave me some fine ideas of where to take this program. :-bd

  • Well done! This one should be included in some library in the default code :)

Sign In or Register to comment.