目的
将来x86上でOSを介さずに動く簡単な3Dエンジンを開発したいと思っています。
今回はポリゴンを理解する為にVGAに直接三角形を描画する検証を行います。
参考にした筆者の過去記事
ブートローダや、32ビットへ移行する方法及びC言語でのベアメタル開発の手順は以下に纏めています。
DVDから直接起動してC言語を動かす手順
x86ベアメタル直線描画
boot.asm,swtc.asm,linker.ldは前記事中のものを流用しています。
動作検証
QEMU
dynabook SS M10 11L/2
コード
kernel.c
#define VGA_MEM 0xA0000
#define SCREEN_W 320
#define SCREEN_H 200
typedef unsigned char u8;
/* 点 */
typedef struct {
int x, y;
} Point;
/* 三角形 */
typedef struct {
Point p0, p1, p2;
u8 color;
} Triangle;
/* (x,y)座標にcで指定した色の点を打つ
メモリは線形。(X,Y)の位置に対応するアドレスを求める公式 =
VRAM開始アドレス + Y*画面の解像度横 + X
*/
void put_pixel(int x, int y, u8 c)
{
// 描画範囲外(負値は unsigned 変換で弾く)
if ((unsigned)x >= SCREEN_W || (unsigned)y >= SCREEN_H)
return;
((volatile u8 *)VGA_MEM)[y * SCREEN_W + x] = c;
}
/* (x0, y)から (x1, y)まで横1本の線を引く */
void draw_hline(int x0, int x1, int y, u8 c)
{
if ((unsigned)y >= SCREEN_H)return; //画面外なら即終了
if (x0 > x1) { int t = x0; x0 = x1; x1 = t; }//左 → 右になるよう入れ替え
for (int x = x0; x <= x1; x++)
put_pixel(x, y, c);
}
/* 三角形塗りつぶし(底辺を揃えない・汎用版) */
void fill_triangle(const Triangle *t)
{
// 元の頂点
int x0 = t->p0.x, y0 = t->p0.y;
int x1 = t->p1.x, y1 = t->p1.y;
int x2 = t->p2.x, y2 = t->p2.y;
// 上下ソート用
// top
// *
// / \
// / \
// / \
// *-------* 実際は斜め
// mid bot
int top_x, top_y;
int mid_x, mid_y;
int bot_x, bot_y;
// 上(Yが最小)
if (y0 <= y1 && y0 <= y2) { top_x = x0; top_y = y0; }
else if (y1 <= y0 && y1 <= y2) { top_x = x1; top_y = y1; }
else { top_x = x2; top_y = y2; }
// 下(Yが最大)
if (y0 >= y1 && y0 >= y2) { bot_x = x0; bot_y = y0; }
else if (y1 >= y0 && y1 >= y2) { bot_x = x1; bot_y = y1; }
else { bot_x = x2; bot_y = y2; }
// 残りが真ん中
if ((top_x == x0 && bot_x == x1) || (top_x == x1 && bot_x == x0))
{ mid_x = x2; mid_y = y2; }
else if ((top_x == x0 && bot_x == x2) || (top_x == x2 && bot_x == x0))
{ mid_x = x1; mid_y = y1; }
else
{ mid_x = x0; mid_y = y0; }
// 分割描画に必要な差分(Yの差 ΔY)
// 上半分と下半分に分けて描画する
int dy_top_mid = mid_y - top_y;
int dy_top_bot = bot_y - top_y;
int dy_mid_bot = bot_y - mid_y;
// 上部(top ~ mid)
// 上現在のY座標 yにおいて、三角形の左端 xaと右端 xbを計算して、その間を横線で塗る
for (int y = top_y; y <= mid_y; y++) { // 行ループ(Y方向)
if ((unsigned)y >= SCREEN_H) continue; // 画面端を越えたら何もしない
int xa = top_x;
int xb = top_x;
// 今のY位置における、top→midの辺のX座標を求める
if (dy_top_mid != 0) // 0除算防止
// 現在の行が全体の何割進んだか(相似比)を求め、
// その割合だけXを加算(或いは減算)して位置を補間する
xa += (mid_x - top_x) * (y - top_y) / dy_top_mid; // 短辺
if (dy_top_bot != 0)
xb += (bot_x - top_x) * (y - top_y) / dy_top_bot; // 長編
if (xa > xb) { int tmp = xa; xa = xb; xb = tmp; } // 常に xa ≤ xbにする (xa→xbへ描写する)
draw_hline(xa, xb, y, t->color);
}
// 下部(mid ~ bot)
for (int y = mid_y + 1; y <= bot_y; y++) {
if ((unsigned)y >= SCREEN_H) continue;
int xa = mid_x;
int xb = top_x;
if (dy_mid_bot != 0)
xa += (bot_x - mid_x) * (y - mid_y) / dy_mid_bot;
if (dy_top_bot != 0)
xb += (bot_x - top_x) * (y - top_y) / dy_top_bot;
if (xa > xb) { int tmp = xa; xa = xb; xb = tmp; }
draw_hline(xa, xb, y, t->color);
}
}
/* 無限待機(省電力) */
static void idle_loop(void)
{
while (1)
__asm__ volatile ("hlt");
}
void kernel_main(void)
{
Triangle tri = {
.p0 = {70, 170},
.p1 = {180, 40},
.p2 = {260, 140},
.color = 12
};
fill_triangle(&tri);
idle_loop();
}