さて、前回のグーローシェーディング付きテクスチャーマッピングと
フォンシェーディング付きテクスチャーマッピングはバグがあったのが
なんとなく推察できたかもしれないですが、作者こと自分の理解度不足
があったことが全ての原因です。
という訳で今回はテクスチャーのグーローとフォンが治るまで
シャドウを作ります。
#include"DXLib.h"
#include<math.h>
#include<stdlib.h>
#include<windows.h>
#include<utility>
using namespace std;
#define CELL 1.0f
const float FOV_F = 600.0f;
const int HEIGHT = 240;
const int WIDTH = 320;
struct VTX {
float x, y, z;
float u, v;
};
struct Camera
{
float x, y, z;
float yaw; // 左右回転のみ
float height;
};
enum WallDir
{
WALL_NORTH,
WALL_SOUTH,
WALL_WEST,
WALL_EAST
};
struct Vec3
{
float x;
float y;
float z;
};
float ZBuf[HEIGHT][WIDTH];
#define RAD (3.14159265/180.0)
Camera cam;
// ==================================================
// ライティング&点光源シャドウ関連
// ==================================================
Vec3 g_LightPos = { 10.0f, 1.8f, 10.0f }; // 点光源の位置
float g_Ambient = 0.35f; // 環境光
float g_FaceBrightness = 1.0f; // 面の明るさ
// 法線ベクトルから明るさ(0〜1)を計算
float ComputeBrightness(Vec3 normal)
{
float len = sqrtf(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);
if (len > 0.0001f) {
normal.x /= len; normal.y /= len; normal.z /= len;
}
Vec3 lightDir = { -0.4f, -1.0f, -0.3f };
float lLen = sqrtf(lightDir.x*lightDir.x + lightDir.y*lightDir.y + lightDir.z*lightDir.z);
lightDir.x /= lLen; lightDir.y /= lLen; lightDir.z /= lLen;
float ndotl = -(normal.x * lightDir.x + normal.y * lightDir.y + normal.z * lightDir.z);
if (ndotl < 0.0f) ndotl = 0.0f;
float b = g_Ambient + (1.0f - g_Ambient) * ndotl;
if (b > 1.0f) b = 1.0f;
return b;
}
// テクスチャ色(0xRRGGBB)に明るさを掛ける
inline int ShadeColor(int col, float brightness)
{
int r = (col >> 16) & 0xFF;
int g = (col >> 8) & 0xFF;
int b = col & 0xFF;
r = (int)(r * brightness);
g = (int)(g * brightness);
b = (int)(b * brightness);
if (r > 255) r = 255; if (r < 0) r = 0;
if (g > 255) g = 255; if (g < 0) g = 0;
if (b > 255) b = 255; if (b < 0) b = 0;
return GetColor(r, g, b);
}
// 頂点変換関数プロトタイプ
bool TransformAndProject(float wx, float wy, float wz, float u, float v, VTX& out);
// 壁の上面頂点を光源から床(Y=0)へ投影
bool TransformAndProjectShadow(float wx, float wy, float wz, VTX& out) {
if (g_LightPos.y <= wy) return false;
float dx = wx - g_LightPos.x;
float dy = wy - g_LightPos.y;
float dz = wz - g_LightPos.z;
float t = -g_LightPos.y / dy;
float sx = g_LightPos.x + dx * t;
float sz = g_LightPos.z + dz * t;
float sy = 0.0f;
return TransformAndProject(sx, sy, sz, 0.0f, 0.0f, out);
}
// ==================================================
int L[HEIGHT];
int R[HEIGHT];
float LU[HEIGHT];
float LV[HEIGHT];
float RU[HEIGHT];
float RV[HEIGHT];
float LW[HEIGHT];
float RW[HEIGHT];
int soft;
int Texture[1024*2048] = {0};
int TexW = 0;
int TexH = 0;
#define W 21
#define H 21
int maze[H][W]; // 0=通路, 1=壁
const float WALL_H = 1.0f;
Vec3 GetWallNormal(WallDir dir);
void DrawWall(float x, float z, WallDir dir);
#define MAX_VTX 1000
#define MAX_POL 3000
VTX v[MAX_VTX] = {0};
int PolygonNumber[MAX_POL][4] = {0};
void TexTri_Complex(VTX n1, VTX n2, VTX n3);
#define TexTri TexTri_Complex
void Line(int x1, int y1, float u1, float v1, float w1,
int x2, int y2, float u2, float v2, float w2) {
if (y1 == y2) return;
if (y1 > y2) {
swap(y1, y2); swap(x1, x2);
swap(u1, u2); swap(v1, v2); swap(w1, w2);
}
float invH = 1.0f / (float)(y2 - y1);
for (int y = y1; y < y2; y++) {
if (y < 0 || y >= HEIGHT) continue;
float t = (float)(y - y1) * invH;
int x = (int)(x1 + (x2 - x1) * t);
float u = u1 + (u2 - u1) * t;
float v = v1 + (v2 - v1) * t;
float w = w1 + (w2 - w1) * t;
if (x < L[y]) { L[y] = x; LU[y] = u; LV[y] = v; LW[y] = w; }
if (x > R[y]) { R[y] = x; RU[y] = u; RV[y] = v; RW[y] = w; }
}
}
void PSET(int x, int y, int c)
{
int r = (c >> 16) & 0xFF;
int g = (c >> 8) & 0xFF;
int b = c & 0xFF;
DrawPixelSoftImage(soft, x, y, r, g, b, 255);
}
// 影専用ラスタライザ
void DrawShadowTriangle(VTX n1, VTX n2, VTX n3) {
int miny = (int)min(n1.y, min(n2.y, n3.y));
int maxy = (int)max(n1.y, max(n2.y, n3.y));
miny = max(0, miny);
maxy = min(HEIGHT - 1, maxy);
for (int y = miny; y <= maxy; y++) {
L[y] = 9999; R[y] = -9999;
}
Line((int)n1.x, (int)n1.y, 0, 0, n1.z, (int)n2.x, (int)n2.y, 0, 0, n2.z);
Line((int)n2.x, (int)n2.y, 0, 0, n2.z, (int)n3.x, (int)n3.y, 0, 0, n3.z);
Line((int)n3.x, (int)n3.y, 0, 0, n3.z, (int)n1.x, (int)n1.y, 0, 0, n1.z);
for (int y = miny; y <= maxy; y++) {
if (L[y] > R[y]) continue;
int lx = max(0, L[y]);
int rx = min(WIDTH - 1, R[y]);
if (lx > rx) continue;
float span = (float)(R[y] - L[y]);
if (span <= 0.0f) span = 1.0f;
float dw = (RW[y] - LW[y]) / span;
float w = LW[y] + dw * (lx - L[y]);
for (int x = lx; x <= rx; x++) {
// 床のZ深度にマージンを設けて判定
if (w >= ZBuf[y][x] - 0.0001f) {
int r, g, b, a;
GetPixelSoftImage(soft, x, y, &r, &g, &b, &a);
r = (int)(r * 0.3f);
g = (int)(g * 0.3f);
b = (int)(b * 0.3f);
PSET(x, y, GetColor(r, g, b));
}
w += dw;
}
}
}
// テクスチャポリゴンラスタライザ
void TexTri_Complex(VTX n1, VTX n2, VTX n3) {
int miny = (int)min(n1.y, min(n2.y, n3.y));
int maxy = (int)max(n1.y, max(n2.y, n3.y));
miny = max(0, miny);
maxy = min(HEIGHT - 1, maxy);
for (int y = miny; y <= maxy; y++) {
L[y] = 9999; R[y] = -9999;
}
Line((int)n1.x, (int)n1.y, n1.u, n1.v, n1.z, (int)n2.x, (int)n2.y, n2.u, n2.v, n2.z);
Line((int)n2.x, (int)n2.y, n2.u, n2.v, n2.z, (int)n3.x, (int)n3.y, n3.u, n3.v, n3.z);
Line((int)n3.x, (int)n3.y, n3.u, n3.v, n3.z, (int)n1.x, (int)n1.y, n1.u, n1.v, n1.z);
for (int y = miny; y <= maxy; y++) {
if (L[y] > R[y]) continue;
int lx = max(0, L[y]);
int rx = min(WIDTH - 1, R[y]);
if (lx > rx) continue;
float span = (float)(R[y] - L[y]);
if (span <= 0.0f) span = 1.0f;
float du = (RU[y] - LU[y]) / span;
float dv = (RV[y] - LV[y]) / span;
float dw = (RW[y] - LW[y]) / span;
float u = LU[y] + du * (lx - L[y]);
float v = LV[y] + dv * (lx - L[y]);
float w = LW[y] + dw * (lx - L[y]);
for (int x = lx; x <= rx; x++) {
if (w > ZBuf[y][x]) {
ZBuf[y][x] = w;
float z = 1.0f / w;
int tx = (int)(u * z * (TexW - 1)) & (TexW - 1);
int ty = (int)(v * z * (TexH - 1)) & (TexH - 1);
int c = Texture[ty * TexW + tx];
c = ShadeColor(c, g_FaceBrightness);
PSET(x, y, c);
}
u += du; v += dv; w += dw;
}
}
}
bool TransformAndProject(float wx, float wy, float wz, float u, float v, VTX& out) {
float tx = wx - cam.x;
float ty = wy - cam.y;
float tz = wz - cam.z;
float c = cosf(-cam.yaw);
float s = sinf(-cam.yaw);
float vx = tx * c - tz * s;
float vz = tx * s + tz * c;
float vy = ty;
if (vz <= 0.01f) return false;
float invZ = 1.0f / vz;
out.x = vx * FOV_F * invZ + (WIDTH / 2);
out.y = -vy * FOV_F * invZ + (HEIGHT / 2);
out.z = invZ;
out.u = u * invZ;
out.v = v * invZ;
return true;
}
Vec3 GetWallNormal(WallDir dir)
{
Vec3 NSWE[4] = {
{ 0, 0, 1},
{ 0, 0, -1},
{ 1, 0, 0},
{-1, 0, 0}
};
switch (dir) {
case WALL_NORTH: return NSWE[0];
case WALL_SOUTH: return NSWE[1];
case WALL_WEST: return NSWE[2];
case WALL_EAST: return NSWE[3];
}
Vec3 zero = {0,0,0};
return zero;
}
void DrawWall(float x, float z, WallDir dir) {
Vec3 p[4];
switch (dir) {
case WALL_NORTH:
p[0].x = x; p[0].y = 0.0f; p[0].z = z;
p[1].x = x + CELL; p[1].y = 0.0f; p[1].z = z;
p[2].x = x + CELL; p[2].y = WALL_H; p[2].z = z;
p[3].x = x; p[3].y = WALL_H; p[3].z = z;
break;
case WALL_SOUTH:
p[0].x = x + CELL; p[0].y = 0.0f; p[0].z = z + CELL;
p[1].x = x; p[1].y = 0.0f; p[1].z = z + CELL;
p[2].x = x; p[2].y = WALL_H; p[2].z = z + CELL;
p[3].x = x + CELL; p[3].y = WALL_H; p[3].z = z + CELL;
break;
case WALL_WEST:
p[0].x = x; p[0].y = 0.0f; p[0].z = z + CELL;
p[1].x = x; p[1].y = 0.0f; p[1].z = z;
p[2].x = x; p[2].y = WALL_H; p[2].z = z;
p[3].x = x; p[3].y = WALL_H; p[3].z = z + CELL;
break;
case WALL_EAST:
p[0].x = x + CELL; p[0].y = 0.0f; p[0].z = z;
p[1].x = x + CELL; p[1].y = 0.0f; p[1].z = z + CELL;
p[2].x = x + CELL; p[2].y = WALL_H; p[2].z = z + CELL;
p[3].x = x + CELL; p[3].y = WALL_H; p[3].z = z;
break;
}
// 1. 影ポリゴンの計算(底面頂点そのまま + 上面頂点を投影)
VTX s[4];
bool shadowOK = true;
shadowOK &= TransformAndProject(p[0].x, p[0].y, p[0].z, 0, 0, s[0]);
shadowOK &= TransformAndProject(p[1].x, p[1].y, p[1].z, 0, 0, s[1]);
shadowOK &= TransformAndProjectShadow(p[2].x, p[2].y, p[2].z, s[2]);
shadowOK &= TransformAndProjectShadow(p[3].x, p[3].y, p[3].z, s[3]);
if (shadowOK) {
DrawShadowTriangle(s[0], s[1], s[2]);
DrawShadowTriangle(s[0], s[2], s[3]);
}
// 2. 壁ポリゴン描画
VTX v[4];
if (!TransformAndProject(p[0].x, p[0].y, p[0].z, 0, 1, v[0])) return;
if (!TransformAndProject(p[1].x, p[1].y, p[1].z, 1, 1, v[1])) return;
if (!TransformAndProject(p[2].x, p[2].y, p[2].z, 1, 0, v[2])) return;
if (!TransformAndProject(p[3].x, p[3].y, p[3].z, 0, 0, v[3])) return;
g_FaceBrightness = ComputeBrightness(GetWallNormal(dir));
TexTri(v[0], v[1], v[2]);
TexTri(v[0], v[2], v[3]);
}
void DrawMaze()
{
for (int z = 0; z < H; z++)
for (int x = 0; x < W; x++)
{
if (maze[z][x] != 0) continue;
float wx = x * CELL;
float wz = z * CELL;
if (z > 0 && maze[z-1][x] == 1) DrawWall(wx, wz, WALL_NORTH);
if (z < H-1 && maze[z+1][x] == 1) DrawWall(wx, wz, WALL_SOUTH);
if (x > 0 && maze[z][x-1] == 1) DrawWall(wx, wz, WALL_WEST);
if (x < W-1 && maze[z][x+1] == 1) DrawWall(wx, wz, WALL_EAST);
}
}
void DrawFloorFast() {
Vec3 floorNormal = { 0.0f, 1.0f, 0.0f };
g_FaceBrightness = ComputeBrightness(floorNormal);
float c = cosf(cam.yaw), s = sinf(cam.yaw);
for (int y = HEIGHT / 2 + 1; y < HEIGHT; y++) {
float dy = y - (HEIGHT / 2);
float distance = (cam.y * FOV_F) / dy;
float leftX = cam.x + ((-WIDTH/2) * c - FOV_F * s) * (distance / FOV_F);
float leftZ = cam.z + ((-WIDTH/2) * s + FOV_F * c) * (distance / FOV_F);
float rightX = cam.x + ((WIDTH/2) * c - FOV_F * s) * (distance / FOV_F);
float rightZ = cam.z + ((WIDTH/2) * s + FOV_F * c) * (distance / FOV_F);
for (int x = 0; x < WIDTH; x++) {
float t = (float)x / WIDTH;
float wx = leftX + (rightX - leftX) * t;
float wz = leftZ + (rightZ - leftZ) * t;
int tx = (int)(wx * TexW / CELL) & (TexW - 1);
int ty = (int)(wz * TexH / CELL) & (TexH - 1);
float w = 1.0f / distance;
if (w > ZBuf[y][x]) {
ZBuf[y][x] = w;
int col = Texture[ty * TexW + tx];
col = ShadeColor(col, g_FaceBrightness);
PSET(x, y, col);
}
}
}
}
bool Load24BitBmpToTexture(const char* fileName, int maxW, int maxH)
{
int siHandle = LoadSoftImage(fileName);
if (siHandle == -1) return false;
int imgW, imgH;
GetSoftImageSize(siHandle, &imgW, &imgH);
int copyW = (imgW > maxW) ? maxW : imgW;
int copyH = (imgH > maxH) ? maxH : imgH;
TexW = copyW;
TexH = copyH;
for (int y = 0; y < copyH; y++) {
for (int x = 0; x < copyW; x++) {
int r, g, b, a;
GetPixelSoftImage(siHandle, x, y, &r, &g, &b, &a);
Texture[y * maxW + x] = GetColor(r, g, b);
}
}
DeleteSoftImage(siHandle);
return true;
}
int dx[4] = { 0, 0, -2, 2 };
int dz[4] = { -2, 2, 0, 0 };
void Shuffle(int* a, int n)
{
for (int i = n - 1; i > 0; i--) {
int j = rand() % (i + 1);
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
void Dig(int x, int z)
{
int dir[4] = {0, 1, 2, 3};
Shuffle(dir, 4);
for (int i = 0; i < 4; i++) {
int nx = x + dx[dir[i]];
int nz = z + dz[dir[i]];
if (nx <= 0 || nz <= 0 || nx >= W - 1 || nz >= H - 1) continue;
if (maze[nz][nx] == 0) continue;
maze[nz][nx] = 0;
maze[z + dz[dir[i]] / 2][x + dx[dir[i]] / 2] = 0;
Dig(nx, nz);
}
}
void UpdateCameraWithCollision(Camera& cam)
{
float speed = 0.05f;
float oldX = cam.x;
float oldZ = cam.z;
if (CheckHitKey(KEY_INPUT_LEFT)) cam.yaw += 0.04f;
if (CheckHitKey(KEY_INPUT_RIGHT)) cam.yaw -= 0.04f;
float dx = sinf(cam.yaw);
float dz = cosf(cam.yaw);
if (CheckHitKey(KEY_INPUT_UP)) {
cam.x -= dx * speed;
cam.z += dz * speed;
}
if (CheckHitKey(KEY_INPUT_DOWN)) {
cam.x += dx * speed;
cam.z -= dz * speed;
}
int mapX = (int)(cam.x / CELL);
int mapZ = (int)(cam.z / CELL);
if (mapX < 0 || mapX >= W || mapZ < 0 || mapZ >= H || maze[mapZ][mapX] == 1) {
cam.x = oldX;
cam.z = oldZ;
}
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
ChangeWindowMode(TRUE);
SetGraphMode(WIDTH * 2, HEIGHT * 2, 32);
SetEmulation320x240(TRUE);
SetMainWindowText("PolygonSample2025lastVer");
if (DxLib_Init() < 0) return -1;
SetDrawScreen(DX_SCREEN_BACK);
soft = MakeARGB8ColorSoftImage(WIDTH, HEIGHT);
int graph = CreateGraphFromSoftImage(soft);
Load24BitBmpToTexture("wall02.bmp", 1024 / 4, 1024 / 4);
srand(GetTickCount());
cam.height = 0.5f;
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
maze[y][x] = 1;
Dig(1, 1);
maze[1][1] = 0;
cam.x = 1.0f * CELL + CELL * 0.5f;
cam.z = 1.0f * CELL + CELL * 0.5f;
cam.y = 0.5f;
cam.yaw = 0.0f;
////cam.height = 0.5f;
while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
{
ClearDrawScreen();
FillSoftImage(soft, 0, 0, 0, 255);
memset(ZBuf, 0, sizeof(ZBuf));
UpdateCameraWithCollision(cam);
DrawFloorFast();
DrawMaze();
graph = CreateGraphFromSoftImage(soft);
DrawGraph(0, 0, graph, FALSE);
ScreenFlip();
WaitTimer(1000 / 60);
}
DeleteGraph(graph);
DeleteSoftImage(soft);
DxLib_End();
return 0;
}
アンチエイリアステクスチャー
というものをやってみようと思います。何故かというと、このすぐ上でジャギーが目立つからです。
バイリニアフィルタを使えば簡単にそして軽く処理できます。
#include"DXLib.h"
#include<math.h>
#include<stdlib.h>
#include<windows.h>
#include<utility>
using namespace std;
#define CELL 1.0f
const float FOV_F = 600.0f;
const int HEIGHT = 240;
const int WIDTH = 320;
struct VTX {
float x, y, z;
float u, v;
};
struct Camera
{
float x, y, z;
float yaw; // 左右回転のみ
float height;
};
enum WallDir
{
WALL_NORTH,
WALL_SOUTH,
WALL_WEST,
WALL_EAST
};
struct Vec3
{
float x;
float y;
float z;
};
float ZBuf[HEIGHT][WIDTH];
#define RAD (3.14159265/180.0)
Camera cam;
// ==================================================
// ライティング&点光源シャドウ関連
// ==================================================
Vec3 g_LightPos = { 10.0f, 1.8f, 10.0f }; // 点光源の位置
float g_Ambient = 0.35f; // 環境光
float g_FaceBrightness = 1.0f; // 面の明るさ
// 法線ベクトルから明るさ(0〜1)を計算
float ComputeBrightness(Vec3 normal)
{
float len = sqrtf(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);
if (len > 0.0001f) {
normal.x /= len; normal.y /= len; normal.z /= len;
}
Vec3 lightDir = { -0.4f, -1.0f, -0.3f };
float lLen = sqrtf(lightDir.x*lightDir.x + lightDir.y*lightDir.y + lightDir.z*lightDir.z);
lightDir.x /= lLen; lightDir.y /= lLen; lightDir.z /= lLen;
float ndotl = -(normal.x * lightDir.x + normal.y * lightDir.y + normal.z * lightDir.z);
if (ndotl < 0.0f) ndotl = 0.0f;
float b = g_Ambient + (1.0f - g_Ambient) * ndotl;
if (b > 1.0f) b = 1.0f;
return b;
}
// テクスチャ色(0xRRGGBB)に明るさを掛ける
inline int ShadeColor(int col, float brightness)
{
int r = (col >> 16) & 0xFF;
int g = (col >> 8) & 0xFF;
int b = col & 0xFF;
r = (int)(r * brightness);
g = (int)(g * brightness);
b = (int)(b * brightness);
if (r > 255) r = 255; if (r < 0) r = 0;
if (g > 255) g = 255; if (g < 0) g = 0;
if (b > 255) b = 255; if (b < 0) b = 0;
return GetColor(r, g, b);
}
// 頂点変換関数プロトタイプ
bool TransformAndProject(float wx, float wy, float wz, float u, float v, VTX& out);
// 壁の上面頂点を光源から床(Y=0)へ投影
bool TransformAndProjectShadow(float wx, float wy, float wz, VTX& out) {
if (g_LightPos.y <= wy) return false;
float dx = wx - g_LightPos.x;
float dy = wy - g_LightPos.y;
float dz = wz - g_LightPos.z;
float t = -g_LightPos.y / dy;
float sx = g_LightPos.x + dx * t;
float sz = g_LightPos.z + dz * t;
float sy = 0.0f;
return TransformAndProject(sx, sy, sz, 0.0f, 0.0f, out);
}
// ==================================================
int L[HEIGHT];
int R[HEIGHT];
float LU[HEIGHT];
float LV[HEIGHT];
float RU[HEIGHT];
float RV[HEIGHT];
float LW[HEIGHT];
float RW[HEIGHT];
int soft;
int Texture[1024*2048] = {0};
int TexW = 0;
int TexH = 0;
#define W 21
#define H 21
int maze[H][W]; // 0=通路, 1=壁
const float WALL_H = 1.0f;
Vec3 GetWallNormal(WallDir dir);
void DrawWall(float x, float z, WallDir dir);
#define MAX_VTX 1000
#define MAX_POL 3000
VTX v[MAX_VTX] = {0};
int PolygonNumber[MAX_POL][4] = {0};
void TexTri_Complex(VTX n1, VTX n2, VTX n3);
#define TexTri TexTri_Complex
void Line(int x1, int y1, float u1, float v1, float w1,
int x2, int y2, float u2, float v2, float w2) {
if (y1 == y2) return;
if (y1 > y2) {
swap(y1, y2); swap(x1, x2);
swap(u1, u2); swap(v1, v2); swap(w1, w2);
}
float invH = 1.0f / (float)(y2 - y1);
for (int y = y1; y < y2; y++) {
if (y < 0 || y >= HEIGHT) continue;
float t = (float)(y - y1) * invH;
int x = (int)(x1 + (x2 - x1) * t);
float u = u1 + (u2 - u1) * t;
float v = v1 + (v2 - v1) * t;
float w = w1 + (w2 - w1) * t;
if (x < L[y]) { L[y] = x; LU[y] = u; LV[y] = v; LW[y] = w; }
if (x > R[y]) { R[y] = x; RU[y] = u; RV[y] = v; RW[y] = w; }
}
}
void PSET(int x, int y, int c)
{
int r = (c >> 16) & 0xFF;
int g = (c >> 8) & 0xFF;
int b = c & 0xFF;
DrawPixelSoftImage(soft, x, y, r, g, b, 255);
}
// 影専用ラスタライザ
void DrawShadowTriangle(VTX n1, VTX n2, VTX n3) {
int miny = (int)min(n1.y, min(n2.y, n3.y));
int maxy = (int)max(n1.y, max(n2.y, n3.y));
miny = max(0, miny);
maxy = min(HEIGHT - 1, maxy);
for (int y = miny; y <= maxy; y++) {
L[y] = 9999; R[y] = -9999;
}
Line((int)n1.x, (int)n1.y, 0, 0, n1.z, (int)n2.x, (int)n2.y, 0, 0, n2.z);
Line((int)n2.x, (int)n2.y, 0, 0, n2.z, (int)n3.x, (int)n3.y, 0, 0, n3.z);
Line((int)n3.x, (int)n3.y, 0, 0, n3.z, (int)n1.x, (int)n1.y, 0, 0, n1.z);
for (int y = miny; y <= maxy; y++) {
if (L[y] > R[y]) continue;
int lx = max(0, L[y]);
int rx = min(WIDTH - 1, R[y]);
if (lx > rx) continue;
float span = (float)(R[y] - L[y]);
if (span <= 0.0f) span = 1.0f;
float dw = (RW[y] - LW[y]) / span;
float w = LW[y] + dw * (lx - L[y]);
for (int x = lx; x <= rx; x++) {
// 床のZ深度にマージンを設けて判定
if (w >= ZBuf[y][x] - 0.0001f) {
int r, g, b, a;
GetPixelSoftImage(soft, x, y, &r, &g, &b, &a);
r = (int)(r * 0.3f);
g = (int)(g * 0.3f);
b = (int)(b * 0.3f);
PSET(x, y, GetColor(r, g, b));
}
w += dw;
}
}
}
// テクスチャからバイリニア補間した色を取得する関数
inline int SampleTextureBilinear(float u_persp, float v_persp, float invZ) {
// 透過除算で元のUV座標(0.0〜1.0)を復元
float u = u_persp * invZ;
float v = v_persp * invZ;
// UV座標をテクスチャのピクセル座標系に変換
float fx = u * (TexW - 1);
float fy = v * (TexH - 1);
// 範囲外のループ(ラップ処理)
int x0 = ((int)floorf(fx)) & (TexW - 1);
int y0 = ((int)floorf(fy)) & (TexH - 1);
int x1 = (x0 + 1) & (TexW - 1);
int y1 = (y0 + 1) & (TexH - 1);
// 小数点以下の比率
float rx = fx - floorf(fx);
float ry = fy - floorf(fy);
// 周辺4ピクセルの色を取得
int c00 = Texture[y0 * TexW + x0];
int c10 = Texture[y0 * TexW + x1];
int c01 = Texture[y1 * TexW + x0];
int c11 = Texture[y1 * TexW + x1];
// RGB各成分の線形補間
int r00 = (c00 >> 16) & 0xFF, g00 = (c00 >> 8) & 0xFF, b00 = c00 & 0xFF;
int r10 = (c10 >> 16) & 0xFF, g10 = (c10 >> 8) & 0xFF, b10 = c10 & 0xFF;
int r01 = (c01 >> 16) & 0xFF, g01 = (c01 >> 8) & 0xFF, b01 = c01 & 0xFF;
int r11 = (c11 >> 16) & 0xFF, g11 = (c11 >> 8) & 0xFF, b11 = c11 & 0xFF;
float r = (1.0f - rx) * (1.0f - ry) * r00 + rx * (1.0f - ry) * r10 + (1.0f - rx) * ry * r01 + rx * ry * r11;
float g = (1.0f - rx) * (1.0f - ry) * g00 + rx * (1.0f - ry) * g10 + (1.0f - rx) * ry * g01 + rx * ry * g11;
float b = (1.0f - rx) * (1.0f - ry) * b00 + rx * (1.0f - ry) * b10 + (1.0f - rx) * ry * b01 + rx * ry * b11;
return GetColor((int)r, (int)g, (int)b);
}
// テクスチャポリゴンラスタライザ
void TexTri_Complex(VTX n1, VTX n2, VTX n3) {
int miny = (int)min(n1.y, min(n2.y, n3.y));
int maxy = (int)max(n1.y, max(n2.y, n3.y));
miny = max(0, miny);
maxy = min(HEIGHT - 1, maxy);
for (int y = miny; y <= maxy; y++) {
L[y] = 9999; R[y] = -9999;
}
Line((int)n1.x, (int)n1.y, n1.u, n1.v, n1.z, (int)n2.x, (int)n2.y, n2.u, n2.v, n2.z);
Line((int)n2.x, (int)n2.y, n2.u, n2.v, n2.z, (int)n3.x, (int)n3.y, n3.u, n3.v, n3.z);
Line((int)n3.x, (int)n3.y, n3.u, n3.v, n3.z, (int)n1.x, (int)n1.y, n1.u, n1.v, n1.z);
for (int y = miny; y <= maxy; y++) {
if (L[y] > R[y]) continue;
int lx = max(0, L[y]);
int rx = min(WIDTH - 1, R[y]);
if (lx > rx) continue;
float span = (float)(R[y] - L[y]);
if (span <= 0.0f) span = 1.0f;
float du = (RU[y] - LU[y]) / span;
float dv = (RV[y] - LV[y]) / span;
float dw = (RW[y] - LW[y]) / span;
float u = LU[y] + du * (lx - L[y]);
float v = LV[y] + dv * (lx - L[y]);
float w = LW[y] + dw * (lx - L[y]);
for (int x = lx; x <= rx; x++) {
if (w > ZBuf[y][x]) {
ZBuf[y][x] = w;
float z = 1.0f / w;
// 従来のニアレストネイバー(最近傍補間)
// int tx = (int)(u * z * (TexW - 1)) & (TexW - 1);
// int ty = (int)(v * z * (TexH - 1)) & (TexH - 1);
// int c = Texture[ty * TexW + tx];
// バイリニア補間に差し替え
int c = SampleTextureBilinear(u, v, z);
c = ShadeColor(c, g_FaceBrightness);
PSET(x, y, c);
}
u += du; v += dv; w += dw;
}
}
}
bool TransformAndProject(float wx, float wy, float wz, float u, float v, VTX& out) {
float tx = wx - cam.x;
float ty = wy - cam.y;
float tz = wz - cam.z;
float c = cosf(-cam.yaw);
float s = sinf(-cam.yaw);
float vx = tx * c - tz * s;
float vz = tx * s + tz * c;
float vy = ty;
if (vz <= 0.01f) return false;
float invZ = 1.0f / vz;
out.x = vx * FOV_F * invZ + (WIDTH / 2);
out.y = -vy * FOV_F * invZ + (HEIGHT / 2);
out.z = invZ;
out.u = u * invZ;
out.v = v * invZ;
return true;
}
Vec3 GetWallNormal(WallDir dir)
{
Vec3 NSWE[4] = {
{ 0, 0, 1},
{ 0, 0, -1},
{ 1, 0, 0},
{-1, 0, 0}
};
switch (dir) {
case WALL_NORTH: return NSWE[0];
case WALL_SOUTH: return NSWE[1];
case WALL_WEST: return NSWE[2];
case WALL_EAST: return NSWE[3];
}
Vec3 zero = {0,0,0};
return zero;
}
void DrawWall(float x, float z, WallDir dir) {
Vec3 p[4];
switch (dir) {
case WALL_NORTH:
p[0].x = x; p[0].y = 0.0f; p[0].z = z;
p[1].x = x + CELL; p[1].y = 0.0f; p[1].z = z;
p[2].x = x + CELL; p[2].y = WALL_H; p[2].z = z;
p[3].x = x; p[3].y = WALL_H; p[3].z = z;
break;
case WALL_SOUTH:
p[0].x = x + CELL; p[0].y = 0.0f; p[0].z = z + CELL;
p[1].x = x; p[1].y = 0.0f; p[1].z = z + CELL;
p[2].x = x; p[2].y = WALL_H; p[2].z = z + CELL;
p[3].x = x + CELL; p[3].y = WALL_H; p[3].z = z + CELL;
break;
case WALL_WEST:
p[0].x = x; p[0].y = 0.0f; p[0].z = z + CELL;
p[1].x = x; p[1].y = 0.0f; p[1].z = z;
p[2].x = x; p[2].y = WALL_H; p[2].z = z;
p[3].x = x; p[3].y = WALL_H; p[3].z = z + CELL;
break;
case WALL_EAST:
p[0].x = x + CELL; p[0].y = 0.0f; p[0].z = z;
p[1].x = x + CELL; p[1].y = 0.0f; p[1].z = z + CELL;
p[2].x = x + CELL; p[2].y = WALL_H; p[2].z = z + CELL;
p[3].x = x + CELL; p[3].y = WALL_H; p[3].z = z;
break;
}
// 1. 影ポリゴンの計算(底面頂点そのまま + 上面頂点を投影)
VTX s[4];
bool shadowOK = true;
shadowOK &= TransformAndProject(p[0].x, p[0].y, p[0].z, 0, 0, s[0]);
shadowOK &= TransformAndProject(p[1].x, p[1].y, p[1].z, 0, 0, s[1]);
shadowOK &= TransformAndProjectShadow(p[2].x, p[2].y, p[2].z, s[2]);
shadowOK &= TransformAndProjectShadow(p[3].x, p[3].y, p[3].z, s[3]);
if (shadowOK) {
DrawShadowTriangle(s[0], s[1], s[2]);
DrawShadowTriangle(s[0], s[2], s[3]);
}
// 2. 壁ポリゴン描画
VTX v[4];
if (!TransformAndProject(p[0].x, p[0].y, p[0].z, 0, 1, v[0])) return;
if (!TransformAndProject(p[1].x, p[1].y, p[1].z, 1, 1, v[1])) return;
if (!TransformAndProject(p[2].x, p[2].y, p[2].z, 1, 0, v[2])) return;
if (!TransformAndProject(p[3].x, p[3].y, p[3].z, 0, 0, v[3])) return;
g_FaceBrightness = ComputeBrightness(GetWallNormal(dir));
TexTri(v[0], v[1], v[2]);
TexTri(v[0], v[2], v[3]);
}
void DrawMaze()
{
for (int z = 0; z < H; z++)
for (int x = 0; x < W; x++)
{
if (maze[z][x] != 0) continue;
float wx = x * CELL;
float wz = z * CELL;
if (z > 0 && maze[z-1][x] == 1) DrawWall(wx, wz, WALL_NORTH);
if (z < H-1 && maze[z+1][x] == 1) DrawWall(wx, wz, WALL_SOUTH);
if (x > 0 && maze[z][x-1] == 1) DrawWall(wx, wz, WALL_WEST);
if (x < W-1 && maze[z][x+1] == 1) DrawWall(wx, wz, WALL_EAST);
}
}
void DrawFloorFast() {
Vec3 floorNormal = { 0.0f, 1.0f, 0.0f };
g_FaceBrightness = ComputeBrightness(floorNormal);
float c = cosf(cam.yaw), s = sinf(cam.yaw);
for (int y = HEIGHT / 2 + 1; y < HEIGHT; y++) {
float dy = y - (HEIGHT / 2);
float distance = (cam.y * FOV_F) / dy;
float leftX = cam.x + ((-WIDTH/2) * c - FOV_F * s) * (distance / FOV_F);
float leftZ = cam.z + ((-WIDTH/2) * s + FOV_F * c) * (distance / FOV_F);
float rightX = cam.x + ((WIDTH/2) * c - FOV_F * s) * (distance / FOV_F);
float rightZ = cam.z + ((WIDTH/2) * s + FOV_F * c) * (distance / FOV_F);
for (int x = 0; x < WIDTH; x++) {
float t = (float)x / WIDTH;
float wx = leftX + (rightX - leftX) * t;
float wz = leftZ + (rightZ - leftZ) * t;
int tx = (int)(wx * TexW / CELL) & (TexW - 1);
int ty = (int)(wz * TexH / CELL) & (TexH - 1);
float w = 1.0f / distance;
if (w > ZBuf[y][x]) {
ZBuf[y][x] = w;
int col = Texture[ty * TexW + tx];
col = ShadeColor(col, g_FaceBrightness);
PSET(x, y, col);
}
}
}
}
bool Load24BitBmpToTexture(const char* fileName, int maxW, int maxH)
{
int siHandle = LoadSoftImage(fileName);
if (siHandle == -1) return false;
int imgW, imgH;
GetSoftImageSize(siHandle, &imgW, &imgH);
int copyW = (imgW > maxW) ? maxW : imgW;
int copyH = (imgH > maxH) ? maxH : imgH;
TexW = copyW;
TexH = copyH;
for (int y = 0; y < copyH; y++) {
for (int x = 0; x < copyW; x++) {
int r, g, b, a;
GetPixelSoftImage(siHandle, x, y, &r, &g, &b, &a);
Texture[y * maxW + x] = GetColor(r, g, b);
}
}
DeleteSoftImage(siHandle);
return true;
}
int dx[4] = { 0, 0, -2, 2 };
int dz[4] = { -2, 2, 0, 0 };
void Shuffle(int* a, int n)
{
for (int i = n - 1; i > 0; i--) {
int j = rand() % (i + 1);
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
void Dig(int x, int z)
{
int dir[4] = {0, 1, 2, 3};
Shuffle(dir, 4);
for (int i = 0; i < 4; i++) {
int nx = x + dx[dir[i]];
int nz = z + dz[dir[i]];
if (nx <= 0 || nz <= 0 || nx >= W - 1 || nz >= H - 1) continue;
if (maze[nz][nx] == 0) continue;
maze[nz][nx] = 0;
maze[z + dz[dir[i]] / 2][x + dx[dir[i]] / 2] = 0;
Dig(nx, nz);
}
}
void UpdateCameraWithCollision(Camera& cam)
{
float speed = 0.05f;
float oldX = cam.x;
float oldZ = cam.z;
if (CheckHitKey(KEY_INPUT_LEFT)) cam.yaw += 0.04f;
if (CheckHitKey(KEY_INPUT_RIGHT)) cam.yaw -= 0.04f;
float dx = sinf(cam.yaw);
float dz = cosf(cam.yaw);
if (CheckHitKey(KEY_INPUT_UP)) {
cam.x -= dx * speed;
cam.z += dz * speed;
}
if (CheckHitKey(KEY_INPUT_DOWN)) {
cam.x += dx * speed;
cam.z -= dz * speed;
}
int mapX = (int)(cam.x / CELL);
int mapZ = (int)(cam.z / CELL);
if (mapX < 0 || mapX >= W || mapZ < 0 || mapZ >= H || maze[mapZ][mapX] == 1) {
cam.x = oldX;
cam.z = oldZ;
}
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
ChangeWindowMode(TRUE);
SetGraphMode(WIDTH * 2, HEIGHT * 2, 32);
SetEmulation320x240(TRUE);
SetMainWindowText("PolygonSample2026 Semptember");
if (DxLib_Init() < 0) return -1;
SetDrawScreen(DX_SCREEN_BACK);
soft = MakeARGB8ColorSoftImage(WIDTH, HEIGHT);
int graph = CreateGraphFromSoftImage(soft);
Load24BitBmpToTexture("wall02.bmp", 1024 / 4, 1024 / 4);
srand(GetTickCount());
cam.height = 0.5f;
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
maze[y][x] = 1;
Dig(1, 1);
maze[1][1] = 0;
cam.x = 1.0f * CELL + CELL * 0.5f;
cam.z = 1.0f * CELL + CELL * 0.5f;
cam.y = 0.5f;
cam.yaw = 0.0f;
////cam.height = 0.5f;
while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
{
ClearDrawScreen();
FillSoftImage(soft, 0, 0, 0, 255);
memset(ZBuf, 0, sizeof(ZBuf));
UpdateCameraWithCollision(cam);
DrawFloorFast();
DrawMaze();
graph = CreateGraphFromSoftImage(soft);
DrawGraph(0, 0, graph, FALSE);
ScreenFlip();
WaitTimer(1000 / 60);
}
DeleteGraph(graph);
DeleteSoftImage(soft);
DxLib_End();
return 0;
}