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:
+29
-1
@@ -533,7 +533,8 @@ Mirrors SAGE `GameEngine/Common`. Everything else speaks this.
|
||||
- `[ ]` pass scheduling, barriers, transient/aliased resources `(v0.6)`
|
||||
- **F3 Terrain render** `[~]`
|
||||
- `[x]` top-down software + GPU heightfield with blend ramp + gutter atlas
|
||||
- `[ ]` perspective terrain mesh, LOD, cliff, water `(v0.6)`
|
||||
- `[ ]` perspective terrain mesh, LOD, cliff `(v0.6)`
|
||||
- `[x]` water surface in the raymarch: SAGE `Water.frag` port (ocean/river) `[~]`
|
||||
- **F4 Model render** `[~]`
|
||||
- `[x]` static map-object triangle soup, world-space, depth-tested over the terrain (Vulkan + software)
|
||||
- `[x]` bind-pose skinning (bone-space vertices) + ground-decal depth bias
|
||||
@@ -550,8 +551,35 @@ Mirrors SAGE `GameEngine/Common`. Everything else speaks this.
|
||||
- `[x]` ARGB framebuffer, blit/line/circle/text, TGA decode, BMP encode
|
||||
- `[x]` map compositing, grid, markers; headless output
|
||||
- **F9 Post-processing** `[ ]`
|
||||
- `[x]` underwater tint/fog in the terrain pass (retail `UnderwaterDeferred.fx`) `[~]`
|
||||
- `[ ]` bloom, color grading, AA, resolution scaling `(v0.7)`
|
||||
|
||||
### M16b shader porting status (retail `Data\Shaders.big` → `*.fxo`)
|
||||
|
||||
The retail set is **88 compiled effects** (`Core12\shaders\compiled`, duplicated
|
||||
in `Misc`/`Shaders`; `Core5`/`Core8` ship only `terrain`; plus a 60-byte
|
||||
`null` stub). OpenRA3 implements the static-map subset only:
|
||||
|
||||
- **Ported (approximate stand-ins, shared by all backends):** `Terrain.fx`
|
||||
(`terrain.*`), the opaque diffuse subset of `BuildingsGeneric.fx` /
|
||||
`BasicW3D.fx` / `ObjectsGeneric.fx` (`object.*`), and the SAGE water model
|
||||
`Ocean.fx` (+ `OceanDisplacement`/`OceanNoVertexTexture`/`RiverWater`/
|
||||
`RiverReflection`/`UnderwaterDeferred`, folded into the `terrain.*` water
|
||||
branch — see `docs/REVERSE_ENGINEERING.md`). The retail water flow and bump
|
||||
maps (`art/terrain/ra3_deepocean.tga`, `ra3_deepocean_nrm.tga`) ride as the
|
||||
last two terrain-atlas layers, so no backend adds a binding.
|
||||
- **Not ported:** all faction/variant model shaders (`buildings*`, `objects*`,
|
||||
`basicw3d*`, `defaultw3d*`, `normalmapped`, `tree`/`treesway`), instances/
|
||||
animation (`infantry*`), particles and beams (`cpuparticle`, `gpuparticle*`,
|
||||
`swarmparticle`, `laser*`, `lightning`, `fx*`, `tracer`, `trail`,
|
||||
`connectionline`, `linerenderers`, `stream`, `rain`, `simple*`), shadows and
|
||||
ground decals (`shadow`, `decal`, `outlines`, `occlusion`, `terraintracks`),
|
||||
post-processing (`postfx_*`), and the 2D/misc shaders (`render2d`, `video`,
|
||||
`bootupscreen`, `debug`, `errormissing`, `rotateenvironmentmap`,
|
||||
`distortingobject`). The shared retail includes `shadowmap.fxh`, `ssao.fxh`,
|
||||
`macrotexture.fxh`, `gamma.fxh` are likewise absent (`skinning.fxh` is done at
|
||||
bind pose only).
|
||||
|
||||
### M17 `ra3.ui` — platform layer & backends `[D]`
|
||||
|
||||
- **F1 Display abstraction** `[D]`
|
||||
|
||||
@@ -324,6 +324,48 @@ vertex color and `TintColor`; `basicw3d.fxo` instead modulates a single
|
||||
macro/lightmap with `(vertexColor + additive) * diffuse * 2` and has no normal
|
||||
map. (Recovered by disassembling the embedded `vs_3_0`/`ps_3_0` bytecode.)
|
||||
|
||||
### Water / ocean (`Ocean.fx`, `OceanDisplacement.fx`, `RiverWater.fx`, `UnderwaterDeferred.fx`)
|
||||
|
||||
The SAGE water surface is reconstructed by OpenSAGE as
|
||||
`Assets/Shaders/Water.vert`+`Water.frag` (same family as retail `Ocean.fx`). The
|
||||
surface is a world-space mesh drawn with alpha blending, fed by two render
|
||||
targets rendered before it: a **reflection** map (scene from the mirrored camera
|
||||
about `GlobalWaterSettings.ReflectionPlaneZ`) and a **refraction** map + depth.
|
||||
The fragment model (`Water.frag`) is:
|
||||
|
||||
- `waterUV = worldPos.xy / 320`; a scrolling `WaterTexture` supplies both a
|
||||
flow distortion (`(tex.xy*2-1)*0.05`) and the flow layer; a `BumpTexture`
|
||||
supplies the surface `worldNormal`.
|
||||
- `fresnelFactor = dot(viewVector, +Z)`; reflection/refraction are sampled in
|
||||
screen space (`gl_FragCoord / ViewportSize`), each displaced by the distortion.
|
||||
- `linearWaterDepth = linearize(RefractionDepth) - linearize(gl_FragCoord.z)`;
|
||||
`alpha = clamp((linearWaterDepth/2)/TransparentWaterDepth, 0, TransparentWaterMinOpacity)`.
|
||||
- `final = diffuseColor * textureColor * cloudColor`, then mixed with
|
||||
`mix(reflectionColor, refractionColor, fresnelFactor)` (both maps on) or just
|
||||
one of them, per `IsRenderReflection` / `IsRenderRefraction`.
|
||||
- Per-time-of-day `WaterSet`: `WaterTexture`, `UScrollPerMS`/`VScrollPerMS`,
|
||||
`DiffuseColor`, `TransparentDiffuseColor`; `WaterTransparency` supplies
|
||||
`TransparentWaterDepth`/`TransparentWaterMinOpacity`, the skybox faces,
|
||||
`RiverTransparencyMultiplier`, `ReflectionPlaneZ`/`ReflectionOn`.
|
||||
|
||||
OpenRA3 has no water mesh or reflection/refraction targets (its terrain is
|
||||
raymarched), so the model is folded into the terrain pass (`shaders/terrain.frag`
|
||||
and its HLSL/GLSL-ES/WGSL twins): the ray's water-plane hit takes a scrolling
|
||||
wave normal built from the retail bump map combined with a de-gridded procedural
|
||||
wave, Schlick fresnel (F0 = 0.02), a sky reflection and a depth-graded
|
||||
refraction, SAGE diffuse + specular lighting, a depth-based transparency fade,
|
||||
and — when the camera is below `ReflectionPlaneZ` — an underwater tint/fog
|
||||
(`UnderwaterDeferred.fx`). The two retail maps
|
||||
`art/terrain/ra3_deepocean.tga` (flow/distortion) and `ra3_deepocean_nrm.tga`
|
||||
(bump normal) are appended as the **last two layers of the terrain atlas**
|
||||
(`ra3::terrain::build_gpu_terrain`, `water_flow = layer_count - 2`,
|
||||
`water_normal = layer_count - 1`), so every backend samples them with the
|
||||
existing atlas binding; absent maps fall back to a neutral layer. True
|
||||
reflection/refraction render targets are still the next step.
|
||||
`GPUParticleOceanDisplacement.fx` drives wave displacement from a GPU particle
|
||||
buffer and has no analogue here.
|
||||
|
||||
|
||||
The map's `ObjectsList` chunk is a list of nested `Object` assets —
|
||||
`Coord3D`, Z `angle`, `RoadType` u32, a `u16`-prefixed type-name and a property
|
||||
list whose keys index the shared name table (`ra3.map::parse_objects`). Each
|
||||
|
||||
Reference in New Issue
Block a user