cとWINAPIで3DCGの理屈その3

記事
IT・テクノロジー
あと、残り少なくなってきた記事ですが、
テクスチャーマッピングについてとりあげます。
のちに、フラットテスクチャ―、グーローテクスチャーとフォンテクスチャーも取り上げます。残る問題は、古典的なところでは、白黒で凹凸を表現する
バンプマッピングですね。他には、フォグや、モーションブラーも行きたい
ですが、そこまで技術力は沸いてこないと思います。


まずは歪んだテクスチャーマッピングから解説します。
int Texture[32][32];という2次元配列によって
uとvという0~31までの画像を参照する座標が
設定できるようになります。
この解説において
テクスチャーデータに格納するために適しているのは何と言っても
bmp形式です。フォーマットが決まっているからというのもあります。

これは、ラッピングによるand演算により繰り返し
が出来る為に、32とか、256などの2のNの累乗が
選ばれる理由です。

wall02.png
こんな画像を自分が用意しました。
UV.png
とここで、UとVを0~1.0に収まるように正規化しておきます。

そしたら、実際の描画処理に移るわけですが、

毎回これを行います。

1.座標バッファ初期化
for(int y=0;y<height;y++)
{
     Lbuf[y]=9000;
     Rbuf[y]=-9000;
}
2.ポリゴンの3つの頂点を線形補間
して、例えば、頂点がAx,Ay,Bx,By,Cx,Cy
とAU,AV,BU,BV,CU,CV,heightとあったら、
以下のLineを

Line(Ax,Ay,AU,AV,Bx,By,BU,BV,height);
Line(Bx,By,BU,BV,Cx,Cy,CU,CV,height);
Line(Cx,Cy,CU,CV,Ax,Ay,AU,AV,height);
と呼び出します。

void Line(int Ax,int Ay,int AU,int AV,int Bx,int By,int BU,int BV,int height)
{
      for(double i=0;i<=1.000f;i+=0.001f)
      {
            int x=Ax*(1-i)+Bx*i;
            int y=Ay*(1-i)+By*i;
            if(y>=height || y<0) continue;

            int u=AU*(1-i)+BU*i;
            int v=AV*(1-i)+BV*i;

            if(x>Rbuf[y]){
                  Rbuf[y]=x;
                  RU[y]=u;
                  RV[y]=v;
             }
            if(x<Lbuf[y])
            {
                  Lbuf[y]=x;     
                  LU[y]=u;
                  LV[y]=v;            
            }
      }
}
3.テクスチャーポリゴンを描画する
for(int y=0;y<height;y++)
{
     if(Lbuf[y]>Rbuf[y])
          //そのままならスキップ
          continue;
     if(Rbuf[y]-Lbuf[y]==0) continue;

     for(int x=Lbuf[y];x<=Rbuf[y];x++)
     {
          int t=(x-Lbuf[y])*256/(Rbuf[y]-Lbuf[y]);
          int u=(LU[y]*(256-t)+RU[y]*t)>>8;
          int v=(LV[y]*(256-t)+RV[y]*t)>>8;          
          int col=tex[(int)u][(int)v];
          PSET(x,y,col);
     }
}

具体的には以下のようにします。
int Lbuf[HEIGHT];
int Rbuf[HEIGHT];

void Line_(int Ax, int Ay, float AU, float AV, float AZ,
           int Bx, int By, float BU, float BV, float BZ)
{
    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; // ← 深度(invZ)も線形補間するだけ

        if (x > Rbuf[y]) {
            Rbuf[y] = x;
            RU[y] = u;
            RV[y] = v;
            RW[y] = z; // ← 深度も保持
        }
        if (x < Lbuf[y])
        {
            Lbuf[y] = x;
            LU[y] = u;
            LV[y] = v;
            LW[y] = z; // ← 深度も保持
        }
    }
}

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

    // n.z には TransformAndProject で invZ が入っている
    Line_(n1.x, n1.y, n1.u, n1.v, n1.z, n2.x, n2.y, n2.u, n2.v, n2.z);
    Line_(n2.x, n2.y, n2.u, n2.v, n2.z, n3.x, n3.y, n3.u, n3.v, n3.z);
    Line_(n3.x, n3.y, n3.u, n3.v, n3.z, n1.x, n1.y, n1.u, n1.v, n1.z);

    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;

            // ← アフィン補間のみ。z で割り戻さない
            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; // invZ

            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];
                PSET(x, y, col);
            }
        }
    }
}

20260901-0853.jpg
こんなテクスチャーマップなんて嫌ですよね。

では次に、遠近補正あり、テクスチャーマップを見ていきます。

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 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; // 【修正1】 >= から > へ

    float span = (float)(R[y] - L[y]);
    if (span <= 0.0f) span = 1.0f; // 【修正2】 0除算防止

    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]) { // 【修正3】 w(=1/z) が手前にある場合
            ZBuf[y][x] = w;
            float z = 1.0f / w; // 実際の深度 Z
            // 【修正4】 u, v が (u/z), (v/z) として補間されている場合:
            int tx = (int)(u * z * (TexW - 1)) & (TexW - 1);
            int ty = (int)(v * z * (TexH - 1)) & (TexH - 1);

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

enkinhosei260901-1542.jpg
見事に歪まずにテクスチャーが貼られている事が分かります。

これに影を付けたものが、フラットテクスチャーマッピングです。
以下、WINAPIも使うかもしれませんが、DXLIBで記述します。

#include"DXLib.h"
#include<math.h>
#include<stdlib.h>
#include<windows.h>
#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;

// ==================================================
// ライティング関連(追加分)
// フラットシェーディング:1ポリゴンにつき1つの明るさを
// 法線と光源方向の内積から計算し、テクスチャ色に掛ける
// ==================================================
Vec3 g_LightDir = { -0.4f, -1.0f, -0.3f }; // 光が「進んでくる」方向。後で正規化する
float g_Ambient = 0.35f; // 環境光(完全な暗闇にならないための下限)
float g_FaceBrightness = 1.0f; // 現在描画中の面の明るさ(0.0〜1.0)。TexTriを呼ぶ前にセットする

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;
    }
}

// 法線ベクトルを受け取り、その面の明るさ(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;
    }
    // 面が光源の方向を向いているほど明るい(ランバート反射の簡易版)
    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;
}

// テクスチャ色(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);
}
// ==================================================

int L[HEIGHT];
int R[HEIGHT];
int LC[HEIGHT];
int RC[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 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, 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;
// 2点を結ぶエッジの情報を保持する構造体

struct Edge {
    float x, invZ, uz, vz;
};
VTX v[MAX_VTX]={0};
int PolygonNumber[MAX_POL][4]={0};


void TexTri_Complex(VTX,VTX,VTX); // 使いたい方をここで選択
void TexTri_Simple(VTX,VTX,VTX); // 使いたい方をここで選択

#define TexTri TexTri_Complex // 使いたい方をここで選択

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;
 //PolygonNumber[num][3]=n4;
}
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;
}
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 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, 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;
}
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, v1p)) return false;
    if (!TransformAndProject(x2,y2,z2, u2,v2, v2p)) return false;
    if (!TransformAndProject(x3,y3,z3, u3,v3, 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;
    // カメラ方向ベクトル(XZ平面)
    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 };
    g_FaceBrightness = ComputeBrightness(billboardNormal);

    VTX v[4];
    bool ok = true;
    // 左下
    ok &= TransformAndProject(
        x - rx * halfW, 0.0f, z - rz * halfW,
        0.0f, 1.0f,
        v[0]
    );
    // 右下
    ok &= TransformAndProject(
        x + rx * halfW, 0.0f, z + rz * halfW,
        1.0f, 1.0f,
        v[1]
    );
    // 右上
    ok &= TransformAndProject(
        x + rx * halfW, h, z + rz * halfW,
        1.0f, 0.0f,
        v[2]
    );
    // 左上
    ok &= TransformAndProject(
        x - rx * halfW, h, z - rz * halfW,
        0.0f, 0.0f,
        v[3]
    );
    if (!ok) return;
    TexTri(v[0], v[1], v[2]);
    TexTri(v[0], v[2], v[3]);
}
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)
{
 int r,g,b,a;
 r=(c>>16)&0xFF;
 g=(c>>8)&0xFF;
 b=c&0xFF;
 a=255;
 DrawPixelSoftImage(soft, x, y, r, g, b, 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,
          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; }
    }
}

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

void Line_(int Ax, int Ay, float AU, float AV, float AZ,
           int Bx, int By, float BU, float BV, float BZ)
{
    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; // ← 深度(invZ)も線形補間するだけ

        if (x > Rbuf[y]) {
            Rbuf[y] = x;
            RU[y] = u;
            RV[y] = v;
            RW[y] = z; // ← 深度も保持
        }
        if (x < Lbuf[y])
        {
            Lbuf[y] = x;
            LU[y] = u;
            LV[y] = v;
            LW[y] = z; // ← 深度も保持
        }
    }
}

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

    // n.z には TransformAndProject で invZ が入っている
    Line_(n1.x, n1.y, n1.u, n1.v, n1.z, n2.x, n2.y, n2.u, n2.v, n2.z);
    Line_(n2.x, n2.y, n2.u, n2.v, n2.z, n3.x, n3.y, n3.u, n3.v, n3.z);
    Line_(n3.x, n3.y, n3.u, n3.v, n3.z, n1.x, n1.y, n1.u, n1.v, n1.z);

    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;

            // ← アフィン補間のみ。z で割り戻さない
            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; // invZ

            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, g_FaceBrightness); // ← ライティング適用
                PSET(x, y, col);
            }
        }
    }
}

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; // 【修正1】 >= から > へ

    float span = (float)(R[y] - L[y]);
    if (span <= 0.0f) span = 1.0f; // 【修正2】 0除算防止

    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]) { // 【修正3】 w(=1/z) が手前にある場合
            ZBuf[y][x] = w;
            float z = 1.0f / w; // 実際の深度 Z
            // 【修正4】 u, v が (u/z), (v/z) として補間されている場合:
            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;
 }
 }
}

// --- 壁の描画 ---
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;
    }
    VTX v[4];
    if (!TransformAndProject(wx[0], 0, wz[0], 0, 1, v[0])) return;
    if (!TransformAndProject(wx[1], 0, wz[1], 1, 1, v[1])) return;
    if (!TransformAndProject(wx[1], WALL_H, wz[1], 1, 0, v[2])) return;
    if (!TransformAndProject(wx[0], WALL_H, wz[0], 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 DrawFloorFast() {
    // 床の法線は常に真上向き。1フレームにつき1回だけ明るさを計算すればよい
    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.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;
                int col = Texture[ty * TexW + tx];
                col = ShadeColor(col, g_FaceBrightness); // ← ライティング適用
                //DrawPixelSoftImage(soft, x, y, (col>>16)&0xFF, (col>>8)&0xFF, col&0xFF, 255);
    PSET(x,y,col);
            }
        }
    }
}
bool Load24BitBmpToTexture(const char* fileName, int maxW, int maxH)
{
    // ソフトイメージとして読み込み (24bit/32bit両対応)
    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);
            // 0xRRGGBB 形式で格納 (必要に応じて 0xAARRGGBB に変更可能)
            Texture[y * maxW + x] = GetColor(r,g,b);
        }
    }
    // 使い終わったソフトイメージを解放
    DeleteSoftImage(siHandle);
    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));
  //DrawPixel(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;
        }
        // 四角形の面(f v/vt/vn 形式)
        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) {
                // fだけど / がない形式 (例: f 1 2 3 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;
    }
    // 簡易当たり判定:移動後の座標が壁(1)なら戻す
    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);
    // ----------------------------
    // SoftImage と Graph を作成
    // ----------------------------
    soft = MakeARGB8ColorSoftImage(WIDTH, HEIGHT);
 // ----------------------------
 // softImageは、グローバル変数(色々と面倒なので・・・)
 //-----------------------------
    int graph = CreateGraphFromSoftImage(soft);
    int x = 0;
    int y = 120;
 int PlayerX = 0 ;
 int PlayerY = 0 ;
 int Key;
 //W=WIDTH;
 //H=HEIGHT;
 //LoadTextureImageFrom256ColorBitmap("wall02.bmp",1024,1024);
Load24BitBmpToTexture("wall02.bmp",1024/4,1024/4);
 srand(GetTickCount());
 NormalizeLightDir(); // ← 光源方向を正規化(追加)
 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; // スタート地点を確実に通路に
    // カメラ(プレイヤー)を迷路の通路(1,1)の中心に配置
    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();
        // ----------------------------
        // SoftImage をクリア(黒・完全不透明)
        // ----------------------------
        // 方法2:ClearSoftImage が使えるならこれ
  // ----------------------------
        // 描画
        // ----------------------------
  FillSoftImage(soft,0,0,0,255); // ① ソフト画面を消す
  memset(ZBuf, 0, sizeof(ZBuf));
        UpdateCameraWithCollision(cam);
  UpdateCamera(cam); // ② カメラ更新
  DrawWorld(); // ③ TexTri → PSET で soft に描画
  //DrawFloor(20.0f); // ← 最初に床
  DrawFloorFast();
  DrawMaze();
//TexTri(n[0],n[1],n[2]);
//TexTri(n[0],n[2],n[3]);
// ④ soft → graph 反映
graph = CreateGraphFromSoftImage(soft);
//UpdateGraphFromSoftImage(soft, graph);
// ⑤ graph を画面に出す
DrawGraph(0, 0, graph, FALSE);
//DrawMaze();
ScreenFlip();
        // ----------------------------
        // 60FPS制御
        // ----------------------------
        WaitTimer(1000/60);
    }
    // 後始末
    DeleteGraph(graph);
    DeleteSoftImage(soft);
    DxLib_End();
    return 0;
}
img260901-1558.jpg




次にグーローテクスチャーマッピングについてです。

原理そのものはフラットシェーディングからグーローシェーディングに
しつつ、テクスチャーを貼ってみた感じです。
いきなり、ソースですが、大丈夫ですか?

#include"DXLib.h"
#include<math.h>
#include<stdlib.h>
#include<windows.h>
#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;
    float b; // ← 追加:頂点の明るさ(グーローシェーディング用)
};
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;

// ==================================================
// ライティング関連
// ・平行光源+環境光で「面の向き」による明るさのベースを作る
// ・さらにプレイヤー位置を松明(点光源)とみなし、距離減衰を加算する
// → 同じ壁の中でも頂点ごとに明るさが変わるので、グーローシェーディングで
// 補間する意味が出てくる(平行光源だけだと壁1枚は全頂点同じ明るさになるため)
// ==================================================
//Vec3 g_LightDir = { -0.4f, -1.0f, -0.3f }; // 光が「進んでくる」方向。後で正規化する
//float g_Ambient = 0.35f; // 環境光(完全な暗闇にならないための下限)
//float g_TorchIntensity = 2.2f; // 松明(プレイヤー携帯の点光源)の強さ
//float g_TorchFalloff = 1.8f; // 松明の距離減衰の強さ(大きいほど早く暗くなる)

// DrawFloorCell 内の頂点y座標を正しく渡す(床は y = 0.0f でOKですが、明るさの初期調整を行う)
// 調整例:環境光を下げて、減衰をはっきりさせる
Vec3 g_LightDir = { -0.4f, -1.0f, -0.3f };
float g_Ambient = 0.1f; // ← 0.35f から 0.1f 等に下げてベースを暗くする
float g_TorchIntensity = 1.0f; // ← 2.2f から 1.0f に下げる
float g_TorchFalloff = 3.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;
    }
}

// 法線ベクトルだけから明るさ(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;
    }
    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;
}

// 頂点のワールド座標+法線から、その頂点1点の明るさを計算する
// (平行光源のベース + プレイヤー位置=松明からの距離減衰)
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;
}

// テクスチャ色(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);
}
// ==================================================

int L[HEIGHT];
int R[HEIGHT];
int LC[HEIGHT];
int RC[HEIGHT];
float LU[HEIGHT];
float LV[HEIGHT];
float RU[HEIGHT];
float RV[HEIGHT];
float LW[HEIGHT];
float RW[HEIGHT];
float LB[HEIGHT]; // ← 追加:左エッジの明るさ
float RB[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 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;
// 2点を結ぶエッジの情報を保持する構造体

struct Edge {
    float x, invZ, uz, vz;
};
VTX v[MAX_VTX]={0};
int PolygonNumber[MAX_POL][4]={0};


void TexTri_Complex(VTX,VTX,VTX); // 使いたい方をここで選択
void TexTri_Simple(VTX,VTX,VTX); // 使いたい方をここで選択

#define TexTri TexTri_Complex // 使いたい方をここで選択

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;
 //PolygonNumber[num][3]=n4;
}
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;
}
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;
        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; // ← u,vと同じ考え方で、明るさもinvZを掛けておく(パースペクティブ補正のため)
    return true;
}
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;
    // カメラ方向ベクトル(XZ平面)
    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 }; // 左上

    // 頂点ごとに明るさを計算(グーローシェーディング)
    float b0 = ComputeVertexBrightness(p0, billboardNormal);
    float b1v = ComputeVertexBrightness(p1, billboardNormal);
    float b2 = ComputeVertexBrightness(p2, billboardNormal);
    float b3 = ComputeVertexBrightness(p3, billboardNormal);

    VTX v[4];
    bool ok = true;
    ok &= TransformAndProject(p0.x, p0.y, p0.z, 0.0f, 1.0f, b0, v[0]);
    ok &= TransformAndProject(p1.x, p1.y, p1.z, 1.0f, 1.0f, b1v, v[1]);
    ok &= TransformAndProject(p2.x, p2.y, p2.z, 1.0f, 0.0f, b2, v[2]);
    ok &= TransformAndProject(p3.x, p3.y, p3.z, 0.0f, 0.0f, b3, v[3]);
    if (!ok) return;
    TexTri(v[0], v[1], v[2]);
    TexTri(v[0], v[2], v[3]);
}
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)
{
 int r,g,b,a;
 r=(c>>16)&0xFF;
 g=(c>>8)&0xFF;
 b=c&0xFF;
 a=255;
 DrawPixelSoftImage(soft, x, y, r, g, b, 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; }
    }
}

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; // ← 深度(invZ)も線形補間するだけ
        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;
    }

    // n.z には TransformAndProject で invZ が入っている
    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;

            // ← アフィン補間のみ。z で割り戻さない(uvと同じ扱い)
            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; // invZ
            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) {
    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; // 【修正1】 >= から > へ

    float span = (float)(R[y] - L[y]);
    if (span <= 0.0f) span = 1.0f; // 【修正2】 0除算防止

    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]) { // 【修正3】 w(=1/z) が手前にある場合
            ZBuf[y][x] = w;
            float z = 1.0f / w; // 実際の深度 Z
            // 【修正4】 u, v が (u/z), (v/z) として補間されている場合:
            int tx = (int)(u * z * (TexW - 1)) & (TexW - 1);
            int ty = (int)(v * z * (TexH - 1)) & (TexH - 1);
            // TexTri_Complex 内の修正箇所
 float brightness = b * z;
 if (brightness > 1.0f) brightness = 1.0f;
 if (brightness < 0.0f) brightness = 0.0f;

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

// --- 壁の描画 ---
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;
    }

    // 壁の法線(4頂点とも同じ向き)
    Vec3 wallNormal = GetWallNormal(dir);

    // 4頂点のワールド座標
    Vec3 p0 = { wx[0], 0.0f, wz[0] };
    Vec3 p1 = { wx[1], 0.0f, wz[1] };
    Vec3 p2 = { wx[1], WALL_H, wz[1] };
    Vec3 p3 = { wx[0], WALL_H, wz[0] };

    // 頂点ごとに明るさを計算する(同じ壁でもプレイヤーからの距離が頂点ごとに違うので
    // 松明の距離減衰分だけ明るさが変わる → これをTexTri内で補間するとグーローシェーディングになる)
    float b0 = ComputeVertexBrightness(p0, wallNormal);
    float b1 = ComputeVertexBrightness(p1, wallNormal);
    float b2 = ComputeVertexBrightness(p2, wallNormal);
    float b3 = ComputeVertexBrightness(p3, wallNormal);

    VTX v[4];
    if (!TransformAndProject(wx[0], 0, wz[0], 0, 1, b0, v[0])) return;
    if (!TransformAndProject(wx[1], 0, wz[1], 1, 1, b1, v[1])) return;
    if (!TransformAndProject(wx[1], WALL_H, wz[1], 1, 0, b2, v[2])) return;
    if (!TransformAndProject(wx[0], WALL_H, wz[0], 0, 0, b3, v[3])) return;

    TexTri(v[0], v[1], v[2]);
    TexTri(v[0], v[2], v[3]);
}
// --- 床(迷路の通路セル1マス分)をグーローシェーディング付きで描画 ---
// 1マスをそのまま1枚の四角形(三角形2枚)にすると、三角形の対角線の継ぎ目で
// 明るさの変化がカクッと折れて見える(画像で見えた斜めの筋の正体)。
// そこで1マスをさらに細かく分割し、それぞれで明るさを計算することで
// なめらかなグラデーションに近づける。
#define FLOOR_SUBDIV 4 // 1マスを縦横何分割するか(大きいほど滑らかだが重くなる)

