I'm working on a minimal ray tracer in C, and I've written a ray tracer a little while ago so I understand the theory behind them, just wanted to do a rewrite for cleanup purposes.
I have the necessary elements for a ray tracer, and nothing more. I've written triangle intersection, transforming pixel space coordinates to NDC (with aspect ratio and FOV accounted for), and writing out the frame buffer.
However, it does not work as expected. The image is entirely black when it should be rendering a single triangle. I've tested writing a single test pixel, and it works fine so I know it isn't an issue with the image writing code.
I've double and triple-checked the code behind the math, and it looks fine to me. Intersection code is basically a duplicate of the source code in the original Moller-Trumbore paper:
/* ray triangle intersection */
bool ray_triangle_intersect(double orig[3], double dir[3], double vert0[3],
double vert1[3], double vert2[3], double* t, double* u, double* v) {
double edge1[3], edge2[3];
double tvec[3], pvec[3], qvec[3];
double det, inv_det;
/* edges */
SUB(edge1, vert1, vert0);
SUB(edge2, vert2, vert0);
/* determinant */
CROSS(pvec, dir, edge2);
/* ray in plane of triangle if near zero */
det = DOT(edge1, pvec);
if(det < EPSILON)
return 0;
SUB(tvec, orig, vert0);
inv_det = 1.0 / det;
/* calculate, check bounds */
*u = DOT(tvec, pvec) * inv_det;
if(*u < 0.0 || *u > 1.0)
return 0;
CROSS(qvec, tvec, edge1);
/* calculate, check bounds */
*v = DOT(dir, qvec) * inv_det;
if(*v < 0.0 || *u + *v > 1.0)
return 0;
*t = DOT(edge2, qvec) * inv_det;
return 1;
}
CROSS, DOT, and SUB are just macros:
#define CROSS(v,v0,v1) \
v[0] = v0[1] * v1[2] - v0[2] * v1[1]; \
v[1] = v0[2] * v1[0] - v0[0] * v1[2]; \
v[2] = v0[0] * v1[1] - v0[1] * v1[0];
#define DOT(v0,v1) (v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2])
/* v = v0 - v1 */
#define SUB(v,v0,v1) \
v[0] = v0[0] - v1[0]; \
v[1] = v0[1] - v1[1]; \
v[2] = v0[2] - v1[2];
Transformation code is as follows:
double ndc[2];
screen_to_ndc(x, y, &ndc[0], &ndc[1]);
double dir[3];
dir[0] = ndc[0] * ar * tfov;
dir[1] = ndc[1] * tfov;
dir[2] = -1;
norm(dir);
And screen_to_ndc:
void screen_to_ndc(unsigned int x, unsigned int y, double* ndcx, double* ndcy) {
*ndcx = 2 * (((double) x + (1.0 / 2.0)) / (double) WIDTH) - 1;
*ndcy = 1 - 2 * (((double) y + (1.0 / 2.0)) / (double) HEIGHT);
}
Any help would be appreciated.
Try reversing the orientation of your triangle. Your ray-triangle intersection code culls backfaces because it returns early when det is negative.
Related
I have been trying to implement the optimized rasterizer outlined in this blog: https://fgiesen.wordpress.com/2013/02/10/optimizing-the-basic-rasterizer/.
The naive approach outlined in his prior blog post https://fgiesen.wordpress.com/2013/02/08/triangle-rasterization-in-practice/, calculates the determinants (for barycentric weights) at each pixel. But his optimized version takes advantage of the fact that for three points a, b, c, the determinant function
(b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
can be rewritten as
A * c.x + B * c.y + C
for
A = (a.y - b.y), B = (b.x - a.x), C = a.x * b.y - a.y * b.x.
Since during traversal, the points a, b are two points on the triangle, c is the only point whose values change.
For any edge function E which outputs the weight for the corresponding triangle partition made by it and the point c, we can express an x difference and y difference in discrete steps.
E(c.x, c.y) = A * c.x + B * c.y + C
so
E(c.x + 1, c.y) - E(c.x, c.y) = A, and E(c.x, c.y + 1) - E(c.x, c.y) = B
So at each iteration instead of recalculating the determinant, we can just find the three determinants for the first c, and then increment them the A or B which corresponds to their edge.
I'm currently trying to implement this in my own rasterizer, but I quickly noticed an issue with the formula. My triangles are fed to the draw function in screen space, so a low y value means high up whereas in vector space it means low down. I thought I would account for this by multiplying every y value in the formula by -1.
This gave me the following formula for the determinant of a, b, c:
(b.x - a.x) * (a.y - c.y) - (a.y - b.y) * (c.x - a.x)
From which I derived the following A, B, C:
A = b.y - a.y, B = a.x - b.x, C = b.x * a.y - a.x * b.y
In my tests, using this new determinant formula (calculated at every pixel) works fine. And for the first point c in traversal, it is equivalent to
A * c.x + B * c.y + C
But as it continues traversing along the triangle's bounding box, the step incremented determinant values go out of sync with the raw calculated determinant values. Somehow this means that the step sizes A and B are faulty-- which makes no sense to me.
The only two causes of this problem I can think of are either I calculated A B and C incorrectly, or I am not mapping from vector space to screen space in a way that preserves area or orientation.
But just in case, here is all of my code for the rasterizer:
helpers
typedef float* point_t;
typedef float* triangle_t;
typedef struct edge {
point_t tail, tip;
float step_x, step_y;
int is_top_left;
} edge_t;
/* ... */
/* tail is the begining of the edge (i.e a), tip is the end of the edge (b) and c is variable */
static float init_edge(edge_t* edge, point_t tail, point_t tip, point_t origin) {
edge->tail = tail;
edge->tip = tip;
edge->is_top_left = is_top_left(tail, tip);
float A = tip[1] - tail[1];
float B = tail[0] - tip[0];
float C = tip[0] * tail[1] - tail[0] * tip[1];
/* step sizes */
edge->step_x = A;
edge->step_y = B;
/* edge function output at origin */
return A * origin[0] + B * origin[1] + C;
}
static float det(point_t a, point_t b, point_t c) {
return (b[0] - a[0]) * (a[1] - c[1]) - (a[1] - b[1]) * (c[0] - a[0]);
}
draw_triangle
void draw_triangle(sr_pipeline_t* pipeline, triangle_t triangle) {
/* orient triangle ccw */
point_t v0 = (point_t)malloc(sizeof(float) * pipeline->num_attr);
point_t v1 = (point_t)malloc(sizeof(float) * pipeline->num_attr);
point_t v2 = (point_t)malloc(sizeof(float) * pipeline->num_attr);
memcpy(v0, triangle, sizeof(float) * pipeline->num_attr);
memcpy(v1, triangle + pipeline->num_attr, sizeof(float) * pipeline->num_attr);
memcpy(v2, triangle + (2 * pipeline->num_attr), sizeof(float) * pipeline->num_attr);
orient_ccw(&v0, &v1, &v2);
/* find bounding box */
float min_x = /* ... */;
float min_y = /* ... */;
float max_x = /* ... */;
float max_y = /* ... */;
/* store current point */
point_t p = (point_t)calloc(pipeline->num_attr, sizeof(float));
p[0] = min_x;
p[1] = min_y;
/* grab edge information */
edge_t e01, e12, e20;
float w0 = init_edge(&e12, v1, v2, p);
float w1 = init_edge(&e20, v2, v0, p);
float w2 = init_edge(&e01, v0, v1, p);
/* rasterize */
for (p[1] = min_y; p[1] <= max_y; p[1]++) {
for (p[0] = min_x; p[0] <= max_x; p[0]++) {
/* determinant calculated at every step (I suspect these are correct) */
float s0 = det(v1, v2, p);
float s1 = det(v2, v0, p);
float s2 = det(v0, v1, p);
if ( (s0 >= 0) && (s1 >= 0) && (s2 >= 0) ) {
draw_point(pipeline, p);
}
w0 += e12.step_x;
w1 += e20.step_x;
w2 += e01.step_x;
}
w0 += e12.step_y;
w1 += e20.step_y;
w2 += e01.step_y;
}
free(v0);
free(v1);
free(v2);
free(p);
}
Code and functions that I have omitted I have verified work correctly.
To reiterate, my question is why are the values w0, w1, w2 not the same as s0, s1, s2 as they should be?
Any help is appreciated, thank you!
Rather than multiply every y value in the formula by -1, replace them with (top - y).
#include <stdio.h>
#include <math.h>
const int TERMS = 7;
const float PI = 3.14159265358979;
int fact(int n) {
return n<= 0 ? 1 : n * fact(n-1);
}
double sine(int x) {
double rad = x * (PI / 180);
double sin = 0;
int n;
for(n = 0; n < TERMS; n++) { // That's Taylor series!!
sin += pow(-1, n) * pow(rad, (2 * n) + 1)/ fact((2 * n) + 1);
}
return sin;
}
double cosine(int x) {
double rad = x * (PI / 180);
double cos = 0;
int n;
for(n = 0; n < TERMS; n++) { // That's also Taylor series!
cos += pow(-1, n) * pow(rad, 2 * n) / fact(2 * n);
}
return cos;
}
int main(void){
int y;
scanf("%d",&y);
printf("sine(%d)= %lf\n",y, sine(y));
printf("cosine(%d)= %lf\n",y, cosine(y));
return 0;
}
The code above was implemented to compute sine and cosine using Taylor series.
I tried testing the code and it works fine for sine(120).
I am getting wrong answers for sine(240) and sine(300).
Can anyone help me find out why those errors occur?
You should calculate the functions in the first quadrant only [0, pi/2). Exploit the properties of the functions to get the values for other angles. For instance, for values of x between [pi/2, pi), sin(x) can be calculated by sin(pi - x).
The sine of 120 degrees, which is 40 past 90 degrees, is the same as 50 degrees: 40 degrees before 90. Sine starts at 0, then rises toward 1 at 90 degrees, and then falls again in a mirror image to zero at 180.
The negative sine values from pi to 2pi are just -sin(x - pi). I'd handle everything by this recursive definition:
sin(x):
cases x of:
[0, pi/2) -> calculate (Taylor or whatever)
[pi/2, pi) -> sin(pi - x)
[pi/2, 2pi) -> -sin(x - pi)
< 0 -> sin(-x)
>= 2pi -> sin(fmod(x, 2pi)) // floating-point remainder
A similar approach for cos, using identity cases appropriate for it.
The key point is:
TERMS is too small to have proper precision. And if you increase TERMS, you have to change fact implementation as it will likely overflow when working with int.
I would use a sign to toggle the -1 power instead of pow(-1,n) overkill.
Then use double for the value of PI to avoid losing too many decimals
Then for high values, you should increase the number of terms (this is the main issue). using long long for your factorial method or you get overflow. I set 10 and get proper results:
#include <stdio.h>
#include <math.h>
const int TERMS = 10;
const double PI = 3.14159265358979;
long long fact(int n) {
return n<= 0 ? 1 : n * fact(n-1);
}
double powd(double x,int n) {
return n<= 0 ? 1 : x * powd(x,n-1);
}
double sine(int x) {
double rad = x * (PI / 180);
double sin = 0;
int n;
int sign = 1;
for(n = 0; n < TERMS; n++) { // That's Taylor series!!
sin += sign * powd(rad, (2 * n) + 1)/ fact((2 * n) + 1);
sign = -sign;
}
return sin;
}
double cosine(int x) {
double rad = x * (PI / 180);
double cos = 0;
int n;
int sign = 1;
for(n = 0; n < TERMS; n++) { // That's also Taylor series!
cos += sign * powd(rad, 2 * n) / fact(2 * n);
sign = -sign;
}
return cos;
}
int main(void){
int y;
scanf("%d",&y);
printf("sine(%d)= %lf\n",y, sine(y));
printf("cosine(%d)= %lf\n",y, cosine(y));
return 0;
}
result:
240
sine(240)= -0.866026
cosine(240)= -0.500001
Notes:
my recusive implementation of pow using successive multiplications is probably not needed, since we're dealing with floating point. It introduces accumulation error if n is big.
fact could be using floating point to allow bigger numbers and better precision. Actually I suggested long long but it would be better not to assume that the size will be enough. Better use standard type like int64_t for that.
fact and pow results could be pre-computed/hardcoded as well. This would save computation time.
const double TERMS = 14;
const double PI = 3.14159265358979;
double fact(double n) {return n <= 0.0 ? 1 : n * fact(n - 1);}
double sine(double x)
{
double rad = x * (PI / 180);
rad = fmod(rad, 2 * PI);
double sin = 0;
for (double n = 0; n < TERMS; n++)
sin += pow(-1, n) * pow(rad, (2 * n) + 1) / fact((2 * n) + 1);
return sin;
}
double cosine(double x)
{
double rad = x * (PI / 180);
rad = fmod(rad,2*PI);
double cos = 0;
for (double n = 0; n < TERMS; n++)
cos += pow(-1, n) * pow(rad, 2 * n) / fact(2 * n);
return cos;
}
int main()
{
printf("sine(240)= %lf\n", sine(240));
printf("cosine(300)= %lf\n",cosine(300));
}
I've been following this paper (notably the section on Fang's Method) in an attempt to achieve a solution to the problem of trilateration using the TDOA technique.
I'm hoping that someone experienced in Fang / TDOA can lend me a helping hand. For some reason, my implementation is returning incorrect roots to the final quadratic. Here's the code I've written so far:
#include <stdio.h>
#include <math.h>
struct Point {
double x;
double y;
};
inline double sqr(double n) {
return n * n;
}
// r1 and r2 are the TDOA of the sound impulse to p1 and p2, respectively
void fang(double r1, double r2) {
// transmitter coords
Point tx = {0.7, -0.1};
// receiver coordinates
Point p0 = {0, 0};
Point p1 = {1.7320508075688772, 0};
Point p2 = {0.8660254037844388, 1.5};
// linear coefficients
double g = ((r2 * (p1.x/r1)) - p2.x) / p2.y;
double h = (sqr(p2.x) + sqr(p2.y) - sqr(r2) + r2 * r1 * sqr(1 - (p1.x / r1))) / (2 * p2.y);
// quadratic coefficents
double d = -(1 - sqr(p1.x / r1) + sqr(g));
double e = p1.x * (1 - sqr(p1.x / r1)) - (2 * g * h);
double f = (sqr(r1) / 4) * sqr(1 - sqr(p1.x / r1)) - sqr(h);
double result_x = (-e - sqrt(sqr(e) - (4 * d * f))) / (2 * d);
}
int main() {
// these values have been calculated a-priori, from the known transmitter coords
double r1 = 0.32977743096231715;
double r2 = 0.90148404145971694;
fang(r1, r2);
}
Ultimately I'd expect the x_result to be equal to the transmitter's x coordinate (tx.x == 0.7), but frustratingly the result is ≈0.237.
An outline of my exact problem (and it's solution, where the two hyperbolas intersect) can be viewed geometrically in the below graph:
Any help would be hugely appreciated!
The paper gives the following calculation for h with the Fang method:
Your code incorrectly squares the entire (1 - (p1.x / r1)) expression, instead of just the (p1.x / r1) part. Moreover, you use the wrong values (p2 and p1) instead of the correct ones (p3 and p2). To fix, simply change h to:
double h = (sqr(p3.x) + sqr(p3.y) - sqr(r3) + r3 * r2 * (1 - sqr(p2.x / r2))) / (2 * p3.y);
This is one of many similar ray-triangle intersection algorithms. Every other algorithm I've tested also returns true for these numbers, while the ray clearly does not cross the triangle. The ray goes from y=0 to y=1, while the triangle is flat across y = 2.3.
This is not a winding issue, as it should never return true (winding issues would explain false negatives, not false positives).
All code necessary to reproduce in C or C++ is included here.
What am I missing?
#define vector(a,b,c) \
(a)[0] = (b)[0] - (c)[0]; \
(a)[1] = (b)[1] - (c)[1]; \
(a)[2] = (b)[2] - (c)[2];
#define crossProduct(a,b,c) \
(a)[0] = (b)[1] * (c)[2] - (c)[1] * (b)[2]; \
(a)[1] = (b)[2] * (c)[0] - (c)[2] * (b)[0]; \
(a)[2] = (b)[0] * (c)[1] - (c)[0] * (b)[1];
#define innerProduct(v,q) \
((v)[0] * (q)[0] + \
(v)[1] * (q)[1] + \
(v)[2] * (q)[2])
#define DOT(A,B) \
((A)[0] * (B)[0] + (A)[1] * (B)[1] + (A)[2] * (B)[2])
int intersect3D_RayTriangle( )
{
// dir, w0, w; // ray vectors
double r, a, b; // params to calc ray-plane intersect
// output: Point* I
//Ray R
double origin[3] = {0,0,0};//{orig[0],orig[1],orig[2]};
double direction[3] = {0,1,0};//{dir[0],dir[1],dir[2]};
//Triangle T
double corner1[3] = {3, 2.3, -4 };//{v0[0],v0[1],v0[2]};
double corner2[3] = {-7, 2.3, 2};//{v1[0],v1[1],v1[2]};
double corner3[3] = {3, 2.3, 2};// v2[0],v2[1],v2[2]};
// Vector u, v, n; // triangle vectors
double u[3] = {corner2[0]-corner1[0],corner2[1]-corner1[1],corner2[2]-corner1[2]};
double v[3] = {corner3[0]-corner1[0],corner3[1]-corner1[1],corner3[2]-corner1[2]};
double n[3] = {0,0,0};
double e1[3],e2[3],h[3],q[3];
double f;
// get triangle edge vectors and plane normal
crossProduct(n, u, v);
if ((n[0] == 0) && (n[1] == 0) && (n[2] == 0)) // triangle is wonky
return -1; // do not deal with this case
// dir = R.P1 - R.P0; // ray direction vector
double rayDirection[3] = {direction[0] - origin[0], direction[1] - origin[1], direction[2] - origin[2]};
//w0 = R.P0 - T.V0;
double w0[3] = {origin[0] - corner1[0], origin[1] - corner1[1], origin[2] - corner1[2]};
a = -DOT(n,w0);
b = DOT(n,rayDirection);
if (fabs(b) < __DBL_EPSILON__) { // ray is parallel to triangle plane
if (a == 0) // ray lies in triangle plane
return 2;
else return 0; // ray disjoint from plane
}
// get intersect point of ray with triangle plane
r = a / b;
if (r < 0.0) // ray goes away from triangle
return 0; // => no intersect
// for a segment, also test if (r > 1.0) => no intersect
//*I = R.P0 + r * dir; // intersect point of ray and plane
double I[3] = {0,0,0};
I[0] = origin[0] + rayDirection[0] * r;
I[1] = origin[1] + rayDirection[1] * r;
I[2] = origin[2] + rayDirection[2] * r;
// is I inside T?
double uu, uv, vv, wu, wv, D;
uu = DOT(u,u);
uv = DOT(u,v);
vv = DOT(v,v);
double w[3] = {0,0,0};
w[0] = I[0] - corner1[0];
w[1] = I[1] - corner1[1];
w[2] = I[2] - corner1[2];
wu = DOT(w,u);
wv = DOT(w,v);
D = uv * uv - uu * vv;
// get and test parametric coords
double s, t;
s = (uv * wv - vv * wu) / D;
if (s < 0.0 || s > 1.0) // I is outside T
return 0;
t = (uv * wu - uu * wv) / D;
if (t < 0.0 || (s + t) > 1.0) // I is outside T
return 0;
return 1; // I is in T
}
Code works fine for "rays".
OP expected that that "ray" code functioned like a "segment" one.
Could use the r value to testing for "segment" exclusion.
if (r > 1.0) return 0;
I'm trying to implement a audio synthesizer using this technique:
https://ccrma.stanford.edu/~stilti/papers/blit.pdf
I'm doing it in standard C, using SDL2_Mixer library.
This is my BLIT function implementation:
double blit(double angle, double M, double P) {
double x = M * angle / P;
double denom = (M * sin(M_PI * angle / P));
if (denom < 1)
return (M / P) * cos(M_PI * x) / cos(M_PI * x / M);
else {
double numerator = sin(M_PI * x);
return (M / P) * numerator / denom;
}
}
The idea is to combine it to generate a square wave, following the paper instructions. I setted up SDL2_mixer with this configuration:
SDL_AudioSpec *desired, *obtained;
SDL_AudioSpec *hardware_spec;
desired = (SDL_AudioSpec*)malloc(sizeof(SDL_AudioSpec));
obtained = (SDL_AudioSpec*)malloc(sizeof(SDL_AudioSpec));
desired->freq=44100;
desired->format=AUDIO_U8;
desired->channels=1;
desired->samples=2048;
desired->callback=create_rect;
desired->userdata=NULL;
And here's my create_rect function. It creates a bipolar impulse train, then it integrates it's value to generate a band-limited rect function.
void create_rect(void *userdata, Uint8 *stream, int len) {
static double angle = 0;
static double integral = 0;
int i = 0;
// This is the freq of my tone
double f1 = tone_table[current_wave.note];
// Sample rate
double fs = 44100;
// Pulse
double P = fs / f1;
int M = 2 * floor(P / 2) + 1;
double oldbipolar = 0;
double bipolar = 0;
for(i = 0; i < len; i++) {
if (++angle > P)
angle -= P;
double angle2 = angle + floor(P/2);
if (angle2 > P)
angle2 -= P;
bipolar = blit(angle2, M, P) - blit(angle, M, P);
integral += (bipolar + old bipolar) * 0.5;
oldbipolar = bipolar;
*stream++ = (integral + 0.5) * 127;
}
}
My problem is: the resulting wave is quite ok, but after few seconds it starts to make noises. I tried to plot the result, and here's it:
Any idea?
EDIT: Here's a plot of the bipolar BLIT before integrating it: