目次
先駆的な知識
- スキャン変換の基本概念を理解します。
- ポリゴン有効エッジテーブル充填アルゴリズムに精通しています。
- ポリゴンエッジ充填アルゴリズムをマスターしてください。
- 4 隣接点および 8 隣接点の領域充填アルゴリズムに精通しています。
- エリアスキャンラインシード充填アルゴリズムをマスターしてください。
使用するシェーディング モードに関係なく、ポリゴンの境界内のすべてのピクセルが指定されたカラーでシェーディングされることを意味します。
ポリゴン表現には ⑴ 頂点表現 ⑵ ラティス表現 の 2 種類があります。
ポリゴンスキャン変換
定義: ポリゴンの記述を頂点表現から格子表現に変換するプロセスは、ポリゴン スキャン変換と呼ばれます。
つまり、ポリゴンの頂点情報からポリゴン内部の各ピクセルの情報を取得し、そのカラー値をフレームバッファの対応するユニットに書き込む。
ポリゴンは、フラットシェーディング モードまたはスムースシェーディング モードを使用して塗りつぶすことができます。
ET テーブル (エッジ テーブル): 水平辺を除く多角形のすべての辺の情報を格納するために使用されます。
AET テーブル (有効エッジ テーブル): 現在のスキャン ラインと交差する多角形のエッジ。
ポリゴン塗りつぶしの主なアルゴリズムは、スキャンライン アルゴリズムです。
まず、ポリゴンに含まれるスキャンラインの数を決定し、スキャンラインごとに、スキャンラインとポリゴン境界との交差間隔を計算し、その間隔がポリゴンの内側にあると判断できれば、その間隔内のピクセルが描画されます指定された色として。
各走査線を処理するとき、走査線アルゴリズムは多角形のすべての辺と交差する必要があるため、処理効率は非常に低くなります。改良されたアルゴリズムは、効率的なエッジ リスト アルゴリズムです。
走査線の塗りつぶしは、通常、次の 4 つのステップに分かれています。
- 交差: 走査線と多角形の各辺との交点を計算します。
- 並べ替え: 走査線上のすべての交点を昇順に並べ替えます。
- ペアリング: 最初の交点と 2 番目の交点、3 番目の交点と 4 番目の交点などをペアにします。交点の各ペアは、スキャン ラインと多角形の交点間隔を表します。
- 色付け: 間隔内のピクセルを塗りつぶしの色として設定します。
シード充填アルゴリズムは、領域内のシード位置から開始され、シードとその隣接ピクセルを内側から外側に塗りつぶし色で、境界ピクセルが異なる色のになるまで描画します。
シード充填アルゴリズムは、主に 4 隣接点アルゴリズムと 8 隣接点アルゴリズムに分かれます。
効率的なエッジテーブル充填アルゴリズム
原理
塗りつぶしの原理は、小さいものから大きいものへのスキャン ラインの移動順序に従って、現在のスキャン ラインと有効なエッジの交点を計算し、これらの交点を x 値の増加順に並べ替えてペアにして、塗りつぶしを決定することです。区間を指定し、最後に指定した全ピクセルの色で区間を塗りつぶせば塗りつぶし作業は完了です。
効率的なエッジ テーブル充填アルゴリズムは、現在最も効果的なポリゴン充填アルゴリズムの 1 つとなっています。
境界ピクセルの処理原理
正方形の左下隅を(1, 1)、右上隅を(3, 3)で塗りつぶす場合、境界線上のピクセルをすべて塗りつぶすと図のような結果が得られます。ポリゴンの塗りつぶしプロセスでは、「左が閉じ、右が開く」および「下が閉じ、上が開く
」の原則が境界ピクセルの処理によく使用されます。
交点の計算方法
ここで、走査線に対するエッジの位置は、左右の関係ではなく、上下の関係にある。
有効なエッジ
新しいエッジがどのスキャン ラインに挿入されるかを決定するには、スキャン ライン上の多角形の各エッジの発生に関する情報を保存するエッジ テーブル (ET) を構築する必要があります。
水平エッジの 1/k は ∞ であり、水平エッジ自体が走査線であるため、エッジ テーブルを作成する際には無視できます。
バケットテーブルとサイドテーブル
新しいエッジがどのスキャン ラインに挿入されるかを決定するには、スキャン ライン上の多角形の各エッジの発生に関する情報を保存するエッジ テーブル (ET) を構築する必要があります。水平エッジの 1/k は ∞ であり、水平エッジ自体が走査線であるため、エッジ テーブルを作成する際には無視できます。
バケットテーブルの表記法
バケット テーブルは、エッジの発生を走査線の順序で管理するデータ構造です。
- 垂直走査線リンク リストを作成します。リンク リストの長さは、ポリゴンが占める走査線の最大数です。リンク リストの各ノードはバケットと呼ばれ、ポリゴンでカバーされる各走査線に対応します。
バケットコード
class CBucket
{
public:
CBucket();
virtual ~CBucket();
public:
int ScanLine; //扫描线
CAET *p; //桶上的边表指针
CBucket *next;
};
実際、本のいわゆる新しいエッジは、最初の走査線 (すべて上から下) から開始して、それと交差するエッジが順番に記録され、x/y に従ってリンク リストに追加されることを意味します ( min). 繰り返しエッジ (前の走査線が自身のリンク リストのエッジに配置されている) は配置されません。最後に、すべてのリンクされたリスト内の要素の数は、ポリゴンの辺の数と等しくなければなりません。したがって、ここでの新しいエッジは、追加のエッジをいくつか追加するということではなく、ポリゴンを最初から構築することになります。また、本書で取り上げられている y=1 ~ y=12 のポリゴンに限定するわけにはいかず、多くのポリゴンは頂点が整数ではなく、ピクセル座標系上で規則的に配置されています。バケット テーブルとエッジ テーブルについて詳しく知るには、まず、スキャン変換中に使用する座標系が実際にはピクセルで構成される座標系であることを知る必要があります。各単位点はピクセルに対応し、そこからスキャン ラインが形成されます。座標系内の各 y=Z (Z は任意の整数) 直線。これから説明することは、ポリゴンを適切な方法で座標系に配置した後、ポリゴンと交差する最初のスキャン ラインから開始してエッジ テーブルを作成することです (下から上へ、交差する頂点もカウントされます)。
上記を理解すると、バレル テーブルとサイド テーブルを正式に理解できるようになります。バケット テーブルとエッジ テーブルは、有効エッジ テーブルと同様に 2 つのテーブルではなく、全体のテーブルであることに注意してください。多角形に対応する走査線が y=1 から y=10 である場合、y=1 の有効エッジから開始して、y=1 のすべての有効エッジを y=1 に対応するリンク リストに入れてから、 y=2 から y=10 まで、中央に走査線の有効なエッジがあり、前の走査線が配置されている場合、再度配置することはできません。すべてのエッジを入力するか、ラインを最後のエッジまでスキャンして終了することを知ってください。手書きする場合は、まず表を描いて多角形で覆われた走査線を書き、次に y=1 で矢印を使用して、これまでに出現したことのない有効なエッジのリンクされたリストを描画します。
最後に、バケットテーブルとサイドテーブルのリンクリストの内容を覚えておくだけです。4 つのパートから構成されており、最初のパートは本の理解に従って間違いを犯しやすいです。最初の部分では、x|y(min) は x を y で割ったものではなく、x が y を割ったものでもありません。この本ではここでは明確にされていません。**x|y(min) ここで、対応する有効エッジを指します。 with 最も下の走査線がこのエッジと交差する点の x 値。** 2 番目の部分は ymax で、対応する有効なエッジが交差する最も高い走査線です。3 番目の部分 k/1 は、有効エッジに対応する傾きの逆数を指します。4 番目の部分はネクスト ポインタで、前のリンク リストは次のリンク リストに接続され、最後のユニット ポインタは空です。非反復の有効なエッジをすべて見つけたら、x|ymin で小さいものから大きいものへ順にリンク リストを構築します (x|ymin が等しい場合は、1/k で小さいものから大きなものへの順に構築します)。走査線に非反復有効エッジがない場合は、書き込まないでください。上記の内容を理解すると、バケットテーブルやサイドテーブルが書けるようになります。これは、データ構造を使用してポリゴンを構築するために使用されるアルゴリズムに似ていますか? これらは本質的にツリー関連のアルゴリズムですが、グラフィックスの多くの側面が明確に説明されていないためです。
—————————————
著作権表示: この記事は CSDN ブロガー「SN Chuanchuan」によるオリジナル記事であり、CC 4.0 BY-SA 著作権契約に従っています。転載する場合は、オリジナルのソースリンクを添付してください。そしてこの発言。
元のリンク: https://blog.csdn.net/weixin_44724323/article/details/124647806
エッジフィリングアルゴリズム
離れる/定義する:
エッジ充填アルゴリズムは、まず多角形の各エッジと走査線の交点を見つけ、次に交点の右側にあるすべてのピクセルの色を補色 (または反転色) として取得します。多角形のすべての辺が任意の順序で処理されると、多角形を塗りつぶすタスクは完了します。
エッジフィリングアルゴリズムは、画像処理における「補数」または「逆数」の概念を使用します。白黒画像の場合、補数とは、RGB(1,1,1) (白) のピクセルを RGB(0,0, 0) (黒)、またはその逆。カラー画像の場合、補色とは背景色を塗りつぶしの色に設定すること、またはその逆も同様です。補完の基本的な特性は、ピクセルが 2 回補完されると、元の色に戻ることです。ポリゴン内のピクセルが偶数回補われた場合は元の色が維持され、奇数回補われた場合は塗りつぶされた色が表示されます。
充填工程
プロセス:
(下の図のシーケンス) 左から右、上から下
コード:
void CTestView::FillPolygon(CDC *pDC)
{
COLORREF BClr=RGB(255,255,255);//背景色
COLORREF FClr=GetClr;//填充色
int yMin,yMax;//边的最小y值与最大y值
double x,y,k;//x,y当前点,k斜率的倒数
for(int i=0;i<7;i++)//循环多边形所有边
{
int j=(i+1)%7;
k=(P[i].x-P[j].x)/(P[i].y-P[j].y);//计算1/k
if(P[i].y<P[j].y)//得到每条边y的最大值与最小值
{
yMin=Round(P[i].y);
yMax=Round(P[j].y);
x=P[i].x;//得到x|ymin
}
else
{
yMin=Round(P[j].y);
yMax=Round(P[i].y);
x=P[j].x;
}
for(y=yMin;y<yMax;y++)//沿每一条边循环扫描线
{
//对每一条扫描线与边的交点的右侧像素循环
for(int m=Round(x);m<MaxX;m++)
//MaxX为包围盒的右边界
{
if(FClr==pDC->GetPixel(m,Round(y))) pDC->SetPixelV(m,Round(y),BClr);
else
pDC->SetPixelV(m,Round(y),FClr);
}
x+=k;
}
}
}
領域充填アルゴリズム/シード充填アルゴリズム
原理
シード塗りつぶしアルゴリズムは、領域内の任意のシード ピクセル位置から開始され、塗りつぶし色を多角形領域全体に内側から外側に広げる塗りつぶしプロセスです。
利点は、任意の複雑な閉じた境界で領域を塗りつぶせることです。
シード充填アルゴリズム
意味;
- シード ピクセルから開始して、隣接する 4 つの点を使用して次のピクセルを検索する充填アルゴリズムは、4 隣接点充填アルゴリズムと呼ばれます。
- シード ピクセルから開始して、8 つの隣接点を使用して次のピクセルを検索する充填アルゴリズムは、8 隣接点充填アルゴリズムと呼ばれます。
8 隣接点充填アルゴリズムの設計は基本的に 4 隣接点充填アルゴリズムと似ており、探索方法が 4 隣接点から 8 隣接点に変更されるだけです。
シード充填アルゴリズムでは通常、領域の境界色と充填色が異なる必要があり、入力パラメータはシード座標位置と充填色のみです。
原則
コード:
void CTestView::FillPolygon(CDC *pDC)//填充多边形
{
COLORREF BoundaryClr=RGB(0,0,0);//边界色
COLORREF PixelClr;//当前像素的颜色
pHead=new CStackNode;//建立栈头结点
pHead->next=NULL;//栈的头结点总是为空
Push(Seed); //种子像素入栈
while(NULL!=pHead->next)//如果栈不为空
{
CP2 PopPoint;
Pop(PopPoint); //种子像素出栈
pDC->SetPixelV(Round(PopPoint.x),
Round(PopPoint.y),SeedClr);
PointLeft.x=PopPoint.x-1;//左方像素
PointLeft.y=PopPoint.y;
PixelClr=pDC->GetPixel(Round(PointLeft.x),
Round(PointLeft.y));
if(BoundaryClr!=PixelClr && SeedClr!=PixelClr)
Push(PointLeft);//左方像素入栈
PointTop.x=PopPoint.x;
PointTop.y=PopPoint.y+1;//上方像素
PixelClr=pDC->GetPixel(Round(PointTop.x),
Round(PointTop.y));
if(BoundaryClr!=PixelClr && SeedClr!=PixelClr)
Push(PointTop); //上方像素入栈
PointRight.x=PopPoint.x+1;//右方像素
PointRight.y=PopPoint.y;
PixelClr=pDC->GetPixel(Round(PointRight.x),
Round(PointRight.y));
if(BoundaryClr!=PixelClr && SeedClr!=PixelClr)
Push(PointRight);//右方像素入栈
PointBottom.x=PopPoint.x;
PointBottom.y=PopPoint.y-1;//下方像素
PixelClr=pDC->GetPixel(Round(PointBottom.x),
Round(PointBottom.y));
if(BoundaryClr!=PixelClr && SeedClr!=PixelClr)
Push(PointBottom);//下方像素入栈
}
pDC->TextOut(rect.left+50,rect.bottom-20,"填充完毕");
delete pHead;
pHead = NULL;
}