void DrawFloorCell(float x, float z)
{
    Vec3 floorNormal = { 0.0f, 1.0f, 0.0f }; // 床の法線(上向き)
    float step = CELL / FLOOR_SUBDIV;

    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;

            // ワールド座標(床の高さは Y = 0.0f)
            Vec3 p0 = { x0, 0.0f, z0 }; // 北西
            Vec3 p1 = { x1, 0.0f, z0 }; // 北東
            Vec3 p2 = { x1, 0.0f, z1 }; // 南東
            Vec3 p3 = { x0, 0.0f, z1 }; // 南西

            // 頂点ごとの明るさ計算
            // (床が見えやすいよう、明るさに若干のベース底上げ 0.2f を加算)
            float b0 = ComputeVertexBrightness(p0, floorNormal) + 0.2f;
            float b1 = ComputeVertexBrightness(p1, floorNormal) + 0.2f;
            float b2 = ComputeVertexBrightness(p2, floorNormal) + 0.2f;
            float b3 = ComputeVertexBrightness(p3, floorNormal) + 0.2f;

            // 1.0f を超えないようクランプ
            if (b0 > 1.0f) b0 = 1.0f;
            if (b1 > 1.0f) b1 = 1.0f;
            if (b2 > 1.0f) b2 = 1.0f;
            if (b3 > 1.0f) b3 = 1.0f;

            // UV座標(セル内で 0.0 〜 1.0 に収める)
            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;

            VTX v[4];
            // 座標変換&スクリーン投影
            if (!TransformAndProject(p0.x, p0.y, p0.z, u0, v0, b0, v[0])) continue;
            if (!TransformAndProject(p1.x, p1.y, p1.z, u1, v0, b1, v[1])) continue;
            if (!TransformAndProject(p2.x, p2.y, p2.z, u1, v1, b2, v[2])) continue;
            if (!TransformAndProject(p3.x, p3.y, p3.z, u0, v1, b3, v[3])) continue;

            // ポリゴン描画(2枚の三角形)
            TexTri(v[0], v[1], v[2]);
            TexTri(v[0], v[2], v[3]);
        }
    }
}



