c+WINAPIで3DCGの理屈その4

記事
IT・テクノロジー
さて、前回のグーローシェーディング付きテクスチャーマッピングと
フォンシェーディング付きテクスチャーマッピングはバグがあったのが
なんとなく推察できたかもしれないですが、作者こと自分の理解度不足
があったことが全ての原因です。

という訳で今回はテクスチャーのグーローとフォンが治るまで
シャドウを作ります。

#include"DXLib.h"
#include<math.h>
#include<stdlib.h>
#include<windows.h>
#include<utility>

using namespace std;

#define CELL 1.0f
const float FOV_F = 600.0f;
const int HEIGHT = 240;
const int WIDTH = 320;

struct VTX {
    float x, y, z;
    float u, v;
};

struct Camera
{
    float x, y, z;
    float yaw; // 左右回転のみ
    float height;
};

enum WallDir
{
    WALL_NORTH,
    WALL_SOUTH,
    WALL_WEST,
    WALL_EAST
};

struct Vec3
{
    float x;
    float y;
    float z;
};

float ZBuf[HEIGHT][WIDTH];
#define RAD (3.14159265/180.0)
Camera cam;

// ==================================================
// ライティング&点光源シャドウ関連
// ==================================================
Vec3 g_LightPos = { 10.0f, 1.8f, 10.0f }; // 点光源の位置
float g_Ambient = 0.35f; // 環境光
float g_FaceBrightness = 1.0f; // 面の明るさ

// 法線ベクトルから明るさ(0〜1)を計算
float ComputeBrightness(Vec3 normal)
{
    float len = sqrtf(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);
    if (len > 0.0001f) {
        normal.x /= len; normal.y /= len; normal.z /= len;
    }
    Vec3 lightDir = { -0.4f, -1.0f, -0.3f };
    float lLen = sqrtf(lightDir.x*lightDir.x + lightDir.y*lightDir.y + lightDir.z*lightDir.z);
    lightDir.x /= lLen; lightDir.y /= lLen; lightDir.z /= lLen;

    float ndotl = -(normal.x * lightDir.x + normal.y * lightDir.y + normal.z * lightDir.z);
    if (ndotl < 0.0f) ndotl = 0.0f;
    float b = g_Ambient + (1.0f - g_Ambient) * ndotl;
    if (b > 1.0f) b = 1.0f;
    return b;
}

// テクスチャ色(0xRRGGBB)に明るさを掛ける
inline int ShadeColor(int col, float brightness)
{
    int r = (col >> 16) & 0xFF;
    int g = (col >> 8) & 0xFF;
    int b = col & 0xFF;
    r = (int)(r * brightness);
    g = (int)(g * brightness);
    b = (int)(b * brightness);
    if (r > 255) r = 255; if (r < 0) r = 0;
    if (g > 255) g = 255; if (g < 0) g = 0;
    if (b > 255) b = 255; if (b < 0) b = 0;
    return GetColor(r, g, b);
}

// 頂点変換関数プロトタイプ
bool TransformAndProject(float wx, float wy, float wz, float u, float v, VTX& out);

// 壁の上面頂点を光源から床(Y=0)へ投影
bool TransformAndProjectShadow(float wx, float wy, float wz, VTX& out) {
    if (g_LightPos.y <= wy) return false;

    float dx = wx - g_LightPos.x;
    float dy = wy - g_LightPos.y;
    float dz = wz - g_LightPos.z;

    float t = -g_LightPos.y / dy;
    float sx = g_LightPos.x + dx * t;
    float sz = g_LightPos.z + dz * t;
    float sy = 0.0f;

    return TransformAndProject(sx, sy, sz, 0.0f, 0.0f, out);
}
// ==================================================

int L[HEIGHT];
int R[HEIGHT];
float LU[HEIGHT];
float LV[HEIGHT];
float RU[HEIGHT];
float RV[HEIGHT];
float LW[HEIGHT];
float RW[HEIGHT];

int soft;
int Texture[1024*2048] = {0};
int TexW = 0;
int TexH = 0;

#define W 21
#define H 21
int maze[H][W]; // 0=通路, 1=壁
const float WALL_H = 1.0f;

Vec3 GetWallNormal(WallDir dir);
void DrawWall(float x, float z, WallDir dir);

#define MAX_VTX 1000
#define MAX_POL 3000
VTX v[MAX_VTX] = {0};
int PolygonNumber[MAX_POL][4] = {0};

void TexTri_Complex(VTX n1, VTX n2, VTX n3);
#define TexTri TexTri_Complex

void Line(int x1, int y1, float u1, float v1, float w1,
          int x2, int y2, float u2, float v2, float w2) {
    if (y1 == y2) return;
    if (y1 > y2) {
        swap(y1, y2); swap(x1, x2);
        swap(u1, u2); swap(v1, v2); swap(w1, w2);
    }
    float invH = 1.0f / (float)(y2 - y1);
    for (int y = y1; y < y2; y++) {
        if (y < 0 || y >= HEIGHT) continue;
        float t = (float)(y - y1) * invH;
        int x = (int)(x1 + (x2 - x1) * t);
        float u = u1 + (u2 - u1) * t;
        float v = v1 + (v2 - v1) * t;
        float w = w1 + (w2 - w1) * t;
        if (x < L[y]) { L[y] = x; LU[y] = u; LV[y] = v; LW[y] = w; }
        if (x > R[y]) { R[y] = x; RU[y] = u; RV[y] = v; RW[y] = w; }
    }
}

void PSET(int x, int y, int c)
{
    int r = (c >> 16) & 0xFF;
    int g = (c >> 8) & 0xFF;
    int b = c & 0xFF;
    DrawPixelSoftImage(soft, x, y, r, g, b, 255);
}

// 影専用ラスタライザ
void DrawShadowTriangle(VTX n1, VTX n2, VTX n3) {
    int miny = (int)min(n1.y, min(n2.y, n3.y));
    int maxy = (int)max(n1.y, max(n2.y, n3.y));
    miny = max(0, miny);
    maxy = min(HEIGHT - 1, maxy);

    for (int y = miny; y <= maxy; y++) {
        L[y] = 9999; R[y] = -9999;
    }
    Line((int)n1.x, (int)n1.y, 0, 0, n1.z, (int)n2.x, (int)n2.y, 0, 0, n2.z);
    Line((int)n2.x, (int)n2.y, 0, 0, n2.z, (int)n3.x, (int)n3.y, 0, 0, n3.z);
    Line((int)n3.x, (int)n3.y, 0, 0, n3.z, (int)n1.x, (int)n1.y, 0, 0, n1.z);

    for (int y = miny; y <= maxy; y++) {
        if (L[y] > R[y]) continue;
        int lx = max(0, L[y]);
        int rx = min(WIDTH - 1, R[y]);
        if (lx > rx) continue;

        float span = (float)(R[y] - L[y]);
        if (span <= 0.0f) span = 1.0f;

        float dw = (RW[y] - LW[y]) / span;
        float w = LW[y] + dw * (lx - L[y]);

        for (int x = lx; x <= rx; x++) {
            // 床のZ深度にマージンを設けて判定
            if (w >= ZBuf[y][x] - 0.0001f) {
                int r, g, b, a;
                GetPixelSoftImage(soft, x, y, &r, &g, &b, &a);
                r = (int)(r * 0.3f);
                g = (int)(g * 0.3f);
                b = (int)(b * 0.3f);
                PSET(x, y, GetColor(r, g, b));
            }
            w += dw;
        }
    }
}

// テクスチャポリゴンラスタライザ
void TexTri_Complex(VTX n1, VTX n2, VTX n3) {
    int miny = (int)min(n1.y, min(n2.y, n3.y));
    int maxy = (int)max(n1.y, max(n2.y, n3.y));
    miny = max(0, miny);
    maxy = min(HEIGHT - 1, maxy);

    for (int y = miny; y <= maxy; y++) {
        L[y] = 9999; R[y] = -9999;
    }
    Line((int)n1.x, (int)n1.y, n1.u, n1.v, n1.z, (int)n2.x, (int)n2.y, n2.u, n2.v, n2.z);
    Line((int)n2.x, (int)n2.y, n2.u, n2.v, n2.z, (int)n3.x, (int)n3.y, n3.u, n3.v, n3.z);
    Line((int)n3.x, (int)n3.y, n3.u, n3.v, n3.z, (int)n1.x, (int)n1.y, n1.u, n1.v, n1.z);

    for (int y = miny; y <= maxy; y++) {
        if (L[y] > R[y]) continue;
        int lx = max(0, L[y]);
        int rx = min(WIDTH - 1, R[y]);
        if (lx > rx) continue;

        float span = (float)(R[y] - L[y]);
        if (span <= 0.0f) span = 1.0f;

        float du = (RU[y] - LU[y]) / span;
        float dv = (RV[y] - LV[y]) / span;
        float dw = (RW[y] - LW[y]) / span;

        float u = LU[y] + du * (lx - L[y]);
        float v = LV[y] + dv * (lx - L[y]);
        float w = LW[y] + dw * (lx - L[y]);

        for (int x = lx; x <= rx; x++) {
            if (w > ZBuf[y][x]) {
                ZBuf[y][x] = w;
                float z = 1.0f / w;
                int tx = (int)(u * z * (TexW - 1)) & (TexW - 1);
                int ty = (int)(v * z * (TexH - 1)) & (TexH - 1);

                int c = Texture[ty * TexW + tx];
                c = ShadeColor(c, g_FaceBrightness);
                PSET(x, y, c);
            }
            u += du; v += dv; w += dw;
        }
    }
}

bool TransformAndProject(float wx, float wy, float wz, float u, float v, VTX& out) {
    float tx = wx - cam.x;
    float ty = wy - cam.y;
    float tz = wz - cam.z;
    float c = cosf(-cam.yaw);
    float s = sinf(-cam.yaw);
    float vx = tx * c - tz * s;
    float vz = tx * s + tz * c;
    float vy = ty;

    if (vz <= 0.01f) return false;
    float invZ = 1.0f / vz;
    out.x = vx * FOV_F * invZ + (WIDTH / 2);
    out.y = -vy * FOV_F * invZ + (HEIGHT / 2);
    out.z = invZ;
    out.u = u * invZ;
    out.v = v * invZ;
    return true;
}

Vec3 GetWallNormal(WallDir dir)
{
    Vec3 NSWE[4] = {
        { 0, 0, 1},
        { 0, 0, -1},
        { 1, 0, 0},
        {-1, 0, 0}
    };
    switch (dir) {
        case WALL_NORTH: return NSWE[0];
        case WALL_SOUTH: return NSWE[1];
        case WALL_WEST: return NSWE[2];
        case WALL_EAST: return NSWE[3];
    }
    Vec3 zero = {0,0,0};
    return zero;
}

void DrawWall(float x, float z, WallDir dir) {
    Vec3 p[4];
    switch (dir) {
        case WALL_NORTH:
            p[0].x = x; p[0].y = 0.0f; p[0].z = z;
            p[1].x = x + CELL; p[1].y = 0.0f; p[1].z = z;
            p[2].x = x + CELL; p[2].y = WALL_H; p[2].z = z;
            p[3].x = x; p[3].y = WALL_H; p[3].z = z;
            break;
        case WALL_SOUTH:
            p[0].x = x + CELL; p[0].y = 0.0f; p[0].z = z + CELL;
            p[1].x = x; p[1].y = 0.0f; p[1].z = z + CELL;
            p[2].x = x; p[2].y = WALL_H; p[2].z = z + CELL;
            p[3].x = x + CELL; p[3].y = WALL_H; p[3].z = z + CELL;
            break;
        case WALL_WEST:
            p[0].x = x; p[0].y = 0.0f; p[0].z = z + CELL;
            p[1].x = x; p[1].y = 0.0f; p[1].z = z;
            p[2].x = x; p[2].y = WALL_H; p[2].z = z;
            p[3].x = x; p[3].y = WALL_H; p[3].z = z + CELL;
            break;
        case WALL_EAST:
            p[0].x = x + CELL; p[0].y = 0.0f; p[0].z = z;
            p[1].x = x + CELL; p[1].y = 0.0f; p[1].z = z + CELL;
            p[2].x = x + CELL; p[2].y = WALL_H; p[2].z = z + CELL;
            p[3].x = x + CELL; p[3].y = WALL_H; p[3].z = z;
            break;
    }

    // 1. 影ポリゴンの計算(底面頂点そのまま + 上面頂点を投影)
    VTX s[4];
    bool shadowOK = true;
    shadowOK &= TransformAndProject(p[0].x, p[0].y, p[0].z, 0, 0, s[0]);
    shadowOK &= TransformAndProject(p[1].x, p[1].y, p[1].z, 0, 0, s[1]);
    shadowOK &= TransformAndProjectShadow(p[2].x, p[2].y, p[2].z, s[2]);
    shadowOK &= TransformAndProjectShadow(p[3].x, p[3].y, p[3].z, s[3]);

    if (shadowOK) {
        DrawShadowTriangle(s[0], s[1], s[2]);
        DrawShadowTriangle(s[0], s[2], s[3]);
    }

    // 2. 壁ポリゴン描画
    VTX v[4];
    if (!TransformAndProject(p[0].x, p[0].y, p[0].z, 0, 1, v[0])) return;
    if (!TransformAndProject(p[1].x, p[1].y, p[1].z, 1, 1, v[1])) return;
    if (!TransformAndProject(p[2].x, p[2].y, p[2].z, 1, 0, v[2])) return;
    if (!TransformAndProject(p[3].x, p[3].y, p[3].z, 0, 0, v[3])) return;

    g_FaceBrightness = ComputeBrightness(GetWallNormal(dir));

    TexTri(v[0], v[1], v[2]);
    TexTri(v[0], v[2], v[3]);
}

void DrawMaze()
{
    for (int z = 0; z < H; z++)
    for (int x = 0; x < W; x++)
    {
        if (maze[z][x] != 0) continue;
        float wx = x * CELL;
        float wz = z * CELL;
        if (z > 0 && maze[z-1][x] == 1) DrawWall(wx, wz, WALL_NORTH);
        if (z < H-1 && maze[z+1][x] == 1) DrawWall(wx, wz, WALL_SOUTH);
        if (x > 0 && maze[z][x-1] == 1) DrawWall(wx, wz, WALL_WEST);
        if (x < W-1 && maze[z][x+1] == 1) DrawWall(wx, wz, WALL_EAST);
    }
}

void DrawFloorFast() {
    Vec3 floorNormal = { 0.0f, 1.0f, 0.0f };
    g_FaceBrightness = ComputeBrightness(floorNormal);

    float c = cosf(cam.yaw), s = sinf(cam.yaw);
    for (int y = HEIGHT / 2 + 1; y < HEIGHT; y++) {
        float dy = y - (HEIGHT / 2);
        float distance = (cam.y * FOV_F) / dy;
        float leftX = cam.x + ((-WIDTH/2) * c - FOV_F * s) * (distance / FOV_F);
        float leftZ = cam.z + ((-WIDTH/2) * s + FOV_F * c) * (distance / FOV_F);
        float rightX = cam.x + ((WIDTH/2) * c - FOV_F * s) * (distance / FOV_F);
        float rightZ = cam.z + ((WIDTH/2) * s + FOV_F * c) * (distance / FOV_F);
        for (int x = 0; x < WIDTH; x++) {
            float t = (float)x / WIDTH;
            float wx = leftX + (rightX - leftX) * t;
            float wz = leftZ + (rightZ - leftZ) * t;
            int tx = (int)(wx * TexW / CELL) & (TexW - 1);
            int ty = (int)(wz * TexH / CELL) & (TexH - 1);
            float w = 1.0f / distance;
            if (w > ZBuf[y][x]) {
                ZBuf[y][x] = w;
                int col = Texture[ty * TexW + tx];
                col = ShadeColor(col, g_FaceBrightness);
                PSET(x, y, col);
            }
        }
    }
}

