c+WINAPIと3DCGの理屈その7

記事
IT・テクノロジー
解像度を640x480にしました。
が、その分遅くなったので、その分高速化しました。

#include"DXLib.h"
#include<math.h>
#include<stdlib.h>
#include<windows.h>
#define CELL 1.0f
// ★変更:解像度を320x240→640x480に引き上げ(今後は640x480を最低ラインとする)
// FOV_Fは解像度に比例させて画角を維持している(320→640/240→480はどちらも2倍なので600→1200)
const float FOV_F = 1200.0f;
const int HEIGHT = 480;
const int WIDTH = 640;
struct VTX {
    float x, y, z;
    float u, v;
    float b; // 旧Gouraud用(頂点の明るさ)。互換のため残しているが新パイプラインでは未使用
};
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_LightDir = { -0.4f, -1.0f, -0.3f };
float g_Ambient = 0.1f;
float g_TorchIntensity = 1.2f/2.0f;
float g_TorchFalloff = 0.3f/2.0f;

// ==== シャドウ判定用パラメータ ====
// 迷路グリッドに沿って光源方向へレイを伸ばし、壁に遮られるかどうかを見る簡易シャドウ
const float SHADOW_STEP = 0.1f; // レイを進める1歩の長さ
const int SHADOW_MAX_STEPS = 40; // 最大何歩まで進めるか(4.0ユニット分)

// ==== 距離フォグ用パラメータ ====
// カメラからの距離(ワールド距離)に応じてピクセル色を霧色にブレンドする
bool g_FogEnabled = true;
float g_FogStart = 3.0f; // ここより手前はフォグ無し
float g_FogEnd = 12.0f; // ここまで届いたら完全に霧色(0を超えて遠いほど濃い)
int g_FogColor = 0; // 実際の色はNormalizeLightDir()と同じタイミングで空の色に合わせて初期化する

// テクスチャを外して明るさだけをグレースケール表示するデバッグモード
bool g_DebugBrightnessOnly = false;

// 床だけに底上げしたい環境光。DrawFloorCell/DrawWall呼び出し時にセットしてから
// DrawTexturedQuadWorldPhong経由でTexTriPhongに渡す(本来はもっと綺麗な引数渡しが
// 望ましいが、既存コードの作りに合わせてグローバル1個で済ませている)
float g_CurrentAmbientBoost = 0.0f;

void NormalizeLightDir()
{
    float len = sqrtf(g_LightDir.x*g_LightDir.x + g_LightDir.y*g_LightDir.y + g_LightDir.z*g_LightDir.z);
    if (len > 0.0001f)
    {
        g_LightDir.x /= len;
        g_LightDir.y /= len;
        g_LightDir.z /= len;
    }
}

// ---- 以下2つは旧Gouraud用(頂点ごとに明るさを1回だけ計算する版)。
// 互換性のために残しているが、新しいDrawWall/DrawFloorCell/DrawBillboardTreeからは呼ばれない ----
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;
    }
    float ndotl = -(normal.x * g_LightDir.x + normal.y * g_LightDir.y + normal.z * g_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;
}
float ComputeVertexBrightness(Vec3 worldPos, Vec3 normal)
{
    float base = ComputeBrightness(normal);
    float dx = worldPos.x - cam.x;
    float dy = worldPos.y - cam.y;
    float dz = worldPos.z - cam.z;
    float distSq = dx*dx + dy*dy + dz*dz;
    float torch = g_TorchIntensity / (1.0f + g_TorchFalloff * distSq);
    float total = base + torch;
    if (total > 1.0f) total = 1.0f;
    if (total < 0.0f) total = 0.0f;
    return total;
}

// ---- フォンシェーディング用:法線(正規化前)・距離^2・環境光底上げ・シャドウ係数から、その1ピクセルの明るさを求める ----
// shadow: 1.0=日向(遮られていない)、0.0=影(平行光源の直接光を遮られている)
inline float ComputePhongBrightness(float nx, float ny, float nz, float distSq, float ambientBoost, float shadow)
{
    float len = sqrtf(nx*nx + ny*ny + nz*nz);
    if (len > 0.0001f) { nx /= len; ny /= len; nz /= len; }
    float ndotl = -(nx * g_LightDir.x + ny * g_LightDir.y + nz * g_LightDir.z);
    if (ndotl < 0.0f) ndotl = 0.0f;
    ndotl *= shadow; // ★追加:影の中では平行光源の直接光成分だけを弱める(環境光・松明は残す)
    float base = g_Ambient + (1.0f - g_Ambient) * ndotl;
    if (base > 1.0f) base = 1.0f;
    float torch = g_TorchIntensity / (1.0f + g_TorchFalloff * distSq);
    float total = base + torch + ambientBoost;
    if (total > 1.0f) total = 1.0f;
    if (total < 0.0f) total = 0.0f;
    return total;
}

// テクスチャ色(0xRRGGBB)にbrightnessを掛けて返す
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);
}
// ==================================================

// ★変更:マルチスレッドで複数バンドを同時にラスタライズできるよう、
// スキャンライン用のエッジ補間バッファは全部スレッドローカル(__declspec(thread))にする。
// こうしないと、複数スレッドが同じ配列に同時に書き込んで結果が壊れる。
__declspec(thread) int L[HEIGHT];
__declspec(thread) int R[HEIGHT];
int LC[HEIGHT]; // 未使用(旧コード互換のため残置)
int RC[HEIGHT]; // 未使用(旧コード互換のため残置)
__declspec(thread) float LU[HEIGHT];
__declspec(thread) float LV[HEIGHT];
__declspec(thread) float RU[HEIGHT];
__declspec(thread) float RV[HEIGHT];
__declspec(thread) float LW[HEIGHT];
__declspec(thread) float RW[HEIGHT];
float LB[HEIGHT]; // 旧Gouraud用(互換のため残置、現在未使用)
float RB[HEIGHT]; // 旧Gouraud用(互換のため残置、現在未使用)

// フォンシェーディング用:法線成分(パースペクティブ補正込み)のエッジ補間バッファ
__declspec(thread) float LNX[HEIGHT], LNY[HEIGHT], LNZ[HEIGHT];
__declspec(thread) float RNX[HEIGHT], RNY[HEIGHT], RNZ[HEIGHT];
// シャドウ係数(パースペクティブ補正込み)のエッジ補間バッファ
__declspec(thread) float LS[HEIGHT], RS[HEIGHT];

int soft;
// ★追加:ソフトイメージの生バッファへ直接書き込むためのポインタとピッチ(PSETのAPI呼び出しコストを消すため)
// soft作成直後に1回だけ取得してキャッシュする(sootは毎フレーム作り直していないのでアドレスは変わらない想定)
unsigned char* g_FrameBufferAddr = NULL;
int g_FrameBufferPitch = 0;
int Texture[1024*2048]={0};
Vec3 BumpMap[1024*2048]; // ★追加:バンプマップ(法線)保持用
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);
float ComputeShadowFactor(float wx, float wy, float wz); // 前方宣言:DrawBillboardTreeなど、定義より前から呼ぶため
void DrawFloorCell(float x, float z);
void TexTri(VTX n1,VTX n2,VTX n3);
bool TransformAndProject(float wx, float wy, float wz, float u, float v, float brightness, VTX& out);
void DrawWall(float x, float z, WallDir dir);
inline void Swap(VTX& a, VTX& b);
#define MAX_VTX 1000
#define MAX_POL 3000
int MAXVTX=0;
int MAXPOL=0;
struct Edge {
    float x, invZ, uz, vz;
};
VTX v[MAX_VTX]={0};
int PolygonNumber[MAX_POL][4]={0};

void TexTri_Complex(VTX,VTX,VTX); // 旧Gouraud用(互換のため残置、現在は未使用)
void TexTri_Simple(VTX,VTX,VTX); // 旧Gouraud用(互換のため残置、現在は未使用)

#define TexTri TexTri_Complex


// ★追加:ピクセルのRGBから輝度(0.0f〜1.0f)を求める関数(VC++2008対応)
static float GetPixelLuminance(int col)
{
    float r = (float)((col >> 16) & 0xFF);
    float g = (float)((col >> 8) & 0xFF);
    float b = (float)(col & 0xFF);
    return (0.299f * r + 0.587f * g + 0.114f * b) / 255.0f;
}

