After parsing the contours from a truetype font file, I generated the points that compose the polygon that I want to triangularize.
These points are generated by merging the different holes into one polygon:
Generated Polygon of the letter A:
As you can see i "merged" the holes by picking two points between the inner and outer polygon with the minimum distance.
This polygon breaks my current implementation of the ear clipping algorithm:
static bool
is_point_in_triangle(triangle Triangle, vec2 Point) {
vec2 AB = Triangle.B - Triangle.A;
vec2 BC = Triangle.C - Triangle.B;
vec2 CA = Triangle.A - Triangle.C;
vec2 AP = Point - Triangle.A;
vec2 BP = Point - Triangle.B;
vec2 CP = Point - Triangle.C;
float Cross1 = cross(AB, AP);
float Cross2 = cross(BC, BP);
float Cross3 = cross(CA, CP);
return (Cross1 <= 0.0f) && (Cross2 <= 0.0f) && (Cross3 <= 0.0f);
}
static triangle_list
triangulate_ear_clip(memory_arena* Arena, polygon SimplePolygon) {
triangle_list List = {};
unsigned int TriangleCount = 0;
triangle* Triangles = allocate_array(Arena, triangle, SimplePolygon.Count - 2);
unsigned int* Indices = allocate_array(Arena, unsigned int, SimplePolygon.Count);
int IndexCount = SimplePolygon.Count;
for(int Index = 0; Index < IndexCount; ++Index) {
Indices[Index] = Index;
}
while(IndexCount > 3) {
for(int Index = 0; Index < IndexCount; ++Index) {
int IndexA = Indices[Index];
int IndexB = Indices[Index ? (Index - 1) : (IndexCount - 1)];
int IndexC = Indices[(Index + 1) % IndexCount];
check((IndexA != IndexB) && (IndexA != IndexC) && (IndexC != IndexB));
vec2 A = SimplePolygon.Elements[IndexA];
vec2 B = SimplePolygon.Elements[IndexB];
vec2 C = SimplePolygon.Elements[IndexC];
vec2 AB = B - A;
vec2 AC = C - A;
check((A != B) && (A != C) && (B != C));
if(cross(AB, AC) >= 0.0f) {
bool IsEar = true;
for(int OtherIndex = 0; OtherIndex < (int)SimplePolygon.Count; ++OtherIndex) {
if((OtherIndex != IndexA) && (OtherIndex != IndexB) && (OtherIndex != IndexC)) {
vec2 D = SimplePolygon.Elements[OtherIndex];
if(is_point_in_triangle(triangle_from(B, A, C), D)) {
IsEar = false;
break;
}
}
}
if(IsEar) {
Triangles[TriangleCount++] = triangle_from(B, A, C);
move(Indices + Index, Indices + Index + 1, sizeof(unsigned int) * (IndexCount - Index - 1));
IndexCount--;
break;
}
}
}
}
check(IndexCount == 3);
check(TriangleCount == SimplePolygon.Count - 3);
Triangles[TriangleCount++] = triangle_from(SimplePolygon.Elements[Indices[0]], SimplePolygon.Elements[Indices[1]], SimplePolygon.Elements[Indices[2]]);
List.Count = TriangleCount;
List.Elements = Triangles;
return List;
}
For some reason cross(AB, AC) is always negative and the loop stalls forever.
I can't figure out why this happens since the polygon should be ok to be triangulated.
I also provided a list of the generated points in the second image link.
Even if some types in the provided code are non-standard they should easily be recognizable, feel free to ask otherwise. memory_arena is just a custom allocator i use.
Thanks for your attention.
PS: I dont want to use a library
The problem was that in is_point_in_triangle() the function was considering points on the outline inside the triangle (and then not consider the triangle as an ear).
This breaks because when merging the polygon and the hole, some points overlap.
I fixed this by removing the =: (Cross1 < 0.0f) && (Cross2 < 0.0f) && (Cross3 < 0.0f) and by doing a special check:
vec2 D = SimplePolygon.Elements[OtherIndex];
if((D != A) && (D != B) && (D != C)) {
if(is_point_in_triangle(triangle_from(B, A, C), D)) {
IsEar = false;
break;
}
}
Related
I'm extruding a sine-wave curve into 3d but when rendering, I can see that the normals are not smoothed.
The sine-wave is generated with parametric normals, as follows:
vector<CurvePoint> sineWave(int n, float x0, float y0, float step, float period)
{
vector<CurvePoint> curve;
for (int i = 0; i < n; i++) {
float a = TWO_PI / period;
float x = x0 + i * step;
float y = y0 - sinf(x * a);
float c = cosf(x * a);
auto normal = glm::vec2(a * c, 1) / sqrtf(a * a * c * c + 1);
curve.emplace_back(glm::vec2(x, y), normal);
}
return curve;
}
The extruding method:
void extrude(IndexedVertexBatch<XYZ.N> &batch, const Matrix &matrix, const vector<CurvePoint> &curve, GLenum frontFace, float distance)
{
auto size = curve.size();
if (size > 1 && distance != 0) {
bool cw = ((frontFace == CW) && (distance > 0)) || ((frontFace == CCW) && (distance < 0));
for (auto i = 0; i < size - 1; i++) {
auto &p0 = curve[i].position;
auto &p1 = curve[i + 1].position;
auto normal = matrix.transformNormal(glm::vec3(curve[i].normal, 0));
batch
.addVertex(matrix.transformPoint(p0), normal)
.addVertex(matrix.transformPoint(p1), normal)
.addVertex(matrix.transformPoint(glm::vec3(p1, distance)), normal)
.addVertex(matrix.transformPoint(glm::vec3(p0, distance)), normal);
if (cw) {
batch.addIndices(0, 3, 2, 2, 1, 0);
} else {
batch.addIndices(0, 1, 2, 2, 3, 0);
}
batch.incrementIndices(4);
}
}
}
The rendering (phong-like shading):
How can I obtain smoothed normals?
Stupid me. It was a small bug in the extruding method, which should be like:
void extrude(IndexedVertexBatch<XYZ.N> &batch, const Matrix &matrix, const vector<CurvePoint> &curve, GLenum frontFace, float distance)
{
auto size = curve.size();
if (size > 1 && distance != 0) {
bool cw = ((frontFace == CW) && (distance > 0)) || ((frontFace == CCW) && (distance < 0));
for (auto i = 0; i < size - 1; i++) {
auto &p0 = curve[i].position;
auto &p1 = curve[i + 1].position;
auto normal0 = matrix.transformNormal(glm::vec3(curve[i].normal, 0));
auto normal1 = matrix.transformNormal(glm::vec3(curve[i + 1].normal, 0));
batch
.addVertex(matrix.transformPoint(p0), normal0)
.addVertex(matrix.transformPoint(p1), normal1)
.addVertex(matrix.transformPoint(glm::vec3(p1, distance)), normal1)
.addVertex(matrix.transformPoint(glm::vec3(p0, distance)), normal0);
if (cw) {
batch.addIndices(0, 3, 2, 2, 1, 0);
} else {
batch.addIndices(0, 1, 2, 2, 3, 0);
}
batch.incrementIndices(4);
}
}
}
I am trying to calculate the RGB value of a pixel using the Blinn-Phong formula. For that I use this function:
Material getPixelColor(Ray ray, double min, int index, std::vector<Object*> Objects, std::vector<Object*> lightSources) {
Vector intersectionPoint = ray.getOrigin() + ray.getDirection() * min;
Vector n = Objects.at(index)->getNormalAt(intersectionPoint);
Vector reflectiondirection = ray.getDirection() - n * Vector::dot(ray.getDirection(), n) * 2;
Ray reflectionRay(intersectionPoint, reflectiondirection);
// check if ray intersects any other object;
double minimum = INFINITY;
int count = 0, indx = -1;
for (auto const& obj : Objects) {
double distance = obj->Intersect(reflectionRay);
if (minimum > distance) {
minimum = distance;
indx = count;
}
count++;
}
Material result(0,0,0);
if (recurseDepth >= 5 || indx == -1) {
recurseDepth = 0;
// Check if object is lit for each light source
for (auto const& light : lightSources) {
// Blinn-Phong
Vector lightDirection = (light->getPosition() - intersectionPoint).normalize();
double nl = Vector::dot(n, lightDirection);
nl = nl > 0 ? nl : 0.0;
result = result + (Objects.at(index)->getMaterial() * light->getMaterial() * nl);
}
}
else{
recurseDepth++;
result = result + getPixelColor(reflectionRay, minimum, indx, Objects, lightSources);
}
return result;
}
The result that I get is this:
This is how it was without shading:
I have been trying to find a solution for hours and can't. Am I using the wrong formula?
After a lot of research, I removed the part where it is getting color from other objects:
Material getPixelColor(Ray ray, double min, int index, std::vector<Object*> Objects, std::vector<Object*> lightSources) {
Vector intersectionPoint = ray.getOrigin() + ray.getDirection() * min;
Vector n = Objects.at(index)->getNormalAt(intersectionPoint);
Material result(0,0,0);
// Check if object is lit for each light source
for (auto const& light : lightSources) {
//create a ray to the light and check if there is an object between the two
Vector lightDirection = (light->getPosition() - intersectionPoint).normalize();
Ray lightRay(intersectionPoint, lightDirection);
bool hit = false;
for (auto const& obj : Objects) {
double distance = obj->Intersect(lightRay);
if (INFINITY > distance && distance > 0.0001) {
hit = true;
break;
}
}
if (!hit) {
// Blinn-Phong
double nl = Vector::dot(n, lightDirection);
// clamp nl between 0 and 1
if (nl > 1.0) {
nl = 1.0;
}
else if (nl < 0.0) {
nl = 0.0;
}
result = result + (Objects.at(index)->getMaterial() * nl);
}
}
return result;
}
And so I got the desired result:
I am trying to draw complex 2d polygons in OpenGL. I wrote all my rendering methods with GL_TRIANGLES so I'm not trying to change to GL_TRIANGLE_STRIP or anything like that.
Essentially, I have a list of ordered coordinates and I want to create a polygon from them like this:
The method I was originally using was to create a triangle between the first vertex and the next two and do that until the triangle is betweeen the first and last two vertices. However, on an L shaped polygon as the one above, I get something like this:
As you can see, indexing the vertices this way draws triangles in areas where there should be no triangles. How can I index the vertices with GL_TRIANGLES to get something like the first result? The vertices will be different every single time but are always in clockwise order so I need a generalized approach for any polygon.
Decompose your polygon into triangles or use the stencil buffer method.
You can think of the problem in two stages consisting of turning the polygon into convex sub-polygons, and then triangulate each of the sub-polygons. The algorithm to triangulate a sub polygon (triangulatePoly) is a fairly simple recursive function that takes in a polygon and checks if it has 3 points. If it does, it returns, if not, it creates a triangle from the first 3 points adds it to a list and decrements the polygon by that triangle, leaving you with a list of triangles that comprise the polygon.
The convex sub-polygon algorithm (decomposePoly) is harder to explain as it is quite complicated and so if you want to understand it, it is here.
Finally, here is an implementation, written with OpenGL2 and quite clustered for brevity.
// ######################
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();
}
}
Demo:
In OpenGL only convex polygons can be drawn correctly. As mentioned in an an other answer you can use the Stencil Test buffet to draw a concave polygons. The algorithm is described in detail at
Drawing Filled, Concave Polygons Using the Stencil Buffer
or Drawing Filled, Concave Polygons Using the Stencil Buffer (OpenGL Programming).
Fraw the polygon by the Triangle primitiv type GL_TRIANGLE_FAN. e.g:
1 2
+-----+
| |
| |3 4
| +-----+
| |
| |
+-----------+
0 5
Draw the GL_TRIANGLE_FAN 1 - 2 - 3 - 4 - 5 - 0
Of course it is possible to start with any point e.g. 3 - 4 - 5 - 0 - 1 - 2
The polygon has to be draw twice. The first time the stencil buffer is set, but nothing is drawn in the color buffer at all. The stencil buffer is inverted, every time when a fragment is drawn. If a pixel is covered an even number of times, the value in the stencil buffers is zero; otherwise, it's 1.
At the end the polygon is drawn a 2nd time. This time the color buffer is drawn. The stencil test is enabled and ensures that only the fragments are drawn where the stencil buffer is 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);
I've successfully implemented BVH as described in PBRT. This one although has a slightly huge issue - the traversal looks through ALL nodes that intersect the ray, which is wrong (in terms of performance).
So I ended up optimizing the ray traversal, currently I use the version from Aila & Laine implementation of their "Understanding the efficiency of ray traveral on GPU". First, here is the code:
INLINE bool BVH::Traverse(TriangleWoop* prims, Ray* ray, IntersectResult* result)
{
unsigned int todo[32];
unsigned int todoOffset = 0;
unsigned int nodeNum = 0;
bool hit = false;
IntersectResult tmp = IntersectResult();
*(int*)&tmp.data.w = -1;
float tmin = 2e30f;
float4 origin = ray->origin;
float4 direction = ray->direction;
float4 invdir = rcp(direction);
float tmpx = 0.0f, tmpy = 0.0f;
while(true)
{
while(this->nodes[nodeNum].prim_count == 0)
{
tmpx += 0.01f;
tmpy += 0.001f;
float4 c0v1 = (this->nodes[nodeNum + 1].bounds.minPt - origin) * invdir;
float4 c0v2 = (this->nodes[nodeNum + 1].bounds.maxPt - origin) * invdir;
float4 c1v1 = (this->nodes[this->nodes[nodeNum].above_child].bounds.minPt - origin) * invdir;
float4 c1v2 = (this->nodes[this->nodes[nodeNum].above_child].bounds.maxPt - origin) * invdir;
float4 c0n = f4min(c0v1, c0v2);
float4 c0f = f4max(c0v1, c0v2);
float4 c1n = f4min(c1v1, c1v2);
float4 c1f = f4max(c1v1, c1v2);
float n0 = max(c0n.x, max(c0n.y, c0n.z));
float f0 = min(c0f.x, min(c0f.y, c0f.z));
float n1 = max(c1n.x, max(c1n.y, c1n.z));
float f1 = min(c1f.x, min(c1f.y, c1f.z));
bool child0 = (f0 > 0.0f) && (n0 < f0);
bool child1 = (f1 > 0.0f) && (n1 < f1);
child0 &= (n0 < tmin);
child1 &= (n1 < tmin);
unsigned int nodeAddr = this->nodes[nodeNum].above_child;
nodeNum = nodeNum + 1;
if(child0 != child1)
{
if(child1)
{
nodeNum = nodeAddr;
}
}
else
{
if(!child0)
{
if(todoOffset == 0)
{
goto result;
}
nodeNum = todo[--todoOffset];
}
else
{
if(n1 < n0)
{
swap(nodeNum, nodeAddr);
}
todo[todoOffset++] = nodeAddr;
}
}
}
if(this->nodes[nodeNum].prim_count > 0)
{
for(unsigned int i = this->nodes[nodeNum].prim_offset; i < this->nodes[nodeNum].prim_offset + this->nodes[nodeNum].prim_count; i++)
{
const TriangleWoop* tri = &prims[this->indexes[i]];
if(IntersectRayTriangleWoop(ray, tri, &tmp))
{
if(tmp.data.z > 0.0f && tmp.data.z < result->data.z)
{
tmin = tmp.data.z;
result->data.z = tmp.data.z;
result->data.x = tmp.data.x;
result->data.y = tmp.data.y;
*(int*)&result->data.w = this->indexes[i];
hit = true;
}
}
}
}
if(todoOffset == 0)
{
goto result;
}
nodeNum = todo[--todoOffset];
}
result:
result->data.x = tmpx;
result->data.y = tmpy;
return hit;
}
Technically it's just a standard while-while stack ray-bvh traversal. Now to the main problem, look at next image (viewing sponza from outside), in color you can see how much nodes in BVH has been visited (full red = 100, full yellow = 1100):
Next image shows similar situation inside:
As you can see this is kind of a problem - it just has to traverse much more nodes than it's supposed to. Can someone see something wrong with my code? Any advice is welcomed as I'm stucked with this for few days already and can't think off some solution.
I'm having a problem with edge detection using Sobel operator: it produces too many false edges, effect is shown on pictures below.
I'm using a 3x3 sobel operator - first extracting vertical then horizontal, final output is magnitude of each filter output.
Edges on synthetic images are extracted properly but natural images produce have too many false edges or "noise" even if image is preprocessed by applying blur or median filter.
What might be cause of this? Is it implementation problem (then: why synthetic images are fine?) or I need to do some more preprocessing?
Original:
Output:
code:
void imageOp::filter(image8* image, int maskSize, int16_t *mask)
{
if((image == NULL) || (maskSize/2 == 0) || maskSize < 1)
{
if(image == NULL)
{
printf("filter: image pointer == NULL \n");
}
else if(maskSize < 1)
{
printf("filter: maskSize must be greater than 1\n");
}
else
{
printf("filter: maskSize must be odd number\n");
}
return;
}
image8* fImage = new image8(image->getHeight(), image->getWidth());
uint16_t sum = 0;
int d = maskSize/2;
int ty, tx;
for(int x = 0; x < image->getHeight(); x++) //
{ // loop over image
for(int y = 0; y < image->getWidth(); y++) //
{
for(int xm = -d; xm <= d; xm++)
{
for(int ym = -d; ym <= d; ym++)
{
ty = y + ym;
if(ty < 0) // edge conditions
{
ty = (-1)*ym - 1;
}
else if(ty >= image->getWidth())
{
ty = image->getWidth() - ym;
}
tx = x + xm;
if(tx < 0) // edge conditions
{
tx = (-1)*xm - 1;
}
else if(tx >= image->getHeight())
{
tx = image->getHeight() - xm;
}
sum += image->img[tx][ty] * mask[((xm+d)*maskSize) + ym + d];
}
}
if(sum > 255)
{
fImage->img[x][y] = 255;
}
else if(sum < 0)
{
fImage->img[x][y] = 0;
}
else
{
fImage->img[x][y] = (uint8_t)sum;
}
sum = 0;
}
}
for(int x = 0; x < image->getHeight(); x++)
{
for(int y = 0; y < image->getWidth(); y++)
{
image->img[x][y] = fImage->img[x][y];
}
}
delete fImage;
}
This appears to be due to a math error somewhere in your code. To follow on my comment, this is what I get when I run your image through a Sobel operator here (edge strength is indicated by brightness of the output image):
I used a GLSL fragment shader to produce this:
precision mediump float;
varying vec2 textureCoordinate;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 topLeftTextureCoordinate;
varying vec2 topRightTextureCoordinate;
varying vec2 bottomTextureCoordinate;
varying vec2 bottomLeftTextureCoordinate;
varying vec2 bottomRightTextureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
float bottomLeftIntensity = texture2D(inputImageTexture, bottomLeftTextureCoordinate).r;
float topRightIntensity = texture2D(inputImageTexture, topRightTextureCoordinate).r;
float topLeftIntensity = texture2D(inputImageTexture, topLeftTextureCoordinate).r;
float bottomRightIntensity = texture2D(inputImageTexture, bottomRightTextureCoordinate).r;
float leftIntensity = texture2D(inputImageTexture, leftTextureCoordinate).r;
float rightIntensity = texture2D(inputImageTexture, rightTextureCoordinate).r;
float bottomIntensity = texture2D(inputImageTexture, bottomTextureCoordinate).r;
float topIntensity = texture2D(inputImageTexture, topTextureCoordinate).r;
float h = -topLeftIntensity - 2.0 * topIntensity - topRightIntensity + bottomLeftIntensity + 2.0 * bottomIntensity + bottomRightIntensity;
float v = -bottomLeftIntensity - 2.0 * leftIntensity - topLeftIntensity + bottomRightIntensity + 2.0 * rightIntensity + topRightIntensity;
float mag = length(vec2(h, v));
gl_FragColor = vec4(vec3(mag), 1.0);
You don't show your mask values, which I assume contain the Sobel kernel. In the above code, I've hardcoded the calculations performed against the red channel of each pixel in a 3x3 Sobel kernel. This is purely for performance on my platform.
One thing I don't notice in your code (again, I may be missing it like I did the sum being set back to 0) is the determination of the magnitude of the vector for the two portions of the Sobel operator. I'd expect to see a square root operation in there somewhere, if that was present.