bool Load24BitBmpToTexture(const char* fileName, int maxW, int maxH)
{
    int siHandle = LoadSoftImage(fileName);
    if (siHandle == -1) return false;
    int imgW, imgH;
    GetSoftImageSize(siHandle, &imgW, &imgH);
    int copyW = (imgW > maxW) ? maxW : imgW;
    int copyH = (imgH > maxH) ? maxH : imgH;
    TexW = copyW;
    TexH = copyH;

    for (int y = 0; y < copyH; y++) {
        for (int x = 0; x < copyW; x++) {
            int r, g, b, a;
            GetPixelSoftImage(siHandle, x, y, &r, &g, &b, &a);
            Texture[y * maxW + x] = GetColor(r, g, b);
        }
    }
    DeleteSoftImage(siHandle);
    return true;
}

int dx[4] = { 0, 0, -2, 2 };
int dz[4] = { -2, 2, 0, 0 };

void Shuffle(int* a, int n)
{
    for (int i = n - 1; i > 0; i--) {
        int j = rand() % (i + 1);
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
}

void Dig(int x, int z)
{
    int dir[4] = {0, 1, 2, 3};
    Shuffle(dir, 4);
    for (int i = 0; i < 4; i++) {
        int nx = x + dx[dir[i]];
        int nz = z + dz[dir[i]];
        if (nx <= 0 || nz <= 0 || nx >= W - 1 || nz >= H - 1) continue;
        if (maze[nz][nx] == 0) continue;
        maze[nz][nx] = 0;
        maze[z + dz[dir[i]] / 2][x + dx[dir[i]] / 2] = 0;
        Dig(nx, nz);
    }
}

void UpdateCameraWithCollision(Camera& cam)
{
    float speed = 0.05f;
    float oldX = cam.x;
    float oldZ = cam.z;
    if (CheckHitKey(KEY_INPUT_LEFT)) cam.yaw += 0.04f;
    if (CheckHitKey(KEY_INPUT_RIGHT)) cam.yaw -= 0.04f;
    float dx = sinf(cam.yaw);
    float dz = cosf(cam.yaw);
    if (CheckHitKey(KEY_INPUT_UP)) {
        cam.x -= dx * speed;
        cam.z += dz * speed;
    }
    if (CheckHitKey(KEY_INPUT_DOWN)) {
        cam.x += dx * speed;
        cam.z -= dz * speed;
    }
    int mapX = (int)(cam.x / CELL);
    int mapZ = (int)(cam.z / CELL);
    if (mapX < 0 || mapX >= W || mapZ < 0 || mapZ >= H || maze[mapZ][mapX] == 1) {
        cam.x = oldX;
        cam.z = oldZ;
    }
}

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    ChangeWindowMode(TRUE);
    SetGraphMode(WIDTH * 2, HEIGHT * 2, 32);
    SetEmulation320x240(TRUE);
    SetMainWindowText("PolygonSample2025lastVer");
    if (DxLib_Init() < 0) return -1;
    SetDrawScreen(DX_SCREEN_BACK);

    soft = MakeARGB8ColorSoftImage(WIDTH, HEIGHT);
    int graph = CreateGraphFromSoftImage(soft);

    Load24BitBmpToTexture("wall02.bmp", 1024 / 4, 1024 / 4);
    srand(GetTickCount());

    cam.height = 0.5f;
    for (int y = 0; y < H; y++)
        for (int x = 0; x < W; x++)
            maze[y][x] = 1;

    Dig(1, 1);
    maze[1][1] = 0;
    cam.x = 1.0f * CELL + CELL * 0.5f;
    cam.z = 1.0f * CELL + CELL * 0.5f;
    cam.y = 0.5f;
    cam.yaw = 0.0f;
    ////cam.height = 0.5f;

    while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
    {
        ClearDrawScreen();
        FillSoftImage(soft, 0, 0, 0, 255);
        memset(ZBuf, 0, sizeof(ZBuf));

        UpdateCameraWithCollision(cam);
        DrawFloorFast();
        DrawMaze();

        graph = CreateGraphFromSoftImage(soft);
        DrawGraph(0, 0, graph, FALSE);
        ScreenFlip();

        WaitTimer(1000 / 60);
    }

    DeleteGraph(graph);
    DeleteSoftImage(soft);
    DxLib_End();
    return 0;
}

shadow-260901-2109.jpg

アンチエイリアステクスチャー
というものをやってみようと思います。何故かというと、このすぐ上でジャギーが目立つからです。
バイリニアフィルタを使えば簡単にそして軽く処理できます。

#include"DXLib.h"
#include<math.h>
#include<stdlib.h>
#include<windows.h>
#include<utility>

using namespace std;

#define CELL 1.0f
const float FOV_F = 600.0f;
const int HEIGHT = 240;
const int WIDTH = 320;

struct VTX {
    float x, y, z;
    float u, v;
};

struct Camera
{
    float x, y, z;
    float yaw; // 左右回転のみ
    float height;
};

enum WallDir
{
    WALL_NORTH,
    WALL_SOUTH,
    WALL_WEST,
    WALL_EAST
};

struct Vec3
{
    float x;
    float y;
    float z;
};

float ZBuf[HEIGHT][WIDTH];
#define RAD (3.14159265/180.0)
Camera cam;

// ==================================================
// ライティング&点光源シャドウ関連
// ==================================================
Vec3 g_LightPos = { 10.0f, 1.8f, 10.0f }; // 点光源の位置
float g_Ambient = 0.35f; // 環境光
float g_FaceBrightness = 1.0f; // 面の明るさ

