我们正在寻找一种填充算法,该算法保证,如果两个三角形共享一个边缘(特别是如果任何两个三角形的顶点相同),则无论绘制顺序和抗锯齿如何,这两个三角形之间的连线上不会有空白、未绘制的像素。 (某些像素被绘制两次是可以接受的。)结果应该在任意缩放下都看起来不错。有些三角形在某些地方可能非常狭长,最窄只有1个像素宽。
理想情况下,它应该是一个相当高效的填充算法!
在三角形渲染中不会使用抗锯齿,因为最终图像需要1位深度。
上下文是一个图像识别应用程序,因此所有顶点坐标将准确到一个像素。
3 2 1
\ | /
\ | /
\|/
4 --------+--------- 0
/|\
/ | \
/ | \
5 6 7
4 -> 0
5 -> 1
6 -> 2
7 -> 3
#include <stddef.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define SCREEN_HEIGHT 22
#define SCREEN_WIDTH 78
// Simulated frame buffer
char Screen[SCREEN_HEIGHT][SCREEN_WIDTH];
void SetPixel(long x, long y, char color)
{
if ((x < 0) || (x >= SCREEN_WIDTH) ||
(y < 0) || (y >= SCREEN_HEIGHT))
{
return;
}
if (Screen[y][x] == ' ')
Screen[y][x] = color;
else
Screen[y][x] = '*';
}
void Visualize(void)
{
long x, y;
for (y = 0; y < SCREEN_HEIGHT; y++)
{
for (x = 0; x < SCREEN_WIDTH; x++)
{
printf("%c", Screen[y][x]);
}
printf("\n");
}
}
typedef struct
{
long x, y;
unsigned char color;
} Point2D;
// min X and max X for every horizontal line within the triangle
long ContourX[SCREEN_HEIGHT][2];
#define ABS(x) ((x >= 0) ? x : -x)
// Scans a side of a triangle setting min X and max X in ContourX[][]
// (using the Bresenham's line drawing algorithm).
void ScanLine(long x1, long y1, long x2, long y2)
{
long sx, sy, dx1, dy1, dx2, dy2, x, y, m, n, k, cnt;
sx = x2 - x1;
sy = y2 - y1;
/*
3 2 1
\ | /
\ | /
\|/
4 --------+--------- 0
/|\
/ | \
/ | \
5 6 7
4 -> 0
5 -> 1
6 -> 2
7 -> 3
*/
if (sy < 0 || sy == 0 && sx < 0)
{
k = x1; x1 = x2; x2 = k;
k = y1; y1 = y2; y2 = k;
sx = -sx;
sy = -sy;
}
if (sx > 0) dx1 = 1;
else if (sx < 0) dx1 = -1;
else dx1 = 0;
if (sy > 0) dy1 = 1;
else if (sy < 0) dy1 = -1;
else dy1 = 0;
m = ABS(sx);
n = ABS(sy);
dx2 = dx1;
dy2 = 0;
if (m < n)
{
m = ABS(sy);
n = ABS(sx);
dx2 = 0;
dy2 = dy1;
}
x = x1; y = y1;
cnt = m + 1;
k = n / 2;
while (cnt--)
{
if ((y >= 0) && (y < SCREEN_HEIGHT))
{
if (x < ContourX[y][0]) ContourX[y][0] = x;
if (x > ContourX[y][1]) ContourX[y][1] = x;
}
k += n;
if (k < m)
{
x += dx2;
y += dy2;
}
else
{
k -= m;
x += dx1;
y += dy1;
}
}
}
void DrawTriangle(Point2D p0, Point2D p1, Point2D p2)
{
long y;
for (y = 0; y < SCREEN_HEIGHT; y++)
{
ContourX[y][0] = LONG_MAX; // min X
ContourX[y][1] = LONG_MIN; // max X
}
ScanLine(p0.x, p0.y, p1.x, p1.y);
ScanLine(p1.x, p1.y, p2.x, p2.y);
ScanLine(p2.x, p2.y, p0.x, p0.y);
for (y = 0; y < SCREEN_HEIGHT; y++)
{
if (ContourX[y][1] >= ContourX[y][0])
{
long x = ContourX[y][0];
long len = 1 + ContourX[y][1] - ContourX[y][0];
// Can draw a horizontal line instead of individual pixels here
while (len--)
{
SetPixel(x++, y, p0.color);
}
}
}
}
int main(void)
{
Point2D p0, p1, p2, p3;
// clear the screen
memset(Screen, ' ', sizeof(Screen));
// generate random triangle coordinates
srand((unsigned)time(NULL));
// p0 - p1 is going to be the shared edge,
// make sure the triangles don't intersect
for (;;)
{
p0.x = rand() % SCREEN_WIDTH;
p0.y = rand() % SCREEN_HEIGHT;
p1.x = rand() % SCREEN_WIDTH;
p1.y = rand() % SCREEN_HEIGHT;
p2.x = rand() % SCREEN_WIDTH;
p2.y = rand() % SCREEN_HEIGHT;
p3.x = rand() % SCREEN_WIDTH;
p3.y = rand() % SCREEN_HEIGHT;
{
long vsx = p0.x - p1.x;
long vsy = p0.y - p1.y;
long v1x = p0.x - p2.x;
long v1y = p0.y - p2.y;
long v2x = p0.x - p3.x;
long v2y = p0.y - p3.y;
long z1 = vsx * v1y - v1x * vsy;
long z2 = vsx * v2y - v2x * vsy;
// break if p2 and p3 are on the opposite sides of p0-p1
if (z1 * z2 < 0) break;
}
}
printf("%ld:%ld %ld:%ld %ld:%ld %ld:%ld\n\n",
p0.x, p0.y,
p1.x, p1.y,
p2.x, p2.y,
p3.x, p3.y);
// draw the triangles
p0.color = '-';
DrawTriangle(p0, p3, p1);
p1.color = '+';
DrawTriangle(p1, p2, p0);
Visualize();
return 0;
}
示例输出:
30:10 5:16 16:6 59:17
+++
++++++++
++++++++++++
+++++++++++++++++
+++++++++++++++****---
+++++++++++++****-----------
++++++++++****-------------------
++++++*****----------------------------
+++****-------------------------------------
****---------------------------------------------
*-----------------------------------------------------
-
说明:
请注意,即使没有未填充的间隙(像素),由于另一个在其上方绘制的三角形覆盖了共享边缘上的像素,因此可能会出现显示为不连续或形状尴尬的三角形,如果它太薄。例如:
2:20 12:8 59:15 4:17
*++++++
*+++++++++++++
*+++++++++++++++++++++
-*++++++++++++++++++++++++++++
-*++++++++++++++++++++++++++++++++++++
*+++++++++++++++++++++++++++++++++++++++++++
*+++++++++++++++++++++++++++++++++++++++++++++++++++
*+++++++++++++++++++++++++++++++++++++++++++++++++++++
*+++++++++++++++++++++++++++++++++++++++++++
-*+++++++++++++++++++++++++++++++
-*+++++++++++++++++++++
*++++++++++
*
void DrawHLine(int y, int x0, int x1);
DrawHLine
来解决双重所有权问题,使其填充从x0
(包括)到x1
(不包括)的像素。因此,共享边缘上的所有双重所有权像素都被定义为属于共享边缘右侧的三角形。这不是最高效的方法,但你可以循环遍历一个包含三角形的正方形,并测试每个像素是否在三角形内。
伪代码:
for(x : minX -> maxX)
for(y : minY -> maxY)
if(triangle.contains(x,y))
drawPixel(x,y);
其中minX是三个顶点之间最小的X坐标,maxX同理适用于最大X坐标,minY和maxY也是如此。
为了更快的算法,您可以先进行一些快速而简单的填充(例如斜杠泛洪填充),然后再对边缘周围的像素进行操作。
有关三角形内点测试的说明,请参见此处。
我的答案符合假设。Adrian McCarthy在他的回答中所说的是真实的。我的算法基于类似的想法,它是有效的。即使在我看来,不考虑像素的覆盖也是不公平的。然而,我不代表N-1个像素的水平线,否则三角形,如果它是一个“碎片”,它将不被表示。
例如:假设我们有两个相邻的三角形:
ABC [A (27.15) -B (32.15) -C (37.15)];
DEF [A (29.15) -B (32.15) -C (35.15)];
表示将重叠,但结果应该是水平类型的一段:
++-------------------------++
因此,仅排除最后一个像素并不足以避免覆盖。
为了表示填充的三角形,以便可以使用所创建的函数表示填充的多边形(例如:四边形),因为它们总是可以被分成三角形,必须能够排除一条边的表示,否则,一个三角形的一侧将被相邻三角形的一侧覆盖(在显示透明多边形时会出现问题)。这是我为表示任何类型的三角形提出给你的 C
算法实现。我建议你尝试一下,因为它快速、虽然相当复杂但高效。这是我对 Bresenham
算法的变体。我将水平线段的例程表示留给你(我已经用 Line()
指令替换了调用 Put
(Shadow)HorizLine ()
来表示水平线段,因为我的 DrawHLine()
实现不能插入到此帖子中;但是,在这里我仅使用 Line()
指令来绘制水平线段)。
这个函数最初是为了在RAM中使用缓冲区而提供的,使用了我自己的格式,称为OBP,它与RASTER格式仅有两个不同之处:扫描线与16字节对齐。数据(每个像素8位)之前有一个16字节的头文件;这个头文件包含了图像大小的前两个字(在汇编实现中,您可以选择使用CPU寄存器,而不是RAM中的变量,因为32位寄存器可以同时包含尺寸和中等规模图像的几何位置的点也可以包含在32位寄存器中)。
这里唯一需要做的就是重写对Line()
函数的调用,因为要指定三角形的一侧颜色,可能与其他颜色不同、透明或不存在,需要修改对象的属性,而不是将颜色直接作为参数传递给line()
函数,虽然曾经可以调用SetColor()
函数,但这里仅作为参考。
这是头文件(triangle.h
):
#define R_OBP_Al 16 /* 16 Byte alignment; it must always be 2^N */
#define DimOBP_H 16 /* 16 Byte of OBP HEADER; it must always be >=4 */
#define True 1 /* Boolean value for true condition */
#define False 0 /* Boolean value for false condition */
typedef char TShadowTable[256]; /* Array for shadows */
typedef TShadowTable *T_Shadow_Table_Ptr; /* Pointer to an array for shadows */
typedef struct {short int X;
short int Y;} T_Coord_XY;
typedef struct {unsigned short int X;
unsigned short int Y;} T_Pos_Coord_XY;
typedef struct {unsigned short int DimX;
unsigned short int DimY;} T_Dim_XY;
typedef struct {T_Pos_Coord_XY XY; /* Coordinates of the clipping-region */
T_Dim_XY Dim_XY; /* Dimensions of the clipping-region */ } T_Clipp_Rect;
typedef T_Clipp_Rect *T_Clipp_Rect_Ptr; /* Pointer to clipping-region's type */
typedef struct {T_Coord_XY XY; /* Coordinates of the rectangle */
T_Dim_XY Dim_XY; /* Dimensions of the rectangle */ } T_Rect;
typedef T_Rect *T_Rect_Ptr; /* Pointer to a rectangle */
typedef char Boolean; /* Boolean type */
void Triangle_Wind(short int X1,
short int Y1,
short int X2,
short int Y2,
short int X3,
short int Y3,
short int FillColor,
short int BrdColAB,
short int BrdColBC,
short int BrdColCA
/* , T_Shadow_Table_Ptr ShadowTable,
void *OBPVBuff
T_Clipp_Rect_Ptr Clipp */);
这里是函数和示例(triangle.c
):
#include <graphics.h>
#include <conio.h>
#include <string.h>
#include <stdio.h>
#include "triangle.h"
static int *DefColors[16]=
{0FF000000H, /* Black */
0FF7F0000H, /* Blue */
0FF007F00H, /* Green */
0FF7F7F00H, /* Cyan */
0FF00007FH, /* Red */
0FF7F007FH, /* Magenta */
0FF007F7FH, /* Brown */
0FF7F7F7FH, /* LightGray */
0FF3F3F3FH, /* DarkGray */
0FFFF0000H, /* LightBlue */
0FF00FF00H, /* LightGreen */
0FFFFFF00H, /* LightCyan */
0FF0000FFH, /* LightRed */
0FFFF00FFH, /* LightMagenta */
0FF00FFFFH, /* Yellow */
0FFFFFFFFH /* White */ };
int main(void)
{
/* int gd = DETECT;
int gm;
initgraph(&gd, &gm, "C:\\TC\\BGI"); */
Triangle_Wind(80,80,320,200,160,300,
4,1,2,7);
getch();
/* closegraph(); */
return 0;
}
/* Here it is the body of the triangle routine: */
void Triangle_Wind(short int X1,
short int Y1,
short int X2,
short int Y2,
short int X3,
short int Y3,
short int FillColor,
short int BrdColAB,
short int BrdColBC,
short int BrdColCA
/* , T_Shadow_Table_Ptr ShadowTable,
void *OBPVBuff
T_Clipp_Rect_Ptr Clipp */)
{short int A=0;
short int B=1;
short int C=2; /* Identificat. vertici triangoli per ordinam. colori */
short int C1=BrdColAB;
short int C2=BrdColBC;
short int C3=BrdColCA; /* Var. temp. per i colori */
short int XT; /* X1-XT è il segmento orizzontale da disegnare */
short int OY2; /* Valore iniziale coord. Y 2° vertice del triangolo */
short int B1L;
short int B1H; /* Coord. X 1° e ultimo punto 1° bordo (segm. orizz.) */
short int B2L;
short int B2H; /* Coord. X 1° e ultimo punto 2° bordo (segm. orizz.) */
short int D0; /* Dimensione 1° bordo (segm. orizz.) */
short int D1; /* Dimensione parte centrale segm. orizz. */
short int D2; /* Dimensione 2° bordo (segm. orizz.) */
short int Tmp; /* Variabile temporanea x scambio di 2 variabili */
short int Col1; /* Colore 1° bordo segm. orizz. */
short int Col2; /* Colore 2° bordo segm. orizz. */
short int CntX1; /* Contat. per coord. X 1° punto segm. orizz. (Bresenham) */
short int IncX1; /* Increm. contat. per coord. X 1° punto segm. or. (Bresenham) */
short int CntY1; /* Contat. per coord. Y 1° punto segm. orizz. (Bresenham) */
short int Lim1; /* Limite per contat. coord. X e Y 1° punto segm. or. (Bresenham) */
short int DirX1; /* Increm. coord. X 1° punto segm. orizz. */
short int IncY1; /* Increm. contat. per coord. Y 1° punto segm. or. (Bresenham) */
short int FX1; /* Valore iniziale coord. X1 segm. orizz. X1-XT */
short int CntXT; /* Contat. per coord. X 2° punto segm. orizz. (Bresenham) */
short int IncXT; /* Increm. contat. per coord. X 2° punto segm. or. (Bresenham) */
short int CntYT; /* Contat. per coord. Y 2° punto segm. orizz. (Bresenham) */
short int LimT; /* Limite per contat. coord. X e Y 2° punto segm. or. (Bresenham) */
short int DirXT; /* Increm. coord. X 2° punto segm. orizz. */
short int IncYT; /* Increm. contat. per coord. Y 2° punto segm. or. (Bresenham) */
short int FXT; /* Valore iniziale coord. XT segm. orizz. X1-XT */
T_Rect Segm; /* Record per la rappresentazione di un segm. orizz. */
Boolean F1; /* 1° cond. iniz. (eccezione), rappresentaz. triang. */
Boolean F24; /* 2° cond. iniz. (eccezione), rappresentaz. triang. */
Boolean Overflow=False; /* FALSE: Calcola segm. orizz.; TRUE: Ha finito */
Boolean Internal; /* Variabile temp.; salva il val. iniz. di Overflow */
Boolean Finished=True; /* FALSE: 2° semi-triang.; TRUE: 1° semi-triang.} */
/* Ordina i vertici in base alla coordinata Y */
if (Y1>Y2)
{Tmp=X1;
X1=X2;
X2=Tmp;
Tmp=Y1;
Y1=Y2;
Y2=Tmp;
Tmp=A;
A=B;
B=Tmp;}
if (Y2>Y3)
{Tmp=X2;
X2=X3;
X3=Tmp;
Tmp=Y2;
Y2=Y3;
Y3=Tmp;
Tmp=B;
B=C;
C=Tmp;}
if (Y1>Y2)
{Tmp=X1;
X1=X2;
X2=Tmp;
Tmp=Y1;
Y1=Y2;
Y2=Tmp;
Tmp=A;
A=B;
B=Tmp;}
/* Calcola il colore effettivo dei lati A-B, B-C e C-A del triangolo */
switch (27*A+9*B+C)
{case 19:{BrdColAB=C3;
BrdColCA=C1;
break;}
case 29:{BrdColBC=C3;
BrdColCA=C2;
break;}
case 45:{BrdColAB=C2;
BrdColBC=C3;
BrdColCA=C1;
break;}
case 55:{BrdColAB=C3;
BrdColBC=C1;
BrdColCA=C2;
break;}
case 63:{BrdColAB=C2;
BrdColBC=C1;
break;
}}
/* Calc. incr. e limiti, inizial. i cont. lato A-C (Bresenham) */
DirXT=-1;
IncXT=X1-X3;
if (X1<X3)
{DirXT=1;
IncXT=-IncXT;}
IncXT+=1;
CntXT=IncXT>>1;
IncYT=Y3-Y1+1;
CntYT=IncYT>>1;
LimT=IncXT;
if (IncXT<IncYT)
LimT=IncYT;
/* Imposta i valori iniziali delle var. locali */
XT=X1;
OY2=Y2;
F1=(Y1>=Y2) || (Y2!=Y3);
F24=((Y1!=Y2) || (Y2>=Y3)) &&
((Y1>=Y2) || (Y2>=Y3));
/* Disegna il primo vertice del triangolo */
if ((X1=X2) && (X2=X3) &&
(Y1=Y2) && (Y2=Y3))
{/* Segm->XY->X=X1;
Segm->XY->Y=Y1;
Segm->Dim_XY->DimX=1; */
Col1=BrdColAB;
if (Col1<0)
Col1=BrdColCA;
if (Col1<0)
Col1=FillColor;
if (Col1>=0)
{setcolor(DefColors[Col1]);
line(X1,Y1,X1,Y1);}
/* if (Col1<256)
PutHorizLine(&Segm,OBPVBuff,Col1,Clipp)
else
PutShadowHorizLine(&Segm,OBPVBuff,ShadowTable,Clipp); */}
/* Disegna il triangolo */
do
{/* Calc. incr. e limiti, inizial. i cont. lato A-B (Bresenham) */
DirX1=-1;
IncX1=X1-X2;
if (X1<X2)
{DirX1=1;
IncX1=-IncX1;}
IncX1+=1;
CntX1=IncX1>>1;
IncY1=Y2-Y1+1;
CntY1=IncY1>>1;
Lim1=IncX1;
if (IncX1<IncY1)
Lim1=IncY1;
FX1=X1;
FXT=XT;
/* Rappresenta un semi-triangolo */
while ((X1!=X2) || (Y1!=Y2))
{
/* Calcola i 4 estremi del segmento orizzontale da disegnare */
do
{Internal=Overflow;
if (Overflow)
{CntY1-=Lim1;
CntYT-=LimT;
Y1+=1;}
Overflow=True;
Tmp=CntY1+IncY1;
if (Tmp<Lim1)
{CntY1=Tmp;
CntX1+=IncX1;
if (CntX1>=Lim1)
{CntX1-=Lim1;
X1+=DirX1;}
Overflow=False;}
Tmp=CntYT+IncYT;
if (Tmp<LimT)
{CntYT=Tmp;
CntXT+=IncXT;
if (CntXT>=LimT)
{CntXT-=LimT;
XT+=DirXT;}
Overflow=False;}
if (Internal)
{FX1=X1;
FXT=XT;}
} while (!Overflow);
/* Ordina (ord. ascend.) i 4 estremi del segmento orizzontale */
B1L=FX1;
B1H=X1;
if (B1L>B1H)
{Tmp=B1L;
B1L=B1H;
B1H=Tmp;}
B2L=FXT;
B2H=XT;
if (B2L>B2H)
{Tmp=B2L;
B2L=B2H;
B2H=Tmp;}
Col1=BrdColAB;
Col2=BrdColCA;
if ((B2L<B1L) || (B2H<B1H))
{Tmp=B1L;
B1L=B2L;
B2L=Tmp;
Tmp=B1H;
B1H=B2H;
B2H=Tmp;
Tmp=Col1;
Col1=Col2;
Col2=Tmp;}
/* Calcola la posizione e la dimensione dei 2 bordi del segm. orizz. */
D1=B1H-B1L+1;
D0=B2L-B1H-1;
D2=B2H-B2L+1;
/* Ove possibile, unisce bordi con parte centrale del segm. orizz. */
if (D0>0)
{if (FillColor==Col2) /* Parte0 unita a parte2, parte0 esistente */
{D0+=D2;
D2=0;}
if (Col1==FillColor) /* Parte0 unita a parte1, parte0 esistente */
{B1H=B1L-1;
D0+=D1;
D1=0;}}
else
{D0=0;
if (Col1==Col2) /* Parte1 unita a parte2, parte0 inesistente */
{D1=B2H-B1L+1;
D2=0;}}
/* Rappresenta il primo bordo del segm. orizz. */
/* Segm->XY->Y=Y1;
Segm->XY->X=B1L;
Segm->Dim_XY->DimX=D1; */
if ((Col1>=0) && (D1>0))
{setcolor(DefColors[Col1]);
line(B1L,Y1,B1L+D1-1,Y1);}
/* if (Col1<256)
PutHorizLine(&Segm,OBPVBuff,Col1,Clipp)
else
PutShadowHorizLine(&Segm,OBPVBuff,ShadowTable,Clipp); */
/* Rappresenta la parte centrale del segm. orizz. */
if (((Y1!=OY2) ||
(!Finished || F1) && (Finished || F24)) && (D0>0))
{
/* Segm->XY->X=B1H+1;
Segm->Dim_XY->DimX=D0; */
if ((FillColor>=0) && (D0!=0))
{setcolor(DefColors[FillColor]);
line(B1H+1,Y1,B1H+D0,Y1);}
/* if (FillColor<256)
PutHorizLine(&Segm,OBPVBuff,FillColor,Clipp)
else
PutShadowHorizLine(&Segm,OBPVBuff,ShadowTable,Clipp); */
}
/* Rappresenta il secondo bordo del segm. orizz. */
/* Segm->XY->X=B2L;
Segm->Dim_XY->DimX=D2; */
if ((Col2>=0) && (D2>0))
{setcolor(DefColors[Col2]);
line(B2L,Y1,B2L+D2-1,Y1);}
/* if (Col2<256)
PutHorizLine(&Segm,OBPVBuff,Col2,Clipp)
else
PutShadowHorizLine(&Segm,OBPVBuff,ShadowTable,Clipp); */
}
X2=X3;
Y2=Y3;
BrdColAB=BrdColBC;
Finished=!Finished;
} while (!Finished);
}