diff --git a/profiles/vcs/config/ProperShaders.ini b/profiles/vcs/config/ProperShaders.ini index 590a080..178c957 100644 --- a/profiles/vcs/config/ProperShaders.ini +++ b/profiles/vcs/config/ProperShaders.ini @@ -4,7 +4,7 @@ ; until their VCS guest-side sources are connected. [VolumetricClouds] Enabled=true -DownscaleDiv=4 +DownscaleDiv=2 Layers=2 ShadowSteps=4 Opacity=1.0 @@ -38,6 +38,6 @@ FogColorB=0.78 FogStart=4500.0 ; ProperShaders temporal resolve controls. -TemporalBlend=0.50 +TemporalBlend=0.85 TemporalDenoise=1.0 TemporalClamp=1.0 diff --git a/profiles/vcs/host/ge_cloudworks_present_shader.hpp b/profiles/vcs/host/ge_cloudworks_present_shader.hpp index 8427786..44b45cd 100644 --- a/profiles/vcs/host/ge_cloudworks_present_shader.hpp +++ b/profiles/vcs/host/ge_cloudworks_present_shader.hpp @@ -261,6 +261,30 @@ float3 PreviousCloudNdc(float3 dir){ return float3(q.xy/max(q.z,1e-8),q.z); } +float CloudSlabEntry(float cameraZ,float rayZ,float bottom,float top){ + if(abs(rayZ)<1e-6)return 50000.0; + if(cameraZ>=bottom&&cameraZ<=top)return 0.0; + float a=(bottom-cameraZ)/rayZ,b=(top-cameraZ)/rayZ; + float entry=min(a,b),leave=max(a,b); + return leave<0.0?50000.0:max(entry,0.0); +} + +// Reproject translation as well as rotation. CloudPadding carries the previous +// camera position. A world point on the nearest enabled physical cloud slab is +// stable under the orbiting VCS third-person camera, unlike a direction-only +// reprojection that forces the history to reset whenever the camera moves. +float3 PreviousCloudDirection(float2 uv){ + float3 dir=WorldRay(uv); + float distance=CloudSlabEntry(CloudCameraPosition.z,dir.z,500.0,900.0); + if((g_Settings&3u)>=2u) + distance=min(distance,CloudSlabEntry(CloudCameraPosition.z,dir.z,3500.0,3600.0)); + if((g_Settings&3u)>=3u) + distance=min(distance,CloudSlabEntry(CloudCameraPosition.z,dir.z,1500.0,1900.0)); + distance=min(max(distance,1.0),50000.0); + float3 worldPoint=CloudCameraPosition+dir*distance; + return normalize(worldPoint-CloudPadding); +} + // Literal SM5 adaptation of ProperShaders' PS_TemporalResolve. The march fills // one Bayer slot per 2x2 block; every other pixel keeps its own reprojected // history and leaks slightly toward the smooth current-frame reconstruction. @@ -287,7 +311,7 @@ float4 CloudTemporalResolvePS(PresentVertexOutput i):SV_TARGET { float4 t11=CloudTexture0.SampleLevel(CloudSampler,sb+marchTexel,0.0); float4 spatial=lerp(lerp(t00,t10,sf.x),lerp(t01,t11,sf.x),sf.y); - float3 previous=PreviousCloudNdc(WorldRay(uv)); + float3 previous=PreviousCloudNdc(PreviousCloudDirection(uv)); float2 prevUV=float2(previous.x*0.5+0.5,0.5-previous.y*0.5); float2 inside=step(float2(0,0),prevUV)*step(prevUV,float2(1,1)); float valid=CloudHistoryValid*inside.x*inside.y*step(1e-8,previous.z); diff --git a/profiles/vcs/host/ge_gpu_backend_dx12.cpp b/profiles/vcs/host/ge_gpu_backend_dx12.cpp index dd3375e..50cf3b9 100644 --- a/profiles/vcs/host/ge_gpu_backend_dx12.cpp +++ b/profiles/vcs/host/ge_gpu_backend_dx12.cpp @@ -2176,7 +2176,11 @@ CloudShaderConstants cloud_present_constants(const Dx12GeState &s) noexcept { out.ray_forward_opacity[axis] = static_cast(center_world[axis]); out.camera_settings[axis] = camera->camera_position[axis]; } - out.ray_right_time[3] = static_cast(s.frame_epoch) * (1.0f / 60.0f); + // ProperShaders supplies g_Time in milliseconds. The stochastic march + // offset deliberately changes every frame so temporal accumulation can + // average it away; seconds made adjacent samples nearly identical and left + // the high-frequency grain fixed on screen. + out.ray_right_time[3] = static_cast(s.frame_epoch) * (1000.0f / 60.0f); out.ray_up_seed[3] = config.random_seed; out.ray_forward_opacity[3] = config.opacity; const std::uint32_t settings = std::clamp(config.layers, 1u, 3u) | @@ -2202,7 +2206,8 @@ void record_clouds_into_world_target(Dx12GeState &s, Dx12FramebufferTarget &targ !s.cloud_march.image || !s.cloud_target_pipeline || !s.cloud_resolve_pipeline || !s.cloud_composite_pipeline) return; - const std::uint32_t settings = std::bit_cast(clouds.camera_settings[3]); + CloudShaderConstants draw_clouds = clouds; + const std::uint32_t settings = std::bit_cast(draw_clouds.camera_settings[3]); if ((settings & 0x10000u) == 0u) return; const std::uint32_t previous_index = s.cloud_history_index & 1u; @@ -2214,16 +2219,18 @@ void record_clouds_into_world_target(Dx12GeState &s, Dx12FramebufferTarget &targ float camera_delta_squared = 0.0f; if (s.cloud_history_valid) { for (std::size_t axis = 0u; axis < 3u; ++axis) { - const float delta = clouds.camera_settings[axis] - s.cloud_previous_camera[axis]; + const float delta = draw_clouds.camera_settings[axis] - s.cloud_previous_camera[axis]; camera_delta_squared += delta * delta; } } - // Like ProperShaders, rotation is reprojected while positional movement - // gets one complete current-frame march. A composite of several cloud - // depths cannot be translated correctly with a single motion vector. - const bool camera_translated = s.cloud_history_valid && - camera_delta_squared > 0.0004f; - const bool full_current_frame = !s.cloud_history_valid || camera_translated; + // VCS' third-person camera translates while orbiting the player. Reproject + // ordinary translation against the physical cloud slabs in the shader and + // reset only for an actual cut/teleport (50 world units in one frame). + const bool camera_cut = s.cloud_history_valid && camera_delta_squared > 2500.0f; + const bool full_current_frame = !s.cloud_history_valid || camera_cut; + for (std::size_t axis = 0u; axis < 3u; ++axis) + draw_clouds.brightness_padding[1u + axis] = s.cloud_history_valid + ? s.cloud_previous_camera[axis] : draw_clouds.camera_settings[axis]; constexpr std::array, 4> kBayerSlots{{ {{0.0f, 0.0f}}, {{1.0f, 1.0f}}, {{1.0f, 0.0f}}, {{0.0f, 1.0f}}}}; const auto &subpixel = kBayerSlots[s.cloud_temporal_frame & 3u]; @@ -2234,15 +2241,15 @@ void record_clouds_into_world_target(Dx12GeState &s, Dx12FramebufferTarget &targ for (std::size_t axis = 0u; axis < 3u; ++axis) destination[axis] = s.cloud_history_valid ? s.cloud_previous_ray_basis[offset + axis] - : (offset == 0u ? clouds.ray_right_time[axis] - : offset == 3u ? clouds.ray_up_seed[axis] - : clouds.ray_forward_opacity[axis]); + : (offset == 0u ? draw_clouds.ray_right_time[axis] + : offset == 3u ? draw_clouds.ray_up_seed[axis] + : draw_clouds.ray_forward_opacity[axis]); }; copy_previous_basis(temporal.previous_right_history, 0u); copy_previous_basis(temporal.previous_up_blend, 3u); copy_previous_basis(temporal.previous_forward_spatial, 6u); temporal.previous_right_history[3] = - s.cloud_history_valid && !camera_translated ? 1.0f : 0.0f; + s.cloud_history_valid && !camera_cut ? 1.0f : 0.0f; temporal.previous_up_blend[3] = std::clamp(config.temporal_blend, 0.0f, 0.95f); temporal.previous_forward_spatial[3] = std::clamp(config.temporal_denoise * 0.012f, 0.0f, 0.25f); @@ -2259,7 +2266,7 @@ void record_clouds_into_world_target(Dx12GeState &s, Dx12FramebufferTarget &targ ID3D12DescriptorHeap *heaps[]{s.srv_heap.Get(), s.sampler_heap.Get()}; s.list->SetDescriptorHeaps(2u, heaps); s.list->SetGraphicsRootSignature(s.cloud_root_signature.Get()); - s.list->SetGraphicsRoot32BitConstants(0u, 40u, &clouds, 0u); + s.list->SetGraphicsRoot32BitConstants(0u, 40u, &draw_clouds, 0u); s.list->SetGraphicsRoot32BitConstants(3u, 20u, &temporal, 0u); s.list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); constexpr float clear_cloud[4]{0.0f, 0.0f, 0.0f, 1.0f}; @@ -2324,10 +2331,10 @@ void record_clouds_into_world_target(Dx12GeState &s, Dx12FramebufferTarget &targ s.cloud_history_index = current_index; ++s.cloud_temporal_frame; for (std::size_t axis = 0u; axis < 3u; ++axis) { - s.cloud_previous_camera[axis] = clouds.camera_settings[axis]; - s.cloud_previous_ray_basis[axis] = clouds.ray_right_time[axis]; - s.cloud_previous_ray_basis[3u + axis] = clouds.ray_up_seed[axis]; - s.cloud_previous_ray_basis[6u + axis] = clouds.ray_forward_opacity[axis]; + s.cloud_previous_camera[axis] = draw_clouds.camera_settings[axis]; + s.cloud_previous_ray_basis[axis] = draw_clouds.ray_right_time[axis]; + s.cloud_previous_ray_basis[3u + axis] = draw_clouds.ray_up_seed[axis]; + s.cloud_previous_ray_basis[6u + axis] = draw_clouds.ray_forward_opacity[axis]; } s.cloud_history_valid = true; @@ -2344,7 +2351,7 @@ void record_clouds_into_world_target(Dx12GeState &s, Dx12FramebufferTarget &targ s.list->RSSetScissorRects(1u, &scissor); s.list->SetPipelineState(s.cloud_composite_pipeline.Get()); s.list->SetGraphicsRootSignature(s.cloud_root_signature.Get()); - s.list->SetGraphicsRoot32BitConstants(0u, 40u, &clouds, 0u); + s.list->SetGraphicsRoot32BitConstants(0u, 40u, &draw_clouds, 0u); temporal.control[0] = 0.0f; s.list->SetGraphicsRoot32BitConstants(3u, 20u, &temporal, 0u); s.list->SetGraphicsRootDescriptorTable(1u, srv_gpu(s, current.srv_index)); diff --git a/profiles/vcs/host/vcs_config.hpp b/profiles/vcs/host/vcs_config.hpp index 1d5a36e..f69d023 100644 --- a/profiles/vcs/host/vcs_config.hpp +++ b/profiles/vcs/host/vcs_config.hpp @@ -180,7 +180,7 @@ struct WidescreenConfiguration { // materially changes CloudWorks' appearance. struct VolumetricCloudsConfiguration { bool enabled{false}; - std::uint32_t downscale_div{4u}; + std::uint32_t downscale_div{2u}; std::uint32_t layers{2u}; std::uint32_t shadow_steps{4u}; float coverage_low{0.35f}; @@ -206,7 +206,7 @@ struct VolumetricCloudsConfiguration { float fog_color_b{0.78f}; float fog_start{4500.0f}; float day_progression{0.88f}; - float temporal_blend{0.50f}; + float temporal_blend{0.85f}; float temporal_denoise{1.0f}; float temporal_clamp{1.0f}; };