全站首发,3D我的世界
2026-08-24 16:32:02
发布于:北京
// =============================================================================
// 3D Minecraft Clone - Pure C++ Software Renderer
// No external libraries - uses only Win32 API and GDI
// Features: 3D rendering with simple lighting, FPS movement, mine/place blocks
// =============================================================================
#include <windows.h>
#include <cmath>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <algorithm>
// =============================================================================
// MATH LIBRARY
// =============================================================================
struct Vec3 {
float x, y, z;
Vec3(float x=0, float y=0, float z=0) : x(x), y(y), z(z) {}
Vec3 operator+(const Vec3& v) const { return Vec3(x+v.x, y+v.y, z+v.z); }
Vec3 operator-(const Vec3& v) const { return Vec3(x-v.x, y-v.y, z-v.z); }
Vec3 operator*(float s) const { return Vec3(x*s, y*s, z*s); }
Vec3 operator/(float s) const { return Vec3(x/s, y/s, z/s); }
float dot(const Vec3& v) const { return x*v.x + y*v.y + z*v.z; }
Vec3 cross(const Vec3& v) const { return Vec3(y*v.z-z*v.y, z*v.x-x*v.z, x*v.y-y*v.x); }
float length() const { return sqrtf(x*x + y*y + z*z); }
Vec3 normalized() const { float l = length(); return l > 0 ? *this / l : Vec3(); }
};
struct Vec2 {
float x, y;
Vec2(float x=0, float y=0) : x(x), y(y) {}
Vec2 operator+(const Vec2& v) const { return Vec2(x+v.x, y+v.y); }
Vec2 operator-(const Vec2& v) const { return Vec2(x-v.x, y-v.y); }
Vec2 operator*(float s) const { return Vec2(x*s, y*s); }
};
// =============================================================================
// GLOBALS
// =============================================================================
const int SCREEN_W = 800;
const int SCREEN_H = 600;
const float FOV = 60.0f * 3.14159f / 180.0f;
const float NEAR_PLANE = 0.1f;
const float FAR_PLANE = 200.0f;
// World dimensions
const int WORLD_X = 16;
const int WORLD_Y = 16;
const int WORLD_Z = 16;
// Block types
enum BlockType { BLOCK_AIR = 0, BLOCK_GRASS = 1, BLOCK_DIRT = 2, BLOCK_STONE = 3,
BLOCK_WOOD = 4, BLOCK_LEAVES = 5, BLOCK_SAND = 6, BLOCK_WATER = 7 };
// Block colors (RGB)
struct Color { unsigned char r, g, b; };
const Color BLOCK_COLORS[] = {
{0, 0, 0}, // AIR
{60, 160, 40}, // GRASS - green top
{120, 80, 40}, // DIRT - brown
{130, 130, 130}, // STONE - gray
{160, 110, 60}, // WOOD - brown
{40, 140, 40}, // LEAVES - dark green
{210, 200, 140}, // SAND - tan
{50, 100, 200}, // WATER - blue
};
// World data: [x][y][z]
BlockType world[WORLD_X][WORLD_Y][WORLD_Z];
// Player
Vec3 playerPos(8.0f, 10.0f, 8.0f);
Vec3 playerVel(0, 0, 0);
float playerYaw = 0.0f; // left/right rotation
float playerPitch = 0.0f; // up/down rotation
const float PLAYER_HEIGHT = 1.7f;
const float PLAYER_RADIUS = 0.3f;
// Input state
bool keys[256] = {false};
bool mouseLeftDown = false;
bool mouseRightDown = false;
int mouseX = SCREEN_W / 2, mouseY = SCREEN_H / 2;
bool cursorCaptured = true;
// Rendering
HBITMAP hBitmap = NULL;
HDC hDCCanvas = NULL;
unsigned char* frameBuffer = NULL;
float* depthBuffer = NULL;
const int FB_SIZE = SCREEN_W * SCREEN_H * 4;
// Timing
DWORD lastTime = 0;
float deltaTime = 0;
// Inventory
BlockType selectedBlock = BLOCK_STONE;
const BlockType INVENTORY[] = {BLOCK_GRASS, BLOCK_DIRT, BLOCK_STONE, BLOCK_WOOD, BLOCK_LEAVES, BLOCK_SAND};
const int INVENTORY_SIZE = 6;
int inventoryIndex = 2;
// Crosshair / UI
bool showHelp = true;
// =============================================================================
// WORLD GENERATION
// =============================================================================
void generateWorld() {
srand(42);
for (int x = 0; x < WORLD_X; x++) {
for (int z = 0; z < WORLD_Z; z++) {
// Height map with simple noise
int height = 6 + (int)(3 * sinf(x * 0.5f) * cosf(z * 0.5f) + 2 * sinf(x * 0.3f + z * 0.7f));
height = std::max(2, std::min(WORLD_Y - 2, height));
for (int y = 0; y < WORLD_Y; y++) {
if (y == 0) {
world[x][y][z] = BLOCK_STONE;
} else if (y < height - 1) {
world[x][y][z] = BLOCK_STONE;
} else if (y < height) {
world[x][y][z] = BLOCK_DIRT;
} else if (y == height) {
// Surface
if (height <= 3) {
world[x][y][z] = BLOCK_SAND;
} else {
world[x][y][z] = BLOCK_GRASS;
}
} else {
world[x][y][z] = BLOCK_AIR;
}
}
}
}
// Add some trees
for (int t = 0; t < 8; t++) {
int tx = 2 + rand() % (WORLD_X - 4);
int tz = 2 + rand() % (WORLD_Z - 4);
int ty = 0;
for (int y = 0; y < WORLD_Y; y++) {
if (world[tx][y][tz] == BLOCK_GRASS) { ty = y + 1; break; }
}
if (ty > 0 && ty + 5 < WORLD_Y) {
// Trunk
for (int i = 0; i < 4; i++) {
world[tx][ty + i][tz] = BLOCK_WOOD;
}
// Leaves
for (int dx = -2; dx <= 2; dx++) {
for (int dz = -2; dz <= 2; dz++) {
for (int dy = -1; dy <= 2; dy++) {
int lx = tx + dx, ly = ty + 3 + dy, lz = tz + dz;
if (lx >= 0 && lx < WORLD_X && ly >= 0 && ly < WORLD_Y && lz >= 0 && lz < WORLD_Z) {
if (world[lx][ly][lz] == BLOCK_AIR) {
if (abs(dx) + abs(dz) + abs(dy) <= 3) {
world[lx][ly][lz] = BLOCK_LEAVES;
}
}
}
}
}
}
}
}
// Water level
int waterLevel = 3;
for (int x = 0; x < WORLD_X; x++) {
for (int z = 0; z < WORLD_Z; z++) {
for (int y = 1; y <= waterLevel; y++) {
if (world[x][y][z] == BLOCK_AIR) {
world[x][y][z] = BLOCK_WATER;
}
}
}
}
}
bool isSolid(BlockType b) {
return b != BLOCK_AIR && b != BLOCK_WATER;
}
bool isOpaque(BlockType b) {
return b != BLOCK_AIR && b != BLOCK_WATER && b != BLOCK_LEAVES;
}
BlockType getBlock(int x, int y, int z) {
if (x < 0 || x >= WORLD_X || y < 0 || y >= WORLD_Y || z < 0 || z >= WORLD_Z) return BLOCK_STONE;
return world[x][y][z];
}
void setBlock(int x, int y, int z, BlockType b) {
if (x < 0 || x >= WORLD_X || y < 0 || y >= WORLD_Y || z < 0 || z >= WORLD_Z) return;
world[x][y][z] = b;
}
// =============================================================================
// RAYCASTING FOR MINE/PLACE
// =============================================================================
struct RayHit {
bool hit;
int bx, by, bz; // block position
int faceX, faceY, faceZ; // face normal
float dist;
};
RayHit raycastBlock(Vec3 origin, Vec3 dir, float maxDist) {
RayHit result = {false, 0, 0, 0, 0, 0, 0, 0};
float t = 0;
Vec3 pos = origin;
Vec3 lastPos = origin;
// DDA-like approach
float stepSize = 0.05f;
while (t < maxDist) {
Vec3 next = origin + dir * t;
int ix = (int)floorf(next.x);
int iy = (int)floorf(next.y);
int iz = (int)floorf(next.z);
if (ix >= 0 && ix < WORLD_X && iy >= 0 && iy < WORLD_Y && iz >= 0 && iz < WORLD_Z) {
BlockType b = world[ix][iy][iz];
if (isSolid(b)) {
result.hit = true;
result.bx = ix;
result.by = iy;
result.bz = iz;
result.dist = t;
// Determine face
Vec3 center(ix + 0.5f, iy + 0.5f, iz + 0.5f);
Vec3 diff = next - center;
float ax = fabsf(diff.x), ay = fabsf(diff.y), az = fabsf(diff.z);
if (ax > ay && ax > az) {
result.faceX = diff.x > 0 ? 1 : -1;
result.faceY = 0; result.faceZ = 0;
} else if (ay > az) {
result.faceX = 0;
result.faceY = diff.y > 0 ? 1 : -1;
result.faceZ = 0;
} else {
result.faceX = 0; result.faceY = 0;
result.faceZ = diff.z > 0 ? 1 : -1;
}
return result;
}
}
t += stepSize;
}
return result;
}
// =============================================================================
// 3D RENDERING ENGINE
// =============================================================================
// Vertex structure for clipping/rendering pipeline
struct Vertex {
Vec3 pos; // world space
Vec3 normal;
Vec2 uv;
Color color;
float light; // lighting value 0-1
};
// Face/Quad definition
struct Quad {
Vertex v[4];
BlockType blockType;
Vec3 faceNormal;
};
// Transform world to camera space
Vec3 worldToCamera(Vec3 p, Vec3 camPos, float yaw, float pitch) {
// Translate
Vec3 t = p - camPos;
// Rotate Y (yaw)
float cy = cosf(-yaw), sy = sinf(-yaw);
Vec3 r1(t.x * cy + t.z * sy, t.y, -t.x * sy + t.z * cy);
// Rotate X (pitch)
float cp = cosf(-pitch), sp = sinf(-pitch);
Vec3 r2(r1.x, r1.y * cp - r1.z * sp, r1.y * sp + r1.z * cp);
return r2;
}
// Project camera-space point to screen
bool projectToScreen(Vec3 camPos, Vec3 p, int& sx, int& sy, float& depth) {
Vec3 cp = worldToCamera(p, camPos, playerYaw, playerPitch);
if (cp.z <= NEAR_PLANE) return false;
if (cp.z > FAR_PLANE) return false;
float fovScale = 1.0f / tanf(FOV * 0.5f);
float aspect = (float)SCREEN_W / SCREEN_H;
float ndcX = (cp.x * fovScale) / (aspect * cp.z);
float ndcY = (cp.y * fovScale) / cp.z;
sx = (int)((ndcX * 0.5f + 0.5f) * SCREEN_W);
sy = (int)((1.0f - (ndcY * 0.5f + 0.5f)) * SCREEN_H);
depth = cp.z;
return true;
}
// Simple Lambertian lighting
float computeLighting(Vec3 blockCenter, Vec3 faceNormal, BlockType bt) {
// Sun direction (light travels FROM sun TO scene)
// Sun is above and slightly to +x,+z
Vec3 sunDir = Vec3(0.3f, 1.0f, 0.4f).normalized();
// Negated: light coming FROM above means sun direction is up,
// but for Lambert: light = max(0, N路L) where L is TO-LIGHT direction
// We want top faces (normal +Y) to be brightest
// Ambient light (minimum visibility)
float ambient = 0.32f;
// Diffuse: N dot L where L is direction TO sun (upward)
// faceNormal with +Y component 鈫?brighter
float ndotl = faceNormal.dot(sunDir);
float diffuse = std::max(0.0f, ndotl) * 0.68f;
// Slight variation based on position (fake AO)
float aoVar = sinf(blockCenter.x * 1.7f) * cosf(blockCenter.z * 1.3f) * 0.5f;
float ao = 1.0f - 0.06f * aoVar;
float total = ambient + diffuse;
total = std::max(0.22f, std::min(1.0f, total * ao));
// Water is translucent - slightly brighter/different
if (bt == BLOCK_WATER) total = std::min(1.0f, total * 1.2f + 0.1f);
return total;
}
// Collect all visible quads
void collectQuads(std::vector<Quad>& quads) {
for (int x = 0; x < WORLD_X; x++) {
for (int y = 0; y < WORLD_Y; y++) {
for (int z = 0; z < WORLD_Z; z++) {
BlockType bt = world[x][y][z];
if (bt == BLOCK_AIR) continue;
// Frustum cull - simple distance check
Vec3 center(x + 0.5f, y + 0.5f, z + 0.5f);
Vec3 toBlock = center - playerPos;
if (toBlock.length() > FAR_PLANE) continue;
// Check each face - only render if neighbor is non-opaque or air
// Negative X face
if (!isOpaque(getBlock(x-1, y, z))) {
Quad q;
q.blockType = bt;
q.faceNormal = Vec3(-1, 0, 0);
q.v[0] = {Vec3(x, y, z), q.faceNormal, Vec2(0,0), BLOCK_COLORS[bt], 0};
q.v[1] = {Vec3(x, y+1, z), q.faceNormal, Vec2(0,1), BLOCK_COLORS[bt], 0};
q.v[2] = {Vec3(x, y+1, z+1), q.faceNormal, Vec2(1,1), BLOCK_COLORS[bt], 0};
q.v[3] = {Vec3(x, y, z+1), q.faceNormal, Vec2(1,0), BLOCK_COLORS[bt], 0};
for (int i = 0; i < 4; i++) q.v[i].light = computeLighting(center, q.faceNormal, bt);
quads.push_back(q);
}
// Positive X face
if (!isOpaque(getBlock(x+1, y, z))) {
Quad q;
q.blockType = bt;
q.faceNormal = Vec3(1, 0, 0);
q.v[0] = {Vec3(x+1, y, z+1), q.faceNormal, Vec2(0,0), BLOCK_COLORS[bt], 0};
q.v[1] = {Vec3(x+1, y+1, z+1), q.faceNormal, Vec2(0,1), BLOCK_COLORS[bt], 0};
q.v[2] = {Vec3(x+1, y+1, z), q.faceNormal, Vec2(1,1), BLOCK_COLORS[bt], 0};
q.v[3] = {Vec3(x+1, y, z), q.faceNormal, Vec2(1,0), BLOCK_COLORS[bt], 0};
for (int i = 0; i < 4; i++) q.v[i].light = computeLighting(center, q.faceNormal, bt);
quads.push_back(q);
}
// Negative Y face (bottom)
if (!isOpaque(getBlock(x, y-1, z))) {
Quad q;
q.blockType = bt;
q.faceNormal = Vec3(0, -1, 0);
q.v[0] = {Vec3(x, y, z), q.faceNormal, Vec2(0,0), BLOCK_COLORS[bt], 0};
q.v[1] = {Vec3(x+1, y, z), q.faceNormal, Vec2(1,0), BLOCK_COLORS[bt], 0};
q.v[2] = {Vec3(x+1, y, z+1), q.faceNormal, Vec2(1,1), BLOCK_COLORS[bt], 0};
q.v[3] = {Vec3(x, y, z+1), q.faceNormal, Vec2(0,1), BLOCK_COLORS[bt], 0};
for (int i = 0; i < 4; i++) q.v[i].light = computeLighting(center, q.faceNormal, bt);
quads.push_back(q);
}
// Positive Y face (top)
if (!isOpaque(getBlock(x, y+1, z))) {
Quad q;
q.blockType = bt;
q.faceNormal = Vec3(0, 1, 0);
q.v[0] = {Vec3(x, y+1, z+1), q.faceNormal, Vec2(0,0), BLOCK_COLORS[bt], 0};
q.v[1] = {Vec3(x+1, y+1, z+1), q.faceNormal, Vec2(1,0), BLOCK_COLORS[bt], 0};
q.v[2] = {Vec3(x+1, y+1, z), q.faceNormal, Vec2(1,1), BLOCK_COLORS[bt], 0};
q.v[3] = {Vec3(x, y+1, z), q.faceNormal, Vec2(0,1), BLOCK_COLORS[bt], 0};
for (int i = 0; i < 4; i++) q.v[i].light = computeLighting(center, q.faceNormal, bt);
quads.push_back(q);
}
// Negative Z face
if (!isOpaque(getBlock(x, y, z-1))) {
Quad q;
q.blockType = bt;
q.faceNormal = Vec3(0, 0, -1);
q.v[0] = {Vec3(x+1, y, z), q.faceNormal, Vec2(0,0), BLOCK_COLORS[bt], 0};
q.v[1] = {Vec3(x+1, y+1, z), q.faceNormal, Vec2(0,1), BLOCK_COLORS[bt], 0};
q.v[2] = {Vec3(x, y+1, z), q.faceNormal, Vec2(1,1), BLOCK_COLORS[bt], 0};
q.v[3] = {Vec3(x, y, z), q.faceNormal, Vec2(1,0), BLOCK_COLORS[bt], 0};
for (int i = 0; i < 4; i++) q.v[i].light = computeLighting(center, q.faceNormal, bt);
quads.push_back(q);
}
// Positive Z face
if (!isOpaque(getBlock(x, y, z+1))) {
Quad q;
q.blockType = bt;
q.faceNormal = Vec3(0, 0, 1);
q.v[0] = {Vec3(x, y, z+1), q.faceNormal, Vec2(0,0), BLOCK_COLORS[bt], 0};
q.v[1] = {Vec3(x, y+1, z+1), q.faceNormal, Vec2(0,1), BLOCK_COLORS[bt], 0};
q.v[2] = {Vec3(x+1, y+1, z+1), q.faceNormal, Vec2(1,1), BLOCK_COLORS[bt], 0};
q.v[3] = {Vec3(x+1, y, z+1), q.faceNormal, Vec2(1,0), BLOCK_COLORS[bt], 0};
for (int i = 0; i < 4; i++) q.v[i].light = computeLighting(center, q.faceNormal, bt);
quads.push_back(q);
}
}
}
}
}
// =============================================================================
// RASTERIZATION
// =============================================================================
// Edge structure for scanline
struct Edge {
float x1, y1, x2, y2;
float z1, z2;
float u1, v1, u2, v2;
float l1, l2;
Color c1, c2;
};
void drawSpan(int y, float x1, float x2, float z1, float z2, float l1, float l2, Color c1, Color c2) {
if (x2 < x1) {
std::swap(x1, x2); std::swap(z1, z2); std::swap(l1, l2);
std::swap(c1, c2);
}
int sx = (int)ceilf(x1);
int ex = (int)ceilf(x2);
if (sx < 0) sx = 0;
if (ex > SCREEN_W) ex = SCREEN_W;
if (sx >= ex) return;
if (y < 0 || y >= SCREEN_H) return;
float dx = x2 - x1;
if (fabsf(dx) < 0.0001f) return;
for (int x = sx; x < ex; x++) {
float t = (x - x1) / dx;
float depth = z1 + (z2 - z1) * t;
if (depth < NEAR_PLANE || depth > FAR_PLANE) continue;
int idx = y * SCREEN_W + x;
if (depth < depthBuffer[idx]) {
depthBuffer[idx] = depth;
float light = l1 + (l2 - l1) * t;
unsigned char r = (unsigned char)(c1.r * light);
unsigned char g = (unsigned char)(c1.g * light);
unsigned char b = (unsigned char)(c1.b * light);
// Simple fog
float fogFactor = (depth - 30.0f) / 80.0f;
fogFactor = std::max(0.0f, std::min(1.0f, fogFactor));
r = (unsigned char)(r * (1 - fogFactor) + 180 * fogFactor);
g = (unsigned char)(g * (1 - fogFactor) + 200 * fogFactor);
b = (unsigned char)(b * (1 - fogFactor) + 220 * fogFactor);
int fbIdx = idx * 4;
frameBuffer[fbIdx] = b;
frameBuffer[fbIdx + 1] = g;
frameBuffer[fbIdx + 2] = r;
frameBuffer[fbIdx + 3] = 255;
}
}
}
void drawScanlineQuad(const Quad& q) {
// Project all 4 vertices
int sx[4], sy[4];
float depth[4];
float l[4];
Color c[4];
for (int i = 0; i < 4; i++) {
if (!projectToScreen(playerPos, q.v[i].pos, sx[i], sy[i], depth[i])) return;
l[i] = q.v[i].light;
c[i] = q.v[i].color;
}
// Backface culling
int dx1 = sx[1] - sx[0];
int dy1 = sy[1] - sy[0];
int dx2 = sx[2] - sx[0];
int dy2 = sy[2] - sy[0];
float cross = dx1 * dy2 - dy1 * dx2;
if (cross <= 0) return; // backface
// Find bounding box
int minY = sy[0], maxY = sy[0];
for (int i = 1; i < 4; i++) {
if (sy[i] < minY) minY = sy[i];
if (sy[i] > maxY) maxY = sy[i];
}
if (minY < 0) minY = 0;
if (maxY >= SCREEN_H) maxY = SCREEN_H - 1;
// For each scanline, interpolate along left and right edges
// Use barycentric-like approach: split quad into 2 triangles
// Triangle 1: 0,1,2
auto drawTriangle = [&](int i0, int i1, int i2) {
// Sort by Y
int indices[3] = {i0, i1, i2};
for (int a = 0; a < 2; a++) {
for (int b = a+1; b < 3; b++) {
if (sy[indices[a]] > sy[indices[b]]) std::swap(indices[a], indices[b]);
}
}
int y0 = sy[indices[0]], y1 = sy[indices[1]], y2 = sy[indices[2]];
if (y0 == y1 && y1 == y2) return;
if (y2 < 0 || y0 >= SCREEN_H) return;
// Edge function
auto edgeEval = [&](int x, int y, int iA, int iB) {
return (x - sx[iA]) * (sy[iB] - sy[iA]) - (y - sy[iA]) * (sx[iB] - sx[iA]);
};
int minX = std::min(std::min(sx[i0], sx[i1]), sx[i2]);
int maxX = std::max(std::max(sx[i0], sx[i1]), sx[i2]);
minX = std::max(0, minX);
maxX = std::min(SCREEN_W - 1, maxX);
int yStart = std::max(0, y0);
int yEnd = std::min(SCREEN_H - 1, y2);
for (int y = yStart; y <= yEnd; y++) {
for (int x = minX; x <= maxX; x++) {
int w0 = edgeEval(x, y, indices[1], indices[2]);
int w1 = edgeEval(x, y, indices[2], indices[0]);
int w2 = edgeEval(x, y, indices[0], indices[1]);
if (w0 >= 0 && w1 >= 0 && w2 >= 0) {
float area = (float)(w0 + w1 + w2);
if (area < 0.5f) continue;
float b0 = w0 / area;
float b1 = w1 / area;
float b2 = w2 / area;
float d = b0 * depth[indices[0]] + b1 * depth[indices[1]] + b2 * depth[indices[2]];
if (d < NEAR_PLANE || d > FAR_PLANE) continue;
int idx = y * SCREEN_W + x;
if (d < depthBuffer[idx]) {
depthBuffer[idx] = d;
float light = b0 * l[indices[0]] + b1 * l[indices[1]] + b2 * l[indices[2]];
unsigned char cr = (unsigned char)(b0 * c[indices[0]].r + b1 * c[indices[1]].r + b2 * c[indices[2]].r);
unsigned char cg = (unsigned char)(b0 * c[indices[0]].g + b1 * c[indices[1]].g + b2 * c[indices[2]].g);
unsigned char cb = (unsigned char)(b0 * c[indices[0]].b + b1 * c[indices[1]].b + b2 * c[indices[2]].b);
unsigned char r = (unsigned char)(cr * light);
unsigned char g = (unsigned char)(cg * light);
unsigned char b = (unsigned char)(cb * light);
// Fog
float fogFactor = (d - 30.0f) / 80.0f;
fogFactor = std::max(0.0f, std::min(1.0f, fogFactor));
r = (unsigned char)(r * (1 - fogFactor) + 180 * fogFactor);
g = (unsigned char)(g * (1 - fogFactor) + 200 * fogFactor);
b = (unsigned char)(b * (1 - fogFactor) + 220 * fogFactor);
int fbIdx = idx * 4;
frameBuffer[fbIdx] = b;
frameBuffer[fbIdx + 1] = g;
frameBuffer[fbIdx + 2] = r;
frameBuffer[fbIdx + 3] = 255;
}
}
}
}
};
drawTriangle(0, 1, 2);
drawTriangle(0, 2, 3);
}
// =============================================================================
// PLAYER MOVEMENT & PHYSICS
// =============================================================================
void updatePlayer(float dt) {
// Mouse look
float sensitivity = 0.002f;
// Get mouse delta (we'll use GetCursorPos)
POINT mousePos;
GetCursorPos(&mousePos);
int dx = mousePos.x - SCREEN_W / 2;
int dy = mousePos.y - SCREEN_H / 2;
if (cursorCaptured) {
playerYaw -= dx * sensitivity;
playerPitch -= dy * sensitivity;
playerPitch = std::max(-1.5f, std::min(1.5f, playerPitch));
// Re-center cursor
SetCursorPos(SCREEN_W / 2, SCREEN_H / 2);
}
// Calculate movement direction
Vec3 forward(sinf(playerYaw), 0, cosf(playerYaw));
Vec3 right(sinf(playerYaw + 3.14159f/2), 0, cosf(playerYaw + 3.14159f/2));
Vec3 moveDir(0, 0, 0);
if (keys['W'] || keys['w']) moveDir = moveDir + forward;
if (keys['S'] || keys['s']) moveDir = moveDir - forward;
if (keys['A'] || keys['a']) moveDir = moveDir + right;
if (keys['D'] || keys['d']) moveDir = moveDir - right;
if (moveDir.length() > 0) moveDir = moveDir.normalized();
float speed = 4.0f;
if (keys[VK_SHIFT]) speed = 8.0f; // sprint
Vec3 newPos = playerPos + moveDir * speed * dt;
// Collision detection (simple AABB vs blocks)
// Check X axis
Vec3 testPos = Vec3(newPos.x, playerPos.y, playerPos.z);
int bx = (int)floorf(testPos.x);
int by1 = (int)floorf(testPos.y - PLAYER_HEIGHT + 0.1f);
int by2 = (int)floorf(testPos.y + 0.1f);
int bz = (int)floorf(testPos.z);
bool collideX = false;
for (int cy = by1; cy <= by2; cy++) {
for (int cz = bz - 1; cz <= bz + 1; cz++) {
if (isSolid(getBlock(bx, cy, cz)) || isSolid(getBlock(bx+1, cy, cz))) {
collideX = true; break;
}
}
if (collideX) break;
}
if (!collideX) playerPos.x = newPos.x;
// Check Z axis
testPos = Vec3(playerPos.x, playerPos.y, newPos.z);
bx = (int)floorf(testPos.x);
bz = (int)floorf(testPos.z);
bool collideZ = false;
for (int cy = by1; cy <= by2; cy++) {
for (int cx = bx - 1; cx <= bx + 1; cx++) {
if (isSolid(getBlock(cx, cy, bz)) || isSolid(getBlock(cx, cy, bz+1))) {
collideZ = true; break;
}
}
if (collideZ) break;
}
if (!collideZ) playerPos.z = newPos.z;
// Gravity and Y collision
playerVel.y -= 15.0f * dt; // gravity
float newY = playerPos.y + playerVel.y * dt;
int by = (int)floorf(newY);
int byTop = (int)floorf(newY - PLAYER_HEIGHT + 0.1f);
bx = (int)floorf(playerPos.x);
bz = (int)floorf(playerPos.z);
bool collideY = false;
for (int cx = bx - 1; cx <= bx + 1; cx++) {
for (int cz = bz - 1; cz <= bz + 1; cz++) {
if (isSolid(getBlock(cx, by, cz)) || isSolid(getBlock(cx, byTop, cz))) {
collideY = true; break;
}
}
if (collideY) break;
}
if (collideY) {
if (playerVel.y < 0) {
// Landing - snap to block top
playerPos.y = floorf(newY) + 1.0f;
}
playerVel.y = 0;
} else {
playerPos.y = newY;
}
// Jump
if ((keys[' '] || keys[VK_SPACE]) && playerVel.y == 0) {
playerVel.y = 7.0f;
}
// Keep player in world bounds
if (playerPos.x < 0.5f) playerPos.x = 0.5f;
if (playerPos.x > WORLD_X - 0.5f) playerPos.x = WORLD_X - 0.5f;
if (playerPos.z < 0.5f) playerPos.z = 0.5f;
if (playerPos.z > WORLD_Z - 0.5f) playerPos.z = WORLD_Z - 0.5f;
if (playerPos.y < 1.0f) playerPos.y = 1.0f;
if (playerPos.y > WORLD_Y + 5) playerPos.y = WORLD_Y + 5;
}
// =============================================================================
// BLOCK INTERACTION
// =============================================================================
void handleBlockInteraction() {
Vec3 dir(sinf(playerYaw) * cosf(playerPitch), sinf(playerPitch), cosf(playerYaw) * cosf(playerPitch));
if (mouseLeftDown) {
RayHit hit = raycastBlock(playerPos, dir, 6.0f);
if (hit.hit && hit.dist >= 1.0f) {
setBlock(hit.bx, hit.by, hit.bz, BLOCK_AIR);
}
mouseLeftDown = false; // require re-click
}
if (mouseRightDown) {
RayHit hit = raycastBlock(playerPos, dir, 6.0f);
if (hit.hit) {
int px = hit.bx + hit.faceX;
int py = hit.by + hit.faceY;
int pz = hit.bz + hit.faceZ;
if (!isSolid(getBlock(px, py, pz))) {
setBlock(px, py, pz, selectedBlock);
}
}
mouseRightDown = false;
}
}
// =============================================================================
// UI RENDERING
// =============================================================================
void drawRect(int x, int y, int w, int h, unsigned char r, unsigned char g, unsigned char b) {
for (int py = y; py < y + h; py++) {
for (int px = x; px < x + w; px++) {
if (px >= 0 && px < SCREEN_W && py >= 0 && py < SCREEN_H) {
int idx = (py * SCREEN_W + px) * 4;
frameBuffer[idx] = b;
frameBuffer[idx + 1] = g;
frameBuffer[idx + 2] = r;
frameBuffer[idx + 3] = 255;
}
}
}
}
void drawCrosshair() {
int cx = SCREEN_W / 2, cy = SCREEN_H / 2;
drawRect(cx - 8, cy - 1, 16, 2, 255, 255, 255);
drawRect(cx - 1, cy - 8, 2, 16, 255, 255, 255);
}
void drawHotbar() {
int slotW = 52, slotH = 52, spacing = 4;
int totalW = INVENTORY_SIZE * slotW + (INVENTORY_SIZE - 1) * spacing;
int startX = (SCREEN_W - totalW) / 2;
int y = SCREEN_H - slotH - 20;
// Draw slots
for (int i = 0; i < INVENTORY_SIZE; i++) {
int sx = startX + i * (slotW + spacing);
Color c = BLOCK_COLORS[INVENTORY[i]];
if (i == inventoryIndex) {
// Selected slot - bright border
drawRect(sx - 3, y - 3, slotW + 6, slotH + 6, 255, 255, 255);
drawRect(sx - 1, y - 1, slotW + 2, slotH + 2, 0, 0, 0);
} else {
drawRect(sx - 1, y - 1, slotW + 2, slotH + 2, 80, 80, 80);
}
// Block color fill
drawRect(sx + 4, y + 4, slotW - 8, slotH - 8, c.r, c.g, c.b);
}
}
void drawHelpText() {
if (!showHelp) return;
// Simple text rendering using small bitmap font approach
// Draw a semi-transparent help panel
int panelW = 320, panelH = 180;
int px = 10, py = 10;
for (int y = py; y < py + panelH; y++) {
for (int x = px; x < px + panelW; x++) {
if (x >= 0 && x < SCREEN_W && y >= 0 && y < SCREEN_H) {
int idx = (y * SCREEN_W + x) * 4;
// Dark semi-transparent overlay
frameBuffer[idx] = (unsigned char)(frameBuffer[idx] * 0.3f + 20);
frameBuffer[idx + 1] = (unsigned char)(frameBuffer[idx + 1] * 0.3f + 20);
frameBuffer[idx + 2] = (unsigned char)(frameBuffer[idx + 2] * 0.3f + 20);
}
}
}
// Draw border
drawRect(px, py, panelW, 2, 200, 200, 200);
drawRect(px, py + panelH - 2, panelW, 2, 200, 200, 200);
drawRect(px, py, 2, panelH, 200, 200, 200);
drawRect(px + panelW - 2, py, 2, panelH, 200, 200, 200);
}
// =============================================================================
// MAIN RENDER FUNCTION
// =============================================================================
void render() {
// Clear framebuffer and depth buffer
memset(frameBuffer, 0, FB_SIZE);
for (int i = 0; i < SCREEN_W * SCREEN_H; i++) {
depthBuffer[i] = FAR_PLANE;
}
// Draw sky gradient
for (int y = 0; y < SCREEN_H; y++) {
float t = (float)y / SCREEN_H;
unsigned char r = (unsigned char)(100 + 80 * (1 - t));
unsigned char g = (unsigned char)(160 + 60 * (1 - t));
unsigned char b = (unsigned char)(220 + 30 * (1 - t));
for (int x = 0; x < SCREEN_W; x++) {
int idx = (y * SCREEN_W + x) * 4;
frameBuffer[idx] = b;
frameBuffer[idx + 1] = g;
frameBuffer[idx + 2] = r;
frameBuffer[idx + 3] = 255;
}
}
// Collect and sort quads by distance (back to front for transparency)
std::vector<Quad> quads;
collectQuads(quads);
// Sort by average depth (back to front)
for (auto& q : quads) {
q.v[0].pos = q.v[0].pos; // keep original world pos
}
std::sort(quads.begin(), quads.end(), [&](const Quad& a, const Quad& b) {
Vec3 ca((a.v[0].pos.x + a.v[2].pos.x) * 0.5f,
(a.v[0].pos.y + a.v[2].pos.y) * 0.5f,
(a.v[0].pos.z + a.v[2].pos.z) * 0.5f);
Vec3 cb((b.v[0].pos.x + b.v[2].pos.x) * 0.5f,
(b.v[0].pos.y + b.v[2].pos.y) * 0.5f,
(b.v[0].pos.z + b.v[2].pos.z) * 0.5f);
return (ca - playerPos).length() > (cb - playerPos).length();
});
// Render all quads
for (const auto& q : quads) {
drawScanlineQuad(q);
}
// Draw UI
drawCrosshair();
drawHotbar();
drawHelpText();
// Highlight targeted block
Vec3 dir(sinf(playerYaw) * cosf(playerPitch), sinf(playerPitch), cosf(playerYaw) * cosf(playerPitch));
RayHit hit = raycastBlock(playerPos, dir, 6.0f);
if (hit.hit) {
// Draw selection outline by rendering wireframe
float blockVerts[8][3] = {
{hit.bx + 0.0f, hit.by + 0.0f, hit.bz + 0.0f},
{hit.bx + 1.0f, hit.by + 0.0f, hit.bz + 0.0f},
{hit.bx + 1.0f, hit.by + 1.0f, hit.bz + 0.0f},
{hit.bx + 0.0f, hit.by + 1.0f, hit.bz + 0.0f},
{hit.bx + 0.0f, hit.by + 0.0f, hit.bz + 1.0f},
{hit.bx + 1.0f, hit.by + 0.0f, hit.bz + 1.0f},
{hit.bx + 1.0f, hit.by + 1.0f, hit.bz + 1.0f},
{hit.bx + 0.0f, hit.by + 1.0f, hit.bz + 1.0f}
};
// Simple: just draw a slightly expanded version with bright color
// (selection indicator done via bright border overlay)
Quad selQuad;
selQuad.blockType = BLOCK_AIR;
selQuad.faceNormal = Vec3(0, 0, 0);
auto drawLine = [&](Vec3 p1, Vec3 p2) {
int sx1, sy1, sx2, sy2;
float d1, d2;
if (projectToScreen(playerPos, p1, sx1, sy1, d1) &&
projectToScreen(playerPos, p2, sx2, sy2, d2)) {
// Simple line draw
int dx = sx2 - sx1, dy = sy2 - sy1;
int steps = std::max(abs(dx), abs(dy));
if (steps < 1) steps = 1;
for (int s = 0; s <= steps; s++) {
int lx = sx1 + dx * s / steps;
int ly = sy1 + dy * s / steps;
if (lx >= 0 && lx < SCREEN_W && ly >= 0 && ly < SCREEN_H) {
int idx = (ly * SCREEN_W + lx) * 4;
frameBuffer[idx] = 255;
frameBuffer[idx + 1] = 255;
frameBuffer[idx + 2] = 255;
}
}
}
};
// Draw edges of selected block
drawLine(Vec3(hit.bx, hit.by, hit.bz), Vec3(hit.bx+1, hit.by, hit.bz));
drawLine(Vec3(hit.bx+1, hit.by, hit.bz), Vec3(hit.bx+1, hit.by, hit.bz+1));
drawLine(Vec3(hit.bx+1, hit.by, hit.bz+1), Vec3(hit.bx, hit.by, hit.bz+1));
drawLine(Vec3(hit.bx, hit.by, hit.bz+1), Vec3(hit.bx, hit.by, hit.bz));
drawLine(Vec3(hit.bx, hit.by+1, hit.bz), Vec3(hit.bx+1, hit.by+1, hit.bz));
drawLine(Vec3(hit.bx+1, hit.by+1, hit.bz), Vec3(hit.bx+1, hit.by+1, hit.bz+1));
drawLine(Vec3(hit.bx+1, hit.by+1, hit.bz+1), Vec3(hit.bx, hit.by+1, hit.bz+1));
drawLine(Vec3(hit.bx, hit.by+1, hit.bz+1), Vec3(hit.bx, hit.by+1, hit.bz));
drawLine(Vec3(hit.bx, hit.by, hit.bz), Vec3(hit.bx, hit.by+1, hit.bz));
drawLine(Vec3(hit.bx+1, hit.by, hit.bz), Vec3(hit.bx+1, hit.by+1, hit.bz));
drawLine(Vec3(hit.bx+1, hit.by, hit.bz+1), Vec3(hit.bx+1, hit.by+1, hit.bz+1));
drawLine(Vec3(hit.bx, hit.by, hit.bz+1), Vec3(hit.bx, hit.by+1, hit.bz+1));
}
}
// =============================================================================
// WINDOW PROCEDURE
// =============================================================================
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_KEYDOWN:
keys[wParam & 0xFF] = true;
if (wParam == VK_ESCAPE) {
cursorCaptured = !cursorCaptured;
ShowCursor(!cursorCaptured);
}
// Number keys for inventory
if (wParam >= '0' && wParam <= '9') {
int num = wParam - '0';
if (num >= 1 && num <= INVENTORY_SIZE) {
inventoryIndex = num - 1;
selectedBlock = INVENTORY[inventoryIndex];
}
}
// Scroll wheel emulation with Q/E
if (wParam == 'Q' || wParam == 'q') {
inventoryIndex = (inventoryIndex - 1 + INVENTORY_SIZE) % INVENTORY_SIZE;
selectedBlock = INVENTORY[inventoryIndex];
}
if (wParam == 'E' || wParam == 'e') {
inventoryIndex = (inventoryIndex + 1) % INVENTORY_SIZE;
selectedBlock = INVENTORY[inventoryIndex];
}
if (wParam == 'H' || wParam == 'h') {
showHelp = !showHelp;
}
break;
case WM_KEYUP:
keys[wParam & 0xFF] = false;
break;
case WM_LBUTTONDOWN:
mouseLeftDown = true;
break;
case WM_RBUTTONDOWN:
mouseRightDown = true;
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
// =============================================================================
// MAIN
// =============================================================================
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// Register window class
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszClassName = "Minecraft3D";
wc.style = CS_OWNDC;
RegisterClass(&wc);
// Create window
HWND hwnd = CreateWindow("Minecraft3D", "3D Minecraft Clone - C++ Software Renderer",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
SCREEN_W, SCREEN_H, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Create off-screen bitmap
HDC hDC = GetDC(hwnd);
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = SCREEN_W;
bmi.bmiHeader.biHeight = -SCREEN_H; // top-down
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
void* bits = NULL;
hBitmap = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, &bits, NULL, 0);
frameBuffer = (unsigned char*)bits;
hDCCanvas = CreateCompatibleDC(hDC);
SelectObject(hDCCanvas, hBitmap);
depthBuffer = new float[SCREEN_W * SCREEN_H];
// Generate world
generateWorld();
// Hide cursor and capture
ShowCursor(FALSE);
SetCursorPos(SCREEN_W / 2, SCREEN_H / 2);
// Initial timing
lastTime = GetTickCount();
// Main loop
MSG msg = {0};
bool running = true;
while (running) {
// Handle Windows messages
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
running = false;
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (!running) break;
// Calculate delta time
DWORD currentTime = GetTickCount();
deltaTime = (currentTime - lastTime) / 1000.0f;
lastTime = currentTime;
if (deltaTime > 0.1f) deltaTime = 0.1f;
// Update
updatePlayer(deltaTime);
handleBlockInteraction();
// Render
render();
// Blit to screen
BitBlt(hDC, 0, 0, SCREEN_W, SCREEN_H, hDCCanvas, 0, 0, SRCCOPY);
}
// Cleanup
delete[] depthBuffer;
DeleteDC(hDCCanvas);
DeleteObject(hBitmap);
ReleaseDC(hwnd, hDC);
return 0;
}
编译时点击工具,选择编译器选项,中写-std=c++11,并在中删掉原来的命令改成 -lgdi32即可运行
制作耗时三周点个不要钱的赞吧









全部评论 6
!!!!!!!!!!!!!!!!!
wow~ ⊙o⊙14小时前 来自 广东
0525 10 C:\Users\sz-cbd-05-02\Documents\未命名1.cpp [Error] 'drawTriangle' does not name a type
17小时前 来自 广东
0看看有没有加连接器
14小时前 来自 北京
0
只不过525行为什么有报错啊
17小时前 来自 广东
0very good,
17小时前 来自 广东
0我才是第一
17小时前 来自 江西
0产品说明:
🎮 3D渲染
- 自定义软件渲染引擎(透视投影 + 三角形光栅化)
- 深度缓冲区(Z-Buffer)实现正确的3D遮挡
- 背面剔除优化性能
- 雾效(Fog)实现远景渐隐
💡 光影系统
- 环境光 + 漫反射光照模型
- 太阳光方向光照(顶面更亮)
- 基于位置的伪环境光遮蔽(AO)
- 逐面光照计算,不同朝向亮度不同
🏃 角色控制
- 第一人称视角(FPS风格)
- WASD移动 + 鼠标视角控制
- 空格跳跃 + Shift加速跑
- 重力系统与碰撞检测
- 鼠标锁定(按ESC释放/捕获鼠标)
⛏️ 方块交互
- 左键 - 挖掘/破坏方块
- 右键 - 放置方块
- 射线检测(Raycasting)精准定位目标方块
- 十字准星 + 白色线框高亮显示目标方块
📦 方块类型
按键 方块 1 草方块 2 泥土 3 石头 4 木头 5 树叶 6 沙子 - 也可用 Q/E 键切换方块类型
- 按 H 显示/隐藏帮助面板
🏗️ 世界生成
- 16×16×16 体素世界
- 基于正弦函数的程序化地形高度图
- 自动生成石头基底 → 泥土层 → 表面方块
- 随机生成8棵树(树干+树冠)
- 水下区域自动填充水方块
17小时前 来自 北京
0



























有帮助,赞一个