fix(lighting): port ACME lighting constants replacing guessed values
Replace guessed sun direction (0.5, 0.4, 0.6) with ACME's verified value (0.5, 0.3, -0.3) from GameScene.cs:238. Replace hardcoded ambient/diffuse (0.25/0.75) with ACME's ambient intensity 0.45 from LandscapeEditorSettings.cs:108. Terrain shaders now match ACME Landscape.vert/frag pattern: - Vertex shader computes Lambert term with xLightDirection uniform - Fragment shader applies: color * (clamp(lambert, 0, 1) + xAmbient) Static object shader matches ACME StaticObject.vert: - LightingFactor = max(dot(N, -L), 0) + ambient - Removed separate uDiffuseIntensity (ACME doesn't have one) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1b3387f991
commit
31d3a4678f
5 changed files with 31 additions and 27 deletions
|
|
@ -142,12 +142,13 @@ public sealed unsafe class InstancedMeshRenderer : IDisposable
|
||||||
var vp = camera.View * camera.Projection;
|
var vp = camera.View * camera.Projection;
|
||||||
_shader.SetMatrix4("uViewProjection", vp);
|
_shader.SetMatrix4("uViewProjection", vp);
|
||||||
|
|
||||||
// Lighting uniforms — match the constants from mesh.frag so the visual
|
// Lighting uniforms matching ACME StaticObject.vert:
|
||||||
// output is identical to the non-instanced path.
|
// LightingFactor = max(dot(Normal, -uLightDirection), 0.0) + uAmbientIntensity
|
||||||
var sunDir = Vector3.Normalize(new Vector3(0.5f, 0.4f, 0.6f));
|
// LightDirection (0.5, 0.3, -0.3) from ACME GameScene.cs:238.
|
||||||
_shader.SetVec3("uLightDirection", sunDir);
|
// AmbientLightIntensity 0.45 from ACME LandscapeEditorSettings.cs:108.
|
||||||
_shader.SetFloat("uAmbientIntensity", 0.25f);
|
var lightDir = Vector3.Normalize(new Vector3(0.5f, 0.3f, -0.3f));
|
||||||
_shader.SetFloat("uDiffuseIntensity", 0.75f);
|
_shader.SetVec3("uLightDirection", lightDir);
|
||||||
|
_shader.SetFloat("uAmbientIntensity", 0.45f);
|
||||||
|
|
||||||
// ── Collect and group instances ───────────────────────────────────────
|
// ── Collect and group instances ───────────────────────────────────────
|
||||||
CollectGroups(landblockEntries, frustum, neverCullLandblockId);
|
CollectGroups(landblockEntries, frustum, neverCullLandblockId);
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,8 @@ layout(location = 5) in vec4 aInstanceRow2;
|
||||||
layout(location = 6) in vec4 aInstanceRow3;
|
layout(location = 6) in vec4 aInstanceRow3;
|
||||||
|
|
||||||
uniform mat4 uViewProjection;
|
uniform mat4 uViewProjection;
|
||||||
uniform vec3 uLightDirection; // world-space sun direction (points toward sun)
|
uniform vec3 uLightDirection; // world-space light direction (points FROM sun, matching ACME)
|
||||||
uniform float uAmbientIntensity;
|
uniform float uAmbientIntensity;
|
||||||
uniform float uDiffuseIntensity;
|
|
||||||
|
|
||||||
out vec2 vTex;
|
out vec2 vTex;
|
||||||
out vec3 vWorldNormal;
|
out vec3 vWorldNormal;
|
||||||
|
|
@ -26,21 +25,16 @@ out float vLightingFactor;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// Reconstruct the per-instance model matrix from its four row vectors.
|
// Reconstruct the per-instance model matrix from its four row vectors.
|
||||||
// Column-major storage: OpenGL/GLSL mat4 columns are constructed from
|
|
||||||
// the rows we receive from the attribute buffer.
|
|
||||||
mat4 model = mat4(aInstanceRow0, aInstanceRow1, aInstanceRow2, aInstanceRow3);
|
mat4 model = mat4(aInstanceRow0, aInstanceRow1, aInstanceRow2, aInstanceRow3);
|
||||||
|
|
||||||
vec4 worldPos = model * vec4(aPosition, 1.0);
|
vec4 worldPos = model * vec4(aPosition, 1.0);
|
||||||
gl_Position = uViewProjection * worldPos;
|
gl_Position = uViewProjection * worldPos;
|
||||||
|
|
||||||
// Transform normal into world space. For uniform-scale transforms the
|
// Transform normal into world space.
|
||||||
// upper-left 3x3 is sufficient; non-uniform scale would require the
|
|
||||||
// inverse transpose, accepted as a future-phase concern (same as mesh.vert).
|
|
||||||
vWorldNormal = normalize(mat3(model) * aNormal);
|
vWorldNormal = normalize(mat3(model) * aNormal);
|
||||||
vTex = aTexCoord;
|
vTex = aTexCoord;
|
||||||
|
|
||||||
// Compute Lambert diffuse + ambient in the vertex shader so the fragment
|
// Lambert + ambient matching ACME StaticObject.vert:
|
||||||
// shader only needs a multiply. Matches ACME StaticObject.vert pattern.
|
// LightingFactor = max(dot(Normal, -uLightDirection), 0.0) + uAmbientIntensity;
|
||||||
float ndotl = max(dot(vWorldNormal, uLightDirection), 0.0);
|
vLightingFactor = max(dot(vWorldNormal, -uLightDirection), 0.0) + uAmbientIntensity;
|
||||||
vLightingFactor = uAmbientIntensity + uDiffuseIntensity * ndotl;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
in vec2 vBaseUV;
|
in vec2 vBaseUV;
|
||||||
in vec3 vWorldNormal;
|
in vec3 vWorldNormal;
|
||||||
|
in float vLightingFactor;
|
||||||
in vec4 vOverlay0;
|
in vec4 vOverlay0;
|
||||||
in vec4 vOverlay1;
|
in vec4 vOverlay1;
|
||||||
in vec4 vOverlay2;
|
in vec4 vOverlay2;
|
||||||
|
|
@ -17,11 +18,7 @@ out vec4 fragColor;
|
||||||
|
|
||||||
uniform sampler2DArray uTerrain; // 33+ layers — TerrainAtlas.GlTexture
|
uniform sampler2DArray uTerrain; // 33+ layers — TerrainAtlas.GlTexture
|
||||||
uniform sampler2DArray uAlpha; // 8+ layers — TerrainAtlas.GlAlphaTexture
|
uniform sampler2DArray uAlpha; // 8+ layers — TerrainAtlas.GlAlphaTexture
|
||||||
|
uniform float xAmbient; // ambient intensity (matching ACME Landscape.frag)
|
||||||
// Phase 3a lighting (in sync with mesh.frag — update both together).
|
|
||||||
const vec3 SUN_DIR = normalize(vec3(0.5, 0.4, 0.6));
|
|
||||||
const float AMBIENT = 0.25;
|
|
||||||
const float DIFFUSE = 0.75;
|
|
||||||
|
|
||||||
// Per-texture tiling repeat count across a cell. WorldBuilder uses
|
// Per-texture tiling repeat count across a cell. WorldBuilder uses
|
||||||
// uTexTiling[36] uploaded from the dats; we default to 1.0 (one tile per
|
// uTexTiling[36] uploaded from the dats; we default to 1.0 (one tile per
|
||||||
|
|
@ -118,10 +115,9 @@ void main() {
|
||||||
vec3 roadMasked = roads.rgb * roads.a;
|
vec3 roadMasked = roads.rgb * roads.a;
|
||||||
vec3 rgb = clamp(baseMasked + ovlMasked + roadMasked, 0.0, 1.0);
|
vec3 rgb = clamp(baseMasked + ovlMasked + roadMasked, 0.0, 1.0);
|
||||||
|
|
||||||
// Phase 3a/3b directional lighting (in sync with mesh.frag constants).
|
// Lighting matching ACME Landscape.frag:
|
||||||
vec3 N = normalize(vWorldNormal);
|
// litColor = finalColor * (saturate(vLightingFactor) + xAmbient);
|
||||||
float ndotl = max(dot(N, SUN_DIR), 0.0);
|
vec3 litColor = rgb * (clamp(vLightingFactor, 0.0, 1.0) + xAmbient);
|
||||||
float lighting = AMBIENT + DIFFUSE * ndotl;
|
|
||||||
|
|
||||||
fragColor = vec4(rgb * lighting, 1.0);
|
fragColor = vec4(litColor, 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,11 @@ layout(location = 5) in uvec4 aPacked3; // bits: rot fields + splitDir (see bel
|
||||||
|
|
||||||
uniform mat4 uView;
|
uniform mat4 uView;
|
||||||
uniform mat4 uProjection;
|
uniform mat4 uProjection;
|
||||||
|
uniform vec3 xLightDirection; // world-space sun direction (matching ACME Landscape.vert)
|
||||||
|
|
||||||
out vec2 vBaseUV;
|
out vec2 vBaseUV;
|
||||||
out vec3 vWorldNormal;
|
out vec3 vWorldNormal;
|
||||||
|
out float vLightingFactor;
|
||||||
// Per-layer "UV.xy in cell-local 0..1 space, tex index .z, alpha index .w".
|
// Per-layer "UV.xy in cell-local 0..1 space, tex index .z, alpha index .w".
|
||||||
// Negative .z means "layer not present, skip it in the fragment shader."
|
// Negative .z means "layer not present, skip it in the fragment shader."
|
||||||
out vec4 vOverlay0;
|
out vec4 vOverlay0;
|
||||||
|
|
@ -91,6 +93,10 @@ void main() {
|
||||||
// Vertices are baked in world space; normals need no model transform.
|
// Vertices are baked in world space; normals need no model transform.
|
||||||
vWorldNormal = normalize(aNormal);
|
vWorldNormal = normalize(aNormal);
|
||||||
|
|
||||||
|
// Lambert diffuse term matching ACME Landscape.vert:
|
||||||
|
// vLightingFactor = max(0.0, dot(vNormal, -normalize(xLightDirection)));
|
||||||
|
vLightingFactor = max(0.0, dot(vWorldNormal, -normalize(xLightDirection)));
|
||||||
|
|
||||||
float baseTex = float(aPacked0.x);
|
float baseTex = float(aPacked0.x);
|
||||||
if (baseTex >= 254.0) baseTex = -1.0;
|
if (baseTex >= 254.0) baseTex = -1.0;
|
||||||
vBaseTexIdx = baseTex;
|
vBaseTexIdx = baseTex;
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,13 @@ public sealed unsafe class TerrainChunkRenderer : IDisposable
|
||||||
_shader.SetMatrix4("uView", camera.View);
|
_shader.SetMatrix4("uView", camera.View);
|
||||||
_shader.SetMatrix4("uProjection", camera.Projection);
|
_shader.SetMatrix4("uProjection", camera.Projection);
|
||||||
|
|
||||||
|
// Lighting uniforms matching ACME Landscape.vert/frag.
|
||||||
|
// LightDirection (0.5, 0.3, -0.3) from ACME GameScene.cs:238.
|
||||||
|
// AmbientLightIntensity 0.45 from ACME LandscapeEditorSettings.cs:108.
|
||||||
|
var lightDir = Vector3.Normalize(new Vector3(0.5f, 0.3f, -0.3f));
|
||||||
|
_shader.SetVec3("xLightDirection", lightDir);
|
||||||
|
_shader.SetFloat("xAmbient", 0.45f);
|
||||||
|
|
||||||
// Terrain atlas on unit 0, alpha atlas on unit 1.
|
// Terrain atlas on unit 0, alpha atlas on unit 1.
|
||||||
_gl.ActiveTexture(TextureUnit.Texture0);
|
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||||
_gl.BindTexture(TextureTarget.Texture2DArray, _atlas.GlTexture);
|
_gl.BindTexture(TextureTarget.Texture2DArray, _atlas.GlTexture);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue