feat(render): SAGE water/ocean shaders, per-user logs, camera & culling

Terrain pass now ports the SAGE water model (Ocean.fx / OpenSAGE Water.frag): a de-gridded procedural wave normal combined with the retail ra3_deepocean flow and ra3_deepocean_nrm bump maps (appended as the last two terrain-atlas layers, no new backend binding), Schlick fresnel, sky reflection + depth-graded refraction, SAGE diffuse/specular lighting, depth-based transparency, and an underwater tint (UnderwaterDeferred.fx). Mirrored across terrain.frag / dx_terrain.hlsl / webgl_terrain_frag.glsl / webgpu_terrain.wgsl.

Also: logs move to the per-user state dir (%LOCALAPPDATA%\\OpenRA3\\logs, else XDG) and archive as openra3.<stamp>.log; the FPS label shows the active backend; middle-drag camera reset; objects and roads below the water plane are culled.
This commit is contained in:
EnderTheCoder
2026-09-30 22:18:08 +08:00
parent 10a1963eec
commit 26e1934a5d
16 changed files with 969 additions and 249 deletions
+17 -1
View File
@@ -765,6 +765,7 @@ export namespace ra3::models {
usize placed = 0;
usize missing = 0;
usize roads = 0;
usize hidden = 0; ///< Objects/roads below the water plane, not drawn.
[[nodiscard]] auto empty() const -> bool { return indices.empty(); }
[[nodiscard]] auto triangle_count() const -> usize { return indices.size() / 3U; }
@@ -823,9 +824,14 @@ export namespace ra3::models {
* @param ground_height Terrain height (world Z) at a world `(x, y)`, so
* objects sit on the relief instead of a flat plane.
* @param texture_size Edge length of the shared texture array (0 = auto).
* @param cull_below_z Skip placements whose base sits below this world Z
* (the water plane): an opaque water surface hides
* submerged objects, so drawing them would float them
* on top of the sea.
*/
[[nodiscard]] inline auto build_scene(const asset_stream &stream, std::span<const placement> placements,
const std::function<float(float, float)> &ground_height = {}, uint32 texture_size = 128U) -> scene {
const std::function<float(float, float)> &ground_height = {}, uint32 texture_size = 128U,
float cull_below_z = -3.4e38F) -> scene {
scene out;
out.texture_size = texture_size == 0U ? 128U : texture_size;
std::unordered_map<std::string, uint32> texture_layers;
@@ -972,6 +978,12 @@ export namespace ra3::models {
if ((a.road_type & 2U) == 0U) continue; // RoadType::Start
const auto &b = placements[i + 1U];
if ((b.road_type & 4U) == 0U || b.type != a.type) continue; // RoadType::End
const auto road_base = (ground_height ? ground_height(a.x, a.y) : 0.0F) + a.z;
if (road_base < cull_below_z) {
++out.hidden;
++i;
continue;
}
emit_road(a, b);
++i;
}
@@ -986,6 +998,10 @@ export namespace ra3::models {
const auto cos_a = std::cos(item.angle);
const auto sin_a = std::sin(item.angle);
const auto base_z = (ground_height ? ground_height(item.x, item.y) : 0.0F) + item.z;
if (base_z < cull_below_z) {
++out.hidden;
continue;
}
bool drawn = false;
for (const auto *mesh_asset: meshes) {
// Only opaque material parts are drawn; meshes with no diffuse