// ★追加:テクスチャの輝度からバンプマップ(法線)を自動生成する関数
void GenerateBumpMapFromTexture(float bumpHeight)
{
    for (int y = 0; y < TexH; y++)
    {
        for (int x = 0; x < TexW; x++)
        {
            // 隣接ピクセル(Wrap処理付き)
            int xL = (x - 1 + TexW) % TexW;
            int xR = (x + 1) % TexW;
            int yU = (y - 1 + TexH) % TexH;
            int yD = (y + 1) % TexH;

            // 輝度(高さ)を取得
            float hL = GetPixelLuminance(Texture[y * TexW + xL]);
            float hR = GetPixelLuminance(Texture[y * TexW + xR]);
            float hU = GetPixelLuminance(Texture[yU * TexW + x]);
            float hD = GetPixelLuminance(Texture[yD * TexW + x]);

            // 輝度の差分から勾配を計算(du, dv)
            float du = (hL - hR) * bumpHeight;
            float dv = (hU - hD) * bumpHeight;

            // 接空間(TBN空間)における法線ベクトル (du, dv, 1.0) を正規化
            Vec3 n;
            n.x = du;
            n.y = dv;
            n.z = 1.0f;

   float len=sqrt(n.x*n.x+n.y*n.y+n.z*n.z+0.01f);
   n.x/=len;
   n.y/=len;
   n.z/=len;

            //n = NormalizeVec3(n);

            BumpMap[y * TexW + x] = n;
        }
    }
}


inline void SetV(int num,float x,float y,float z)
{
 if(num<0 || num>MAX_VTX) return;
 v[num].x=x;
 v[num].y=y;
 v[num].z=z;
}
inline void Set3Poly(int num,int n1,int n2,int n3)
{
 if(num<0 || num>MAX_POL) return;
 PolygonNumber[num][0]=n1;
 PolygonNumber[num][1]=n2;
 PolygonNumber[num][2]=n3;
}
inline void Set4Poly(int num,int n1,int n2,int n3,int n4)
{
 if(num<0 || num>MAX_POL) return;
 PolygonNumber[num][0]=n1;
 PolygonNumber[num][1]=n2;
 PolygonNumber[num][2]=n3;
 PolygonNumber[num][3]=n4;
}
// ==================================================
// 視野角ポータルカリング(曲がり角の先の壁を描画しないための可視セル探索)
// ・迷路はDig()による生成木構造(通路はループなしの1本道の集合)なので、
// カメラのいるセルから「扉(セル境界)」を通って進む形の探索が自然に対応する。
// ・扉を通るたびに「その扉がカメラから見て張る角度範囲」と「今持っている視野角範囲」の
// 共通部分を取り、空になったらそこで打ち切る(=壁の陰で見えないので探索しない)。
// ・見えているセルだけをDrawMaze()で処理するので、曲がり角の先の壁は
// 三角形リストに一切載らず、変換・ラスタライズのコストがかからない。
// ==================================================
bool g_CellVisible[H][W];

// カメラから見た点(wx,wz)の水平方向の角度(正面=0、右が正、単位はラジアン)
float AngleFromCamera(float wx, float wz)
{
    float tx = wx - cam.x;
    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;
    return atan2f(vx, vz);
}

// (ix,iz)セルを、角度範囲[angMin,angMax]の中でだけ可視として探索する
void VisitCellForVisibility(int ix, int iz, float angMin, float angMax, int depth)
{
    if (ix < 0 || ix >= W || iz < 0 || iz >= H) return;
    if (maze[iz][ix] != 0) return; // 壁セルには入らない(通路だけを辿る)
    if (g_CellVisible[iz][ix]) return; // 探索済み
    if (angMin > angMax) return; // 角度範囲が空=見えない
    if (depth > W * H) return; // 安全弁(本来は迷路セル数を超えることはない)

    // このセルの中心が遠すぎる(フォグで完全に見えない距離)なら、それより奥は描く意味が薄いので打ち切る
    float cx = ix * CELL + CELL * 0.5f;
    float cz = iz * CELL + CELL * 0.5f;
    float dx = cx - cam.x, dz = cz - cam.z;
    if (dx*dx + dz*dz > (g_FogEnd + CELL) * (g_FogEnd + CELL)) return;

    g_CellVisible[iz][ix] = true;

    // 4方向の扉(セル境界)を調べ、それぞれの扉がカメラから見て張る角度範囲と
    // 現在の角度範囲との共通部分を取ってから、開いていれば隣のセルへ進む
    struct Portal { int nix, niz; float ex0, ez0, ex1, ez1; };
    float x0 = ix * CELL, x1 = x0 + CELL;
    float z0 = iz * CELL, z1 = z0 + CELL;
    Portal portals[4] = {
        { ix, iz-1, x0, z0, x1, z0 }, // 北
        { ix, iz+1, x0, z1, x1, z1 }, // 南
        { ix-1, iz, x0, z0, x0, z1 }, // 西
        { ix+1, iz, x1, z0, x1, z1 }, // 東
    };
    for (int i = 0; i < 4; i++)
    {
        Portal& p = portals[i];
        if (p.nix < 0 || p.nix >= W || p.niz < 0 || p.niz >= H) continue;
        if (maze[p.niz][p.nix] != 0) continue; // 壁なので扉ではない(通り抜けられない)

        float a0 = AngleFromCamera(p.ex0, p.ez0);
        float a1 = AngleFromCamera(p.ex1, p.ez1);
        // カメラのすぐ近くで扉が±180°をまたぐ稀なケースは、安全側に倒してそのまま範囲を引き継ぐ
        if (fabsf(a0 - a1) > 3.0f) continue;
        float portalMin = (a0 < a1) ? a0 : a1;
        float portalMax = (a0 < a1) ? a1 : a0;

        float newMin = (angMin > portalMin) ? angMin : portalMin;
        float newMax = (angMax < portalMax) ? angMax : portalMax;
        if (newMin <= newMax)
            VisitCellForVisibility(p.nix, p.niz, newMin, newMax, depth + 1);
    }
}

void DrawMaze()
{
    memset(g_CellVisible, 0, sizeof(g_CellVisible));

    int camIx = (int)(cam.x / CELL);
    int camIz = (int)(cam.z / CELL);
    // 画面端でのポップインを避けるため、実際のFOVより少し広めに探索する
    const float VIEW_MARGIN = 1.2f;
    float halfFov = atanf((WIDTH / 2.0f) / FOV_F) * VIEW_MARGIN;
    VisitCellForVisibility(camIx, camIz, -halfFov, halfFov, 0);

    for (int z = 0; z < H; z++)
    for (int x = 0; x < W; x++)
    {
        if (!g_CellVisible[z][x]) continue;
        if (maze[z][x] != 0) continue;
        float wx = x * CELL;
        float wz = z * CELL;
        DrawFloorCell(wx, wz);
        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 SwapVTX(VTX &a,VTX &b)
{
 VTX tmp=a;
 a=b;
 b=tmp;
}
void swap(int &a,int &b)
{
 int tmp=a;
 a=b;
 b=tmp;
}
void swap(float &a,float &b)
{
 float tmp=a;
 a=b;
 b=tmp;
}
inline void Swap(VTX& a, VTX& b)
{
    VTX t = a; a = b; b = t;
}
void SortByY(VTX& a, VTX& b, VTX& c)
{
    if (a.y > b.y) SwapVTX(a, b);
    if (a.y > c.y) SwapVTX(a, c);
    if (b.y > c.y) SwapVTX(b, c);
}
void WorldToView(
    float wx, float wy, float wz,
    Camera& cam,
    float& vx, float& vy, float& vz
)
{
    float x = wx - cam.x;
    float y = wy - cam.y;
    float z = wz - cam.z;
    float c = cosf(-cam.yaw);
    float s = sinf(-cam.yaw);
    vx = x * c - z * s;
    vz = x * s + z * c;
    vy = y;
}
Vec3 GetWallNormal(WallDir dir)
{
 Vec3 NSWE[4]={
  0,0,1,
  0,0,-1,
  1,0,0,
  -1,0,0,
 };
 Vec3 ret={0,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];
    }
    return ret;
}
bool Project(
    float vx, float vy, float vz,
    float& sx, float& sy,
    float& invZ
)
{
    if (vz <= 0.01f) return false;
    float f = 600.0f;
    invZ = 1.0f / vz;
    sx = vx * f * invZ + 160;
    sy = -vy * f * invZ + 120;
    return true;
}
bool TransformAndProject(float wx, float wy, float wz, float u, float v, float brightness, 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;
    out.b = brightness * invZ;
    return true;
}

// ==================================================
// フォンシェーディング用パイプライン
// ・頂点では「法線」と「UV」だけを渡す(明るさは計算しない)
// ・スキャンライン内で法線を補間 → 1ピクセルごとに正規化してライティング計算する
// ・カメラからの距離^2は、スクリーン座標とinvZから view空間位置を復元して求める
// (view空間はカメラ原点からの回転移動なので、距離^2はワールド座標での距離^2と一致する)
// ・シャドウ係数(s)も法線と同じ扱いで頂点だけで計算し、ピクセル間は補間するだけにする
// (毎ピクセルでレイマーチすると解像度アップで重くなりすぎるため)
// ==================================================
struct VTXP { float x, y, z, u, v, nx, ny, nz, s; };

bool TransformAndProjectPhong(float wx, float wy, float wz, float u, float v, Vec3 normal, float shadow, VTXP& 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;
    out.nx = normal.x * invZ;
    out.ny = normal.y * invZ;
    out.nz = normal.z * invZ;
    out.s = shadow * invZ;
    return true;
}

// ワールド座標(四角形の4隅)+UV+法線+シャドウ係数を持つクリッピング用頂点
struct WVtxP { float wx, wy, wz, u, v, nx, ny, nz, shadow; };

int ClipPolygonNearPlanePhong(WVtxP* in, int n, WVtxP* out)
{
    const float EPS = 0.05f;
    float c = cosf(-cam.yaw), s = sinf(-cam.yaw);
    int outCount = 0;
    for (int i = 0; i < n; i++)
    {
        WVtxP& curr = in[i];
        WVtxP& next = in[(i + 1) % n];

        float cvz = (curr.wx - cam.x) * s + (curr.wz - cam.z) * c;
        float nvz = (next.wx - cam.x) * s + (next.wz - cam.z) * c;

        bool currIn = cvz > EPS;
        bool nextIn = nvz > EPS;

        if (currIn) out[outCount++] = curr;

        if (currIn != nextIn)
        {
            float t = (EPS - cvz) / (nvz - cvz);
            WVtxP mid;
            mid.wx = curr.wx + (next.wx - curr.wx) * t;
            mid.wy = curr.wy + (next.wy - curr.wy) * t;
            mid.wz = curr.wz + (next.wz - curr.wz) * t;
            mid.u = curr.u + (next.u - curr.u ) * t;
            mid.v = curr.v + (next.v - curr.v ) * t;
            mid.nx = curr.nx + (next.nx - curr.nx) * t;
            mid.ny = curr.ny + (next.ny - curr.ny) * t;
            mid.nz = curr.nz + (next.nz - curr.nz) * t;
            mid.shadow = curr.shadow + (next.shadow - curr.shadow) * t;
            out[outCount++] = mid;
        }
    }
    return outCount;
}

// ==================================================
// マルチスレッドラスタライズ用:三角形リスト
// ・DrawWorld()/DrawMaze()は頂点変換・クリッピングまでだけ行い、結果の三角形をここに「記録」する
// ・実際のピクセル処理(TexTriPhong)は、記録が終わったあとで複数スレッドが
// このリストを読みながら、担当する画面の行範囲(バンド)だけ処理する
// ・g_CurrentAmbientBoostのようなグローバルをラスタライズ時に読むとスレッド間で
// 値が競合するため、記録した時点の値を三角形ごとに持たせておく
// ==================================================
#define MAX_TRIS 8000
struct RecordedTri { VTXP v0, v1, v2; float ambientBoost; };
RecordedTri g_TriList[MAX_TRIS];
int g_TriCount = 0;

inline void RecordTri(const VTXP& v0, const VTXP& v1, const VTXP& v2, float ambientBoost)
{
    if (g_TriCount >= MAX_TRIS) return; // 上限を超えたら諦めて捨てる(欠けるだけで安全)
    RecordedTri& t = g_TriList[g_TriCount++];
    t.v0 = v0; t.v1 = v1; t.v2 = v2;
    t.ambientBoost = ambientBoost;
}

// 実際のラスタライズ本体。ambientBoostは記録時の値を、yStart/yEndは担当バンドを明示的に受け取る
void TexTriPhong(VTXP n1, VTXP n2, VTXP n3, float ambientBoost, int yStart, int yEnd);

void DrawTexturedQuadWorldPhong(WVtxP p0, WVtxP p1, WVtxP p2, WVtxP p3)
{
    WVtxP in[4] = { p0, p1, p2, p3 };
    WVtxP clipped[5];
    int n = ClipPolygonNearPlanePhong(in, 4, clipped);
    if (n < 3) return;

    VTXP proj[5];
    for (int i = 0; i < n; i++)
    {
        Vec3 normal = { clipped[i].nx, clipped[i].ny, clipped[i].nz };
        if (!TransformAndProjectPhong(clipped[i].wx, clipped[i].wy, clipped[i].wz,
                                       clipped[i].u, clipped[i].v, normal, clipped[i].shadow, proj[i]))
            return;
    }
    // ★変更:ここではもう塗らない。三角形として記録するだけ(軽い処理)
    for (int i = 1; i + 1 < n; i++)
        RecordTri(proj[0], proj[i], proj[i + 1], g_CurrentAmbientBoost);
}
// ==================================================

bool DrawTexturedTriangle(
    float x1, float y1, float z1, float u1, float v1,
    float x2, float y2, float z2, float u2, float v2,
    float x3, float y3, float z3, float u3, float v3,
    Camera& cam
)
{
    VTX v1p, v2p, v3p;
    if (!TransformAndProject(x1,y1,z1, u1,v1, 1.0f, v1p)) return false;
    if (!TransformAndProject(x2,y2,z2, u2,v2, 1.0f, v2p)) return false;
    if (!TransformAndProject(x3,y3,z3, u3,v3, 1.0f, v3p)) return false;
    TexTri(v1p, v2p, v3p);
    return true;
}
void DrawBillboardTree(float x, float z, Camera& cam)
{
    const float halfW = 0.5f;
    const float h = 1.0f;
    float dx = cam.x - x;
    float dz = cam.z - z;
    float len = sqrtf(dx * dx + dz * dz);
    if (len < 0.0001f) return;
    dx /= len;
    dz /= len;
    float rx = -dz;
    float rz = dx;

    Vec3 billboardNormal = { dx, 0.0f, dz };

    Vec3 p0 = { x - rx * halfW, 0.0f, z - rz * halfW };
    Vec3 p1 = { x + rx * halfW, 0.0f, z + rz * halfW };
    Vec3 p2 = { x + rx * halfW, h, z + rz * halfW };
    Vec3 p3 = { x - rx * halfW, h, z - rz * halfW };

    g_CurrentAmbientBoost = 0.0f;

    // ★変更:シャドウ判定は頂点ごとに1回だけ行う(ピクセル毎のレイマーチはしない)
    float s0v = ComputeShadowFactor(p0.x, p0.y, p0.z);
    float s1v = ComputeShadowFactor(p1.x, p1.y, p1.z);
    float s2v = ComputeShadowFactor(p2.x, p2.y, p2.z);
    float s3v = ComputeShadowFactor(p3.x, p3.y, p3.z);

    WVtxP w0 = { p0.x, p0.y, p0.z, 0.0f, 1.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z, s0v };
    WVtxP w1 = { p1.x, p1.y, p1.z, 1.0f, 1.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z, s1v };
    WVtxP w2 = { p2.x, p2.y, p2.z, 1.0f, 0.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z, s2v };
    WVtxP w3 = { p3.x, p3.y, p3.z, 0.0f, 0.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z, s3v };
    DrawTexturedQuadWorldPhong(w0, w1, w2, w3);
}
void UpdateCamera(Camera& cam)
{
    float speed = 0.1f;
    if (CheckHitKey(KEY_INPUT_LEFT)) cam.yaw += 0.03f;
    if (CheckHitKey(KEY_INPUT_RIGHT)) cam.yaw -= 0.03f;
    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;
    }
}
void PSET(int x,int y,int c)
{
 // ★変更:DrawPixelSoftImageの呼び出し(引数分解・境界チェック等の関数コールオーバーヘッド)を
 // やめて、ソフトイメージの生バッファへ直接書き込む。307,200ピクセル/フレーム分の
 // 関数呼び出しコストが消えるので、これが一番効く最適化。
 // バイト順はB,G,R,A(WindowsのDIBと同じ並び)を想定。もし赤と青が入れ替わって見える場合は
 // 下のp[0]とp[2]の代入を入れ替えてください。
 if ((unsigned)x >= (unsigned)WIDTH || (unsigned)y >= (unsigned)HEIGHT) return;
 unsigned char* p = g_FrameBufferAddr + (size_t)y * g_FrameBufferPitch + (size_t)x * 4;
 p[0] = (unsigned char)(c & 0xFF); // B
 p[1] = (unsigned char)((c >> 8) & 0xFF); // G
 p[2] = (unsigned char)((c >> 16) & 0xFF); // R
 p[3] = 255; // A
}
void DrawWorld()
{
    DrawBillboardTree(0.0f, 5.0f, cam);
    DrawBillboardTree(2.0f, 8.0f, cam);
    DrawBillboardTree(-2.0f, 10.0f, cam);
}
static inline float clampf(float v, float minv, float maxv)
{
    if (v < minv) return minv;
    if (v > maxv) return maxv;
    return v;
}
static inline int clamp(int v, int minv, int maxv)
{
    if (v < minv) return minv;
    if (v > maxv) return maxv;
    return v;
}
void Line(int x1, int y1, float u1, float v1, float w1, float b1,
          int x2, int y2, float u2, float v2, float w2, float b2) {
    if (y1 == y2) return;
    if (y1 > y2) {
        swap(y1, y2); swap(x1, x2);
        swap(u1, u2); swap(v1, v2); swap(w1, w2); swap(b1, b2);
    }
    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;
        float b = b1 + (b2 - b1) * t;
        if (x < L[y]) { L[y] = x; LU[y] = u; LV[y] = v; LW[y] = w; LB[y] = b; }
        if (x > R[y]) { R[y] = x; RU[y] = u; RV[y] = v; RW[y] = w; RB[y] = b; }
    }
}

// フォンシェーディング用のライン走査(明るさbの代わりに法線nx,ny,nzとシャドウ係数sを補間する)
void LinePhong(int x1, int y1, float u1, float v1, float w1, float nx1, float ny1, float nz1, float s1,
               int x2, int y2, float u2, float v2, float w2, float nx2, float ny2, float nz2, float s2) {
    if (y1 == y2) return;
    if (y1 > y2) {
        swap(y1, y2); swap(x1, x2);
        swap(u1, u2); swap(v1, v2); swap(w1, w2);
        swap(nx1, nx2); swap(ny1, ny2); swap(nz1, nz2);
        swap(s1, s2);
    }
    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;
        float nx = nx1 + (nx2 - nx1) * t;
        float ny = ny1 + (ny2 - ny1) * t;
        float nz = nz1 + (nz2 - nz1) * t;
        float s = s1 + (s2 - s1 ) * t;
        if (x < L[y]) { L[y] = x; LU[y] = u; LV[y] = v; LW[y] = w; LNX[y] = nx; LNY[y] = ny; LNZ[y] = nz; LS[y] = s; }
        if (x > R[y]) { R[y] = x; RU[y] = u; RV[y] = v; RW[y] = w; RNX[y] = nx; RNY[y] = ny; RNZ[y] = nz; RS[y] = s; }
    }
}

int Lbuf[HEIGHT];
int Rbuf[HEIGHT];

void Line_(int Ax, int Ay, float AU, float AV, float AZ, float AB,
           int Bx, int By, float BU, float BV, float BZ, float BB)
{
    for (int i = 0; i <= 256; i++)
    {
        float t = (float)i / 256.0f;
        int x = (int)(Ax * (1.0f - t) + Bx * t);
        int y = (int)(Ay * (1.0f - t) + By * t);
        if (y >= HEIGHT || y < 0) continue;

        float u = AU * (1.0f - t) + BU * t;
        float v = AV * (1.0f - t) + BV * t;
        float z = AZ * (1.0f - t) + BZ * t;
        float b = AB * (1.0f - t) + BB * t;

        if (x > Rbuf[y]) {
            Rbuf[y] = x;
            RU[y] = u;
            RV[y] = v;
            RW[y] = z;
            RB[y] = b;
        }
        if (x < Lbuf[y])
        {
            Lbuf[y] = x;
            LU[y] = u;
            LV[y] = v;
            LW[y] = z;
            LB[y] = b;
        }
    }
}

void TexTri_Simple(VTX n1, VTX n2, VTX n3)
{
    for (int y = 0; y < HEIGHT; y++)
    {
        Lbuf[y] = 9000;
        Rbuf[y] = -9000;
    }

    Line_(n1.x, n1.y, n1.u, n1.v, n1.z, n1.b, n2.x, n2.y, n2.u, n2.v, n2.z, n2.b);
    Line_(n2.x, n2.y, n2.u, n2.v, n2.z, n2.b, n3.x, n3.y, n3.u, n3.v, n3.z, n3.b);
    Line_(n3.x, n3.y, n3.u, n3.v, n3.z, n3.b, n1.x, n1.y, n1.u, n1.v, n1.z, n1.b);

    for (int y = 0; y < HEIGHT; y++)
    {
        if (Lbuf[y] > Rbuf[y]) continue;

        int lx = max(0, Lbuf[y]);
        int rx = min(WIDTH - 1, Rbuf[y]);
        if (lx > rx) continue;

        float span = (float)(Rbuf[y] - Lbuf[y]);
        if (span <= 0.0f) span = 1.0f;

        for (int x = lx; x <= rx; x++)
        {
            float t = (float)(x - Lbuf[y]) / span;

            float u = LU[y] * (1.0f - t) + RU[y] * t;
            float v = LV[y] * (1.0f - t) + RV[y] * t;
            float w = LW[y] * (1.0f - t) + RW[y] * t;
            float b = LB[y] * (1.0f - t) + RB[y] * t;

            if (w > ZBuf[y][x])
            {
                ZBuf[y][x] = w;

                int tx = ((int)(u * (TexW - 1))) & (TexW - 1);
                int ty = ((int)(v * (TexH - 1))) & (TexH - 1);

                int col = Texture[ty * TexW + tx];
                col = ShadeColor(col, b);
                PSET(x, y, col);
            }
        }
    }
}

void TexTri_Complex(VTX n1, VTX n2, VTX n3) {
    // 背面カリング(2Dスクリーン座標の外積判定)
    float cross = (n2.x - n1.x) * (n3.y - n1.y) - (n2.y - n1.y) * (n3.x - n1.x);
    if (cross <= 0.0f) return;

    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, n1.b, (int)n2.x, (int)n2.y, n2.u, n2.v, n2.z, n2.b);
    Line((int)n2.x, (int)n2.y, n2.u, n2.v, n2.z, n2.b, (int)n3.x, (int)n3.y, n3.u, n3.v, n3.z, n3.b);
    Line((int)n3.x, (int)n3.y, n3.u, n3.v, n3.z, n3.b, (int)n1.x, (int)n1.y, n1.u, n1.v, n1.z, n1.b);
    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 db = (RB[y] - LB[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]);
    float b = LB[y] + db * (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);
            float brightness = b * z;
            if (brightness > 1.0f) brightness = 1.0f;
            if (brightness < 0.0f) brightness = 0.0f;

            int c;
            if (g_DebugBrightnessOnly)
            {
                int gray = (int)(brightness * 255.0f);
                if (gray > 255) gray = 255;
                if (gray < 0) gray = 0;
                c = GetColor(gray, gray, gray);
            }
            else
            {
                c = Texture[ty * TexW + tx];
                c = ShadeColor(c, brightness);
            }
            PSET(x, y, c);
        }
        u += du; v += dv; w += dw; b += db;
    }
    }
}

// ==== 簡易シャドウ判定 ====
// ワールド座標wp(wx,wy,wz)から光源方向(-g_LightDir)へレイを伸ばし、
// 迷路の壁セル(maze[iz][ix]==1)かつ壁の高さ範囲(0〜WALL_H)を通過したら「影」とみなす。
// 迷路のグリッドがそのままシャドウ判定に使えるため、シャドウマップなど重い仕組みは使わない。
float ComputeShadowFactor(float wx, float wy, float wz)
{
    float dirX = -g_LightDir.x;
    float dirY = -g_LightDir.y;
    float dirZ = -g_LightDir.z;

    // 自セルとの誤判定(セルフシャドウのちらつき)を避けるため、少し進めてから判定を始める
    float px = wx + dirX * SHADOW_STEP;
    float py = wy + dirY * SHADOW_STEP;
    float pz = wz + dirZ * SHADOW_STEP;

    for (int i = 0; i < SHADOW_MAX_STEPS; i++)
    {
        if (py >= 0.0f && py <= WALL_H)
        {
            int ix = (int)floorf(px / CELL);
            int iz = (int)floorf(pz / CELL);
            if (ix >= 0 && ix < W && iz >= 0 && iz < H && maze[iz][ix] == 1)
            {
                return 0.0f; // 壁に遮られている=影
            }
        }
        px += dirX * SHADOW_STEP;
        py += dirY * SHADOW_STEP;
        pz += dirZ * SHADOW_STEP;
    }
    return 1.0f; // 遮るものが無い=日向
}

// ==== 距離フォグ ====
// distはカメラからそのピクセルまでのワールド距離。g_FogStart〜g_FogEndの間で
// 色をg_FogColorへ線形にブレンドする(g_FogEnd以遠は完全に霧色)
inline int ApplyFog(int col, float dist)
{
    if (!g_FogEnabled) return col;
    float t = (dist - g_FogStart) / (g_FogEnd - g_FogStart);
    if (t < 0.0f) t = 0.0f;
    if (t > 1.0f) t = 1.0f;

    int r = (col >> 16) & 0xFF;
    int g = (col >> 8) & 0xFF;
    int b = col & 0xFF;
    int fr = (g_FogColor >> 16) & 0xFF;
    int fg = (g_FogColor >> 8) & 0xFF;
    int fb = g_FogColor & 0xFF;

    r = (int)(r * (1.0f - t) + fr * t);
    g = (int)(g * (1.0f - t) + fg * t);
    b = (int)(b * (1.0f - t) + fb * t);
    return GetColor(clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255));
}

// フォンシェーディング用ラスタライザ:明るさbの代わりに法線nx,ny,nzを補間し、
// 1ピクセルごとに正規化+ライティング計算してからテクスチャに掛ける
// ★変更:ambientBoostは記録時の値を引数で受け取る(グローバルを読むとスレッド競合するため)。
// yStart/yEndは「このスレッドが担当する画面の行範囲」。エッジ計算(L[]/R[]など)は
// 三角形の全高さ分行うが、実際にピクセルを塗るのはこの範囲内だけに制限する。
void TexTriPhong(VTXP n1, VTXP n2, VTXP n3, float ambientBoost, int yStart, int yEnd) {
    // 背面カリング(2Dスクリーン座標の外積判定)
    // 裏向き(時計回り/反時計回りの条件が外れる)ポリゴンはラスタライズ前にスキップする
    float cross = (n2.x - n1.x) * (n3.y - n1.y) - (n2.y - n1.y) * (n3.x - n1.x);
    if (cross <= 0.0f) return;

    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);
    if (miny > maxy) return;
    // このスレッドの担当範囲と三角形の高さ範囲が全く重ならなければ何もしない
    if (maxy < yStart || miny >= yEnd) return;

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

    // ★変更:ピクセルを実際に塗るのは自分のバンド範囲だけ
    int rasterMiny = max(miny, yStart);
    int rasterMaxy = min(maxy, yEnd - 1);

    for (int y = rasterMiny; y <= rasterMaxy; 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 dnx = (RNX[y] - LNX[y]) / span;
        float dny = (RNY[y] - LNY[y]) / span;
        float dnz = (RNZ[y] - LNZ[y]) / span;
        float ds = (RS[y] - LS[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]);
        float nx = LNX[y] + dnx * (lx - L[y]);
        float ny = LNY[y] + dny * (lx - L[y]);
        float nz = LNZ[y] + dnz * (lx - L[y]);
        float s = LS[y] + ds * (lx - L[y]);

        // --- TexTriPhong 関数の中身 ---
for (int x = lx; x <= rx; x++) {
    if (w > ZBuf[y][x]) {
        ZBuf[y][x] = w;
        float z = 1.0f / w; // 実際の深度

        float pu = u * z;
        float pv = v * z;
        float pnx = nx * z;
        float pny = ny * z;
        float pnz = nz * z;
        float pshadow = s * z; // ★変更:頂点で計算済みのシャドウ係数を補間するだけ(ピクセル毎のレイマーチはしない)

        // 面の補間法線を正規化
        //Vec3 surfN = NormalizeVec3({ pnx, pny, pnz });
  float len=sqrt(pnx*pnx+pny*pny+pnz*pnz+0.01f);

  pnx/=len;
  pny/=len;
  pnz/=len;
  Vec3 surfN;
  surfN.x=pnx;surfN.y=pny;surfN.z=pnz;

        int tx = ((int)(pu * (TexW - 1))) & (TexW - 1);
        int ty = ((int)(pv * (TexH - 1))) & (TexH - 1);

        // ★追加:バンプマップから局所法線を取得
        Vec3 localN = BumpMap[ty * TexW + tx];

        // 簡易接空間(TBN)変換: 面法線に合わせて局所法線を傾ける
        Vec3 finalN;
        if (fabsf(surfN.y) > 0.8f) { // 床・天井など(Y軸方向の面)
            finalN.x = surfN.x + localN.x;
            finalN.y = surfN.y;
            finalN.z = surfN.z + localN.y;
        } else { // 壁面(XZ平面方向の面)
            finalN.x = surfN.x + localN.x * (-surfN.z);
            finalN.y = surfN.y + localN.y;
            finalN.z = surfN.z + localN.x * (surfN.x);
        }
        //finalN = NormalizeVec3(finalN);
  float len2=sqrt(finalN.x*finalN.x+finalN.y*finalN.y+finalN.z*finalN.z+0.01f);
  finalN.x=finalN.x/len2;
  finalN.y=finalN.y/len2;
  finalN.z=finalN.z/len2;

        float vx = (x - WIDTH / 2) * z / FOV_F;
        float vy = -(y - HEIGHT / 2) * z / FOV_F;
        float vz = z;
        float distSq = vx * vx + vy * vy + vz * vz;

        // シャドウ係数はクランプだけしておく(補間でわずかに範囲外に出ることがあるため)
        float shadow = clampf(pshadow, 0.0f, 1.0f);

        // ★変更:傾けた finalN の各成分を使って明るさを計算(影を反映)
        float brightness = ComputePhongBrightness(finalN.x, finalN.y, finalN.z, distSq, ambientBoost, shadow);

        int c;
        if (g_DebugBrightnessOnly)
        {
            int gray = (int)(brightness * 255.0f);
            gray = clamp(gray, 0, 255);
            c = GetColor(gray, gray, gray);
        }
        else
        {
            c = Texture[ty * TexW + tx];
            c = ShadeColor(c, brightness);
        }

        // ★追加:カメラからの実距離(distSqの平方根)でフォグを掛ける
        c = ApplyFog(c, sqrtf(distSq));

        PSET(x, y, c);
    }
    u += du; v += dv; w += dw;
    nx += dnx; ny += dny; nz += dnz;
    s += ds;
}
    }
}

// ★追加:記録済みの三角形リストのうち、指定した行範囲[yStart,yEnd)だけをラスタライズする。
// これをワーカースレッドごとに別の行範囲で呼び出すことで並列化する。
void RasterizeBand(int yStart, int yEnd)
{
    for (int i = 0; i < g_TriCount; i++)
    {
        RecordedTri& t = g_TriList[i];
        TexTriPhong(t.v0, t.v1, t.v2, t.ambientBoost, yStart, yEnd);
    }
}

// --- 壁の描画 ---
#define WALL_SUBDIV_H 1 // 壁の横方向(幅)の分割数
#define WALL_SUBDIV_V 1 // 壁の縦方向(高さ)の分割数

#define BLEND_FLOOR_WALL 0.15f // 床↔壁の境界をぼかす幅の割合(0~1)
#define BLEND_WALL_CORNER 0.15f // 壁同士の曲がり角をぼかす幅の割合(0~1)

Vec3 NormalizeVec3(Vec3 v)
{
    float len = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z);
    if (len > 0.0001f) { v.x /= len; v.y /= len; v.z /= len; }
    return v;
}

// 壁セル(ix,iz)から見て、dir方向の壁が実際に存在するか(DrawMazeと同じ判定)
bool IsWallDirPresent(int ix, int iz, WallDir dir)
{
    switch (dir) {
        case WALL_NORTH: return iz > 0 && maze[iz-1][ix] == 1;
        case WALL_SOUTH: return iz < H - 1 && maze[iz+1][ix] == 1;
        case WALL_WEST: return ix > 0 && maze[iz][ix-1] == 1;
        case WALL_EAST: return ix < W - 1 && maze[iz][ix+1] == 1;
    }
    return false;
}

WallDir GetPerpDirAtT0(WallDir dir)
{
    switch (dir) {
        case WALL_NORTH: return WALL_WEST;
        case WALL_SOUTH: return WALL_EAST;
        case WALL_WEST: return WALL_SOUTH;
        case WALL_EAST: return WALL_NORTH;
    }
    return dir;
}
WallDir GetPerpDirAtT1(WallDir dir)
{
    switch (dir) {
        case WALL_NORTH: return WALL_EAST;
        case WALL_SOUTH: return WALL_WEST;
        case WALL_WEST: return WALL_NORTH;
        case WALL_EAST: return WALL_SOUTH;
    }
    return dir;
}

Vec3 GetSmoothedWallNormal(WallDir dir, int ix, int iz, float t, float yFrac)
{
    Vec3 wallNormal = GetWallNormal(dir);

    float wFloor = 1.0f - clampf(yFrac / BLEND_FLOOR_WALL, 0.0f, 1.0f);

    WallDir perpT0 = GetPerpDirAtT0(dir);
    WallDir perpT1 = GetPerpDirAtT1(dir);
    bool hasPerp0 = IsWallDirPresent(ix, iz, perpT0);
    bool hasPerp1 = IsWallDirPresent(ix, iz, perpT1);
    float wPerp0 = hasPerp0 ? (1.0f - clampf(t / BLEND_WALL_CORNER, 0.0f, 1.0f)) : 0.0f;
    float wPerp1 = hasPerp1 ? (1.0f - clampf((1.0f - t) / BLEND_WALL_CORNER, 0.0f, 1.0f)) : 0.0f;

    Vec3 sum = wallNormal;
    if (wFloor > 0.0f) { sum.x += 0.0f * wFloor; sum.y += 1.0f * wFloor; sum.z += 0.0f * wFloor; }
    if (wPerp0 > 0.0f) { Vec3 n = GetWallNormal(perpT0); sum.x += n.x*wPerp0; sum.y += n.y*wPerp0; sum.z += n.z*wPerp0; }
    if (wPerp1 > 0.0f) { Vec3 n = GetWallNormal(perpT1); sum.x += n.x*wPerp1; sum.y += n.y*wPerp1; sum.z += n.z*wPerp1; }
    return NormalizeVec3(sum);
}

Vec3 GetSmoothedFloorNormal(int ix, int iz, float uGlobal, float vGlobal)
{
    Vec3 floorNormal = { 0.0f, 1.0f, 0.0f };

    bool hasNorth = IsWallDirPresent(ix, iz, WALL_NORTH);
    bool hasSouth = IsWallDirPresent(ix, iz, WALL_SOUTH);
    bool hasWest = IsWallDirPresent(ix, iz, WALL_WEST);
    bool hasEast = IsWallDirPresent(ix, iz, WALL_EAST);

    float wNorth = hasNorth ? (1.0f - clampf(vGlobal / BLEND_FLOOR_WALL, 0.0f, 1.0f)) : 0.0f;
    float wSouth = hasSouth ? (1.0f - clampf((1.0f - vGlobal) / BLEND_FLOOR_WALL, 0.0f, 1.0f)) : 0.0f;
    float wWest = hasWest ? (1.0f - clampf(uGlobal / BLEND_FLOOR_WALL, 0.0f, 1.0f)) : 0.0f;
    float wEast = hasEast ? (1.0f - clampf((1.0f - uGlobal) / BLEND_FLOOR_WALL, 0.0f, 1.0f)) : 0.0f;

    Vec3 sum = floorNormal;
    if (wNorth > 0.0f) { Vec3 n = GetWallNormal(WALL_NORTH); sum.x += n.x*wNorth; sum.y += n.y*wNorth; sum.z += n.z*wNorth; }
    if (wSouth > 0.0f) { Vec3 n = GetWallNormal(WALL_SOUTH); sum.x += n.x*wSouth; sum.y += n.y*wSouth; sum.z += n.z*wSouth; }
    if (wWest > 0.0f) { Vec3 n = GetWallNormal(WALL_WEST); sum.x += n.x*wWest; sum.y += n.y*wWest; sum.z += n.z*wWest; }
    if (wEast > 0.0f) { Vec3 n = GetWallNormal(WALL_EAST); sum.x += n.x*wEast; sum.y += n.y*wEast; sum.z += n.z*wEast; }
    return NormalizeVec3(sum);
}

void DrawWall(float x, float z, WallDir dir) {
    float wx[4], wz[4];
    switch (dir) {
        case WALL_NORTH: wx[0]=x; wz[0]=z; wx[1]=x+CELL; wz[1]=z; break;
        case WALL_SOUTH: wx[0]=x+CELL; wz[0]=z+CELL; wx[1]=x; wz[1]=z+CELL; break;
        case WALL_WEST: wx[0]=x; wz[0]=z+CELL; wx[1]=x; wz[1]=z; break;
        case WALL_EAST: wx[0]=x+CELL; wz[0]=z; wx[1]=x+CELL; wz[1]=z+CELL; break;
    }

    int ix = (int)(x / CELL + 0.5f);
    int iz = (int)(z / CELL + 0.5f);
    float dx = wx[1] - wx[0];
    float dz = wz[1] - wz[0];

    g_CurrentAmbientBoost = 0.0f; // 壁は底上げなし

    for (int j = 0; j < WALL_SUBDIV_V; j++)
    {
        float y0 = WALL_H * (float)j / WALL_SUBDIV_V;
        float y1 = WALL_H * (float)(j + 1) / WALL_SUBDIV_V;
        float v0 = 1.0f - (float)j / WALL_SUBDIV_V;
        float v1 = 1.0f - (float)(j + 1) / WALL_SUBDIV_V;

        for (int i = 0; i < WALL_SUBDIV_H; i++)
        {
            float t0 = (float)i / WALL_SUBDIV_H;
            float t1 = (float)(i + 1) / WALL_SUBDIV_H;

            Vec3 p0 = { wx[0] + dx * t0, y0, wz[0] + dz * t0 };
            Vec3 p1 = { wx[0] + dx * t1, y0, wz[0] + dz * t1 };
            Vec3 p2 = { wx[0] + dx * t1, y1, wz[0] + dz * t1 };
            Vec3 p3 = { wx[0] + dx * t0, y1, wz[0] + dz * t0 };

            Vec3 n0 = GetSmoothedWallNormal(dir, ix, iz, t0, y0 / WALL_H);
            Vec3 n1 = GetSmoothedWallNormal(dir, ix, iz, t1, y0 / WALL_H);
            Vec3 n2 = GetSmoothedWallNormal(dir, ix, iz, t1, y1 / WALL_H);
            Vec3 n3 = GetSmoothedWallNormal(dir, ix, iz, t0, y1 / WALL_H);

            // ★変更:シャドウ判定は頂点ごとに1回だけ行う(ピクセル毎のレイマーチはしない)
            float s0v = ComputeShadowFactor(p0.x, p0.y, p0.z);
            float s1v = ComputeShadowFactor(p1.x, p1.y, p1.z);
            float s2v = ComputeShadowFactor(p2.x, p2.y, p2.z);
            float s3v = ComputeShadowFactor(p3.x, p3.y, p3.z);

            WVtxP w0 = { p0.x, p0.y, p0.z, t0, v0, n0.x, n0.y, n0.z, s0v };
            WVtxP w1 = { p1.x, p1.y, p1.z, t1, v0, n1.x, n1.y, n1.z, s1v };
            WVtxP w2 = { p2.x, p2.y, p2.z, t1, v1, n2.x, n2.y, n2.z, s2v };
            WVtxP w3 = { p3.x, p3.y, p3.z, t0, v1, n3.x, n3.y, n3.z, s3v };

            DrawTexturedQuadWorldPhong(w0, w1, w2, w3);
        }
    }
}

// --- 床(迷路の通路セル1マス分)をフォンシェーディング付きで描画 ---
#define FLOOR_SUBDIV 1 // 1マスを縦横何分割するか(大きいほど滑らかだが重くなる)
#define FLOOR_AMBIENT_BOOST 0.2f // 床を見やすくするための底上げ

void DrawFloorCell(float x, float z)
{
    float step = CELL / FLOOR_SUBDIV;
    int ix = (int)(x / CELL + 0.5f);
    int iz = (int)(z / CELL + 0.5f);

    g_CurrentAmbientBoost = FLOOR_AMBIENT_BOOST; // 床だけ底上げ

    for (int sz = 0; sz < FLOOR_SUBDIV; sz++)
    {
        for (int sx = 0; sx < FLOOR_SUBDIV; sx++)
        {
            float x0 = x + sx * step;
            float z0 = z + sz * step;
            float x1 = x0 + step;
            float z1 = z0 + step;

            Vec3 p0 = { x0, 0.0f, z0 };
            Vec3 p1 = { x1, 0.0f, z0 };
            Vec3 p2 = { x1, 0.0f, z1 };
            Vec3 p3 = { x0, 0.0f, z1 };

            float u0 = (float)sx / FLOOR_SUBDIV;
            float v0 = (float)sz / FLOOR_SUBDIV;
            float u1 = (float)(sx + 1) / FLOOR_SUBDIV;
            float v1 = (float)(sz + 1) / FLOOR_SUBDIV;

            Vec3 n0 = GetSmoothedFloorNormal(ix, iz, u0, v0);
            Vec3 n1 = GetSmoothedFloorNormal(ix, iz, u1, v0);
            Vec3 n2 = GetSmoothedFloorNormal(ix, iz, u1, v1);
            Vec3 n3 = GetSmoothedFloorNormal(ix, iz, u0, v1);
   //n0.y*=-1;
   //n1.y*=-1;
   //n2.y*=-1;
   //n3.y*=-1;

            // ★変更:シャドウ判定は頂点ごとに1回だけ行う(ピクセル毎のレイマーチはしない)
            float s0v = ComputeShadowFactor(p0.x, p0.y, p0.z);
            float s1v = ComputeShadowFactor(p1.x, p1.y, p1.z);
            float s2v = ComputeShadowFactor(p2.x, p2.y, p2.z);
            float s3v = ComputeShadowFactor(p3.x, p3.y, p3.z);

            WVtxP w0 = { p0.x, p0.y, p0.z, u0, v0, n0.x, n0.y, n0.z, s0v };
            WVtxP w1 = { p1.x, p1.y, p1.z, u1, v0, n1.x, n1.y, n1.z, s1v };
            WVtxP w2 = { p2.x, p2.y, p2.z, u1, v1, n2.x, n2.y, n2.z, s2v };
            WVtxP w3 = { p3.x, p3.y, p3.z, u0, v1, n3.x, n3.y, n3.z, s3v };

            DrawTexturedQuadWorldPhong(w0, w3, w2, w1);
        }
    }
}

// --- 床の高速描画(レイキャスト式・1ピクセルずつ厳密計算) ---
// ↓ 迷路内の床をグーロー方式に切り替えたので現在は未使用(比較用に残してあります)
void DrawFloorFast() {
    Vec3 floorNormal = { 0.0f, 1.0f, 0.0f };
    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.height * 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;
                Vec3 worldPos = { wx, 0.0f, wz };
                float brightness = ComputeVertexBrightness(worldPos, floorNormal);
                int col = Texture[ty * TexW + tx];
                col = ShadeColor(col, brightness);
                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);

 GenerateBumpMapFromTexture(4.0f); // ★追加(強度は3.0f〜5.0f程度で調整可能)

    return true;
}
void LoadTextureImageFrom256ColorBitmap
(char* filename,
    int W_, int H_)
{
    FILE* fp = fopen(filename, "rb");
    if (!fp) return;
 TexW=W_;
 TexH=H_;
    unsigned char B, G, R, A;
    int RGBPal[256];
    fseek(fp, 0x36, SEEK_SET);
    for (int i = 0; i < 256; i++)
    {
        fread(&B, 1, 1, fp);
        fread(&G, 1, 1, fp);
        fread(&R, 1, 1, fp);
        fread(&A, 1, 1, fp);
        RGBPal[i] = RGB(B, G, R);
    }
    fseek(fp, 0x436, SEEK_SET);
    int pitch = (W_ + 3) & ~3;
    unsigned char palnum;
    for (int y = 0; y < H_; y++)
    {
        for (int x = 0; x < W_; x++)
        {
            fread(&palnum, 1, 1, fp);
            Texture[(H_ - 1 - y) * W_ + x] = RGBPal[palnum];
        }
        fseek(fp, pitch - W_, SEEK_CUR);
    }
    fclose(fp);
}
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 HorizonLine(int x1,int x2,int Nowy,int c1,int c2)
{
 int r1=(c1>>16)&0xFF;
 int g1=(c1>>8)&0xFF;
 int b1=(c1)&0xFF;
 int r2=(c2>>16)&0xFF;
 int g2=(c2>>8)&0xFF;
 int b2=(c2)&0xFF;
 for(int x=x1;x<x2;x++)
 {
  int t=((x-x1)<<5)/(x2-x1);
  int r=(r1*((1<<5)-t)+r2*t)>>5;
  int g=(g1*((1<<5)-t)+g2*t)>>5;
  int b=(b1*((1<<5)-t)+b2*t)>>5;
  PSET(x,Nowy,GetColor(r,g,b));
 }
}
const float SCALE = 200;
void LoadOBJFile02(char filename[10000], HWND hwnd)
{
    FILE *fp = fopen(filename, "r");
    if(fp == NULL) {
        MessageBox(NULL, "ファイルがありません。", "OK?", MB_OK);
        PostQuitMessage(0);
        return;
    }
    char line[256];
    int vtxCount = 0, faceCount = 0;
    while (fgets(line, sizeof(line), fp)) {
        if (strncmp(line, "v ", 2) == 0) {
            double x, y, z;
            sscanf(line, "v %lf %lf %lf", &x, &y, &z);
            SetV(vtxCount, x*SCALE, y*SCALE, z*SCALE);
            ++vtxCount;
            MAXVTX = vtxCount;
        }
        else if (strncmp(line, "f ", 2) == 0) {
            int v[4], vt[4], vn[4];
            int matched = sscanf(line,
                "f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d",
                &v[0], &vt[0], &vn[0],
                &v[1], &vt[1], &vn[1],
                &v[2], &vt[2], &vn[2],
                &v[3], &vt[3], &vn[3]
            );
            if (matched == 12) {
                Set4Poly(faceCount, v[0]-1, v[1]-1, v[2]-1, v[3]-1);
                ++faceCount;
                MAXPOL = faceCount;
            } else if(matched==4) {
                int v1, v2, v3, v4;
                if (sscanf(line, "f %d %d %d %d", &v1, &v2, &v3, &v4) == 4) {
                    Set4Poly(faceCount, v1-1, v2-1, v3-1, v4-1);
                    ++faceCount;
                    MAXPOL = faceCount;
                }
            }else if(matched==3)
   {
    int v1, v2, v3, v4;
                if (sscanf(line, "f %d %d %d", &v1, &v2, &v3) == 3) {
                    Set3Poly(faceCount, v1-1, v2-1, v3-1);
                    ++faceCount;
                    MAXPOL = faceCount;
                }
   }
        }
    }
    fclose(fp);
}
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;
    }
}
// ==================================================
// マルチスレッドラスタライズ用ワーカースレッドプール
// ・毎フレームCreateThreadすると生成コストが無視できないので、起動時に1回だけスレッドを作り、
// 以降は「Windowsのイベントで起こす→処理する→終わったことを知らせる」を繰り返す。
// ・ワーカースレッドの数はCPUのコア数から決める(メインスレッド分は1つ引く)。
// ==================================================
#define MAX_RASTER_THREADS 7 // 8コア機まで想定(それ以上は自動的に丸められる)