// 法線ベクトルから明るさ(0〜1)を計算
float ComputeBrightness(Vec3 normal)
{
    float len = sqrtf(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);
    if (len > 0.0001f) {
        normal.x /= len; normal.y /= len; normal.z /= len;
    }
    Vec3 lightDir = { -0.4f, -1.0f, -0.3f };
    float lLen = sqrtf(lightDir.x*lightDir.x + lightDir.y*lightDir.y + lightDir.z*lightDir.z);
    lightDir.x /= lLen; lightDir.y /= lLen; lightDir.z /= lLen;

    float ndotl = -(normal.x * lightDir.x + normal.y * lightDir.y + normal.z * lightDir.z);
    if (ndotl < 0.0f) ndotl = 0.0f;
    float b = g_Ambient + (1.0f - g_Ambient) * ndotl;
    if (b > 1.0f) b = 1.0f;
    return b;
}

// テクスチャ色(0xRRGGBB)に明るさを掛ける
inline int ShadeColor(int col, float brightness)
{
    int r = (col >> 16) & 0xFF;
    int g = (col >> 8) & 0xFF;
    int b = col & 0xFF;
    r = (int)(r * brightness);
    g = (int)(g * brightness);
    b = (int)(b * brightness);
    if (r > 255) r = 255; if (r < 0) r = 0;
    if (g > 255) g = 255; if (g < 0) g = 0;
    if (b > 255) b = 255; if (b < 0) b = 0;
    return GetColor(r, g, b);
}

// 頂点変換関数プロトタイプ
bool TransformAndProject(float wx, float wy, float wz, float u, float v, VTX& out);

// 壁の上面頂点を光源から床(Y=0)へ投影
bool TransformAndProjectShadow(float wx, float wy, float wz, VTX& out) {
    if (g_LightPos.y <= wy) return false;

    float dx = wx - g_LightPos.x;
    float dy = wy - g_LightPos.y;
    float dz = wz - g_LightPos.z;

    float t = -g_LightPos.y / dy;
    float sx = g_LightPos.x + dx * t;
    float sz = g_LightPos.z + dz * t;
    float sy = 0.0f;

    return TransformAndProject(sx, sy, sz, 0.0f, 0.0f, out);
}
// ==================================================

int L[HEIGHT];
int R[HEIGHT];
float LU[HEIGHT];
float LV[HEIGHT];
float RU[HEIGHT];
float RV[HEIGHT];
float LW[HEIGHT];
float RW[HEIGHT];

int soft;
int Texture[1024*2048] = {0};
int TexW = 0;
int TexH = 0;

#define W 21
#define H 21
int maze[H][W]; // 0=通路, 1=壁
const float WALL_H = 1.0f;

Vec3 GetWallNormal(WallDir dir);
void DrawWall(float x, float z, WallDir dir);

#define MAX_VTX 1000
#define MAX_POL 3000
VTX v[MAX_VTX] = {0};
int PolygonNumber[MAX_POL][4] = {0};

void TexTri_Complex(VTX n1, VTX n2, VTX n3);
#define TexTri TexTri_Complex

void Line(int x1, int y1, float u1, float v1, float w1,
          int x2, int y2, float u2, float v2, float w2) {
    if (y1 == y2) return;
    if (y1 > y2) {
        swap(y1, y2); swap(x1, x2);
        swap(u1, u2); swap(v1, v2); swap(w1, w2);
    }
    float invH = 1.0f / (float)(y2 - y1);
    for (int y = y1; y < y2; y++) {
        if (y < 0 || y >= HEIGHT) continue;
        float t = (float)(y - y1) * invH;
        int x = (int)(x1 + (x2 - x1) * t);
        float u = u1 + (u2 - u1) * t;
        float v = v1 + (v2 - v1) * t;
        float w = w1 + (w2 - w1) * t;
        if (x < L[y]) { L[y] = x; LU[y] = u; LV[y] = v; LW[y] = w; }
        if (x > R[y]) { R[y] = x; RU[y] = u; RV[y] = v; RW[y] = w; }
    }
}

void PSET(int x, int y, int c)
{
    int r = (c >> 16) & 0xFF;
    int g = (c >> 8) & 0xFF;
    int b = c & 0xFF;
    DrawPixelSoftImage(soft, x, y, r, g, b, 255);
}

// 影専用ラスタライザ
void DrawShadowTriangle(VTX n1, VTX n2, VTX n3) {
    int miny = (int)min(n1.y, min(n2.y, n3.y));
    int maxy = (int)max(n1.y, max(n2.y, n3.y));
    miny = max(0, miny);
    maxy = min(HEIGHT - 1, maxy);

    for (int y = miny; y <= maxy; y++) {
        L[y] = 9999; R[y] = -9999;
    }
    Line((int)n1.x, (int)n1.y, 0, 0, n1.z, (int)n2.x, (int)n2.y, 0, 0, n2.z);
    Line((int)n2.x, (int)n2.y, 0, 0, n2.z, (int)n3.x, (int)n3.y, 0, 0, n3.z);
    Line((int)n3.x, (int)n3.y, 0, 0, n3.z, (int)n1.x, (int)n1.y, 0, 0, n1.z);

    for (int y = miny; y <= maxy; y++) {
        if (L[y] > R[y]) continue;
        int lx = max(0, L[y]);
        int rx = min(WIDTH - 1, R[y]);
        if (lx > rx) continue;

        float span = (float)(R[y] - L[y]);
        if (span <= 0.0f) span = 1.0f;

        float dw = (RW[y] - LW[y]) / span;
        float w = LW[y] + dw * (lx - L[y]);

        for (int x = lx; x <= rx; x++) {
            // 床のZ深度にマージンを設けて判定
            if (w >= ZBuf[y][x] - 0.0001f) {
                int r, g, b, a;
                GetPixelSoftImage(soft, x, y, &r, &g, &b, &a);
                r = (int)(r * 0.3f);
                g = (int)(g * 0.3f);
                b = (int)(b * 0.3f);
                PSET(x, y, GetColor(r, g, b));
            }
            w += dw;
        }
    }
}

