ソフトウェアラスタライザー基本実装編 02.ワイヤフレーム描画
前回は座標変換を行い頂点位置に点を打ちました。
今回はポリゴン毎にワイヤフレームの描画を行います。
点を打つか線を引くかの違いだけなのでソースの変更点は殆どありません。
リポジトリ
ビルドして実行するとこんな感じの絵が出ます。
頂点データを座標変換してポリゴン単位でラインを描画しています。
レンダリング
レンダリング処理の違いは点を打つか線を引くかだけなので変更点もレンダリングの箇所だけになります。
Renderer.cpp
void Renderer::RenderTriangle(const IMeshData* pMeshData, const Vector4 Positions[], const int32 VertexCount, const uint16* pIndex, const int32 IndexCount)
{
const auto WidthF = SCREEN_WIDTH_F - 1.0f;
const auto HeightF = SCREEN_HEIGHT_F - 1.0f;
const int32 PointCount = 3;
Vector4 Temp[PointCount];
auto index = 0;
while (index < IndexCount)
{
const auto i0 = pIndex[index++];
const auto i1 = pIndex[index++];
const auto i2 = pIndex[index++];
Temp[0] = Positions[i0];
Temp[1] = Positions[i1];
Temp[2] = Positions[i2];
// 画面外にはみ出すポリゴンは破棄する
if (Temp[0].w <= 0.0f) continue;
if (Temp[1].w <= 0.0f) continue;
if (Temp[2].w <= 0.0f) continue;
if (Temp[0].x < -Temp[0].w) continue;
if (Temp[1].x < -Temp[1].w) continue;
if (Temp[2].x < -Temp[2].w) continue;
if (Temp[0].x > +Temp[0].w) continue;
if (Temp[1].x > +Temp[1].w) continue;
if (Temp[2].x > +Temp[2].w) continue;
if (Temp[0].y < -Temp[0].w) continue;
if (Temp[1].y < -Temp[1].w) continue;
if (Temp[2].y < -Temp[2].w) continue;
if (Temp[0].y > +Temp[0].w) continue;
if (Temp[1].y > +Temp[1].w) continue;
if (Temp[2].y > +Temp[2].w) continue;
for (int32 j = 0; j < PointCount; ++j)
{
auto& pt = Temp[j];
pt.x = (+pt.x / pt.w * 0.5f + 0.5f) * WidthF;
pt.y = (-pt.y / pt.w * 0.5f + 0.5f) * HeightF;
}
DrawLine(int32(Temp[0].x + 0.5f), int32(Temp[0].y + 0.5f), int32(Temp[1].x + 0.5f), int32(Temp[1].y + 0.5f));
DrawLine(int32(Temp[1].x + 0.5f), int32(Temp[1].y + 0.5f), int32(Temp[2].x + 0.5f), int32(Temp[2].y + 0.5f));
DrawLine(int32(Temp[2].x + 0.5f), int32(Temp[2].y + 0.5f), int32(Temp[0].x + 0.5f), int32(Temp[0].y + 0.5f));
}
}
前回は座標変換後の3点をそれぞれ点を打っていましたが、今回はそれぞれを結ぶラインを描画しています。
ラインの描画としてはブレンゼンハムが有名で私も昔勉強した記憶があります。
正確なアルゴリズムは覚えてないので今回はライン描画する処理は適当に作りました。
Renderer.cpp
void Renderer::DrawLine(int32 x0, int32 y0, int32 x1, int32 y1)
{
const int32 dx = std::abs(x1 - x0);
const int32 dy = std::abs(y1 - y0);
if (dx > dy)
{
if (x0 > x1)
{
std::swap(x0, x1);
std::swap(y0, y1);
}
y0 <<= 16;
y1 <<= 16;
const int32 add = dx == 0 ? 0 : (y1 - y0) / dx;
int32 y = y0;
for (int32 x = x0; x <= x1; ++x)
{
const auto dx = x;
const auto dy = y >> 16;
_pColorBuffer->SetPixel(dx, dy, 0xFFFFFFFF);
y += add;
}
}
else
{
if (y0 > y1)
{
std::swap(x0, x1);
std::swap(y0, y1);
}
x0 <<= 16;
x1 <<= 16;
const int32 add = dx == 0 ? 0 : (x1 - x0) / dy;
int32 x = x0;
for (int32 y = y0; y <= y1; ++y)
{
const auto dx = x >> 16;
const auto dy = y;
_pColorBuffer->SetPixel(dx, dy, 0xFFFFFFFF);
x += add;
}
}
}
最初にX軸方向とY軸方向で移動量の大きい方を求めてそちらを基準にforを回します。
仮にXが大きい場合であればXが1ドット移動する際のYの移動量を求めてfor内でYを進めます。
この際必ずYは1以下になるのでfloatでなくてはなりませんが、キャストが面倒だったので16ビット固定小数でYを扱っています。
最後に
今回はワイヤフレーム描画を実装しました。
線を描くだけの処理なので難しい内容ではないですが次以降の解説で必要になるので章を分けました。
今回は画面外のポリゴンを破棄しましたが次回は画面外のポリゴンをどうするかを扱います。