struct RasterJob { int yStart, yEnd; };

int g_NumRasterThreads = 0;
HANDLE g_RasterThreadHandles[MAX_RASTER_THREADS];
HANDLE g_RasterStartEvents[MAX_RASTER_THREADS]; // メイン→ワーカー:「このバンドを処理して」
HANDLE g_RasterDoneEvents[MAX_RASTER_THREADS]; // ワーカー→メイン:「終わった」
RasterJob g_RasterJobs[MAX_RASTER_THREADS];
volatile bool g_RasterThreadsShouldExit = false;

DWORD WINAPI RasterThreadProc(LPVOID param)
{
    int idx = (int)(intptr_t)param;
    while (true)
    {
        WaitForSingleObject(g_RasterStartEvents[idx], INFINITE);
        if (g_RasterThreadsShouldExit) break;
        RasterizeBand(g_RasterJobs[idx].yStart, g_RasterJobs[idx].yEnd);
        SetEvent(g_RasterDoneEvents[idx]);
    }
    return 0;
}

// 起動時に1回だけ呼ぶ:CPUのコア数を見てワーカースレッドを作る
void InitRasterThreads()
{
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    int cores = (int)si.dwNumberOfProcessors;
    if (cores < 1) cores = 1;

    // メインスレッドも1バンド担当するので、ワーカーは (コア数-1) 個で十分
    g_NumRasterThreads = cores - 1;
    if (g_NumRasterThreads < 0) g_NumRasterThreads = 0;
    if (g_NumRasterThreads > MAX_RASTER_THREADS) g_NumRasterThreads = MAX_RASTER_THREADS;

    for (int i = 0; i < g_NumRasterThreads; i++)
    {
        g_RasterStartEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
        g_RasterDoneEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
        g_RasterThreadHandles[i] = CreateThread(NULL, 0, RasterThreadProc, (LPVOID)(intptr_t)i, 0, NULL);
    }
}

void ShutdownRasterThreads()
{
    g_RasterThreadsShouldExit = true;
    for (int i = 0; i < g_NumRasterThreads; i++)
        SetEvent(g_RasterStartEvents[i]);
    if (g_NumRasterThreads > 0)
        WaitForMultipleObjects(g_NumRasterThreads, g_RasterThreadHandles, TRUE, 2000);
    for (int i = 0; i < g_NumRasterThreads; i++)
    {
        CloseHandle(g_RasterThreadHandles[i]);
        CloseHandle(g_RasterStartEvents[i]);
        CloseHandle(g_RasterDoneEvents[i]);
    }
}

// 毎フレーム呼ぶ:画面をワーカー数+1(メイン分)のバンドに分割し、
// ワーカーには先に仕事を渡して起こし、残り1バンドはメインスレッド自身が処理する。
// 全ワーカーの完了を待ってから戻る。
void RenderFrameMultiThreaded()
{
    int totalBands = g_NumRasterThreads + 1;
    int bandH = HEIGHT / totalBands;
    if (bandH < 1) bandH = 1;

    for (int i = 0; i < g_NumRasterThreads; i++)
    {
        g_RasterJobs[i].yStart = i * bandH;
        g_RasterJobs[i].yEnd = (i + 1) * bandH;
        SetEvent(g_RasterStartEvents[i]);
    }

    // 残り(端数含む)はメインスレッドが担当
    int mainStart = g_NumRasterThreads * bandH;
    int mainEnd = HEIGHT;
    RasterizeBand(mainStart, mainEnd);

    if (g_NumRasterThreads > 0)
        WaitForMultipleObjects(g_NumRasterThreads, g_RasterDoneEvents, TRUE, INFINITE);
}

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    ChangeWindowMode(TRUE);
    // ★変更:内部解像度が320x240から640x480になったため、
    // 従来の「320x240を2倍に引き伸ばして640x480表示」用の設定(*2倍とSetEmulation320x240)は不要になった。
    // 640x480をそのままウィンドウ解像度として使う。
    SetGraphMode(WIDTH, HEIGHT, 32);
 SetMainWindowText("PolygonSample2025lastVer") ;
 if (DxLib_Init() < 0) return -1;
    SetDrawScreen(DX_SCREEN_BACK);
    soft = MakeARGB8ColorSoftImage(WIDTH, HEIGHT);
    // ★追加:PSETで直接書き込むための生バッファアドレスとピッチを取得しておく
    g_FrameBufferAddr = (unsigned char*)GetImageAddressSoftImage(soft);
    g_FrameBufferPitch = GetPitchSoftImage(soft);
    // グラフィックハンドルの毎フレーム生成・破棄はオーバーヘッドが大きいため、事前に1つ作成して使い回す
    int graph = CreateGraphFromSoftImage(soft);
    // ★追加:ラスタライズ用ワーカースレッドプールを起動時に1回だけ作る
    InitRasterThreads();
    int x = 0;
    int y = 120;
 int PlayerX = 0 ;
 int PlayerY = 0 ;
 int Key;
Load24BitBmpToTexture("wall02.bmp",1024/4,1024/4);
 srand(GetTickCount());
 NormalizeLightDir();
 g_FogColor = GetColor(0, 200, 255); // 毎フレームのFillSoftImage(soft,0,200,255,255)と同じ空の色に合わせる
 cam.height = 0.5f;
int startX = 1;
int startZ = 1;
int goalX = W-2;
int goalZ = H-2;
    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.6f;
    cam.yaw = 0.0f;
    cam.height = 0.5f;
    while (ProcessMessage() == 0 && CheckHitKey( KEY_INPUT_ESCAPE ) == 0)
    {
        ClearDrawScreen();
  FillSoftImage(soft,0,200,255,255);

        memset(ZBuf, 0, sizeof(ZBuf));
        UpdateCameraWithCollision(cam);
        UpdateCamera(cam);

        // ★変更:ここではDrawWorld()/DrawMaze()は「三角形リストを作るだけ」(軽い・シングルスレッド)
        g_TriCount = 0;
        DrawWorld();
        DrawMaze();
        // ★追加:記録した三角形リストを、画面を分割して複数スレッドで並列にラスタライズする
        RenderFrameMultiThreaded();

        // 既存ハンドルを再利用して転送(毎フレームの CreateGraph/DeleteGraph を廃止して高速化)
        ReCreateGraphFromSoftImage(soft, graph);
        DrawGraph(0, 0, graph, FALSE);

        ScreenFlip();
        WaitTimer(1000/60);
    }
    // ★追加:ワーカースレッドを止めて後始末する
    ShutdownRasterThreads();
    DeleteGraph(graph);
    DeleteSoftImage(soft);
    DxLib_End();
    return 0;
}
640x480_260905-1730.jpg
画面が細かくなりました。これにより、よりリアルな3DCGを描けます。
また、マルチスレッドや高度な高速化技術を使いましたので古い2015年以前のCPUだと対応しておらず実行やコンパイルが出来ない可能性があります。


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