Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ add_executable(
srcs/world/terrain_gen.cpp
srcs/world/mesh_builder.cpp
srcs/world/world.cpp
srcs/world/inventory.cpp
srcs/world/recipe.cpp
srcs/core/engine.cpp
srcs/renderer/shader.cpp
srcs/renderer/frustum.cpp
Expand Down
Binary file added Torch.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions assets/shaders/chunk.frag
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ in float vLight;
in float vLightShadow;
in vec4 vShadowCoord;
in float vFogFactor;
in float vTorchLight;

uniform sampler2D uAtlas;
uniform sampler2D uShadowMap;
Expand Down Expand Up @@ -63,6 +64,15 @@ void main() {
float light = vLightShadow * ssao + sun_direct * shadowFactor;

vec3 lit = col.rgb * light;

// ── 松明光源: 暖色を加算 ───────────────────────────────────────────────
// 暗い場所ほど目立つように、light の不足分を埋める形でブレンド。
// 同時に warm tint (オレンジ寄り) も足し込み、ロウソク色の雰囲気を出す。
vec3 torch_warm = vec3(1.10, 0.88, 0.55);
lit += col.rgb * torch_warm * vTorchLight;
// オーバーブライト防止 (極端な白飛びを避ける)
lit = min(lit, col.rgb * 1.6);

vec3 fogged = mix(lit, uFogColor, clamp(vFogFactor, 0.0, 1.0));
FragColor = vec4(fogged, col.a);
}
22 changes: 22 additions & 0 deletions assets/shaders/chunk.vert
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@ uniform float uSunStrength; // sun diffuse strength [0.0 .. 0.65]
uniform float uFogStart;
uniform float uFogEnd;

// 松明光源 (Forward point lights, 最大 16 個)
// 距離フォールオフで暖色光を加算する。
uniform vec3 uTorches[16];
uniform int uTorchCount; // 実際に有効なエントリ数 (0..16)
uniform float uTorchRange; // 完全減衰する距離 (ブロック単位)

out vec2 vUV;
out float vLight;
out float vLightShadow; // 影の中にいるときの明るさ (ambient のみ)
out vec4 vShadowCoord; // 光源クリップ空間の座標
out float vFogFactor;
out float vTorchLight; // 松明光の累積強度 (0.0 .. 1.0+)

void main() {
gl_Position = uMVP * vec4(aPos, 1.0);
Expand All @@ -38,6 +45,21 @@ void main() {
// 光源空間での頂点位置 (PCF shadow sampling に使用)
vShadowCoord = uLightSpaceMat * uModel * vec4(aPos, 1.0);

vec3 world_pos = (uModel * vec4(aPos, 1.0)).xyz;

// ── 松明光源: 全松明からの寄与を線形フォールオフで加算 ──
// 各光源につき max(0, 1 - dist/range) を加算。複数松明が重なれば明るくなる。
float torch_accum = 0.0;
for (int i = 0; i < 16; ++i) {
if (i >= uTorchCount) break;
float d = distance(world_pos, uTorches[i]);
float a = max(0.0, 1.0 - d / uTorchRange);
torch_accum += a * a; // 2乗フォールオフでなめらか
}
// 面の向きを少し考慮 (上面は明るく、下面は暗く)。完全な点光源では物足りないので
// top/side/bottom の face_shade を再利用してメリハリを付ける。
vTorchLight = clamp(torch_accum, 0.0, 1.5) * face_shade;

vec3 view_pos = (uView * uModel * vec4(aPos, 1.0)).xyz;
float view_dist = length(view_pos);
if (uFogEnd > uFogStart + 1.0)
Expand Down
8 changes: 8 additions & 0 deletions assets/shaders/gbuffer.frag
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
#version 410 core
in vec2 vUV;
in vec3 vViewPos;
in vec3 vViewNormal;

uniform sampler2D uAtlas;

layout(location = 0) out vec4 gNormal;

void main() {
// 透過テクセル (松明の炎クロスビルボードの余白等) は GBuffer に書き込まないことで
// SSAO がそれを四角いオクルーダーとして誤検出するのを防ぐ。
float a = texture(uAtlas, vUV).a;
if (a < 0.1) discard;

// ビュー空間の法線を正規化して格納。深度はFBOが自動書き込み。
gNormal = vec4(normalize(vViewNormal), 0.0);
}
3 changes: 3 additions & 0 deletions assets/shaders/gbuffer.vert
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
#version 410 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aUV;
layout(location = 2) in vec3 aNormal;

uniform mat4 uMVP;
uniform mat4 uModelView; // view * model

out vec2 vUV;
out vec3 vViewPos;
out vec3 vViewNormal;

void main() {
gl_Position = uMVP * vec4(aPos, 1.0);
vUV = aUV;
vViewPos = (uModelView * vec4(aPos, 1.0)).xyz;
// チャンクのmodel行列は平行移動のみなので mat3(uModelView) で正しい
vViewNormal = mat3(uModelView) * aNormal;
Expand Down
7 changes: 7 additions & 0 deletions assets/shaders/shadow.frag
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#version 410 core
in vec2 vUV;

uniform sampler2D uAtlas;

void main() {
// 透過テクセル (松明炎ビルボードの余白等) はシャドウマップに深度を書き込まない。
// これにより透明部分が四角い影として現れる問題を防ぐ。
float a = texture(uAtlas, vUV).a;
if (a < 0.1) discard;
// 深度値はGPUが自動的にDepth Bufferへ書き込む
}
4 changes: 4 additions & 0 deletions assets/shaders/shadow.vert
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#version 410 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aUV;

uniform mat4 uLightMVP;

out vec2 vUV;

void main() {
gl_Position = uLightMVP * vec4(aPos, 1.0);
vUV = aUV;
}
4 changes: 4 additions & 0 deletions include/interfaces/IWorld.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ class IWorld {
virtual uint32_t getSeed() const = 0;
virtual std::vector<WorldPos> stepWater(ChunkPos min_chunk, ChunkPos max_chunk) = 0;
virtual void applyMods(Chunk* /*chunk*/) const {}
// 永続的な松明 (BlockType::Torch) のワールド座標一覧を返す。
// ChunkManager がこれらを含むチャンクを LRU から保護するために使う。
// デフォルト実装は空配列を返す (テスト/モック向け)。
virtual std::vector<WorldPos> torchPositions() const { return {}; }
};
4 changes: 3 additions & 1 deletion include/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ enum class BlockType : uint8_t {
PinkLeaves = 16, // 桜(春バイオーム)
OrangeLeaves = 17, // 紅葉オレンジ(秋バイオーム)
Bow = 18, // 弓(アイテム: 設置不可・スタック上限1)
Stick = 19, // 棒(アイテム: 設置不可・クラフト素材)
Torch = 20, // 松明(設置可: 細い柱型 3D メッシュ)
COUNT
};

// ブロックではなく "アイテム" として扱う BlockType を判定する。
// アイテムは右クリックで設置されず、種別固有の挙動を発火する。
inline bool isItem(BlockType t) {
return t == BlockType::Bow;
return t == BlockType::Bow || t == BlockType::Stick;
}

enum class Face : uint8_t {
Expand Down
48 changes: 47 additions & 1 deletion srcs/core/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ static void rebuildModified(int wx, int wz, ChunkManager& mgr) {
static void inventoryAdd(Inventory& inv, BlockType type) {
if (type == BlockType::Air || type == BlockType::Water ||
type == BlockType::ShortGrass || isItem(type)) return;
// Torch はスロット 8 に無限補充されるため、拾っても追加せず捨てる
// (インベントリが松明で埋まるのを防ぐ)
if (type == BlockType::Torch) return;
for (int i = 0; i < HOTBAR_SIZE; ++i) {
if (inv.slots[i].type == type && inv.slots[i].count < STACK_MAX) {
++inv.slots[i].count;
Expand Down Expand Up @@ -362,6 +365,8 @@ bool Engine::init(uint32_t seed, int width, int height) {

// 弓を最後のホットバースロットに常備(無限矢扱い)
impl_->inventory.slots[HOTBAR_SIZE - 1] = {BlockType::Bow, 1};
// 松明をスロット 8(インデックス 7)に常備(無限供給)
impl_->inventory.slots[HOTBAR_SIZE - 2] = {BlockType::Torch, STACK_MAX};

running_ = true;
fprintf(stderr, "[Engine] init OK seed=%u %dx%d\n", seed, width_, height_);
Expand Down Expand Up @@ -461,7 +466,8 @@ void Engine::run() {
&& t != BlockType::Water
&& t != BlockType::ShortGrass
&& t != BlockType::Flower
&& t != BlockType::Mushroom;
&& t != BlockType::Mushroom
&& t != BlockType::Torch;
};
// isWater: 「このブロックは水か?」を調べる関数。
// 水中では重力・移動速度が変わる。
Expand Down Expand Up @@ -582,6 +588,13 @@ void Engine::run() {
InputHandler& inp = impl_->player.input();
Inventory& inv = impl_->inventory;

// 松明スロット(インデックス 7 = 数字キー 8)を毎フレーム補充する。
// 「常に入れておく」要件のため、設置で減っても次フレームで満タンに戻る。
if (inv.slots[HOTBAR_SIZE - 2].type != BlockType::Torch ||
inv.slots[HOTBAR_SIZE - 2].count < STACK_MAX) {
inv.slots[HOTBAR_SIZE - 2] = {BlockType::Torch, STACK_MAX};
}

// キー 1〜9: ホットバースロットを選択(ワンショット: 前フレームと差分)
static bool prev_num[9] = {};
for (int k = 0; k < 9; ++k) {
Expand Down Expand Up @@ -807,6 +820,39 @@ void Engine::run() {
// 毎フレーム数チャンクずつ処理し、ゲームが止まらないようにする。
impl_->chunk_mgr->update(ppos.x, ppos.z, frame_);

// ── 松明光源の近傍 16 件をシェーダーへ送る ───────────────────────────
// World が持つ全松明をプレイヤー距離でソートし、近い順に最大 16 件選別。
// chunk.vert が頂点ごとに距離フォールオフを計算する。
{
std::vector<WorldPos> all = impl_->world.torchPositions();
struct Scored { glm::vec3 pos; float d2; };
std::vector<Scored> scored;
scored.reserve(all.size());
const float cull_d2 = 64.0f * 64.0f; // 64 ブロック超は無視 (range 10 なので余裕)
for (const WorldPos& tp : all) {
glm::vec3 p((float)tp.x + 0.5f,
(float)tp.y + 0.6f, // 炎中央 (棒+10/16〜炎+15/16の中間)
(float)tp.z + 0.5f);
glm::vec3 d = p - ppos;
float d2 = d.x*d.x + d.y*d.y + d.z*d.z;
if (d2 > cull_d2) continue;
scored.push_back({p, d2});
}
constexpr size_t kMaxLights = 16;
if (scored.size() > kMaxLights) {
std::nth_element(scored.begin(), scored.begin() + kMaxLights,
scored.end(),
[](const Scored& a, const Scored& b) {
return a.d2 < b.d2;
});
scored.resize(kMaxLights);
}
std::vector<glm::vec3> near_lights;
near_lights.reserve(scored.size());
for (const Scored& s : scored) near_lights.push_back(s.pos);
impl_->renderer.setTorchLights(near_lights);
}

// ── 行列の計算 ───────────────────────────────────────────────────────
// 【View行列】: カメラの位置・向きによって「ワールドがどう見えるか」を表す行列。
// カメラが動いたり向きを変えるたびに変わる。
Expand Down
44 changes: 39 additions & 5 deletions srcs/renderer/renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,7 @@ void Renderer::drawHotbar(const Inventory& inv) {
const ItemStack& s = inv.slots[i];
if (s.type == BlockType::Air || s.count <= 0) continue;
if (s.type == BlockType::Bow) continue;
if (s.type == BlockType::Torch) continue;

AtlasUV uv = atlas_.getUV(s.type);
float uc = (uv.u0 + uv.u1) * 0.5f;
Expand Down Expand Up @@ -1085,17 +1086,18 @@ void Renderer::drawHotbar(const Inventory& inv) {
}
}

// 弓はブロックではないので、Bow.png の平面アイコンとして表示する。
// 弓・松明は立方体としてではなく 2D アイコンとして表示する。
{
hotbar_shader_.setFloat("uBright", 1.0f);
float verts[HOTBAR_SIZE * 6 * 4] = {};
int vc = 0, drawn = 0;

for (int i = 0; i < HOTBAR_SIZE; ++i) {
const ItemStack& s = inv.slots[i];
if (s.type != BlockType::Bow || s.count <= 0) continue;
if ((s.type != BlockType::Bow && s.type != BlockType::Torch)
|| s.count <= 0) continue;

AtlasUV uv = atlas_.getUV(BlockType::Bow);
AtlasUV uv = atlas_.getUV(s.type);
const float cx = nx(sx0_px + i * (SLOT_PX + GAP_PX) + SLOT_PX * 0.5f);
const float cy = ny(BOTTOM_PX + SLOT_PX * 0.5f);
const float iw = 34.0f / hw;
Expand Down Expand Up @@ -1144,9 +1146,9 @@ void Renderer::drawHotbar(const Inventory& inv) {
appendLine(verts.data(), cnt, x0, sy0, x0, sy1);
appendLine(verts.data(), cnt, x1, sy0, x1, sy1);

// 個数(2個以上のとき右下に表示)
// 個数(2個以上のとき右下に表示)。Torch は無限供給なので個数は隠す。
const ItemStack& s = inv.slots[i];
if (s.type != BlockType::Air && s.count > 1) {
if (s.type != BlockType::Air && s.type != BlockType::Torch && s.count > 1) {
float dw = 8.0f / hw, dh = 11.0f / hh, dg = 2.0f / hw;
appendNumber(verts.data(), cnt, s.count,
x1 - 3.0f / hw, sy0 + dh + 3.0f / hh, dw, dh, dg);
Expand Down Expand Up @@ -1354,6 +1356,24 @@ static void setChunkLightingUniforms(Shader& shader,
shader.setFloat("uSunStrength", sun_strength);
}

void Renderer::setTorchLights(const std::vector<glm::vec3>& positions) {
torch_count_ = (int)std::min((size_t)MAX_TORCH_LIGHTS, positions.size());
for (int i = 0; i < torch_count_; ++i) {
torch_positions_[i * 3 + 0] = positions[i].x;
torch_positions_[i * 3 + 1] = positions[i].y;
torch_positions_[i * 3 + 2] = positions[i].z;
}
}

// chunk.vert の uTorches/uTorchCount/uTorchRange を一括で設定する。
static void setTorchUniforms(Shader& shader, int count,
const float* positions_xyz, float range) {
shader.setInt ("uTorchCount", count);
shader.setFloat("uTorchRange", range);
if (count > 0)
shader.setVec3Array("uTorches[0]", count, positions_xyz);
}

static void setFogUniforms(Shader& shader, const float sky_horizon[3],
bool underwater) {
if (underwater) {
Expand Down Expand Up @@ -1392,6 +1412,7 @@ void Renderer::drawChunk(const Chunk* chunk, const float* view4x4, const float*
setChunkLightingUniforms(chunk_shader_, sun_dir_, ambient_, sun_strength_);
setFogUniforms(chunk_shader_, sky_horizon_, underwater_);
chunk_shader_.setFloat("uSunStrength", sun_strength_);
setTorchUniforms(chunk_shader_, torch_count_, torch_positions_, TORCH_RANGE);

atlas_.bind(0);
chunk_shader_.setInt("uAtlas", 0);
Expand Down Expand Up @@ -1445,6 +1466,7 @@ void Renderer::drawChunkWater(const Chunk* chunk, const float* view4x4, const fl
setChunkLightingUniforms(chunk_shader_, sun_dir_, ambient_, sun_strength_);
setFogUniforms(chunk_shader_, sky_horizon_, underwater_);
chunk_shader_.setFloat("uSunStrength", sun_strength_);
setTorchUniforms(chunk_shader_, torch_count_, torch_positions_, TORCH_RANGE);

atlas_.bind(0);
chunk_shader_.setInt("uAtlas", 0);
Expand Down Expand Up @@ -1546,6 +1568,12 @@ void Renderer::beginShadowPass() {
// シャドウマップから除外されてしまうため、通常の GL_BACK カリングのままにする。
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(2.0f, 4.0f);

// 松明炎のような透過テクセルが「四角い影」を作らないよう、シャドウ FBO の
// フラグメントシェーダーでアルファ discard する。アトラスをスロット 0 にバインド。
atlas_.bind(0);
shadow_shader_.use();
shadow_shader_.setInt("uAtlas", 0);
}

// ─────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -1761,6 +1789,12 @@ void Renderer::beginGBufferPass() {
glBindFramebuffer(GL_FRAMEBUFFER, gbuffer_fbo_);
glViewport(0, 0, width_, height_);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 松明炎のような透過テクセルが SSAO で四角いオクルーダー扱いされないよう、
// GBuffer フラグメントシェーダーでアルファ discard する。アトラスをスロット 0 に。
atlas_.bind(0);
gbuffer_shader_.use();
gbuffer_shader_.setInt("uAtlas", 0);
}

// ─────────────────────────────────────────────────────────────────────────────
Expand Down
15 changes: 13 additions & 2 deletions srcs/renderer/renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ class Renderer : public IRenderer {
void setTimeOfDay(float t);
void setUnderwater(bool underwater) { underwater_ = underwater; }

// 松明光源 (forward point lights) の位置リストを設定する。
// 最大 MAX_TORCH_LIGHTS 件まで採用、それ以上は捨てる。
// chunk.vert で各頂点ごとに距離フォールオフを計算するために使われる。
void setTorchLights(const std::vector<glm::vec3>& positions);

// Shadow mapping
void updateShadowMatrix(float px, float py, float pz);
void beginShadowPass();
Expand All @@ -84,8 +89,10 @@ class Renderer : public IRenderer {
void appendHeart(float* verts, int& count, float left, float top, float w, float h) const;
void appendSignedNumberLeft(float* verts, int& count, int value, float left, float top, float w, float h, float gap) const;

static constexpr int SHADOW_MAP_SIZE = 2048;
static constexpr int SSAO_SAMPLES = 64;
static constexpr int SHADOW_MAP_SIZE = 2048;
static constexpr int SSAO_SAMPLES = 64;
static constexpr int MAX_TORCH_LIGHTS = 16;
static constexpr float TORCH_RANGE = 10.0f; // ブロック

GLFWwindow* window_ = nullptr;
Shader chunk_shader_;
Expand Down Expand Up @@ -150,4 +157,8 @@ class Renderer : public IRenderer {
float sky_ground_[3] = {0.35f, 0.30f, 0.25f};
float sun_color_[3] = {1.00f, 0.98f, 0.85f};
bool underwater_ = false;

// 松明光源 (近傍の最大 MAX_TORCH_LIGHTS 件を格納する xyz パックド配列)
int torch_count_ = 0;
float torch_positions_[MAX_TORCH_LIGHTS * 3] = {};
};
Loading