さて、前回のグーローシェーディング付きテクスチャーマッピングと
フォンシェーディング付きテクスチャーマッピングはバグがあったのが
なんとなく推察できたかもしれないですが、作者こと自分の理解度不足
があったことが全ての原因です。
という訳で今回はテクスチャーのグーローとフォンが治るまで
シャドウを作ります。
#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;
}
#include"DXLib.h"
#include<math.h>
#include<stdlib.h>
#include<windows.h>
#define CELL 1.0f
const float FOV_F = 600.0f;
const int HEIGHT = 240;
const int WIDTH = 320;
struct VTX {
float x, y, z;
float u, v;
float b; // 頂点の明るさ(グーローシェーディング用)
};
struct Camera
{
float x, y, z;
float yaw; // 左右回転のみ
float height;
};
enum WallDir
{
WALL_NORTH,
WALL_SOUTH,
WALL_WEST,
WALL_EAST
};
struct Vec3
{
float x;
float y;
float z;
};
float ZBuf[HEIGHT][WIDTH];
#define RAD (3.14159265/180.0)
Camera cam;
// ==================================================
// ライティング関連
// ・平行光源+環境光で「面の向き」による明るさのベースを作る
// ・さらにプレイヤー位置を松明(点光源)とみなし、距離減衰を加算する
// → 同じ壁の中でも頂点ごとに明るさが変わるので、グーローシェーディングで
// 補間する意味が出てくる(平行光源だけだと壁1枚は全頂点同じ明るさになるため)
// ==================================================
Vec3 g_LightDir = { -0.4f, -1.0f, -0.3f };
float g_Ambient = 0.1f;
float g_TorchIntensity = 1.2f/2.0f; // デバッグ中は8.0fにしたりしたが、通常運用値に戻す
float g_TorchFalloff = 0.3f/2.0f; // デバッグ中は0.15fにしたりしたが、通常運用値に戻す
// テクスチャを外して明るさだけをグレースケール表示するデバッグモード
// (原因切り分け用。普段はfalseのままでOK)
bool g_DebugBrightnessOnly = false;
void NormalizeLightDir()
{
float len = sqrtf(g_LightDir.x*g_LightDir.x + g_LightDir.y*g_LightDir.y + g_LightDir.z*g_LightDir.z);
if (len > 0.0001f)
{
g_LightDir.x /= len;
g_LightDir.y /= len;
g_LightDir.z /= len;
}
}
// 法線ベクトルだけから明るさ(0〜1)を計算(平行光源+環境光のみ)
float ComputeBrightness(Vec3 normal)
{
float len = sqrtf(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);
if (len > 0.0001f)
{
normal.x /= len;
normal.y /= len;
normal.z /= len;
}
float ndotl = -(normal.x * g_LightDir.x + normal.y * g_LightDir.y + normal.z * g_LightDir.z);
if (ndotl < 0.0f) ndotl = 0.0f;
float b = g_Ambient + (1.0f - g_Ambient) * ndotl;
if (b > 1.0f) b = 1.0f;
return b;
}
// 頂点のワールド座標+法線から、その頂点1点の明るさを計算する
// (平行光源のベース + プレイヤー位置=松明からの距離減衰)
float ComputeVertexBrightness(Vec3 worldPos, Vec3 normal)
{
float base = ComputeBrightness(normal);
float dx = worldPos.x - cam.x;
float dy = worldPos.y - cam.y;
float dz = worldPos.z - cam.z;
float distSq = dx*dx + dy*dy + dz*dz;
float torch = g_TorchIntensity / (1.0f + g_TorchFalloff * distSq);
float total = base + torch;
if (total > 1.0f) total = 1.0f;
if (total < 0.0f) total = 0.0f;
return total;
}
// テクスチャ色(0xRRGGBB)にbrightnessを掛けて返す
inline int ShadeColor(int col, float brightness)
{
int r = (col >> 16) & 0xFF;
int g = (col >> 8) & 0xFF;
int b = col & 0xFF;
r = (int)(r * brightness);
g = (int)(g * brightness);
b = (int)(b * brightness);
if (r > 255) r = 255; if (r < 0) r = 0;
if (g > 255) g = 255; if (g < 0) g = 0;
if (b > 255) b = 255; if (b < 0) b = 0;
return GetColor(r, g, b);
}
// ==================================================
int L[HEIGHT];
int R[HEIGHT];
int LC[HEIGHT];
int RC[HEIGHT];
float LU[HEIGHT];
float LV[HEIGHT];
float RU[HEIGHT];
float RV[HEIGHT];
float LW[HEIGHT];
float RW[HEIGHT];
float LB[HEIGHT]; // 左エッジの明るさ
float RB[HEIGHT]; // 右エッジの明るさ
int soft;
int Texture[1024*2048]={0};
int TexW=0;
int TexH=0;
#define W 21
#define H 21
int maze[H][W]; // 0=通路, 1=壁
const float WALL_H = 1.0f;
Vec3 GetWallNormal(WallDir dir);
void DrawFloorCell(float x, float z);
void TexTri(VTX n1,VTX n2,VTX n3);
bool TransformAndProject(float wx, float wy, float wz, float u, float v, float brightness, VTX& out);
void DrawWall(float x, float z, WallDir dir);
inline void Swap(VTX& a, VTX& b);
#define MAX_VTX 1000
#define MAX_POL 3000
int MAXVTX=0;
int MAXPOL=0;
// 2点を結ぶエッジの情報を保持する構造体
struct Edge {
float x, invZ, uz, vz;
};
VTX v[MAX_VTX]={0};
int PolygonNumber[MAX_POL][4]={0};
void TexTri_Complex(VTX,VTX,VTX);
void TexTri_Simple(VTX,VTX,VTX);
#define TexTri TexTri_Complex
inline void SetV(int num,float x,float y,float z)
{
if(num<0 || num>MAX_VTX) return;
v[num].x=x;
v[num].y=y;
v[num].z=z;
}
inline void Set3Poly(int num,int n1,int n2,int n3)
{
if(num<0 || num>MAX_POL) return;
PolygonNumber[num][0]=n1;
PolygonNumber[num][1]=n2;
PolygonNumber[num][2]=n3;
}
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;
}
// ==================================================
// ニアプレーン・クリッピング
// ・カメラのすぐ近く/背後にまたがる四角形を、丸ごと捨てるのではなく
// ニアプレーンで切って新しい凸多角形を作ってから描画するための仕組み
// ・DrawFloorCell と DrawWall はどちらもこれ経由で描画する
// ==================================================
struct WVtx { float wx, wy, wz, u, v, b; };
// 凸多角形(ワールド座標)を view空間の z > EPS でクリッピングする
// out には最大 n+1 個まで書き込まれる可能性がある
int ClipPolygonNearPlane(WVtx* in, int n, WVtx* out)
{
const float EPS = 0.05f; // TransformAndProjectの0.01fよりわずかに手前で切る
float c = cosf(-cam.yaw), s = sinf(-cam.yaw);
int outCount = 0;
for (int i = 0; i < n; i++)
{
WVtx& curr = in[i];
WVtx& 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);
WVtx 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.b = curr.b + (next.b - curr.b ) * t;
out[outCount++] = mid;
}
}
return outCount;
}
// クリッピング+投影+ファン三角形分割をまとめた共通関数
void DrawTexturedQuadWorld(WVtx p0, WVtx p1, WVtx p2, WVtx p3)
{
WVtx in[4] = { p0, p1, p2, p3 };
WVtx clipped[5]; // 四角形をニアプレーンで切ると最大5頂点になる
int n = ClipPolygonNearPlane(in, 4, clipped);
if (n < 3) return; // 完全にカメラの裏側 → 描画しない
VTX proj[5];
for (int i = 0; i < n; i++)
{
if (!TransformAndProject(clipped[i].wx, clipped[i].wy, clipped[i].wz,
clipped[i].u, clipped[i].v, clipped[i].b, proj[i]))
return; // クリッピング後なら通常ここには来ないはず
}
for (int i = 1; i + 1 < n; i++)
TexTri(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 };
float b0 = ComputeVertexBrightness(p0, billboardNormal);
float b1v = ComputeVertexBrightness(p1, billboardNormal);
float b2 = ComputeVertexBrightness(p2, billboardNormal);
float b3 = ComputeVertexBrightness(p3, billboardNormal);
WVtx w0 = { p0.x, p0.y, p0.z, 0.0f, 1.0f, b0 };
WVtx w1 = { p1.x, p1.y, p1.z, 1.0f, 1.0f, b1v };
WVtx w2 = { p2.x, p2.y, p2.z, 1.0f, 0.0f, b2 };
WVtx w3 = { p3.x, p3.y, p3.z, 0.0f, 0.0f, b3 };
DrawTexturedQuadWorld(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; }
}
}
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) {
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;
}
}
}
// --- 壁の描画 ---
#define WALL_SUBDIV_H 4 // 壁の横方向(幅)の分割数
#define WALL_SUBDIV_V 2 // 壁の縦方向(高さ)の分割数
#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;
}
// 同じセル内で、壁dirのt=0端 / t=1端にそれぞれ隣接する「直交する壁」の方向
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;
}
// 壁1頂点の「なめらかにした法線」
// t: 壁の横方向位置(0~1) yFrac: 壁の高さ方向位置(0=床側, 1=天井側)
Vec3 GetSmoothedWallNormal(WallDir dir, int ix, int iz, float t, float yFrac)
{
Vec3 wallNormal = GetWallNormal(dir);
// 床との境界(y=0に近いほど床の法線を混ぜる)
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; // 自分の法線を基準の重み1.0として開始
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);
}
// 床1頂点の「なめらかにした法線」
// uGlobal, vGlobal: セル全体を0~1とした位置(DrawFloorCellのu0,v0等をそのまま渡せる)
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];
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);
float b0 = ComputeVertexBrightness(p0, n0);
float b1 = ComputeVertexBrightness(p1, n1);
float b2 = ComputeVertexBrightness(p2, n2);
float b3 = ComputeVertexBrightness(p3, n3);
WVtx w0 = { p0.x, p0.y, p0.z, t0, v0, b0 };
WVtx w1 = { p1.x, p1.y, p1.z, t1, v0, b1 };
WVtx w2 = { p2.x, p2.y, p2.z, t1, v1, b2 };
WVtx w3 = { p3.x, p3.y, p3.z, t0, v1, b3 };
DrawTexturedQuadWorld(w0, w1, w2, w3);
}
}
}
// --- 床(迷路の通路セル1マス分)をグーローシェーディング付きで描画 ---
#define FLOOR_SUBDIV 4 // 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);
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);
float b0 = ComputeVertexBrightness(p0, n0) + FLOOR_AMBIENT_BOOST;
float b1 = ComputeVertexBrightness(p1, n1) + FLOOR_AMBIENT_BOOST;
float b2 = ComputeVertexBrightness(p2, n2) + FLOOR_AMBIENT_BOOST;
float b3 = ComputeVertexBrightness(p3, n3) + FLOOR_AMBIENT_BOOST;
if (b0 > 1.0f) b0 = 1.0f;
if (b1 > 1.0f) b1 = 1.0f;
if (b2 > 1.0f) b2 = 1.0f;
if (b3 > 1.0f) b3 = 1.0f;
WVtx w0 = { p0.x, p0.y, p0.z, u0, v0, b0 };
WVtx w1 = { p1.x, p1.y, p1.z, u1, v0, b1 };
WVtx w2 = { p2.x, p2.y, p2.z, u1, v1, b2 };
WVtx w3 = { p3.x, p3.y, p3.z, u0, v1, b3 };
DrawTexturedQuadWorld(w0, w1, w2, w3);
}
}
}
// --- 床の高速描画(レイキャスト式・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);
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);
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,0,0,255);
FillSoftImage(soft,0,200,255,255);
memset(ZBuf, 0, sizeof(ZBuf));
UpdateCameraWithCollision(cam);
UpdateCamera(cam);
DrawWorld();
DrawMaze();
graph = CreateGraphFromSoftImage(soft);
DrawGraph(0, 0, graph, FALSE);
DeleteGraph(graph);
ScreenFlip();
WaitTimer(1000/60);
}
DeleteGraph(graph);
DeleteSoftImage(soft);
DxLib_End();
return 0;
}
こんな感じのグーローシェーディング付きテクスチャーマッピングができました。
次回の記事3DCGの理屈記事に、フォンシェーディング付きテクスチャーマッピングを載せます。
次の3DCGの理屈の記事をまた見てもらえると嬉しいです。