Related
I'm currently following Andre Lamothe's book Tricks of the Windows Game Programming Gurus and trying to write my own software renderer with Win32 GDI. However, I got stuck at the triangle rasterization part of the book where the author is using Bresenham's line algorithm. I managed to get the rasterization to work on DirectX, but not on Win32 GDI despite identical rendering code. Both renders a triangle but the DirectX one renders it correctly while the Win32 almost renders it correctly
Link to my code: https://pastebin.com/VwySmQXS
It could be worth mention that the DirectX version is written in c++, while my own is written in C. I wasn't able to get a good screenshot of the two triangles so here is a picture painted in paint of how they both look.
Left is DirectX (how I want it to look), right is Win32 GDI:
Am I missing something here? Both draw_triangle_2d are called with the same arguments.
static void draw_top_tri(struct render_buffer *buffer,
int x1, int y1,
int x2, int y2,
int x3, int y3,
float r, float g, float b, float a)
{
unsigned int color = vec4_to_uint(vec4_mul(Vec4(r, g, b, a), 255.0f));
// this function draws a triangle that has a flat top
float dx_right, // the dx/dy ratio of the right edge of line
dx_left, // the dx/dy ratio of the left edge of line
xs, xe, // the starting and ending points of the edges
height; // the height of the triangle
int temp_x, // used during sorting as temps
temp_y,
right, // used by clipping
left;
// destination address of next scanline
unsigned int *dest_addr = NULL;
// test order of x1 and x2
if(x2 < x1)
{
temp_x = x2;
x2 = x1;
x1 = temp_x;
} // end if swap
// compute delta's
height = (float)(y3 - y1);
dx_left = (x3 - x1) / height;
dx_right = (x3 - x2) / height;
// set starting points
xs = (float)x1;
xe = (float)x2 + (float)0.5;
// perform y clipping
if(y1 < MIN_CLIP_Y)
{
// compute new xs and ys
xs = xs + dx_left * (float)(-y1 + MIN_CLIP_Y);
xe = xe + dx_right * (float)(-y1 + MIN_CLIP_Y);
// reset y1
y1 = MIN_CLIP_Y;
} // end if top is off screen
if(y3 > MAX_CLIP_X)
y3 = MAX_CLIP_X;
// compute starting address in video memory
dest_addr = (unsigned int *)buffer->memory + y1 * buffer->width;
// test if x clipping is needed
if(x1 >= MIN_CLIP_X && x1 <= MAX_CLIP_X &&
x2 >= MIN_CLIP_X && x2 <= MAX_CLIP_X &&
x3 >= MIN_CLIP_X && x3 <= MAX_CLIP_X)
{
// draw the triangle
for(temp_y = y1; temp_y <= y3; temp_y++, dest_addr += buffer->width)
{
memset((unsigned int *)dest_addr + (unsigned int)xs,
color, (unsigned int)(xe - xs + 1) * 4);
// adjust starting point and ending point
xs += dx_left;
xe += dx_right;
} // end for
} // end if no x clipping needed
else
{
// clip x axis with slower version
// draw the triangle
for(temp_y = y1; temp_y <= y3; temp_y++, dest_addr += buffer->width)
{
// do x clip
left = (int)xs;
right = (int)xe;
// adjust starting point and ending point
xs += dx_left;
xe += dx_right;
// clip line
if(left < MIN_CLIP_X)
{
left = MIN_CLIP_X;
if(right < MIN_CLIP_X)
continue;
}
if(right > MAX_CLIP_X)
{
right = MAX_CLIP_X;
if(left > MAX_CLIP_X)
continue;
}
memset((unsigned int *)dest_addr + (unsigned int)left,
color, (unsigned int)(right - left + 1) * 4);
} // end for
} // end else x clipping needed
} // end Draw_Top_Tri
static void
draw_bottom_tri(struct render_buffer *buffer,
int x1, int y1,
int x2, int y2,
int x3, int y3,
float r, float g, float b, float a)
{
unsigned int color = vec4_to_uint(vec4_mul(Vec4(r, g, b, a), 255.0f));
// this function draws a triangle that has a flat bottom
float dx_right, // the dx/dy ratio of the right edge of line
dx_left, // the dx/dy ratio of the left edge of line
xs, xe, // the starting and ending points of the edges
height; // the height of the triangle
int temp_x, // used during sorting as temps
temp_y,
right, // used by clipping
left;
// destination address of next scanline
unsigned int *dest_addr;
// test order of x1 and x2
if(x3 < x2)
{
temp_x = x2;
x2 = x3;
x3 = temp_x;
} // end if swap
// compute delta's
height = (float)(y3 - y1);
dx_left = (x2 - x1) / height;
dx_right = (x3 - x1) / height;
// set starting points
xs = (float)x1;
xe = (float)x1; // +(float)0.5;
// perform y clipping
if(y1 < MIN_CLIP_Y)
{
// compute new xs and ys
xs = xs + dx_left * (float)(-y1 + MIN_CLIP_Y);
xe = xe + dx_right * (float)(-y1 + MIN_CLIP_Y);
// reset y1
y1 = MIN_CLIP_Y;
} // end if top is off screen
if(y3 > MAX_CLIP_X)
y3 = MAX_CLIP_X;
// compute starting address in video memory
dest_addr = (unsigned int *)buffer->memory + y1 * buffer->width;
// test if x clipping is needed
if(x1 >= MIN_CLIP_X && x1 <= MAX_CLIP_X &&
x2 >= MIN_CLIP_X && x2 <= MAX_CLIP_X &&
x3 >= MIN_CLIP_X && x3 <= MAX_CLIP_X)
{
// draw the triangle
for(temp_y = y1; temp_y <= y3; temp_y++, dest_addr += buffer->width)
{
memset((unsigned int *)dest_addr + (unsigned int)xs,
color, (unsigned int)(xe - xs + 1) * 4);
// adjust starting point and ending point
xs += dx_left;
xe += dx_right;
} // end for
} // end if no x clipping needed
else
{
// clip x axis with slower version
// draw the triangle
for(temp_y = y1; temp_y <= y3; temp_y++, dest_addr += buffer->width)
{
// do x clip
left = (int)xs;
right = (int)xe;
// adjust starting point and ending point
xs += dx_left;
xe += dx_right;
// clip line
if(left < MIN_CLIP_X)
{
left = MIN_CLIP_X;
if(right < MIN_CLIP_X)
continue;
}
if(right > MAX_CLIP_X)
{
right = MAX_CLIP_X;
if(left > MAX_CLIP_X)
continue;
}
memset((unsigned int *)dest_addr + (unsigned int)left,
color, (unsigned int)(right - left + 1) * 4);
} // end for
} // end else x clipping needed
} // end Draw_Bottom_Tri}
ENGINE_CORE_EXPORT void
draw_triangle_2d(struct render_buffer *buffer,
int x1, int y1,
int x2, int y2,
int x3, int y3,
float r, float g, float b, float a)
{
// this function draws a triangle on the destination buffer
// it decomposes all triangles into a pair of flat top, flat bottom
int temp_x, // used for sorting
temp_y,
new_x;
// test for h lines and v lines
if((x1 == x2 && x2 == x3) || (y1 == y2 && y2 == y3))
return;
// sort p1,p2,p3 in ascending y order
if(y2 < y1)
{
temp_x = x2;
temp_y = y2;
x2 = x1;
y2 = y1;
x1 = temp_x;
y1 = temp_y;
} // end if
// now we know that p1 and p2 are in order
if(y3 < y1)
{
temp_x = x3;
temp_y = y3;
x3 = x1;
y3 = y1;
x1 = temp_x;
y1 = temp_y;
} // end if
// finally test y3 against y2
if(y3 < y2)
{
temp_x = x3;
temp_y = y3;
x3 = x2;
y3 = y2;
x2 = temp_x;
y2 = temp_y;
} // end if
// do trivial rejection tests for clipping
if(y3 < MIN_CLIP_Y || y1 > MAX_CLIP_X ||
(x1 < MIN_CLIP_X && x2 < MIN_CLIP_X && x3 < MIN_CLIP_X) ||
(x1 > MAX_CLIP_X && x2 > MAX_CLIP_X && x3 > MAX_CLIP_X))
return;
// test if top of triangle is flat
if(y1 == y2)
{
draw_top_tri(buffer, x1, y1, x2, y2, x3, y3, r, g, b, a);
} // end if
else
if(y2 == y3)
{
draw_bottom_tri(buffer, x1, y1, x2, y2, x3, y3, r, g, b, a);
} // end if bottom is flat
else
{
// general triangle that's needs to be broken up along long edge
new_x = x1 + (int)(0.5 + (float)(y2 - y1) * (float)(x3 - x1) / (float)(y3 - y1));
// draw each sub-triangle
draw_bottom_tri(buffer, x1, y1, new_x, y2, x2, y2, r, g, b, a);
draw_top_tri(buffer, x2, y2, new_x, y2, x3, y3, r, g, b, a);
} // end else
} // end Draw_Triangle_2D
I am working on z buffering with lines, and I am trying to find the depth of a certain pixel.
Here is my line drawing code:
void l(int x1, int y1, int z1, int x2, int y2, int z2, int cl)
{
int deltax = x2 - x1; // The difference in the x's
int deltay = y2 - y1; // The difference in the y's
int m = deltay / deltax; // The slope of the line (with deltax > 0)
for (int x = 0; x <= deltax; x++)
{
int y = m * x; // Calculate the y-value
int xcalc = x + x1;
int ycalc = round(y) + y1;
color[xcalc][ycalc].c = cl; // Draw the current pixel
}
}
and here is my struct and color variable:
struct distancecolor { //c = color, d = depth
int c;
float d;
};
distancecolor color[250][250];
d = depth of pixel and c = color of pixel, stored from 1-9, because it is a custom rendering engine.
All I want to do is take in x1, y1, and z1, and then use x2, y2, and z2 with an equation to find the distance from 0 to x, or the pixel being placed, on the line. Ask me for any other needed information.
I have been trying to work out this problem with my Sutherland-Hodgman Polygon Clipper algorithm. When filling in the polygon i keep getting this result:
I cant seem to figure out what causing the bug, here is my code:
// #param in the number of vertices in the polygon to be clipped
// #param inV the incoming vertex list
// #param outV the outgoing vertex list
// #param ll the lower-left corner of the clipping rectangle
// #param ur the upper-right corner of the clipping rectangle
//
// #return number of vertices in the polygon resulting after clipping
int Clipper::clipPolygon( int in, const Vertex inV[], Vertex outV[],
Vertex ll, Vertex ur )
{
int out = 0, out2 = 0, out3 = 0, out4 = 0;
Vertex *outV2, *outV3, *outV4;
// We clip entire figure by clipping on the 4 edges of the clipping window
// each step will be stored, in *out pointers.
SHPC(inV, outV, in, out, ll.x, ur.y, ll.x, ll.y); // left
outV2 = new Vertex[out];
SHPC(outV, outV2, out, out2, ll.x, ll.y, ur.x, ll.y); // bottom
outV3 = new Vertex[out2];
SHPC(outV2, outV3, out2, out3, ur.x, ll.y, ur.x, ur.y); // right
outV4 = new Vertex[out3];
SHPC(outV3, outV4, out3, out4, ur.x, ur.y, ll.x, ur.y); // top
// we want outx and outy to store the final clipped polygons, which
// means we need to get content of outx4 and outy4
for (int i = 0; i < out; i++) { outV[i].x = outV4[i].x; outV[i].y = outV4[i].y; }
// same principle for out value
out = out4;
delete[] outV2;
delete[] outV3;
delete[] outV4;
return out;
}
void Clipper::SHPC(const Vertex inV[],
Vertex outV[], int in, int &out,
float x0, float y0, float x1, float y1) {
float px = inV[in - 1].x, py = inV[in - 1].y; // last vertex is the initial “predecessor”
float _x = px, _y = py;
for (int j = 0; j < in; j++) {
if (inside(inV[j].x, inV[j].y, x0, y0, x1, y1)) { // Cases 1 & 4
if (inside(px, py, x0, y0, x1, y1)) { // Case 1
output(inV[j].x, inV[j].y, out, outV);
}
else { // Case 4
intersect(px, py, inV[j].x, inV[j].y, x0, y0, x1, y1, _x, _y);
output(_x, _y, out, outV);
output(inV[j].x, inV[j].y, out, outV);
}
}
else { // Cases 2 & 3
if (inside(px, py, x0, y0, x1, y1)) { // Case 2
intersect(px, py, inV[j].x, inV[j].y, x0, y0, x1, y1, _x, _y);
output(_x, _y, out, outV);
} // Case 3 has no output
}
px = inV[j].x;
py = inV[j].y;
} // for
}
// is point inside boundary?
bool Clipper::inside(float _x, float _y, float x0, float y0, float x1, float y1) {
if (y0 == y1) { // horizontal edge
if (x0 < x1) return _y >= y0;
if (x0 > x1) return _y <= y0;
} else { // vertical edge
if (y1 > y0) return _x <= x0;
if (y0 > y1) return _x >= x0;
}
return false;
}
// put point into vector, update length
void Clipper::output(float _x, float _y, int &out, Vertex outV[]) {
outV[out].x = _x;
outV[out++].y = _y;
}
// compute intersection point, put point into newpoint parameter
void Clipper::intersect(float sx, float sy, float ex, float ey, float x0, float y0, float x1, float y1, float &_x, float &_y) {
if (x0 == x1) { // if it's a vertical edge
_x = x0;
_y = sy + (x0 - sx) * (ey - sy) / (ex - sx);
}
else { // if it's a horizontal edge
_y = y0;
_x = sx + (y0 - sy) * (ex - sx) / (ey - sy);
}
}
If I would have to guess I would say it is an issue with my inside function, but that raises the question of why is it only not working for the star and not for the rest of the shapes? I have tried rewriting the inside function, but it usually results in the same type of error only in a different direction. If anyone can point me in the right direction I would appreciate it.
I'm trying to draw a cube using software rendering. So I have I'm drawing a cube out of triangles. So I render a solid triangle. The cube is rendered successfully, but there are flicker while rotating it.
here is my solid triangle filling, can somebody notice a problem in the algorithm?
void Rasterizer::DrawSolidTriangle(int x0, int y0, int x1, int y1, int x2, int y2, Color&color)
{
// Sort our points into order of y
// 0 top
// 2 middle
// 1 bottom
if (y1 < y0)
{
swap(y1, y0);
swap(x1, x0);
}
if (y2 < y0)
{
swap(y2, y0);
swap(x2, x0);
}
if (y1 < y2)
{
swap(y2, y1);
swap(x2, x1);
}
float xl_edge = (float)x0; // left edge
float xr_edge = (float)x0; // right edge
float dxldy;
float dxrdy;
float dxdy1 = (float)(x2 - x0) / (y2 - y0);
float dxdy2 = (float)(x1 - x0) / (y1 - y0);
if (dxdy1 < dxdy2)
{
dxldy = dxdy1;
dxrdy = dxdy2;
}
else
{
dxldy = dxdy2;
dxrdy = dxdy1;
}
// Top of the triangle
for (int y = y0; y<y2; y++)
{
for (int x = xl_edge; x<xr_edge; x++)
{
SetPixel((unsigned int)x, (unsigned int)y, color);
}//end for loop x
xl_edge = xl_edge + dxldy;
xr_edge = xr_edge + dxrdy;
}// end for loop y
// Bottom half of the triangle
if (dxdy1 < dxdy2)
{
dxldy = (float)(x2 - x1) / (y2 - y1);
}
else
{
dxrdy = (float)(x2 - x1) / (y2 - y1);
}
for (int y = y2; y<y1; y++)
{
for (int x = xl_edge; x<xr_edge; x++)
{
SetPixel((unsigned int)x, (unsigned int)y, color);
}//end for loop x
xl_edge = xl_edge + dxldy;
xr_edge = xr_edge + dxrdy;
}// end for loop y
}
here is a screenshot of the problem, when rotating the cube so slow it does appear here:
I've made simple method to draw line with fixed thickness.
Here's my function:
void ColoringScene::drawThickLine(int x1, int y1, int x2, int y2, int r, int g, int b, int a, float wd, bool began, unsigned char* data, unsigned char* areasData){
if(began){
//if just began draw "dot" with filled circle
drawFilledCircle(x1, y1, 1, wd, r, g, b, a, data, areasData);
return;
}
//otherwise calculate angle between two points
int angle = (int)(atan2(-y2 + y1, x2 - x1) * 180 / M_PI);
int a1 = angle - 90;
int a2 = angle + 90;
//calculate a distance between two points
float diff = sqrtf(powf(x1 - x2, 2) + powf(y1 - y2, 2));
if(diff > wd) diff = wd;
//find all points in arc from angle - 90 to angle + 90
for(float _r = a1; _r < a2; _r+=1.0f){
float _x1 = cos(CC_DEGREES_TO_RADIANS(_r)) * wd + x1;
float _y1 = -sin(CC_DEGREES_TO_RADIANS(_r)) * wd + y1;
float _x2 = cos(CC_DEGREES_TO_RADIANS(_r)) * wd + x2;
float _y2 = -sin(CC_DEGREES_TO_RADIANS(_r)) * wd + y2;
//connect point from 2 arcs with line
drawLine(floor(_x1), floor(_y1), floor(_x2), floor(_y2), r, g, b, a, data, areasData);
}
}
it should draw line from (x1, y1) to (x2, y2) with color (r, g, b, a) and thickness wd. I use this method on drawing while moving finger on screen so I've also added extra parameter "began", which says if it's touch began or touch move. data is an array of pixels. areasData does not matter.
But it's not working as expected, here's an example result:
There could be a bit of compression, but you can see 2 dots, which were drawn fine and a curve build from many "thick" lines with holes.
If "wd" is not big enough the problem is not present. I'm almost sure it's some sort of problem with precision.
I tried:
- changing for loop from 0 to 360 (not angle-90, angle+90).
- using round instead of floor
- using sin instead of -sin (y is inverted anyway in my case)
- using _r lower than 1.0 like: 0.05.
And by given fixed thickness (in this example's 60 pixels) there's no way to set parameters like _r increment or angles to not draw without holes.
I decided to write my own function to do this, because other methods I found on web just didn't work as expected (especially with anti-aliasing, which will be perfect solution for me).
Here's drawLine function taken from website: http://willperone.net/Code/codeline.php
void ColoringScene::drawLine(int x1, int y1, int x2, int y2, int r, int g, int b, int a, unsigned char* data, unsigned char* areasData){
int dy = y2 - y1;
int dx = x2 - x1;
int stepx, stepy;
if (dy < 0) { dy = -dy; stepy = -1; } else { stepy = 1; }
if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; }
dy <<= 1; // dy is now 2*dy
dx <<= 1; // dx is now 2*dx
setPixelWithCheckingArea(x1, y1, r, g, b, a, data, areasData);
if (dx > dy)
{
int fraction = dy - (dx >> 1); // same as 2*dy - dx
while (x1 != x2)
{
if (fraction >= 0)
{
y1 += stepy;
fraction -= dx; // same as fraction -= 2*dx
}
x1 += stepx;
fraction += dy; // same as fraction -= 2*dy
setPixelWithCheckingArea(x1, y1, r, g, b, a, data, areasData);
}
} else {
int fraction = dx - (dy >> 1);
while (y1 != y2) {
if (fraction >= 0) {
x1 += stepx;
fraction -= dy;
}
y1 += stepy;
fraction += dx;
setPixelWithCheckingArea(x1, y1, r, g, b, a, data, areasData);
}
}
}