【问题标题】:Getting errors when trying to draw complex polygons with triangles in OpenGL尝试在 OpenGL 中使用三角形绘制复杂多边形时出错
【发布时间】:2020-01-19 04:03:19
【问题描述】:

我正在尝试在 OpenGL 中绘制复杂的二维多边形。我用 GL_TRIANGLES 编写了所有渲染方法,所以我不想更改为 GL_TRIANGLE_STRIP 或类似的东西。

基本上,我有一个有序坐标列表,我想从它们创建一个多边形,如下所示:

我最初使用的方法是在第一个顶点和接下来的两个顶点之间创建一个三角形,并这样做直到三角形位于第一个和最后两个顶点之间。但是,在上面的 L 形多边形上,我得到这样的结果:

如您所见,以这种方式索引顶点会在不应该有三角形的区域绘制三角形。如何使用 GL_TRIANGLES 索引顶点以获得类似于第一个结果的结果?顶点每次都会不同,但总是按顺时针顺序排列,所以我需要一种适用于任何多边形的通用方法。

【问题讨论】:

    标签: opengl lwjgl


    【解决方案1】:

    Decompose你的多边形变成三角形或使用stencil buffer method

    【讨论】:

      【解决方案2】:

      您可以将问题分为两个阶段,包括将多边形转换为凸子多边形,然后对每个子多边形进行三角剖分。对子多边形 (triangulatePoly) 进行三角剖分的算法是一个相当简单的递归函数,它接受一个多边形并检查它是否有 3 个点。如果是,则返回,如果不是,则从前 3 个点创建一个三角形,将其添加到一个列表中,并按该三角形递减多边形,为您留下一个组成多边形的三角形列表。

      凸子多边形算法 (decomposePoly) 很难解释,因为它相当复杂,所以如果你想理解它,那就是 here

      最后,这是一个用 OpenGL2 编写的实现,并且为了简洁起见非常集群化。

      // ######################
      
      public class Point {
      public float x;
      public float y;
      
      public Point(float _x, float _y) {
          x = _x;
          y = _y;
      }
      
      public static float area(Point a, Point b, Point c) {
          return (((b.x - a.x)*(c.y - a.y))-((c.x - a.x)*(b.y - a.y)));
      }
      
      public static boolean left(Point a, Point b, Point c) {
          return area(a, b, c) > 0;
      }
      
      public static boolean leftOn(Point a, Point b, Point c) {
          return area(a, b, c) >= 0;
      }
      
      public static boolean rightOn(Point a, Point b, Point c) {
          return area(a, b, c) <= 0;
      }
      
      
      public static boolean right(Point a, Point b, Point c) {
          return area(a, b, c) < 0;
      }
      
      public static float sqdist(Point a, Point b) {
          float dx = b.x - a.x;
          float dy = b.y - a.y;
          return dx * dx + dy * dy;
      }
      } 
      
      // ######################
      
      import java.util.Vector;
      public class Polygon extends Vector<Point> {
              @Override
              public Point get(int i)  {
                  // hacky way of getting the modulo
                  return super.get(((i % this.size()) + this.size()) % this.size());
              }
      }
      
      // ######################
      
      import org.lwjgl.*;
      import org.lwjgl.glfw.*;
      import org.lwjgl.opengl.*;
      import org.lwjgl.system.*;
      
      import java.nio.*;
      
      import static org.lwjgl.glfw.Callbacks.*;
      import static org.lwjgl.glfw.GLFW.*;
      import static org.lwjgl.opengl.GL11.*;
      import static org.lwjgl.system.MemoryStack.*;
      import static org.lwjgl.system.MemoryUtil.*;
      
      import java.util.Collections;
      import java.util.Vector;
      
      public class DecomposePolyExample {
      
          private long window;
      
          private int WIDTH = 300;
          private int HEIGHT = 300;
      
          private float mouse_x = WIDTH / 2;
          private float mouse_y = HEIGHT / 2;
      
          private Polygon incPoly = new Polygon();
      
          private Vector<Polygon> polys = new Vector<Polygon>();
          private Vector<Polygon> tris = new Vector<Polygon>();
          private Vector<Point> steinerPoints = new Vector<Point>();
          private Vector<Point> reflexVertices = new Vector<Point>();
      
          private boolean polyComplete = false;
      
          public void run() {
              System.out.println("Hello LWJGL" + Version.getVersion() + "!");
      
              init();
              loop();
      
              // Free the window callbacks and destroy the window
              glfwFreeCallbacks(window);
              glfwDestroyWindow(window);
      
              // Terminate GLFW and free the error callback
              glfwTerminate();
              glfwSetErrorCallback(null).free();
          }
      
          private void init() {
      
              // Setup and error callback. The default implementation
              // will print the error message in System.err.
              GLFWErrorCallback.createPrint(System.err).set();
      
              // Initialize GLFW. Most GLFW functions will not work before doing this.
              if (!glfwInit()) {
                  throw new IllegalStateException("Unable to initialize GLFW");
              }
      
              // Create the window
              window = glfwCreateWindow(WIDTH, HEIGHT, "Hello World!", NULL, NULL);
              if (window == NULL) {
                  throw new RuntimeException("Failed to create the GLFW window");
              }
      
              // Setup a key callback. It will be called every time a key is pressed, repeated or released.
              glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
                  if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
                      glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
                  }
              });
      
              glfwSetCursorPosCallback(window, (window, x, y) -> {
                  mouse_x = (float)x;
                  mouse_y = HEIGHT - (float)y;
              });
      
              glfwSetMouseButtonCallback(window, (window, button, action, mods) -> {
                  if (action != GLFW_PRESS){
                      return;
                  }
                  int lClick = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT);
                  if (lClick == GLFW_PRESS)
                  {
                      Point p = new Point(mouse_x, mouse_y);
                      incPoly.add(p);
                  }
                  int rClick = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT);
                  if (rClick == GLFW_PRESS)
                  {
                      polyComplete = true;
                      incPoly = makeCCW(incPoly);
                      decomposePoly(incPoly);
                      triangulatePoly(polys); 
                  }
              });
      
              // Make the OpenGL context current
              glfwMakeContextCurrent(window);
              // Enable v-sync
              glfwSwapInterval(1);
      
              // Make the window visible
              glfwShowWindow(window);
          }
      
          public Point toNDC(Point p) {
              float x = 2*p.x / WIDTH - 1;
              float y = 2*p.y / HEIGHT - 1;
              return new Point(x, y);
          }
      
          public Polygon makeCCW(Polygon poly) {
              int br = 0;
      
              // find bottom right point
              for (int i = 1; i < poly.size(); ++i) {
                  if (poly.get(i).y < poly.get(br).y || (poly.get(i).y == poly.get(br).y && poly.get(i).x > poly.get(br).x)) {
                      br = i;
                  }
              }
      
              // reverse poly if clockwise
              if (!Point.left(poly.get(br - 1), poly.get(br), poly.get(br + 1))) {
                  Collections.reverse(poly);
              }
             return poly;
          }
      
          public boolean isReflex(Polygon poly, int i) {  
              return Point.right(poly.get(i - 1), poly.get(i), poly.get(i + 1));
          }
      
          public boolean eq(float a, float b) {
              return Math.abs(a - b) <= 1e-8;
          }
      
          Point intersection(Point p1, Point p2, Point q1, Point q2) {
              Point i = new Point(0,0);
              float a1, b1, c1, a2, b2, c2, det;
              a1 = p2.y - p1.y;
              b1 = p1.x - p2.x;
              c1 = a1 * p1.x + b1 * p1.y;
              a2 = q2.y - q1.y;
              b2 = q1.x - q2.x;
              c2 = a2 * q1.x + b2 * q1.y;
              det = a1 * b2 - a2*b1;
              if (!eq(det, 0)) { // lines are not parallel
                  i.x = (b2 * c1 - b1 * c2) / det;
                  i.y = (a1 * c2 - a2 * c1) / det;
              }
              return i;
          }
      
          public void decomposePoly(Polygon poly) {
              Point upperInt = new Point(0,0);
              Point lowerInt = new Point(0,0);
              Point p = new Point(0,0);
              Point closestVert = new Point(0,0);
      
              float upperDist, lowerDist, d, closestDist;
              int upperIndex = 0;
              int lowerIndex = 0;
              int closestIndex = 0;
              Polygon lowerPoly = new Polygon();
              Polygon upperPoly = new Polygon();
              for (int i = 0; i < poly.size(); ++i) {
                  if (isReflex(poly, i)) {
                      reflexVertices.add(poly.get(i));
                      upperDist = lowerDist = Float.MAX_VALUE;
                      for (int j = 0; j < poly.size(); ++j) {
                          if (Point.left(poly.get(i - 1), poly.get(i), poly.get(j))
                                  && Point.rightOn(poly.get(i - 1), poly.get(i), poly.get(j - 1))) { // if line intersects with an edge
                              p = intersection(poly.get(i - 1), poly.get(i), poly.get(j), poly.get(j - 1)); // find the point of intersection
                              if (Point.right(poly.get(i + 1), poly.get(i), p)) { // make sure it's inside the poly
                                  d = Point.sqdist(poly.get(i), p);
                                  if (d < lowerDist) { // keep only the closest intersection
                                      lowerDist = d;
                                      lowerInt = p;
                                      lowerIndex = j;
                                  }
                              }
                          }
                          if (Point.left(poly.get(i + 1), poly.get(i), poly.get(j + 1))
                                  && Point.rightOn(poly.get(i + 1), poly.get(i), poly.get(j))) {
                              p = intersection(poly.get(i + 1), poly.get(i), poly.get(j), poly.get(j + 1));
                              if (Point.left(poly.get(i - 1), poly.get(i), p)) {
                                  d = Point.sqdist(poly.get(i), p);
                                  if (d < upperDist) {
                                      upperDist = d;
                                      upperInt = p;
                                      upperIndex = j;
                                  }
                              }
                          }
                      }
      
                      // if there are no vertices to connect to, choose a point in the middle
                      if (lowerIndex == (upperIndex + 1) % poly.size()) {
                          p.x = (lowerInt.x + upperInt.x) / 2;
                          p.y = (lowerInt.y + upperInt.y) / 2;
                          steinerPoints.add(p);
      
                          if (i < upperIndex) {
                              for (int j = i; j < upperIndex + 1; j++) {
                                  lowerPoly.add(poly.get(j));
                              }
                              lowerPoly.add(p);
                              upperPoly.add(p);
                              if (lowerIndex != 0) {
                                  for (int j = lowerIndex; j < poly.size(); j++) {
                                      upperPoly.add(poly.get(j));
                                  }
                              }
                              for (int j = 0; j < i + 1; j++) {
                                  upperPoly.add(poly.get(j));
                              }
      
                          } else {
                              if (i != 0) {
                                  for (int j = 0; j < i; j++) {
                                      lowerPoly.add(poly.get(j));
                                  }
                              }
                              for (int j = 0; j < upperIndex + 1; j++) {
                                  lowerPoly.add(poly.get(j));
                              }
                              lowerPoly.add(p);
                              upperPoly.add(p);
                              for (int j = lowerIndex; j < i + 1; j++) {
                                  upperPoly.add(poly.get(j));
                              }
                          }
                      } else {
                          // connect to the closest point within the triangle
      
                          if (lowerIndex > upperIndex) {
                              upperIndex += poly.size();
                          }
                          closestDist = Float.MAX_VALUE;
                          for (int j = lowerIndex; j <= upperIndex; ++j) {
                              if (Point.leftOn(poly.get(i - 1), poly.get(i), poly.get(j))
                                      && Point.rightOn(poly.get(i + 1), poly.get(i), poly.get(j))) {
                                  d = Point.sqdist(poly.get(i), poly.get(j));
                                  if (d < closestDist) {
                                      closestDist = d;
                                      closestVert = poly.get(j);
                                      closestIndex = j % poly.size();
                                  }
                              }
                          }
                          if (i < closestIndex) {
                              for (int j = i; j < closestIndex + 1; j++) {
                                  lowerPoly.add(poly.get(j));
                              }
      
                              if (closestIndex != 0) {
                                  for (int j = closestIndex; j < poly.size(); j++) {
                                      upperPoly.add(poly.get(j));
                                  }
                              }
                              for (int j = 0; j < i + 1; j++) {
                                  upperPoly.add(poly.get(j));
                              }
                          } else {
                              if (i != 0) {
                                  for (int j = i; j < poly.size(); j++) {
                                      lowerPoly.add(poly.get(j));
                                  }
                              }
                              for (int j = 0; j < closestIndex + 1; j++) {
                                  lowerPoly.add(poly.get(j));
                              }
                              for (int j = closestIndex; j < i + 1; j++) {
                                  upperPoly.add(poly.get(j));
                              }
                          }
                      }
                      // solve smallest poly first
                      if (lowerPoly.size() < upperPoly.size()) {
                          decomposePoly(lowerPoly);
                          decomposePoly(upperPoly);
                      } else {
                          decomposePoly(upperPoly);
                          decomposePoly(lowerPoly);
                      }
                      return;
                  }
              }
              polys.add(poly);
          }
      
      
          public void triangulatePoly(Vector<Polygon> polys) {
              for (int i = 0; i < polys.size(); i++) {
                  Polygon poly = polys.get(i);
                  // return if poly is a triangle
                  if (poly.size() == 3) {
                      tris.add(poly);
                      polys.remove(i);
                  }
                  else {
                      // split poly into new triangle and poly
                      Polygon tri = new Polygon();
                      for (int j = 0; j < 3; j++) {
                          tri.add(poly.get(j));
                      }
                      Polygon newPoly = new Polygon();
                      newPoly.add(poly.get(0));
                      for (int k = 2; k < poly.size(); k++) {
                          newPoly.add(poly.get(k));
                      }
                      polys.set(i, newPoly);
                      tris.add(tri);
                  }
              }
              if (polys.size() != 0) {
                  triangulatePoly(polys);
              }
          }
      
          private void loop() {
              GL.createCapabilities();
      
              // Set the clear color
              glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
      
              while (!glfwWindowShouldClose(window)) {
                  glClear(GL_COLOR_BUFFER_BIT); // clear the framebuffer
                  System.out.println(tris.size());
                  if (!polyComplete) {
                      GL11.glBegin(GL_LINE_STRIP);
                      for (int i = 0; i < incPoly.size(); ++i) {
                          Point p_ndc = toNDC(incPoly.get(i));
                          GL11.glVertex2f(p_ndc.x, p_ndc.y);
                      }
                      GL11.glEnd();
                  } else {
                      // polygon outlines (thin)
                      for (int i = 0; i < tris.size(); ++i) {
                          GL11.glBegin(GL_LINE_LOOP);
                          for (int j = 0; j < tris.get(i).size(); ++j) {
                              Point p_ndc = toNDC(tris.get(i).get(j));
                              GL11.glVertex2f(p_ndc.x, p_ndc.y);
                          }
                          GL11.glEnd();
                      }
      
                      GL11.glBegin(GL_LINE_LOOP);
                      for (int i = 0; i < incPoly.size(); ++i) {
                          Point p_ndc = toNDC(incPoly.get(i));
                          GL11.glVertex2f(p_ndc.x, p_ndc.y);
                      }
                      GL11.glEnd();
                  }
      
                  glfwSwapBuffers(window); // swap the color buffers
      
                  // Poll for window events. The key callback above will only be
                  // invoked during this call.
                  glfwPollEvents();
              }
          }
      
          public static void main(String[] args) {
              new DecomposePolyExample().run();
          }
      }
      

      演示:

      【讨论】:

      • 显然,但是对于任何多边形,您如何确定您需要从点 0 开始?如果索引缓冲区以 1 开头,则问题仍然存在。
      • 我不确定我是否理解。您是在询问顶点的具体顺序吗?因为它没有任何区别; 0 2 1 与 2 1 0 相同。请发布您的索引缓冲区。
      • 我正在尝试为任何多边形自动生成这些顶点。如原始问题所示,如何自动生成这些索引并确保 OpenGL 不会在多边形之外绘制?我可以手动创建有效的索引缓冲区,但问题是如何自动创建。
      • 哦!我很抱歉。因此,您可以使用一种算法将多边形分解为凸多边形。应该有另一种算法来正确地对每个子网格进行三角剖分。
      【解决方案3】:

      在 OpenGL 中,只能正确绘制凸多边形。如另一个答案中所述,您可以使用Stencil Test自助餐来绘制凹多边形。算法详细描述在
      Drawing Filled, Concave Polygons Using the Stencil Buffer
      Drawing Filled, Concave Polygons Using the Stencil Buffer (OpenGL Programming)

      通过Triangle primitiv 类型GL_TRIANGLE_FAN 绘制多边形。例如:

      1       2
       +-----+
       |     |
       |     |3     4
       |     +-----+
       |           |
       |           |
       +-----------+
      0             5
      

      GL_TRIANGLE_FAN 1 - 2 - 3 - 4 - 5 - 0
      当然可以从任何点开始,例如3 - 4 - 5 - 0 - 1 - 2

      多边形必须绘制两次。第一次设置模板缓冲区,但在颜色缓冲区中根本没有绘制任何内容。每次绘制片段时,模板缓冲区都会反转。如果一个像素被偶数次覆盖,则模板缓冲区中的值为零;否则为 1。
      最后,第二次绘制多边形。这次绘制了颜色缓冲区。启用模板测试并确保仅在模板缓冲区为 1 的位置绘制片段:

      GL11.glDisable(GL11.GL_DEPTH_TEST);
      GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
      
      GL11.glEnable(GL11.GL_STENCIL_TEST);
      GL11.glColorMask(false, false, false, false);
      GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_INVERT);
      GL11.glStencilFunc(GL11.GL_ALWAYS, 0x1, 0x1);
      
      // draw the polygon the 1st time: set the stencil buffer
      // GL_TRIANGLE_FAN: 1 - 2 - 3 - 4 - 5 - 0
      
      GL11.glColorMask(true, true, true, true);
      GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
      GL11.glStencilFunc(GL11.GL_EQUAL, 0x1, 0x1);
      
      // draw the polygon the 2nd time: draw to color buffer by using the stencil test
      // GL_TRIANGLE_FAN: 1 - 2 - 3 - 4 - 5 - 0
      
      GL11.glDisable(GL11.GL_STENCIL_TEST);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-05-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-08
        • 2015-05-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多