影(シャドウ)を加えて見ました。
#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; // 旧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_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);
}
// ==================================================
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]; // 旧Gouraud用(互換のため残置)
float RB[HEIGHT]; // 旧Gouraud用(互換のため残置)
// フォンシェーディング用:法線成分(パースペクティブ補正込み)のエッジ補間バッファ
float LNX[HEIGHT], LNY[HEIGHT], LNZ[HEIGHT];
float RNX[HEIGHT], RNY[HEIGHT], RNZ[HEIGHT];
int soft;
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);
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;
}
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;
return true;
}
// ==================================================
// フォンシェーディング用パイプライン
// ・頂点では「法線」と「UV」だけを渡す(明るさは計算しない)
// ・スキャンライン内で法線を補間 → 1ピクセルごとに正規化してライティング計算する
// ・カメラからの距離^2は、スクリーン座標とinvZから view空間位置を復元して求める
// (view空間はカメラ原点からの回転移動なので、距離^2はワールド座標での距離^2と一致する)
// ==================================================
struct VTXP { float x, y, z, u, v, nx, ny, nz; };
bool TransformAndProjectPhong(float wx, float wy, float wz, float u, float v, Vec3 normal, 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;
return true;
}
// ワールド座標(四角形の4隅)+UV+法線を持つクリッピング用頂点
struct WVtxP { float wx, wy, wz, u, v, nx, ny, nz; };
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;
out[outCount++] = mid;
}
}
return outCount;
}
void TexTriPhong(VTXP n1, VTXP n2, VTXP n3);
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, proj[i]))
return;
}
for (int i = 1; i + 1 < n; i++)
TexTriPhong(proj[0], proj[i], proj[i + 1]);
}
// ==================================================
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;
WVtxP w0 = { p0.x, p0.y, p0.z, 0.0f, 1.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z };
WVtxP w1 = { p1.x, p1.y, p1.z, 1.0f, 1.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z };
WVtxP w2 = { p2.x, p2.y, p2.z, 1.0f, 0.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z };
WVtxP w3 = { p3.x, p3.y, p3.z, 0.0f, 0.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z };
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)
{
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; }
}
}
// フォンシェーディング用のライン走査(明るさbの代わりに法線nx,ny,nzを補間する)
void LinePhong(int x1, int y1, float u1, float v1, float w1, float nx1, float ny1, float nz1,
int x2, int y2, float u2, float v2, float w2, float nx2, float ny2, float nz2) {
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);
}
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;
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; }
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; }
}
}
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; // 遮るものが無い=日向
}
// フォンシェーディング用ラスタライザ:明るさbの代わりに法線nx,ny,nzを補間し、
// 1ピクセルごとに正規化+ライティング計算してからテクスチャに掛ける
void TexTriPhong(VTXP n1, VTXP n2, VTXP 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;
// ワールド座標復元用(シャドウ判定に使う):view→world回転の逆変換に使う値
// ※三角形単位で1回だけ計算し、ピクセル毎のcosf/sinf呼び出しを避ける
float camCos = cosf(-cam.yaw);
float camSin = sinf(-cam.yaw);
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;
}
LinePhong((int)n1.x, (int)n1.y, n1.u, n1.v, n1.z, n1.nx, n1.ny, n1.nz,
(int)n2.x, (int)n2.y, n2.u, n2.v, n2.z, n2.nx, n2.ny, n2.nz);
LinePhong((int)n2.x, (int)n2.y, n2.u, n2.v, n2.z, n2.nx, n2.ny, n2.nz,
(int)n3.x, (int)n3.y, n3.u, n3.v, n3.z, n3.nx, n3.ny, n3.nz);
LinePhong((int)n3.x, (int)n3.y, n3.u, n3.v, n3.z, n3.nx, n3.ny, n3.nz,
(int)n1.x, (int)n1.y, n1.u, n1.v, n1.z, n1.nx, n1.ny, n1.nz);
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 dnx = (RNX[y] - LNX[y]) / span;
float dny = (RNY[y] - LNY[y]) / span;
float dnz = (RNZ[y] - LNZ[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]);
// --- 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;
// 面の補間法線を正規化
//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;
// ★追加:view空間座標からワールド座標を復元し、光源方向への簡易シャドウ判定を行う
float wtx = vx * camCos + vz * camSin;
float wtz = -vx * camSin + vz * camCos;
float worldX = cam.x + wtx;
float worldY = cam.y + vy;
float worldZ = cam.z + wtz;
float shadow = ComputeShadowFactor(worldX, worldY, worldZ);
// ★変更:傾けた finalN の各成分を使って明るさを計算(影を反映)
float brightness = ComputePhongBrightness(finalN.x, finalN.y, finalN.z, distSq, g_CurrentAmbientBoost, 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);
}
PSET(x, y, c);
}
u += du; v += dv; w += dw;
nx += dnx; ny += dny; nz += dnz;
}
}
}
// --- 壁の描画 ---
#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);
WVtxP w0 = { p0.x, p0.y, p0.z, t0, v0, n0.x, n0.y, n0.z };
WVtxP w1 = { p1.x, p1.y, p1.z, t1, v0, n1.x, n1.y, n1.z };
WVtxP w2 = { p2.x, p2.y, p2.z, t1, v1, n2.x, n2.y, n2.z };
WVtxP w3 = { p3.x, p3.y, p3.z, t0, v1, n3.x, n3.y, n3.z };
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;
WVtxP w0 = { p0.x, p0.y, p0.z, u0, v0, n0.x, n0.y, n0.z };
WVtxP w1 = { p1.x, p1.y, p1.z, u1, v0, n1.x, n1.y, n1.z };
WVtxP w2 = { p2.x, p2.y, p2.z, u1, v1, n2.x, n2.y, n2.z };
WVtxP w3 = { p3.x, p3.y, p3.z, u0, v1, n3.x, n3.y, n3.z };
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;
}
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
ChangeWindowMode(TRUE);
SetGraphMode(WIDTH*2, HEIGHT*2, 32);
SetEmulation320x240(TRUE);
SetMainWindowText("PolygonSample2025lastVer") ;
if (DxLib_Init() < 0) return -1;
SetDrawScreen(DX_SCREEN_BACK);
soft = MakeARGB8ColorSoftImage(WIDTH, HEIGHT);
// グラフィックハンドルの毎フレーム生成・破棄はオーバーヘッドが大きいため、事前に1つ作成して使い回す
int graph = CreateGraphFromSoftImage(soft);
int x = 0;
int y = 120;
int PlayerX = 0 ;
int PlayerY = 0 ;
int Key;
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;
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();
// 既存ハンドルを再利用して転送(毎フレームの CreateGraph/DeleteGraph を廃止して高速化)
ReCreateGraphFromSoftImage(soft, graph);
DrawGraph(0, 0, graph, FALSE);
ScreenFlip();
WaitTimer(1000/60);
}
DeleteGraph(graph);
DeleteSoftImage(soft);
DxLib_End();
return 0;
}
影がより自然になった気がします。
フォグ(霧)を加えてみます。
#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; // 旧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);
}
// ==================================================
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]; // 旧Gouraud用(互換のため残置)
float RB[HEIGHT]; // 旧Gouraud用(互換のため残置)
// フォンシェーディング用:法線成分(パースペクティブ補正込み)のエッジ補間バッファ
float LNX[HEIGHT], LNY[HEIGHT], LNZ[HEIGHT];
float RNX[HEIGHT], RNY[HEIGHT], RNZ[HEIGHT];
int soft;
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);
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;
}
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;
return true;
}
// ==================================================
// フォンシェーディング用パイプライン
// ・頂点では「法線」と「UV」だけを渡す(明るさは計算しない)
// ・スキャンライン内で法線を補間 → 1ピクセルごとに正規化してライティング計算する
// ・カメラからの距離^2は、スクリーン座標とinvZから view空間位置を復元して求める
// (view空間はカメラ原点からの回転移動なので、距離^2はワールド座標での距離^2と一致する)
// ==================================================
struct VTXP { float x, y, z, u, v, nx, ny, nz; };
bool TransformAndProjectPhong(float wx, float wy, float wz, float u, float v, Vec3 normal, 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;
return true;
}
// ワールド座標(四角形の4隅)+UV+法線を持つクリッピング用頂点
struct WVtxP { float wx, wy, wz, u, v, nx, ny, nz; };
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;
out[outCount++] = mid;
}
}
return outCount;
}
void TexTriPhong(VTXP n1, VTXP n2, VTXP n3);
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, proj[i]))
return;
}
for (int i = 1; i + 1 < n; i++)
TexTriPhong(proj[0], proj[i], proj[i + 1]);
}
// ==================================================
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;
WVtxP w0 = { p0.x, p0.y, p0.z, 0.0f, 1.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z };
WVtxP w1 = { p1.x, p1.y, p1.z, 1.0f, 1.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z };
WVtxP w2 = { p2.x, p2.y, p2.z, 1.0f, 0.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z };
WVtxP w3 = { p3.x, p3.y, p3.z, 0.0f, 0.0f, billboardNormal.x, billboardNormal.y, billboardNormal.z };
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)
{
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; }
}
}
// フォンシェーディング用のライン走査(明るさbの代わりに法線nx,ny,nzを補間する)
void LinePhong(int x1, int y1, float u1, float v1, float w1, float nx1, float ny1, float nz1,
int x2, int y2, float u2, float v2, float w2, float nx2, float ny2, float nz2) {
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);
}
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;
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; }
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; }
}
}
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ピクセルごとに正規化+ライティング計算してからテクスチャに掛ける
void TexTriPhong(VTXP n1, VTXP n2, VTXP 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;
// ワールド座標復元用(シャドウ判定に使う):view→world回転の逆変換に使う値
// ※三角形単位で1回だけ計算し、ピクセル毎のcosf/sinf呼び出しを避ける
float camCos = cosf(-cam.yaw);
float camSin = sinf(-cam.yaw);
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;
}
LinePhong((int)n1.x, (int)n1.y, n1.u, n1.v, n1.z, n1.nx, n1.ny, n1.nz,
(int)n2.x, (int)n2.y, n2.u, n2.v, n2.z, n2.nx, n2.ny, n2.nz);
LinePhong((int)n2.x, (int)n2.y, n2.u, n2.v, n2.z, n2.nx, n2.ny, n2.nz,
(int)n3.x, (int)n3.y, n3.u, n3.v, n3.z, n3.nx, n3.ny, n3.nz);
LinePhong((int)n3.x, (int)n3.y, n3.u, n3.v, n3.z, n3.nx, n3.ny, n3.nz,
(int)n1.x, (int)n1.y, n1.u, n1.v, n1.z, n1.nx, n1.ny, n1.nz);
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 dnx = (RNX[y] - LNX[y]) / span;
float dny = (RNY[y] - LNY[y]) / span;
float dnz = (RNZ[y] - LNZ[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]);
// --- 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;
// 面の補間法線を正規化
//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;
// ★追加:view空間座標からワールド座標を復元し、光源方向への簡易シャドウ判定を行う
float wtx = vx * camCos + vz * camSin;
float wtz = -vx * camSin + vz * camCos;
float worldX = cam.x + wtx;
float worldY = cam.y + vy;
float worldZ = cam.z + wtz;
float shadow = ComputeShadowFactor(worldX, worldY, worldZ);
// ★変更:傾けた finalN の各成分を使って明るさを計算(影を反映)
float brightness = ComputePhongBrightness(finalN.x, finalN.y, finalN.z, distSq, g_CurrentAmbientBoost, 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;
}
}
}
// --- 壁の描画 ---
#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);
WVtxP w0 = { p0.x, p0.y, p0.z, t0, v0, n0.x, n0.y, n0.z };
WVtxP w1 = { p1.x, p1.y, p1.z, t1, v0, n1.x, n1.y, n1.z };
WVtxP w2 = { p2.x, p2.y, p2.z, t1, v1, n2.x, n2.y, n2.z };
WVtxP w3 = { p3.x, p3.y, p3.z, t0, v1, n3.x, n3.y, n3.z };
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;
WVtxP w0 = { p0.x, p0.y, p0.z, u0, v0, n0.x, n0.y, n0.z };
WVtxP w1 = { p1.x, p1.y, p1.z, u1, v0, n1.x, n1.y, n1.z };
WVtxP w2 = { p2.x, p2.y, p2.z, u1, v1, n2.x, n2.y, n2.z };
WVtxP w3 = { p3.x, p3.y, p3.z, u0, v1, n3.x, n3.y, n3.z };
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;
}
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
ChangeWindowMode(TRUE);
SetGraphMode(WIDTH*2, HEIGHT*2, 32);
SetEmulation320x240(TRUE);
SetMainWindowText("PolygonSample2025lastVer") ;
if (DxLib_Init() < 0) return -1;
SetDrawScreen(DX_SCREEN_BACK);
soft = MakeARGB8ColorSoftImage(WIDTH, HEIGHT);
// グラフィックハンドルの毎フレーム生成・破棄はオーバーヘッドが大きいため、事前に1つ作成して使い回す
int graph = CreateGraphFromSoftImage(soft);
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();
// 既存ハンドルを再利用して転送(毎フレームの CreateGraph/DeleteGraph を廃止して高速化)
ReCreateGraphFromSoftImage(soft, graph);
DrawGraph(0, 0, graph, FALSE);
ScreenFlip();
WaitTimer(1000/60);
}
DeleteGraph(graph);
DeleteSoftImage(soft);
DxLib_End();
return 0;
}
まだまだ3DCGには様々な技術がありますが、次回以降は、より解像度を
上げてリアルになるようにしていきたいと思います。(あくまで予定ですが...