// テクスチャからバイリニア補間した色を取得する関数
inline int SampleTextureBilinear(float u_persp, float v_persp, float invZ) {
    // 透過除算で元のUV座標(0.0〜1.0)を復元
    float u = u_persp * invZ;
    float v = v_persp * invZ;

    // UV座標をテクスチャのピクセル座標系に変換
    float fx = u * (TexW - 1);
    float fy = v * (TexH - 1);

    // 範囲外のループ(ラップ処理)
    int x0 = ((int)floorf(fx)) & (TexW - 1);
    int y0 = ((int)floorf(fy)) & (TexH - 1);
    int x1 = (x0 + 1) & (TexW - 1);
    int y1 = (y0 + 1) & (TexH - 1);

    // 小数点以下の比率
    float rx = fx - floorf(fx);
    float ry = fy - floorf(fy);

    // 周辺4ピクセルの色を取得
    int c00 = Texture[y0 * TexW + x0];
    int c10 = Texture[y0 * TexW + x1];
    int c01 = Texture[y1 * TexW + x0];
    int c11 = Texture[y1 * TexW + x1];

    // RGB各成分の線形補間
    int r00 = (c00 >> 16) & 0xFF, g00 = (c00 >> 8) & 0xFF, b00 = c00 & 0xFF;
    int r10 = (c10 >> 16) & 0xFF, g10 = (c10 >> 8) & 0xFF, b10 = c10 & 0xFF;
    int r01 = (c01 >> 16) & 0xFF, g01 = (c01 >> 8) & 0xFF, b01 = c01 & 0xFF;
    int r11 = (c11 >> 16) & 0xFF, g11 = (c11 >> 8) & 0xFF, b11 = c11 & 0xFF;

    float r = (1.0f - rx) * (1.0f - ry) * r00 + rx * (1.0f - ry) * r10 + (1.0f - rx) * ry * r01 + rx * ry * r11;
    float g = (1.0f - rx) * (1.0f - ry) * g00 + rx * (1.0f - ry) * g10 + (1.0f - rx) * ry * g01 + rx * ry * g11;
    float b = (1.0f - rx) * (1.0f - ry) * b00 + rx * (1.0f - ry) * b10 + (1.0f - rx) * ry * b01 + rx * ry * b11;

    return GetColor((int)r, (int)g, (int)b);
}

// テクスチャポリゴンラスタライザ
void TexTri_Complex(VTX n1, VTX n2, VTX n3) {
    int miny = (int)min(n1.y, min(n2.y, n3.y));
    int maxy = (int)max(n1.y, max(n2.y, n3.y));
    miny = max(0, miny);
    maxy = min(HEIGHT - 1, maxy);

    for (int y = miny; y <= maxy; y++) {
        L[y] = 9999; R[y] = -9999;
    }
    Line((int)n1.x, (int)n1.y, n1.u, n1.v, n1.z, (int)n2.x, (int)n2.y, n2.u, n2.v, n2.z);
    Line((int)n2.x, (int)n2.y, n2.u, n2.v, n2.z, (int)n3.x, (int)n3.y, n3.u, n3.v, n3.z);
    Line((int)n3.x, (int)n3.y, n3.u, n3.v, n3.z, (int)n1.x, (int)n1.y, n1.u, n1.v, n1.z);

    for (int y = miny; y <= maxy; y++) {
        if (L[y] > R[y]) continue;
        int lx = max(0, L[y]);
        int rx = min(WIDTH - 1, R[y]);
        if (lx > rx) continue;

        float span = (float)(R[y] - L[y]);
        if (span <= 0.0f) span = 1.0f;

        float du = (RU[y] - LU[y]) / span;
        float dv = (RV[y] - LV[y]) / span;
        float dw = (RW[y] - LW[y]) / span;

        float u = LU[y] + du * (lx - L[y]);
        float v = LV[y] + dv * (lx - L[y]);
        float w = LW[y] + dw * (lx - L[y]);

        for (int x = lx; x <= rx; x++) {
            if (w > ZBuf[y][x]) {
                ZBuf[y][x] = w;
                float z = 1.0f / w;

                // 従来のニアレストネイバー(最近傍補間)
                // int tx = (int)(u * z * (TexW - 1)) & (TexW - 1);
                // int ty = (int)(v * z * (TexH - 1)) & (TexH - 1);
                // int c = Texture[ty * TexW + tx];

                // バイリニア補間に差し替え
                int c = SampleTextureBilinear(u, v, z);

                c = ShadeColor(c, g_FaceBrightness);
                PSET(x, y, c);
            }
            u += du; v += dv; w += dw;
        }
    }
}

