Skip to content

Fix the rotation direction of the normals#7041

Merged
BlackYps merged 2 commits intodevelopfrom
shader-texture-fix
Apr 10, 2026
Merged

Fix the rotation direction of the normals#7041
BlackYps merged 2 commits intodevelopfrom
shader-texture-fix

Conversation

@BlackYps
Copy link
Copy Markdown
Contributor

@BlackYps BlackYps commented Feb 18, 2026

The normals of the rotated texture layers of the Terrain200 shader need to be rotated to face the right direction. Unfortunately this rotation happened in the wrong direction. This is now fixed. Flipping the arguments of a matrix multiplication flips the rotation direction. The map editor needs the same fix.

Video of the issue:

wronglyrotatedlighting.mp4

Checklist

Summary by CodeRabbit

  • Bug Fixes
    • Corrected terrain normal rotation so rotated texture layers render with proper lighting and visual appearance on affected maps using the Terrain200 shader.
  • Documentation
    • Added a changelog snippet documenting the fix and its scope (applies to maps using the Terrain200 shader).

@github-actions github-actions bot marked this pull request as draft February 18, 2026 22:25
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 541969bc-187a-445f-b3ec-b83f2ecdfe68

📥 Commits

Reviewing files that changed from the base of the PR and between 79ccc5c and 0039480.

📒 Files selected for processing (1)
  • changelog/snippets/graphics.7041.md
✅ Files skipped from review due to trivial changes (1)
  • changelog/snippets/graphics.7041.md

📝 Walkthrough

Walkthrough

This change reverses the order of vector-matrix multiplication for rotating 2D normal components in two terrain pixel shaders (Terrain200NormalsPS and Terrain200BNormalsPS). The multiplication order is changed from mul(vector, matrix) to mul(matrix, vector) for normals across multiple strata, affecting how rotations are applied to normal vectors.

Changes

Cohort / File(s) Summary
Terrain Normal Rotation
effects/terrain.fx
Reverses vector-matrix multiplication order in Terrain200NormalsPS and Terrain200BNormalsPS for stratum 0, 2, 4, and 6 normal rotations: replaces mul(stratumXNormal.xy, rotationMatrix) with mul(rotationMatrix, stratumXNormal.xy).
Changelog snippet
changelog/snippets/graphics.7041.md
Adds a changelog snippet documenting the normals fix for rotated texture layers (scope: Terrain200 shader).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 I flipped the math, gave normals a whirl,

Strata now spin in a tidier twirl,
Pixels align with a softer hum,
Terrain sighs—rotation's come,
Hoppity hops, the shaders purr.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing the rotation direction of normals in the Terrain200 shader by flipping matrix multiplication arguments.
Description check ✅ Passed The description covers all major required sections: it explains the proposed changes clearly with a video demonstration, mentions the Terrain200 shader context, includes a changelog snippet, and addresses the checklist items.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch shader-texture-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
effects/terrain.fx (1)

1841-1847: sampleNormal helper has the same missing inverse rotation on normalRotated — not currently active.

normalRotated is sampled at mul(position, rotationMatrix) (clockwise UV rotation) but its XY components are not un-rotated before being passed to splatBlendNormal. This means the rotated sample contributes incorrectly-oriented normals to the blend. This does not affect any currently rendered output because the only callers (TerrainPBRNormalsPS / technique TerrainPBR) are fully commented out, but the bug would reappear if those techniques were re-enabled.

🛠️ Proposed fix for when this code is un-commented
 float3 sampleNormal(sampler2D s, float2 position, uniform float2 scale, float mask) {
     // 30° rotation
     float2x2 rotationMatrix = float2x2(float2(0.866, -0.5), float2(0.5, 0.866));
     float3 normal = tex2D(s, position * scale).rgb;
     float3 normalRotated = tex2D(s, mul(position, rotationMatrix) * scale).rgb;
-    return splatBlendNormal(normalize(2 * normal - 1), normalize(2 * normalRotated - 1), 0.5, mask, 0.03);
+    float3 normalRotatedDecoded = normalize(2 * normalRotated - 1);
+    normalRotatedDecoded.xy = mul(rotationMatrix, normalRotatedDecoded.xy);
+    return splatBlendNormal(normalize(2 * normal - 1), normalRotatedDecoded, 0.5, mask, 0.03);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@effects/terrain.fx` around lines 1841 - 1847, The sampled rotated normal
(normalRotated) is not being un-rotated before blending, so compute the normal
vector from that sample (e.g., vec3 nRot = normalize(2 * normalRotated - 1)),
apply the inverse rotation to its XY components (since rotationMatrix is a pure
rotation the inverse is its transpose, e.g., float2x2 invRot =
transpose(rotationMatrix); nRot.xy = mul(nRot.xy, invRot)), and then pass the
corrected nRot into splatBlendNormal instead of the raw rotated sample; keep the
other call parameters (0.5, mask, 0.03) and the original normal handling
unchanged so the function (sampleNormal) uses properly un-rotated normals for
splatBlendNormal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@effects/terrain.fx`:
- Around line 1841-1847: The sampled rotated normal (normalRotated) is not being
un-rotated before blending, so compute the normal vector from that sample (e.g.,
vec3 nRot = normalize(2 * normalRotated - 1)), apply the inverse rotation to its
XY components (since rotationMatrix is a pure rotation the inverse is its
transpose, e.g., float2x2 invRot = transpose(rotationMatrix); nRot.xy =
mul(nRot.xy, invRot)), and then pass the corrected nRot into splatBlendNormal
instead of the raw rotated sample; keep the other call parameters (0.5, mask,
0.03) and the original normal handling unchanged so the function (sampleNormal)
uses properly un-rotated normals for splatBlendNormal.

@BlackYps BlackYps marked this pull request as ready for review April 10, 2026 15:26
@BlackYps BlackYps merged commit 27307d8 into develop Apr 10, 2026
6 checks passed
@BlackYps BlackYps deleted the shader-texture-fix branch April 10, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant