マイペースなプログラミング日記

x86エミュレータやFPGA、WebGLにお熱なd-kamiがマイペースに書くブログ

UEFI Advent Calendar 2014 12/20 UEFI 3D!(違)

UEFI Advent Calendarでグラフィックを扱う人はいないだろうと思いネタを決定したわけだけど、私がわかってなかった!以前 GraphicsOutputProtocolでBMPを表示させて遊んでみた を参考にして2Dを表示した後、2D行ければ3D行けるだろ!ってことでレイトレースしたやつをまた使う!本当はラスタライザ実装して、下のスクリーンショットようなことをしたかった(MMD on RubyあるいはGWTMMD)

が!
現実は厳しいもので(私がやらなかっただけだが)、実際はレイトレースで下のスクリーンショットのようなのが限界だった

肝心のコードについてだが、先ほどの GraphicsOutputProtocolでBMPを表示させて遊んでみた を参考にレイトレースをしているだけなので設定なのは多分同じ。Visual Studioでの開発とエントリポイントがmainになってるぐらい?コーディング規約とかわからずに、最初に参考にしたコードにより変数名が基本的に大文字で始まっているとか、関数がーとかあるけど気にしない。というわけでコード貼り付けます。

#include <stdio.h>

#include <Protocol/GraphicsOutput.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>

//視点の位置
#define EYE {0.0f, 0.0f, -1.0f}

//光源の位置
#define LIGHTPOS {0.0, 1.0f, -1.0f}

//球の色のRGB値(0〜255)
#define DIFFUSE {125, 125, 255}

//描画領域のサイズ
#define WIDTH 400
#define HEIGHT 400
#define PIXEL_COUNT WIDTH * HEIGHT

//適当に設定した関数の成功・失敗を表す定数
#define SUCCESS 0
#define ERROR 1

//スクリーン座標をワールド座標に変換
void screenToWorld(float Position[], float X, float Y){
    Position[0] = (X / (float)WIDTH) * 2.0f - 1.0f;
    Position[1] = -((Y / (float)HEIGHT) * 2.0f - 1.0f);
    Position[2] = 0.0f;

    return;
}

//ベクトルIn1とベクトルIn2を足してベクトルOutに結果を入れる
void vecadd(float Out[], float In1[], float In2[]){
    Out[0] = In1[0] + In2[0];
    Out[1] = In1[1] + In2[1];
    Out[2] = In1[2] + In2[2];

    return;
}

//ベクトルIn1からベクトルIn2を引いてベクトルOutに結果を入れる
void vecsub(float Out[], float In1[], float In2[]){
    Out[0] = In1[0] - In2[0];
    Out[1] = In1[1] - In2[1];
    Out[2] = In1[2] - In2[2];

    return;
}

//ベクトルIn1をValue倍してベクトルOutに入れる
void vecscale(float Out[], float In1[], float Value){
    Out[0] = In1[0] * Value;
    Out[1] = In1[1] * Value;
    Out[2] = In1[2] * Value;

    return;
}

//ベクトルIn1とベクトルIn2の内積を計算して返す
float vecdot(float In1[], float In2[]){
    return In1[0] * In2[0] + In1[1] * In2[1] + In1[2] * In2[2];
}

//小数Valueの平方根を求めて返す
float sqrt(float Value){
    UINTN Index;
    float X = Value;

    for(Index = 0; Index < 5; Index++){
        X = (X + Value / X) / 2.0f;
    }

    return X;
}

//ベクトルIn1を正規化したベクトルをOutに入れる
void vecnormalize(float Out[], float In1[]){
    float Length = sqrt(vecdot(In1, In1));
    
    Out[0] = In1[0] / Length;
    Out[1] = In1[1] / Length;
    Out[2] = In1[2] / Length;
}

//レイトレースで求めた視線と球の交点にある球の法線を求める
void calcNormal(float Normal[], float Intersect[], float Eye[], float Dir[], float Center[], float T){
    vecscale(Intersect, Dir, T);
    vecadd(Intersect, Eye, Intersect);
    vecsub(Normal, Intersect, Center);
    vecnormalize(Normal, Normal);
}

//レイトレースの視線と球の交点計算
float raySphere(float Eye[], float Dir[], float R, float Intersect[], float Normal[]){
    float A = vecdot(Dir, Dir);
    float B = 2 * vecdot(Eye, Dir);
    float C = vecdot(Eye, Eye) - R * R;
    float D = B * B - 4 * A * C;
    float T = 0.0;

    static float Zero[] = {0.0f, 0.0f, 0.0f};
    
    if(D >= 0.0){
        T = (-B - sqrt(D)) / (2.0f * A);
    }else{
        T = -1.0f;
    }
    
    calcNormal(Normal, Intersect, Eye, Dir, Zero, T);
    
    return T;
}

//視点の位置からスクリーン座標(XIndex, YIndex, 0)を見たときの方向を求める
void calcEyeDir(float Dir[], float Eye[], UINTN XIndex, UINTN YIndex){
    float Pixel[3];

    screenToWorld(Pixel, (float)XIndex, (float)YIndex);
    vecsub(Dir, Pixel, Eye);
    vecnormalize(Dir, Dir);
}

//法線と光源への向きの内積を計算して返す
float calcDotNL(float LightPos[], float Intersect[], float Normal[]){
    float DotNL;
    float LightDir[3];

    vecsub(LightDir, LightPos, Intersect);
    vecnormalize(LightDir, LightDir);
    DotNL = vecdot(LightDir, Normal);

    if(DotNL < 0.0f){
        DotNL = 0.0f;
    }

    return DotNL;
}

//法線と光源の内積と球の色から2D画面に出力する色を決定する
void drawColor(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, UINTN BltPos, float dotNL, UINTN Diffuse[]){
    BltBuffer[BltPos].Red      = (UINT8)(dotNL * Diffuse[0]);
    BltBuffer[BltPos].Green    = (UINT8)(dotNL * Diffuse[1]);
    BltBuffer[BltPos].Blue     = (UINT8)(dotNL * Diffuse[2]);
}

//強制的に白にする
void drawWhite(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, UINTN BltPos){
    BltBuffer[BltPos].Red      = (UINT8)255;
    BltBuffer[BltPos].Green    = (UINT8)255;
    BltBuffer[BltPos].Blue     = (UINT8)255;
}

//レイトレースを使って3Dの球を描画するように頑張る(この関数では1ピクセルだけ判定し塗りつぶす)
void draw(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, UINTN BltPos, float Eye[], float Dir[], float LightPos[], UINTN Diffuse[]){
    float DotNL;

    float Normal[3];
    float Intersect[3];

    if(raySphere(Eye, Dir, 0.3f, Intersect, Normal) > 0.0){
        DotNL = calcDotNL(LightPos, Intersect, Normal);
        drawColor(BltBuffer, BltPos, DotNL, Diffuse);
    }else{
        drawWhite(BltBuffer, BltPos);
    }
}

//レイトレースで球を描画するように頑張る
void draw3D(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, float Eye[], float LightPos[], UINTN Diffuse[]){
    float Dir[3];
    UINTN XIndex, YIndex;

    for(YIndex = 0; YIndex < HEIGHT; YIndex++){
        for(XIndex = 0; XIndex < WIDTH; XIndex++){
            calcEyeDir(Dir, Eye, XIndex, YIndex);
            draw(BltBuffer, YIndex * WIDTH + XIndex, Eye, Dir, LightPos, Diffuse);
        }
    }
}

//グラフィック関係の準備
UINTN createGraphics(EFI_GRAPHICS_OUTPUT_PROTOCOL **GraphicsOutput, EFI_GRAPHICS_OUTPUT_BLT_PIXEL **BltBuffer){
    EFI_STATUS Status;

    Status = gST->BootServices->LocateProtocol(
               &gEfiGraphicsOutputProtocolGuid,
               NULL,
               GraphicsOutput
           );

    if(EFI_ERROR(Status)){
        puts("Error LocateProtocol!");
        return ERROR;
    }

    *BltBuffer = AllocateZeroPool(sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL) * PIXEL_COUNT);

    if(*BltBuffer == NULL){
        puts("Allocate Error!");
        return ERROR;
    }

    return SUCCESS;
}

//球を画面へ出力
UINTN outputGraphics(EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer){
    EFI_STATUS Status;
    
    Status = GraphicsOutput->Blt(
        GraphicsOutput,
        BltBuffer,
        EfiBltBufferToVideo,
        0 , 0, 0, 0,
        WIDTH, HEIGHT, 0);

    if(EFI_ERROR(Status)){
        puts("Error!");
        return ERROR;
    }

    return SUCCESS;
}

//エントリポイント
int main (IN int Argc, IN char **Argv){
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer;
    EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput;

    float Eye[] = EYE;
    float LightPos[] = LIGHTPOS;
    UINTN Diffuse[] = DIFFUSE;

    if(createGraphics(&GraphicsOutput, &BltBuffer) == ERROR){
        return ERROR;
    }

    draw3D(BltBuffer, Eye, LightPos, Diffuse);
    outputGraphics(GraphicsOutput, BltBuffer);

    FreePool(BltBuffer);

    return 0;
}