From 5dc8fc5d627727bdc17ef187ae1443be82ba053b Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 28 Aug 2023 17:15:52 -0700 Subject: [PATCH 1/5] fix: keep offset from billboard right eye --- src/Features/LightLimitFix.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Features/LightLimitFix.cpp b/src/Features/LightLimitFix.cpp index f083e6a176..87a8c25664 100644 --- a/src/Features/LightLimitFix.cpp +++ b/src/Features/LightLimitFix.cpp @@ -544,10 +544,11 @@ bool LightLimitFix::AddCachedParticleLights(eastl::vector& lightsData auto scaledTimer = timer * config.flickerSpeed; - light.positionWS[0].x += (float)perlin1.noise1D(scaledTimer) * config.flickerMovement; - light.positionWS[0].y += (float)perlin2.noise1D(scaledTimer) * config.flickerMovement; - light.positionWS[0].z += (float)perlin3.noise1D(scaledTimer) * config.flickerMovement; - light.positionWS[1] = light.positionWS[0]; + for (int eyeIndex = 0; eyeIndex < eyeCount; eyeIndex++) { + light.positionWS[eyeIndex].x += (float)perlin1.noise1D(scaledTimer) * config.flickerMovement; + light.positionWS[eyeIndex].y += (float)perlin2.noise1D(scaledTimer) * config.flickerMovement; + light.positionWS[eyeIndex].z += (float)perlin3.noise1D(scaledTimer) * config.flickerMovement; + } dimmer = std::max(0.0f, dimmer - ((float)perlin4.noise1D_01(scaledTimer) * config.flickerIntensity)); } From ddeac9fca9ed4dd8e2486a509115b2fb6901d4f4 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 28 Aug 2023 17:16:39 -0700 Subject: [PATCH 2/5] chore: parse light object in logs This adds a custom structure to fmt. --- src/Features/LightLimitFix.h | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/Features/LightLimitFix.h b/src/Features/LightLimitFix.h index 7ed9cbc0b6..48cb2e9c35 100644 --- a/src/Features/LightLimitFix.h +++ b/src/Features/LightLimitFix.h @@ -283,3 +283,39 @@ struct LightLimitFix : Feature } }; }; + +template <> +struct fmt::formatter +{ + // Presentation format: 'f' - fixed. + char presentation = 'f'; + + // Parses format specifications of the form ['f']. + constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator + { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && (*it == 'f')) + presentation = *it++; + + // Check if reached the end of the range: + if (it != end && *it != '}') + throw_format_error("invalid format"); + + // Return an iterator past the end of the parsed range: + return it; + } + + // Formats the point p using the parsed format specification (presentation) + // stored in this formatter. + auto format(const LightLimitFix::LightData& l, format_context& ctx) const -> format_context::iterator + { + // ctx.out() is an output iterator to write to. + return fmt::format_to(ctx.out(), "{{address {:x} color {} radius {} posWS {} {} posVS {} {} shadow {}}}", + reinterpret_cast(&l), + (Vector3)l.color, + l.radius, + (Vector3)l.positionWS[0], (Vector3)l.positionWS[1], + (Vector3)l.positionVS[0], (Vector3)l.positionVS[1], + l.shadowMode); + } +}; \ No newline at end of file From 51e7d074c732ed0329fa966f4cd448a701b3227a Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 28 Aug 2023 21:52:19 -0700 Subject: [PATCH 3/5] refactor: simplify ContactShadows --- features/Grass Lighting/Shaders/RunGrass.hlsl | 5 +-- .../Shaders/LightLimitFix/LightLimitFix.hlsli | 45 +++++-------------- package/Shaders/Lighting.hlsl | 4 +- 3 files changed, 16 insertions(+), 38 deletions(-) diff --git a/features/Grass Lighting/Shaders/RunGrass.hlsl b/features/Grass Lighting/Shaders/RunGrass.hlsl index c197fe3e8f..44c7f5413d 100644 --- a/features/Grass Lighting/Shaders/RunGrass.hlsl +++ b/features/Grass Lighting/Shaders/RunGrass.hlsl @@ -517,7 +517,6 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace if (lightCount > 0) { uint lightOffset = lightGrid[clusterIndex].offset; - float2 screenUV = ViewToUV(viewPosition, true, eyeIndex); float screenNoise = InterleavedGradientNoise(screenUV * perPassLLF[0].BufferDim); [loop] for (uint i = 0; i < lightCount; i++) @@ -540,9 +539,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace float3 normalizedLightDirectionVS = WorldToView(normalizedLightDirection, true, eyeIndex); if (light.shadowMode == 2) - lightColor *= ContactShadows(viewPosition, screenUV, screenNoise, normalizedLightDirectionVS, eyeIndex); + lightColor *= ContactShadows(viewPosition, screenUV, screenNoise, normalizedLightDirectionVS, 0.0, eyeIndex); else if (light.shadowMode == 1) - lightColor *= ContactShadowsLong(viewPosition, screenUV, screenNoise, normalizedLightDirectionVS, light.radius, eyeIndex); + lightColor *= ContactShadows(viewPosition, screenUV, screenNoise, normalizedLightDirectionVS, light.radius, eyeIndex); float3 lightDiffuseColor = lightColor * saturate(lightAngle.xxx); diff --git a/features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli b/features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli index b76dfbc844..f87d6da3df 100644 --- a/features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli +++ b/features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli @@ -76,47 +76,26 @@ float GetScreenDepth(float2 uv) return GetScreenDepth(depth); } -float ContactShadows(float3 rayPos, float2 texcoord, float offset, float3 lightDirectionVS, uint a_eyeIndex = 0) +float ContactShadows(float3 rayPos, float2 texcoord, float offset, float3 lightDirectionVS, float radius = 0.0, uint a_eyeIndex = 0) { - lightDirectionVS *= 1.5; - - // Offset starting position with interleaved gradient noise - rayPos += lightDirectionVS * offset; - - // Accumulate samples - float shadow = 0.0; - [loop] for (uint i = 0; i < 4; i++) - { - // Step the ray - rayPos += lightDirectionVS; - float2 rayUV = ViewToUV(rayPos, true, a_eyeIndex); - - // Ensure the UV coordinates are inside the screen - if (!IsSaturated(rayUV)) - break; - - // Compute the difference between the ray's and the camera's depth - float rayDepth = GetScreenDepth(rayUV); - - // Difference between the current ray distance and the marched light - float depthDelta = rayPos.z - rayDepth; - if (rayDepth > 16.5) // First person - shadow += saturate(depthDelta * 0.20) - saturate(depthDelta * 0.1); + float lightDirectionMult = 1.5; + float2 depthDeltaMult = float2(0.20, 0.1); + uint loopMax = 4; + + if (radius > 0) { // long + lightDirectionMult = radius / 32; + depthDeltaMult = float2(1.0, 0.05); + loopMax = 32; } - return 1.0 - saturate(shadow); -} - -float ContactShadowsLong(float3 rayPos, float2 texcoord, float offset, float3 lightDirectionVS, float radius, uint a_eyeIndex = 0) -{ - lightDirectionVS *= radius / 32; + lightDirectionVS *= lightDirectionMult; // Offset starting position with interleaved gradient noise rayPos += lightDirectionVS * offset; // Accumulate samples float shadow = 0.0; - [loop] for (uint i = 0; i < 32; i++) + [loop] for (uint i = 0; i < loopMax; i++) { // Step the ray rayPos += lightDirectionVS; @@ -132,7 +111,7 @@ float ContactShadowsLong(float3 rayPos, float2 texcoord, float offset, float3 li // Difference between the current ray distance and the marched light float depthDelta = rayPos.z - rayDepth; if (rayDepth > 16.5) // First person - shadow += saturate(depthDelta) - saturate(depthDelta * 0.05); + shadow += saturate(depthDelta * depthDeltaMult.x) - saturate(depthDelta * depthDeltaMult.y); } return 1.0 - saturate(shadow); diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index baace8040b..7a8ff3d082 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1825,9 +1825,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace if (!FrameParams.z && FrameParams.y && light.shadowMode) { float3 normalizedLightDirectionVS = WorldToView(normalizedLightDirection, true, eyeIndex); if (light.shadowMode == 2) - lightColor *= ContactShadows(viewPosition, screenUV, screenNoise, normalizedLightDirectionVS, eyeIndex); + lightColor *= ContactShadows(viewPosition, screenUV, screenNoise, normalizedLightDirectionVS, 0.0, eyeIndex); else - lightColor *= ContactShadowsLong(viewPosition, screenUV, screenNoise, normalizedLightDirectionVS, light.radius, eyeIndex); + lightColor *= ContactShadows(viewPosition, screenUV, screenNoise, normalizedLightDirectionVS, light.radius, eyeIndex); } # if defined(CPM_AVAILABLE) From 16e4290755ae9bf41e04b362f0047d89d845427a Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 28 Aug 2023 21:56:15 -0700 Subject: [PATCH 4/5] fix: fix contact shadows in VR viewport appears to be broken in VR. Switch to depth buffer interrogation from screen space shadows feature. --- .../Shaders/LightLimitFix/LightLimitFix.hlsli | 13 +++-- package/Shaders/Common/VR.hlsl | 53 ++++++++++++------- src/Features/LightLimitFix.cpp | 12 ++++- src/Features/LightLimitFix.h | 1 + 4 files changed, 52 insertions(+), 27 deletions(-) diff --git a/features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli b/features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli index f87d6da3df..927d1b60d4 100644 --- a/features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli +++ b/features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli @@ -1,3 +1,5 @@ +#include "Common/VR.hlsl" + struct LightGrid { uint offset; @@ -44,9 +46,10 @@ float GetFarPlane() return perPassLLF[0].CameraFar; } -// Get a raw depth from the depth buffer. -float GetDepth(float2 uv) +// Get a raw depth from the depth buffer. [0,1] in uv space +float GetDepth(float2 uv, uint a_eyeIndex = 0) { + uv = ConvertToStereoUV(uv, a_eyeIndex); return TexDepthSampler.Load(int3(uv * perPassLLF[0].BufferDim, 0)); } @@ -70,9 +73,9 @@ float GetScreenDepth(float depth) return (perPassLLF[0].CameraData.w / (-depth * perPassLLF[0].CameraData.z + perPassLLF[0].CameraData.x)); } -float GetScreenDepth(float2 uv) +float GetScreenDepth(float2 uv, uint a_eyeIndex = 0) { - float depth = GetDepth(uv); + float depth = GetDepth(uv, a_eyeIndex); return GetScreenDepth(depth); } @@ -106,7 +109,7 @@ float ContactShadows(float3 rayPos, float2 texcoord, float offset, float3 lightD break; // Compute the difference between the ray's and the camera's depth - float rayDepth = GetScreenDepth(rayUV); + float rayDepth = GetScreenDepth(rayUV, a_eyeIndex); // Difference between the current ray distance and the marched light float depthDelta = rayPos.z - rayDepth; diff --git a/package/Shaders/Common/VR.hlsl b/package/Shaders/Common/VR.hlsl index 3e19082ab2..42bd7b53fe 100644 --- a/package/Shaders/Common/VR.hlsl +++ b/package/Shaders/Common/VR.hlsl @@ -1,22 +1,35 @@ -/* -* Multiply for matrixes that will use an a_eyeIndex. -* This uses a standard mul if in flat -* -* @param a_matrix The matrix to multiple against -* @param a_vector The vector to multiply -* @param a_eyeIndex The a_eyeIndex, normally 0 for flat -* @return The result of a mul that works in VR with eyeindex +/** +Converts to the eye specific uv. +In VR, texture buffers include the left and right eye in the same buffer. Flat only has a single camera for the entire width. +This means the x value [0, .5] represents the left eye, and the y value (.5, 1] are the right eye. +This returns the adjusted value +@param uv - uv coords [0,1] to be encoded for VR +@param a_eyeIndex The eyeIndex; 0 is left, 1 is right +@returns uv with x coords adjusted for the VR texture buffer */ -float4 NG_mul(float4x4 a_matrix, float4 a_vector, uint a_eyeIndex = 0) +float2 ConvertToStereoUV(float2 uv, uint a_eyeIndex) { -#if !defined(VR) - float4 result = mul(a_matrix, a_vector); -#else - float4 result; - result.x = dot(a_matrix[a_eyeIndex + 0].xyzw, a_vector.xyzw); - result.y = dot(a_matrix[a_eyeIndex + 1].xyzw, a_vector.xyzw); - result.z = dot(a_matrix[a_eyeIndex + 2].xyzw, a_vector.xyzw); - result.w = dot(a_matrix[a_eyeIndex + 3].xyzw, a_vector.xyzw); -#endif // VR - return result; -} \ No newline at end of file +#ifdef VR + // convert [0,1] to eye specific [0,.5] and [.5, 1] dependent on a_eyeIndex + uv.x = (uv.x + (float)a_eyeIndex) / 2; +#endif + return uv; +} + +/** +Converts from eye specific uv to general uv. +In VR, texture buffers include the left and right eye in the same buffer. +This means the x value [0, .5] represents the left eye, and the y value (.5, 1] are the right eye. +This returns the adjusted value +@param uv - eye specific uv coords [0,1]; if uv.x < 0.5, it's a left eye; otherwise right +@param a_eyeIndex The eyeIndex; 0 is left, 1 is right +@returns uv with x coords adjusted to full range for either left or right eye +*/ +float2 ConvertFromStereoUV(float2 uv, uint a_eyeIndex) +{ +#ifdef VR + // convert [0,.5] to [0, 1] and [.5, 1] to [0,1] + uv.x = 2 * uv.x - (float)a_eyeIndex; +#endif + return uv; +} diff --git a/src/Features/LightLimitFix.cpp b/src/Features/LightLimitFix.cpp index 87a8c25664..2d47655063 100644 --- a/src/Features/LightLimitFix.cpp +++ b/src/Features/LightLimitFix.cpp @@ -353,8 +353,16 @@ void LightLimitFix::Bind() perPassData.CameraData.w = accumulator->kCamera->GetRuntimeData2().viewFrustum.fFar * accumulator->kCamera->GetRuntimeData2().viewFrustum.fNear; auto viewport = RE::BSGraphics::State::GetSingleton(); - float resolutionX = viewport->screenWidth * viewport->GetRuntimeData().dynamicResolutionCurrentWidthScale; - float resolutionY = viewport->screenHeight * viewport->GetRuntimeData().dynamicResolutionCurrentHeightScale; + if (!screenSpaceShadowsTexture) { + auto shadowMask = RE::BSGraphics::Renderer::GetSingleton()->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kSHADOW_MASK]; + D3D11_TEXTURE2D_DESC texDesc{}; + shadowMask.texture->GetDesc(&texDesc); + texDesc.Format = DXGI_FORMAT_R16_FLOAT; + texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_RENDER_TARGET; + screenSpaceShadowsTexture = new Texture2D(texDesc); + } + float resolutionX = screenSpaceShadowsTexture->desc.Width * viewport->GetRuntimeData().dynamicResolutionCurrentWidthScale; + float resolutionY = screenSpaceShadowsTexture->desc.Height * viewport->GetRuntimeData().dynamicResolutionCurrentHeightScale; perPassData.LightsNear = lightsNear; perPassData.LightsFar = lightsFar; diff --git a/src/Features/LightLimitFix.h b/src/Features/LightLimitFix.h index 48cb2e9c35..6d4ee06723 100644 --- a/src/Features/LightLimitFix.h +++ b/src/Features/LightLimitFix.h @@ -92,6 +92,7 @@ struct LightLimitFix : Feature std::uint32_t lightCount = 0; RE::BSRenderPass* currentPass = nullptr; + Texture2D* screenSpaceShadowsTexture = nullptr; eastl::hash_map> queuedParticleLights; eastl::hash_map> particleLights; From 037a47a980cf9ef34286cb4a3ece11d965cd68b7 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 28 Aug 2023 21:56:47 -0700 Subject: [PATCH 5/5] chore: update commented assembly code Primarily for future reference and potential testing --- package/Shaders/Lighting.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 7a8ff3d082..2948b1556e 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1127,7 +1127,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace // In VR, there is no worldPosition or PreviousWorldPosition as an input. This code is used to determine position // float4 worldPositionVR; // worldPositionVR.x = - // (uint)cb13[0].y ? 2 * (-eyeIndex * 0.5 + stereoUV.x) : stereoUV.x; + // (uint)cb13 ? 2 * (-eyeIndex * 0.5 + stereoUV.x) : stereoUV.x; // worldPositionVR.y = -stereoUV.y * DynamicResolutionParams2.y + 1; // worldPositionVR.xy = worldPositionVR.xy * float2(2, 2) + float2(-1, -1); // worldPositionVR.z = input.Position.z;