// --- 床の高速描画(レイキャスト式・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;
                // 床は元々1ピクセルずつワールド座標を計算しているので、
                // ここでは頂点補間ではなく直接その場所の明るさを計算する(=グーローより高精度)
                Vec3 worldPos = { wx, 0.0f, wz };
                float brightness = ComputeVertexBrightness(worldPos, floorNormal);
                int col = Texture[ty * TexW + tx];
                col = ShadeColor(col, brightness);
                //DrawPixelSoftImage(soft, x, y, (col>>16)&0xFF, (col>>8)&0xFF, col&0xFF, 255);
    PSET(x,y,col);
            }
        }
    }
}
bool Load24BitBmpToTexture(const char* fileName, int maxW, int maxH)
{
    // ソフトイメージとして読み込み (24bit/32bit両対応)
    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);
            // 0xRRGGBB 形式で格納 (必要に応じて 0xAARRGGBB に変更可能)
            Texture[y * maxW + x] = GetColor(r,g,b);
        }
    }
    // 使い終わったソフトイメージを解放
    DeleteSoftImage(siHandle);
    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));
  //DrawPixel(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;
        }
        // 四角形の面(f v/vt/vn 形式)
        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) {
                // fだけど / がない形式 (例: f 1 2 3 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;
    }
    // 簡易当たり判定:移動後の座標が壁(1)なら戻す
    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);
    // ----------------------------
    // SoftImage と Graph を作成
    // ----------------------------
    soft = MakeARGB8ColorSoftImage(WIDTH, HEIGHT);
 // ----------------------------
 // softImageは、グローバル変数(色々と面倒なので・・・)
 //-----------------------------
    int graph = CreateGraphFromSoftImage(soft);
    int x = 0;
    int y = 120;
 int PlayerX = 0 ;
 int PlayerY = 0 ;
 int Key;
 //W=WIDTH;
 //H=HEIGHT;
 //LoadTextureImageFrom256ColorBitmap("wall02.bmp",1024,1024);
Load24BitBmpToTexture("wall02.bmp",1024/4,1024/4);
 srand(GetTickCount());
 NormalizeLightDir(); // ← 光源方向を正規化
 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; // スタート地点を確実に通路に
    // カメラ(プレイヤー)を迷路の通路(1,1)の中心に配置
    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();
        // ----------------------------
        // SoftImage をクリア(黒・完全不透明)
        // ----------------------------
        // 方法2:ClearSoftImage が使えるならこれ
  // ----------------------------
        // 描画
        // ----------------------------
  FillSoftImage(soft,0,0,0,255); // ① ソフト画面を消す
  memset(ZBuf, 0, sizeof(ZBuf));
        UpdateCameraWithCollision(cam);
  UpdateCamera(cam); // ② カメラ更新
  DrawWorld(); // ③ TexTri → PSET で soft に描画
  //DrawFloor(20.0f); // ← 最初に床
  //DrawFloorFast(); // ← レイキャスト式の全画面床描画は廃止。DrawMaze内でセルごとに床を描くように変更
  DrawMaze(); // 床(DrawFloorCell)と壁(DrawWall)をまとめて描画
//TexTri(n[0],n[1],n[2]);
//TexTri(n[0],n[2],n[3]);
// ④ soft → graph 反映
graph = CreateGraphFromSoftImage(soft);
//UpdateGraphFromSoftImage(soft, graph);
// ⑤ graph を画面に出す
DrawGraph(0, 0, graph, FALSE);
//DrawMaze();
ScreenFlip();
        // ----------------------------
        // 60FPS制御
        // ----------------------------
        WaitTimer(1000/60);
    }
    // 後始末
    DeleteGraph(graph);
    DeleteSoftImage(soft);
    DxLib_End();
    return 0;
}

image260901-1804.jpg




そろそろこの記事も重くなってきたので、この記事の後ろに
フォンテクスチャーマッピングの画像だけ載せておきます。

IMAGE-260901-1822.jpg



当ブログを見ていただきありがとうございました。
次の3DCGの技術の記事も楽しみにしててください。

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