feat: shadows, baked vertex AO, post-processing + central config.js
Visuals:
- PCFSoftShadowMap (2048²), directional light follows player with a tight
shadow frustum (SHADOW_AREA) for high texel density; opak blocks cast+receive,
water receives.
- Baked vertex ambient occlusion ("smooth lighting"): per-vertex solid-neighbor
lookup (0fps algorithm) written into a color attribute, with flip-triangulation
to avoid brightness seams across quad diagonals.
- EffectComposer: RenderPass -> UnrealBloomPass (subtle) -> OutputPass with
ACES filmic tone-mapping + sRGB. HalfFloat render target (anti-banding) + MSAA.
Controls:
- Sprint moved from Ctrl to Shift (Ctrl is problematic in browsers). Shift in
fly mode still means "descend" (no conflict, sprint only applies when walking).
config.js:
- New central js/config.js for all tweakable values (world, performance,
renderer/tone-mapping, sky & fog, camera, lighting, shadows, bloom, AO,
player physics, mouse sensitivity, reach) with SHADOWS_ENABLED / BLOOM_ENABLED
feature toggles. Structural engine constants stay in constants.js.
- SEA_LEVEL / WORLD_SEED moved constants.js -> config.js (consumers rewired).
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
1049253d54
commit
f009789c42
9 files changed
+282
-59
No files matched your search
+66
-19
@@ -3,14 +3,30 @@
|
||||
// Nachbarn werden erzeugt.
|
||||
import * as THREE from 'three';
|
||||
import {
|
||||
CHUNK_SIZE, CHUNK_HEIGHT, BLOCK, ATLAS_COLS, tileFor, isRenderTransparent,
|
||||
CHUNK_SIZE, CHUNK_HEIGHT, BLOCK, ATLAS_COLS, tileFor, isRenderTransparent, isSolid,
|
||||
} from './constants.js';
|
||||
import { AO_LEVEL } from './config.js';
|
||||
|
||||
// Baked Ambient Occlusion (Minecraft-"Smooth Lighting"): fuer jeden Vertex
|
||||
// wird aus den soliden Nachbarbloecken in der Aussen-Schicht ein Verdunkelungs-
|
||||
// wert berechnet. Billiger als SSAO und bei Voxels korrekt (keine Bildraum-
|
||||
// Artifacts). Siehe 0fps "Ambient occlusion for Minecraft-like worlds".
|
||||
// Index 0 = staerkste Verdeckung (dunkelste Ecke), 3 = keine Verdeckung.
|
||||
// Die Stufen-Werte selbst liegen in config.js (AO_LEVEL).
|
||||
|
||||
// side1/side2/corner: je 0 (offen) oder 1 (solid). Liefert AO-Stufe 0..3.
|
||||
function vertexAO(s1, s2, c) {
|
||||
if (s1 && s2) return 0; // beide Seiten zu -> Ecke voll verdeckt
|
||||
return 3 - (s1 + s2 + c);
|
||||
}
|
||||
|
||||
// Die 6 Wuerfelseiten mit Eck-Positionen und Tile-UVs (0..1).
|
||||
// side wird aus dir abgeleitet: dir[1]===1 -> 'top', -1 -> 'bottom', sonst 'side'.
|
||||
// uAxis/vAxis: die zwei zu dir (Normalen-Achse) senkrechten Achsen (0=x,1=y,2=z),
|
||||
// benoetigt fuer die AO-Nachbar-Lookups je Vertex.
|
||||
const FACES = [
|
||||
{ // links -X
|
||||
dir: [-1, 0, 0],
|
||||
{ // links -X (normal-Achse 0)
|
||||
dir: [-1, 0, 0], uAxis: 2, vAxis: 1,
|
||||
corners: [
|
||||
{ pos: [0, 1, 0], uv: [0, 1] },
|
||||
{ pos: [0, 0, 0], uv: [0, 0] },
|
||||
@@ -18,8 +34,8 @@ const FACES = [
|
||||
{ pos: [0, 0, 1], uv: [1, 0] },
|
||||
],
|
||||
},
|
||||
{ // rechts +X
|
||||
dir: [1, 0, 0],
|
||||
{ // rechts +X (normal-Achse 0)
|
||||
dir: [1, 0, 0], uAxis: 2, vAxis: 1,
|
||||
corners: [
|
||||
{ pos: [1, 1, 1], uv: [0, 1] },
|
||||
{ pos: [1, 0, 1], uv: [0, 0] },
|
||||
@@ -27,8 +43,8 @@ const FACES = [
|
||||
{ pos: [1, 0, 0], uv: [1, 0] },
|
||||
],
|
||||
},
|
||||
{ // unten -Y
|
||||
dir: [0, -1, 0],
|
||||
{ // unten -Y (normal-Achse 1)
|
||||
dir: [0, -1, 0], uAxis: 0, vAxis: 2,
|
||||
corners: [
|
||||
{ pos: [1, 0, 1], uv: [1, 0] },
|
||||
{ pos: [0, 0, 1], uv: [0, 0] },
|
||||
@@ -36,8 +52,8 @@ const FACES = [
|
||||
{ pos: [0, 0, 0], uv: [0, 1] },
|
||||
],
|
||||
},
|
||||
{ // oben +Y
|
||||
dir: [0, 1, 0],
|
||||
{ // oben +Y (normal-Achse 1)
|
||||
dir: [0, 1, 0], uAxis: 0, vAxis: 2,
|
||||
corners: [
|
||||
{ pos: [0, 1, 1], uv: [1, 1] },
|
||||
{ pos: [1, 1, 1], uv: [0, 1] },
|
||||
@@ -45,8 +61,8 @@ const FACES = [
|
||||
{ pos: [1, 1, 0], uv: [0, 0] },
|
||||
],
|
||||
},
|
||||
{ // hinten -Z
|
||||
dir: [0, 0, -1],
|
||||
{ // hinten -Z (normal-Achse 2)
|
||||
dir: [0, 0, -1], uAxis: 0, vAxis: 1,
|
||||
corners: [
|
||||
{ pos: [1, 0, 0], uv: [0, 0] },
|
||||
{ pos: [0, 0, 0], uv: [1, 0] },
|
||||
@@ -54,8 +70,8 @@ const FACES = [
|
||||
{ pos: [0, 1, 0], uv: [1, 1] },
|
||||
],
|
||||
},
|
||||
{ // vorne +Z
|
||||
dir: [0, 0, 1],
|
||||
{ // vorne +Z (normal-Achse 2)
|
||||
dir: [0, 0, 1], uAxis: 0, vAxis: 1,
|
||||
corners: [
|
||||
{ pos: [0, 0, 1], uv: [0, 0] },
|
||||
{ pos: [1, 0, 1], uv: [1, 0] },
|
||||
@@ -81,21 +97,51 @@ function shouldDrawFace(block, neighbor) {
|
||||
}
|
||||
|
||||
function makeBuffer() {
|
||||
return { positions: [], normals: [], uvs: [], indices: [] };
|
||||
return { positions: [], normals: [], uvs: [], colors: [], indices: [] };
|
||||
}
|
||||
|
||||
function addFace(buf, x, y, z, face, block) {
|
||||
function addFace(buf, x, y, z, face, block, getBlockWorld, baseX, baseZ) {
|
||||
const side = sideOf(face.dir);
|
||||
const tile = tileFor(block, side);
|
||||
const ndx = buf.positions.length / 3;
|
||||
for (const c of face.corners) {
|
||||
const dir = face.dir;
|
||||
const ua = face.uAxis, va = face.vAxis;
|
||||
const wx = baseX + x, wz = baseZ + z; // Welt-Koordinaten des Blocks
|
||||
const ao = [0, 0, 0, 0];
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const c = face.corners[i];
|
||||
buf.positions.push(x + c.pos[0], y + c.pos[1], z + c.pos[2]);
|
||||
buf.normals.push(face.dir[0], face.dir[1], face.dir[2]);
|
||||
buf.normals.push(dir[0], dir[1], dir[2]);
|
||||
const u = (tile.col + c.uv[0]) / ATLAS_COLS;
|
||||
const v = 1 - (tile.row + 1 - c.uv[1]) / ATLAS_COLS;
|
||||
buf.uvs.push(u, v);
|
||||
|
||||
// AO: Nachbarn in der Aussen-Schicht (Block + dir). du/dv = -1/+1 je nach
|
||||
// Ecke. side1 in uAxis-, side2 in vAxis-Richtung, corner diagonal.
|
||||
const du = c.pos[ua] === 1 ? 1 : -1;
|
||||
const dv = c.pos[va] === 1 ? 1 : -1;
|
||||
const s1 = [dir[0], dir[1], dir[2]]; s1[ua] += du;
|
||||
const s2 = [dir[0], dir[1], dir[2]]; s2[va] += dv;
|
||||
const co = [dir[0], dir[1], dir[2]]; co[ua] += du; co[va] += dv;
|
||||
const s1Solid = isSolid(getBlockWorld(wx + s1[0], y + s1[1], wz + s1[2])) ? 1 : 0;
|
||||
const s2Solid = isSolid(getBlockWorld(wx + s2[0], y + s2[1], wz + s2[2])) ? 1 : 0;
|
||||
const cSolid = isSolid(getBlockWorld(wx + co[0], y + co[1], wz + co[2])) ? 1 : 0;
|
||||
|
||||
const level = vertexAO(s1Solid, s2Solid, cSolid);
|
||||
ao[i] = level;
|
||||
const b = AO_LEVEL[level];
|
||||
buf.colors.push(b, b, b);
|
||||
}
|
||||
|
||||
// Flip-Triangulation: Diagonale entlang der Eck-Paarung mit hoeherer AO-Summe
|
||||
// legen -> reduziert Interpolations-Artifacts (Helligkeitssprung) an der Naht.
|
||||
// Winding bleibt erhalten (geprueft: beide Varianten ergeben gleiche Normalen).
|
||||
if (ao[0] + ao[3] > ao[1] + ao[2]) {
|
||||
buf.indices.push(ndx, ndx + 1, ndx + 3, ndx, ndx + 3, ndx + 2);
|
||||
} else {
|
||||
buf.indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
|
||||
}
|
||||
buf.indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
|
||||
}
|
||||
|
||||
function toGeometry(buf) {
|
||||
@@ -104,6 +150,7 @@ function toGeometry(buf) {
|
||||
g.setAttribute('position', new THREE.Float32BufferAttribute(buf.positions, 3));
|
||||
g.setAttribute('normal', new THREE.Float32BufferAttribute(buf.normals, 3));
|
||||
g.setAttribute('uv', new THREE.Float32BufferAttribute(buf.uvs, 2));
|
||||
g.setAttribute('color', new THREE.Float32BufferAttribute(buf.colors, 3));
|
||||
// WICHTIG: setIndex braucht ein BufferAttribute, kein rohes TypedArray.
|
||||
// In three r0.165 speichert setIndex(rohesTypedArray) es ungepackt, so dass
|
||||
// der Renderer beim Render auf attribute.array undefined trifft und crasht
|
||||
@@ -134,7 +181,7 @@ export function buildChunkGeometry(getBlockWorld, chunk) {
|
||||
const nz = z + face.dir[2];
|
||||
const neighbor = getBlockWorld(baseX + nx, ny, baseZ + nz);
|
||||
if (shouldDrawFace(block, neighbor)) {
|
||||
addFace(buf, x, y, z, face, block);
|
||||
addFace(buf, x, y, z, face, block, getBlockWorld, baseX, baseZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user