bool TransformAndProject(float wx, float wy, float wz, float u, float v, VTX& out) {
    float tx = wx - cam.x;
    float ty = wy - cam.y;
    float tz = wz - cam.z;
    float c = cosf(-cam.yaw);
    float s = sinf(-cam.yaw);
    float vx = tx * c - tz * s;
    float vz = tx * s + tz * c;
    float vy = ty;

    if (vz <= 0.01f) return false;
    float invZ = 1.0f / vz;
    out.x = vx * FOV_F * invZ + (WIDTH / 2);
    out.y = -vy * FOV_F * invZ + (HEIGHT / 2);
    out.z = invZ;
    out.u = u * invZ;
    out.v = v * invZ;
    return true;
}

Vec3 GetWallNormal(WallDir dir)
{
    Vec3 NSWE[4] = {
        { 0, 0, 1},
        { 0, 0, -1},
        { 1, 0, 0},
        {-1, 0, 0}
    };
    switch (dir) {
        case WALL_NORTH: return NSWE[0];
        case WALL_SOUTH: return NSWE[1];
        case WALL_WEST: return NSWE[2];
        case WALL_EAST: return NSWE[3];
    }
    Vec3 zero = {0,0,0};
    return zero;
}

void DrawWall(float x, float z, WallDir dir) {
    Vec3 p[4];
    switch (dir) {
        case WALL_NORTH:
            p[0].x = x; p[0].y = 0.0f; p[0].z = z;
            p[1].x = x + CELL; p[1].y = 0.0f; p[1].z = z;
            p[2].x = x + CELL; p[2].y = WALL_H; p[2].z = z;
            p[3].x = x; p[3].y = WALL_H; p[3].z = z;
            break;
        case WALL_SOUTH:
            p[0].x = x + CELL; p[0].y = 0.0f; p[0].z = z + CELL;
            p[1].x = x; p[1].y = 0.0f; p[1].z = z + CELL;
            p[2].x = x; p[2].y = WALL_H; p[2].z = z + CELL;
            p[3].x = x + CELL; p[3].y = WALL_H; p[3].z = z + CELL;
            break;
        case WALL_WEST:
            p[0].x = x; p[0].y = 0.0f; p[0].z = z + CELL;
            p[1].x = x; p[1].y = 0.0f; p[1].z = z;
            p[2].x = x; p[2].y = WALL_H; p[2].z = z;
            p[3].x = x; p[3].y = WALL_H; p[3].z = z + CELL;
            break;
        case WALL_EAST:
            p[0].x = x + CELL; p[0].y = 0.0f; p[0].z = z;
            p[1].x = x + CELL; p[1].y = 0.0f; p[1].z = z + CELL;
            p[2].x = x + CELL; p[2].y = WALL_H; p[2].z = z + CELL;
            p[3].x = x + CELL; p[3].y = WALL_H; p[3].z = z;
            break;
    }

    // 1. 影ポリゴンの計算(底面頂点そのまま + 上面頂点を投影)
    VTX s[4];
    bool shadowOK = true;
    shadowOK &= TransformAndProject(p[0].x, p[0].y, p[0].z, 0, 0, s[0]);
    shadowOK &= TransformAndProject(p[1].x, p[1].y, p[1].z, 0, 0, s[1]);
    shadowOK &= TransformAndProjectShadow(p[2].x, p[2].y, p[2].z, s[2]);
    shadowOK &= TransformAndProjectShadow(p[3].x, p[3].y, p[3].z, s[3]);

    if (shadowOK) {
        DrawShadowTriangle(s[0], s[1], s[2]);
        DrawShadowTriangle(s[0], s[2], s[3]);
    }

    // 2. 壁ポリゴン描画
    VTX v[4];
    if (!TransformAndProject(p[0].x, p[0].y, p[0].z, 0, 1, v[0])) return;
    if (!TransformAndProject(p[1].x, p[1].y, p[1].z, 1, 1, v[1])) return;
    if (!TransformAndProject(p[2].x, p[2].y, p[2].z, 1, 0, v[2])) return;
    if (!TransformAndProject(p[3].x, p[3].y, p[3].z, 0, 0, v[3])) return;

    g_FaceBrightness = ComputeBrightness(GetWallNormal(dir));

    TexTri(v[0], v[1], v[2]);
    TexTri(v[0], v[2], v[3]);
}

void DrawMaze()
{
    for (int z = 0; z < H; z++)
    for (int x = 0; x < W; x++)
    {
        if (maze[z][x] != 0) continue;
        float wx = x * CELL;
        float wz = z * CELL;
        if (z > 0 && maze[z-1][x] == 1) DrawWall(wx, wz, WALL_NORTH);
        if (z < H-1 && maze[z+1][x] == 1) DrawWall(wx, wz, WALL_SOUTH);
        if (x > 0 && maze[z][x-1] == 1) DrawWall(wx, wz, WALL_WEST);
        if (x < W-1 && maze[z][x+1] == 1) DrawWall(wx, wz, WALL_EAST);
    }
}

void DrawFloorFast() {
    Vec3 floorNormal = { 0.0f, 1.0f, 0.0f };
    g_FaceBrightness = ComputeBrightness(floorNormal);

    float c = cosf(cam.yaw), s = sinf(cam.yaw);
    for (int y = HEIGHT / 2 + 1; y < HEIGHT; y++) {
        float dy = y - (HEIGHT / 2);
        float distance = (cam.y * FOV_F) / dy;
        float leftX = cam.x + ((-WIDTH/2) * c - FOV_F * s) * (distance / FOV_F);
        float leftZ = cam.z + ((-WIDTH/2) * s + FOV_F * c) * (distance / FOV_F);
        float rightX = cam.x + ((WIDTH/2) * c - FOV_F * s) * (distance / FOV_F);
        float rightZ = cam.z + ((WIDTH/2) * s + FOV_F * c) * (distance / FOV_F);
        for (int x = 0; x < WIDTH; x++) {
            float t = (float)x / WIDTH;
            float wx = leftX + (rightX - leftX) * t;
            float wz = leftZ + (rightZ - leftZ) * t;
            int tx = (int)(wx * TexW / CELL) & (TexW - 1);
            int ty = (int)(wz * TexH / CELL) & (TexH - 1);
            float w = 1.0f / distance;
            if (w > ZBuf[y][x]) {
                ZBuf[y][x] = w;
                int col = Texture[ty * TexW + tx];
                col = ShadeColor(col, g_FaceBrightness);
                PSET(x, y, col);
            }
        }
    }
}

bool Load24BitBmpToTexture(const char* fileName, int maxW, int maxH)
{
    int siHandle = LoadSoftImage(fileName);
    if (siHandle == -1) return false;
    int imgW, imgH;
    GetSoftImageSize(siHandle, &imgW, &imgH);
    int copyW = (imgW > maxW) ? maxW : imgW;
    int copyH = (imgH > maxH) ? maxH : imgH;
    TexW = copyW;
    TexH = copyH;

    for (int y = 0; y < copyH; y++) {
        for (int x = 0; x < copyW; x++) {
            int r, g, b, a;
            GetPixelSoftImage(siHandle, x, y, &r, &g, &b, &a);
            Texture[y * maxW + x] = GetColor(r, g, b);
        }
    }
    DeleteSoftImage(siHandle);
    return true;
}

int dx[4] = { 0, 0, -2, 2 };
int dz[4] = { -2, 2, 0, 0 };

void Shuffle(int* a, int n)
{
    for (int i = n - 1; i > 0; i--) {
        int j = rand() % (i + 1);
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
}

void Dig(int x, int z)
{
    int dir[4] = {0, 1, 2, 3};
    Shuffle(dir, 4);
    for (int i = 0; i < 4; i++) {
        int nx = x + dx[dir[i]];
        int nz = z + dz[dir[i]];
        if (nx <= 0 || nz <= 0 || nx >= W - 1 || nz >= H - 1) continue;
        if (maze[nz][nx] == 0) continue;
        maze[nz][nx] = 0;
        maze[z + dz[dir[i]] / 2][x + dx[dir[i]] / 2] = 0;
        Dig(nx, nz);
    }
}

void UpdateCameraWithCollision(Camera& cam)
{
    float speed = 0.05f;
    float oldX = cam.x;
    float oldZ = cam.z;
    if (CheckHitKey(KEY_INPUT_LEFT)) cam.yaw += 0.04f;
    if (CheckHitKey(KEY_INPUT_RIGHT)) cam.yaw -= 0.04f;
    float dx = sinf(cam.yaw);
    float dz = cosf(cam.yaw);
    if (CheckHitKey(KEY_INPUT_UP)) {
        cam.x -= dx * speed;
        cam.z += dz * speed;
    }
    if (CheckHitKey(KEY_INPUT_DOWN)) {
        cam.x += dx * speed;
        cam.z -= dz * speed;
    }
    int mapX = (int)(cam.x / CELL);
    int mapZ = (int)(cam.z / CELL);
    if (mapX < 0 || mapX >= W || mapZ < 0 || mapZ >= H || maze[mapZ][mapX] == 1) {
        cam.x = oldX;
        cam.z = oldZ;
    }
}

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    ChangeWindowMode(TRUE);
    SetGraphMode(WIDTH * 2, HEIGHT * 2, 32);
    SetEmulation320x240(TRUE);
    SetMainWindowText("PolygonSample2026 Semptember");
    if (DxLib_Init() < 0) return -1;
    SetDrawScreen(DX_SCREEN_BACK);

    soft = MakeARGB8ColorSoftImage(WIDTH, HEIGHT);
    int graph = CreateGraphFromSoftImage(soft);

    Load24BitBmpToTexture("wall02.bmp", 1024 / 4, 1024 / 4);
    srand(GetTickCount());

    cam.height = 0.5f;
    for (int y = 0; y < H; y++)
        for (int x = 0; x < W; x++)
            maze[y][x] = 1;

    Dig(1, 1);
    maze[1][1] = 0;
    cam.x = 1.0f * CELL + CELL * 0.5f;
    cam.z = 1.0f * CELL + CELL * 0.5f;
    cam.y = 0.5f;
    cam.yaw = 0.0f;
    ////cam.height = 0.5f;

    while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
    {
        ClearDrawScreen();
        FillSoftImage(soft, 0, 0, 0, 255);
        memset(ZBuf, 0, sizeof(ZBuf));

        UpdateCameraWithCollision(cam);
        DrawFloorFast();
        DrawMaze();

        graph = CreateGraphFromSoftImage(soft);
        DrawGraph(0, 0, graph, FALSE);
        ScreenFlip();

        WaitTimer(1000 / 60);
    }

    DeleteGraph(graph);
    DeleteSoftImage(soft);
    DxLib_End();
    return 0;
}
tmpimg260901-2147.jpg


サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す