diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 7ad4a6467b..c96df65ba3 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -7687,14 +7687,14 @@ RenderScreenSpaceReflectionIterations Comment - Number of times the ray march algorithm runs to find a potential hit. + X = max ray march steps. Y, Z unused. Persist 1 Type Vector3 Value - 30 + 64 16 16 @@ -7702,14 +7702,14 @@ RenderScreenSpaceReflectionRayStep Comment - How big the step is between each run. + X = initial ray march step size. Y, Z unused. Persist 1 Type Vector3 Value - 0.035 + 0.025 0.75 1 @@ -7717,14 +7717,14 @@ RenderScreenSpaceReflectionDistanceBias Comment - Distance bias to apply when rejecting a potential sample. + X = max step size cap. Y, Z unused. Persist 1 Type Vector3 Value - 0.01 + 10.0 0.4 10 @@ -7732,29 +7732,29 @@ RenderScreenSpaceReflectionDepthRejectBias Comment - Bias against the depth buffer before rejecting a sample. + X = max thickness for hit validation. Y = depth bias scale for ray origin offset. Z unused. Persist 1 Type Vector3 Value - 0.001 - 0.001 + 1.0 + 0 0.001 RenderScreenSpaceReflectionAdaptiveStepMultiplier Comment - Multiplier to scale adaptive stepping. + X = step growth rate per iteration. Y, Z unused. Persist 1 Type Vector3 Value - 1.197 + 1.13 1.5 2 @@ -7798,7 +7798,7 @@ Type S32 Value - 2 + 4 RenderScreenSpaceReflectionMaxDepth @@ -7820,7 +7820,7 @@ Type F32 Value - 0.45 + 1 RenderBumpmapMinDistanceSquared diff --git a/indra/newview/app_settings/shaders/class3/deferred/screenSpaceReflUtil.glsl b/indra/newview/app_settings/shaders/class3/deferred/screenSpaceReflUtil.glsl index 1197f8f737..7c5095fff4 100644 --- a/indra/newview/app_settings/shaders/class3/deferred/screenSpaceReflUtil.glsl +++ b/indra/newview/app_settings/shaders/class3/deferred/screenSpaceReflUtil.glsl @@ -1,9 +1,5 @@ /** * @file class3/deferred/screenSpaceReflUtil.glsl - * @brief Utility functions for implementing Screen Space Reflections (SSR). - * - * This file contains the core logic for ray marching in screen space to find reflections, - * including adaptive step sizes, binary search refinement, and handling of glossy reflections. * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code @@ -27,73 +23,18 @@ * $/LicenseInfo$ */ -// --- Uniforms --- -// These are variables passed from the CPU to the shader. +// Based on https://imanolfotia.com/blog/1 -/** @brief Sampler for the current frame's scene color. This is what reflections will be sampled from. */ uniform sampler2D sceneMap; -/** @brief Sampler for the current frame's scene depth. Used to find intersections during ray marching. */ uniform sampler2D sceneDepth; -/** @brief Resolution of the screen in pixels (width, height). */ uniform vec2 screen_res; -/** @brief The current view's projection matrix. Transforms view space coordinates to clip space. */ uniform mat4 projection_matrix; -/** @brief The inverse of the projection matrix. Transforms clip space coordinates back to view space. */ uniform mat4 inv_proj; -/** @brief Transformation matrix from the last frame's camera space to the current frame's camera space. Used for temporal reprojection.*/ uniform mat4 modelview_delta; -/** @brief Inverse of the modelview_delta matrix. Transforms current camera space back to last frame's camera space. */ uniform mat4 inv_modelview_delta; -// --- Forward declaration for a function defined elsewhere or later in the file --- -/** - * @brief Reconstructs view space position from screen coordinates and depth. - * @param pos_screen Screen space texture coordinates (0-1 range). - * @param depth Depth value sampled from the depth buffer (typically non-linear). - * @return vec4 The reconstructed position in view space. The .xyz components are position, .w is typically 1.0. - */ -vec4 getPositionWithDepth(vec2 pos_screen, float depth); - -/** - * @brief Generates a pseudo-random float value based on a 2D input vector. - * @param uv A 2D vector used as a seed for the random number generation. - * @return float A pseudo-random value in the range [0, 1). - */ -float random (vec2 uv) -{ - // A common simple hash function to generate pseudo-random numbers in GLSL. - return fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453123); -} - -// Based off of https://github.com/RoundedGlint585/ScreenSpaceReflection/ -// A few tweaks here and there to suit our needs. - -/** - * @brief Projects a 3D position (typically in view space) to 2D screen space coordinates. - * @param pos The 3D position to project (assumed to be in view space). - * @return vec2 The 2D screen space coordinates, in the range [0, 1] for x and y. - */ -vec2 generateProjectedPosition(vec3 pos) -{ - // Project the 3D position to clip space. - vec4 samplePosition = projection_matrix * vec4(pos, 1.f); - // Perform perspective divide and map to [0, 1] texture coordinate range. - samplePosition.xy = (samplePosition.xy / samplePosition.w) * 0.5 + 0.5; - return samplePosition.xy; -} - -// These booleans control various optimizations and features of the SSR algorithm. -/** @brief If true, a binary search step is performed to refine the hit point after an initial overshoot. */ -bool isBinarySearchEnabled = true; -/** @brief If true, the ray marching step size is adapted based on the distance to the nearest surface. */ -bool isAdaptiveStepEnabled = true; -/** @brief If true, the base ray marching step size increases exponentially with each step. */ -bool isExponentialStepEnabled = true; -/** @brief If true, debug colors are used to visualize aspects of the ray marching process (e.g., delta values). */ -bool debugDraw = false; - -// Near Pass Uniforms +// Declared to keep pipeline uniform setup happy uniform vec3 iterationCount; uniform vec3 rayStep; uniform vec3 distanceBias; @@ -101,541 +42,124 @@ uniform vec3 depthRejectBias; uniform vec3 adaptiveStepMultiplier; uniform vec3 splitParamsStart; uniform vec3 splitParamsEnd; - -float blurStepY = 0.01; -float blurStepX = 0.01; -float blurIterationX = 2; -float blurIterationY = 4; - -/** @brief Number of samples to take for glossy reflections. Higher values give smoother but more expensive reflections. Debug setting: RenderScreenSpaceReflectionGlossySamples */ uniform float glossySampleCount; -/** @brief A time-varying sine value, used to introduce temporal variation with the poisson sphere sampling. */ uniform float noiseSine; +uniform float maxZDepth; +uniform float maxRoughness; -/** @brief A small constant value used for floating-point comparisons, typically to avoid issues with precision. Used in self-intersection checks. */ -float epsilon = 0.000001; - -/** - * @brief Samples the scene depth texture and converts it to linear view space Z depth. - * @param tc Texture coordinates (screen space, 0-1 range) at which to sample the depth. - * @return float The linear depth value. - */ -float getLinearDepth(vec2 tc) -{ - // Sample the raw depth value from the depth texture. - float depth = texture(sceneDepth, tc).r; - // Reconstruct the view space position using the sampled depth. - vec4 pos = getPositionWithDepth(tc, depth); - // Return the Z component of the view space position, which is the linear depth. - return -pos.z; // Negated as view space Z is typically negative. -} - -/** - * @brief Calculates a fade factor based on the proximity of a screen position to the screen edges. - * The fade increases as the position gets closer to any edge. - * @param screenPos The screen position in normalized device coordinates (0-1 range). - * @return float The edge fade factor, ranging from 1.0 (no fade, away from edges) to 0.0 (full fade, at an edge). - */ -float calculateEdgeFade(vec2 screenPos) { - // Defines how close to the edge the fade effect starts. - // 0.9 means fading begins when the point is 10% away from the screen edge. - const float edgeFadeStart = 0.9; - - // Convert screen position from [0,1] to [-1,1] range and take absolute value. - // This gives distance from the center: 0 at center, 1 at edges. - vec2 distFromCenter = abs(screenPos * 2.0 - 1.0); - - // Calculate a smooth fade for each axis using smoothstep. - // smoothstep(edgeFadeStart, 1.0, x) will be: - // - 0 if x < edgeFadeStart - // - 1 if x > 1.0 - // - A smooth transition between 0 and 1 if edgeFadeStart <= x <= 1.0 - vec2 fade = smoothstep(edgeFadeStart, 1.0, distFromCenter); - - // Use the maximum fade value between X and Y axes. - // We want to fade if the point is close to ANY edge. - // If fade.x is high (close to X-edge) or fade.y is high (close to Y-edge), - // max(fade.x, fade.y) will be high. - // 1.0 - max(...) inverts this, so it's 1.0 (no fade) in the center and 0.0 (full fade) at edges. - return 1.0 - max(fade.x, fade.y); -} +// Ray march parameters wired to uniforms (.x component of each) +// rayStep.x = step size (default 0.5) +// iterationCount.x = max steps (default 96) +// adaptiveStepMultiplier.x = step growth rate (default 1.03) +// distanceBias.x = max step size cap (default 5.0) +// depthRejectBias.x = max thickness for hit validation (default 1.0) +#define STEP_SIZE rayStep.x +#define STEP_GROWTH adaptiveStepMultiplier.x +#define MAX_STEP_SIZE distanceBias.x +#define MAX_THICKNESS depthRejectBias.x +#define DEPTH_BIAS depthRejectBias.y +const int BINARY_STEPS = 8; -/** - * @brief Calculates the approximate number of mipmap levels for a texture of a given resolution. - * This can be used to determine a suitable blur radius or LOD for texture sampling. - * @param resolution The 2D resolution of the texture (width, height). - * @return float The estimated number of mipmap levels. - */ -float calculateMipmapLevels(vec2 resolution) { - // Find the larger dimension of the texture. - float maxDimension = max(resolution.x, resolution.y); - // The number of mip levels is related to log2 of the largest dimension. - // Adding 1.0 accounts for the base level (mip 0). - return floor(log2(maxDimension)) + 1.0; -} +vec4 getPositionWithDepth(vec2 pos_screen, float depth); -/** - * @brief Processes a confirmed ray intersection, calculating hit color, depth, and edge fade. - * This function encapsulates the logic for sampling the source texture and applying debug visualization. - * @param screenPos Current screen position (texture coordinates) of the hit. - * @param hitScreenDepth Depth value from the scene's depth buffer at the hit point (linear view space Z). - * @param signedDelta Signed difference between the ray's current depth and the scene's depth. Used for debug coloring. - * @param texSource The texture (e.g., scene color) to sample for the reflection color. - * @param reflRoughness Roughness of the reflecting surface, used to adjust mip level for blurring. - * @param outHitColor Output: The calculated color of the reflection. - * @param outHitDepthValue Output: The depth of the reflection hit. - * @param outEdgeFactor Output: The edge fade factor calculated at the hit position. - */ -void processRayIntersection( - vec2 screenPos, - float hitScreenDepth, - float signedDelta, - sampler2D texSource, - float reflRoughness, - out vec4 outHitColor, - out float outHitDepthValue, - out float outEdgeFactor -) +float random(vec2 uv) { - vec4 color = vec4(1.0); // Default color multiplier. - if(debugDraw) { - // If debug drawing is enabled, colorize based on the sign of delta. - // Helps visualize if the ray hit in front of or behind the surface. - color = vec4(0.5 + sign(signedDelta) / 2.0, 0.3, 0.5 - sign(signedDelta) / 2.0, 0.0); - } - - // Calculate mip level for texture sampling based on screen resolution and roughness. - // Higher roughness leads to sampling from higher (blurrier) mip levels. - float mipLevel = calculateMipmapLevels(screen_res) * reflRoughness; - - // Sample the source texture at the hit position using the calculated mip level. - outHitColor = textureLod(texSource, screenPos, mipLevel) * color; - outHitColor.a = 1.0; // Ensure full opacity for the hit. - outHitDepthValue = hitScreenDepth; // Store the depth of the hit. - outEdgeFactor = calculateEdgeFade(screenPos); // Calculate edge fade at the hit position. + return fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453123); } -/** - * @brief Checks if the ray's projected screen position is outside the [0,1] screen bounds. - * If off-screen, it calculates and updates the edge fade factor. - * @param screenPos The ray's current projected screen position (texture coordinates). - * @param currentEdgeFade Input/Output: The current edge fade value, which will be updated if the ray is off-screen. - * @return bool True if the ray is off-screen, false otherwise. - */ -bool checkAndUpdateOffScreen(vec2 screenPos, inout float currentEdgeFade) +vec2 generateProjectedPosition(vec3 pos) { - if (screenPos.x > 1.0 || screenPos.x < 0.0 || - screenPos.y > 1.0 || screenPos.y < 0.0) - { - // If off-screen, clamp the position to the edges and calculate edge fade. - vec2 clampedPos = clamp(screenPos, 0.0, 1.0); - currentEdgeFade = calculateEdgeFade(clampedPos); - return true; // Is off-screen - } - return false; // Is on-screen + vec4 samplePosition = projection_matrix * vec4(pos, 1.0); + samplePosition.xy = (samplePosition.xy / samplePosition.w) * 0.5 + 0.5; + return samplePosition.xy; } -/** - * @brief Checks if the ray is intersecting too close to the surface it originated from (self-intersection). - * This helps prevent artifacts where a reflection ray immediately hits its own surface. - * @param reflectingSurfaceDepth The view space Z depth of the surface from which the ray originates. - * @param currentRayMarchDepth The view space Z depth of the scene surface at the ray's current projected position. - * @param depthEpsilon A small tolerance value for the depth comparison. - * @return bool True if a self-intersection is detected, false otherwise. - */ -bool checkSelfIntersection(float reflectingSurfaceDepth, float currentRayMarchDepth, float depthEpsilon) +float getLinearDepth(vec2 tc) { - // Check if the ray's current depth is within epsilon distance of the original surface's depth. - return reflectingSurfaceDepth < currentRayMarchDepth + depthEpsilon && - reflectingSurfaceDepth > currentRayMarchDepth - depthEpsilon; + float depth = texture(sceneDepth, tc).r; + vec4 pos = getPositionWithDepth(tc, depth); + return -pos.z; } -/** - * @brief Advances the ray marching position based on adaptive and/or exponential stepping logic. - * @param deltaFromSurface Current difference between the ray's depth and the scene depth at the projected point. - * Negative means ray is in front of surface, positive means behind. - * @param currentMarchingPos Input/Output: The current 3D position of the ray in view space. Will be updated. - * @param currentBaseStepVec Input/Output: The base step vector. Its direction is the ray direction. Its magnitude - * can be scaled by exponential stepping and provides a reference for adaptive steps. - * @param minStepLenScalar The minimum step length ('rayStep' uniform), a scalar. - * @param useAdaptiveStepping Boolean flag to enable adaptive stepping. - * @param useExponentialStepping Boolean flag to enable exponential step scaling. - * @param expStepMultiplier Multiplier for exponential step scaling ('adaptiveStepMultiplier' uniform). - */ -void advanceRayMarch( - float deltaFromSurface, - inout vec3 currentMarchingPos, - inout vec3 currentBaseStepVec, - float minStepLenScalar, - bool useAdaptiveStepping, - bool useExponentialStepping, - float expStepMultiplier -) +vec3 binarySearch(vec3 dir, inout vec3 hitCoord, inout float dDepth) { - vec3 actualMarchingVector; - - // Calculate a minimum step size based on the current position's depth - // This prevents steps that are smaller than the depth buffer's precision - float currentDepth = abs(currentMarchingPos.z); - float depthBasedMinStep = max(minStepLenScalar, currentDepth * 0.001); // 0.1% of current depth - - if (useAdaptiveStepping) { - if (deltaFromSurface < 0.0f) { // Ray is in front of the surface - vec3 stepDir = normalize(currentBaseStepVec); - if (abs(stepDir.z) > 0.0001f) { // Avoid division by zero if ray is mostly horizontal - // Project the Z-difference onto the ray's direction to estimate distance to intersection. - float distToPotentialIntersection = abs(deltaFromSurface) / abs(stepDir.z); - // Determine adaptive step length with enhanced minimum: - float adaptiveLength = clamp(distToPotentialIntersection * 0.75f, - depthBasedMinStep, - length(currentBaseStepVec)); - actualMarchingVector = stepDir * adaptiveLength; - } else { // Ray is mostly horizontal, use a conservative step. - float stepLength = max(length(currentBaseStepVec) * 0.5f, depthBasedMinStep); - actualMarchingVector = stepDir * stepLength; - } - } else { // deltaFromSurface >= 0.0f (Ray is at or behind the surface) - float directionSign = sign(deltaFromSurface); - // Ensure retreat step is also not too small - vec3 baseStepForRetreat = currentBaseStepVec * max(0.5f, 1.0f - minStepLenScalar * max(directionSign, 0.0f)); - actualMarchingVector = baseStepForRetreat * (-directionSign); - - // Ensure minimum retreat distance - if (length(actualMarchingVector) < depthBasedMinStep) { - actualMarchingVector = normalize(actualMarchingVector) * depthBasedMinStep; - } - } - } else { // Not adaptive stepping, use the current base step vector. - actualMarchingVector = currentBaseStepVec; - // But still enforce minimum step size - if (length(actualMarchingVector) < depthBasedMinStep) { - actualMarchingVector = normalize(actualMarchingVector) * depthBasedMinStep; - } - } - - currentMarchingPos += actualMarchingVector; // Advance the ray position. + float depth; + float initialStepLen = length(dir); - if (useExponentialStepping) { - // If exponential stepping is enabled, increase the magnitude of the base step vector for subsequent iterations. - currentBaseStepVec *= expStepMultiplier; + for (int i = 0; i < BINARY_STEPS; i++) + { + vec2 projectedCoord = generateProjectedPosition(hitCoord); + depth = getLinearDepth(projectedCoord); + dDepth = abs(hitCoord.z) - depth; + + dir *= 0.5; + if (dDepth > 0.0) + hitCoord -= dir; + else + hitCoord += dir; } -} -uniform float maxZDepth; -uniform float maxRoughness; + vec2 projectedCoord = generateProjectedPosition(hitCoord); -/** - * @brief Checks if a hit point is within the specified distance range from the reflector. - * @param reflectorDepth The depth of the reflecting surface. - * @param hitDepth The depth of the hit point. - * @param rangeStart The start of the valid range (minimum distance from reflector). - * @param rangeEnd The end of the valid range (maximum distance from reflector). - * @return bool True if the hit is within the valid range, false otherwise. - */ -bool isWithinReflectionRange(float reflectorDepth, float hitDepth, float rangeStart, float rangeEnd) -{ - float distanceFromReflector = abs(hitDepth - reflectorDepth); - return distanceFromReflector >= rangeStart && distanceFromReflector <= rangeEnd; + // After 8 binary steps, precision is initialStep / 256. + // Scale acceptance with depth — precision degrades at distance. + float depthScale = max(1.0, depth * 0.01); + float maxError = max(initialStepLen * 0.1, 0.05) * depthScale; + if (abs(dDepth) > maxError) + return vec3(-1.0, -1.0, depth); + + return vec3(projectedCoord, depth); } -/** - * @brief Traces a single reflection ray through the scene using screen-space ray marching. - * It attempts to find an intersection with the scene geometry represented by a depth buffer. - * Features include adaptive step size, exponential step increase, binary search refinement, - * and edge fading. - * - * @param initialPosition The starting position of the ray in current view space. - * @param initialReflection The initial reflection vector (direction) in current view space. - * @param hitColor Output: If an intersection is found, this will be the color sampled from the 'source' texture at the hit point. Otherwise, it's (0,0,0,0). - * @param hitDepth Output: If an intersection is found, this will be the linear view space Z depth of the hit point. - * @param source The sampler2D (e.g., current frame's color buffer) to fetch reflection color from upon a hit. - * @param edgeFade Output: A factor (0-1) indicating how close the ray path or hit point is to the screen edges. 1 means no fade. - * @param roughness Surface roughness (0-1), used to adjust mip level for texture sampling to simulate blurriness. - * @return bool True if the ray hits a surface, false otherwise. - */ -bool traceScreenRay( - vec3 initialPosition, - vec3 initialReflection, - out vec4 hitColor, - out float hitDepth, - sampler2D source, - out float edgeFade, - float roughness, - int passIterationCount, - float passRayStep, - float passDistanceBias, - float passDepthRejectBias, - float passAdaptiveStepMultiplier, - float passDepthScaleExponent, - vec2 distanceParams -) +vec3 rayMarch(vec3 dir, inout vec3 hitCoord, out float dDepth, float startDepth) { - // Transform initialPosition and the target of initialReflection vector from current camera space to previous camera space. - vec3 reflectionTargetPoint = initialPosition + initialReflection; - vec3 currentPosition_transformed = (inv_modelview_delta * vec4(initialPosition, 1.0)).xyz; - - vec2 initialScreenPos = generateProjectedPosition(currentPosition_transformed); - if (initialScreenPos.x < 0.0 || initialScreenPos.x > 1.0 || - initialScreenPos.y < 0.0 || initialScreenPos.y > 1.0) { - hitColor = vec4(0.0); - edgeFade = 0.0; - hitDepth = 0.0; - return false; - } + dir *= STEP_SIZE; - vec3 reflectionTarget_transformed = (inv_modelview_delta * vec4(reflectionTargetPoint, 1.0)).xyz; - vec3 reflectionVector_transformed = reflectionTarget_transformed - currentPosition_transformed; + for (int i = 0; i < int(iterationCount.x); i++) + { + hitCoord += dir; - // Depth of the reflecting surface in the transformed view space. - float reflectingSurfaceViewDepth = -currentPosition_transformed.z; + vec2 projectedCoord = generateProjectedPosition(hitCoord); - if (reflectingSurfaceViewDepth > maxZDepth) { - // Do a sanity check: if the reflecting surface is too far away, skip ray tracing. - hitColor = vec4(0.0); - edgeFade = 0.0; - hitDepth = 0.0; - return false; - } + if (projectedCoord.x < 0.0 || projectedCoord.x > 1.0 || + projectedCoord.y < 0.0 || projectedCoord.y > 1.0) + return vec3(-1.0); - // Extract range parameters - float rangeStart = distanceParams.x; - float rangeEnd = distanceParams.y; + float depth = getLinearDepth(projectedCoord); + dDepth = abs(hitCoord.z) - depth; - // Initialize ray marching variables - NO SCALING - vec3 normalizedReflection = normalize(reflectionVector_transformed); + if (i < 1) + continue; - if (normalizedReflection.z >= 0.5) { - hitColor = vec4(0.0); - edgeFade = 0.0; - hitDepth = 0.0; - return false; - } + if (depth > maxZDepth) + return vec3(-1.0); - float depthScale = pow(reflectingSurfaceViewDepth, passDepthScaleExponent); - vec3 baseStepVector = passRayStep * max(1.0, depthScale) * normalizedReflection; - vec3 marchingPosition = currentPosition_transformed + baseStepVector; // First step from origin. - - float delta; // Difference between ray's Z depth and scene's Z depth at the projected screen position. - float prevDelta = 0.0; - vec3 prevPosition = marchingPosition; - bool crossedSurface = false; - - vec2 screenPosition; // Ray's current projected 2D screen position. - bool hit = false; // Flag to indicate if an intersection was found. - hitColor = vec4(0.0); // Initialize output hit color. - edgeFade = 1.0; // Initialize output edge fade. - float depthFromScreen = 0.0; // Linear depth sampled from the sceneDepth texture. - float furthestValidDepth = 0.0; // The furthest valid depth encountered during ray marching. - int i = 0; // Iteration counter. - // Only trace if the reflecting surface itself isn't too shallow (depthRejectBias). - if (reflectingSurfaceViewDepth > passDepthRejectBias) { - // --- Main Ray Marching Loop --- - for (; i < int(passIterationCount) && !hit; i++) { - // Project current 3D marching position to 2D screen space. - screenPosition = generateProjectedPosition(marchingPosition); - if (checkAndUpdateOffScreen(screenPosition, edgeFade)) { - hit = false; - break; + if (dDepth > 0.0) + { + float stepLen = length(dir); + float thickness = max(MAX_THICKNESS, stepLen * 1.5); + if (dDepth > thickness) + { + dir = normalize(dir) * min(stepLen * STEP_GROWTH, MAX_STEP_SIZE); + continue; } - depthFromScreen = getLinearDepth(screenPosition); - if (depthFromScreen >= maxZDepth) { - hit = false; - break; - } - float rayTravelDistance = abs(abs(marchingPosition.z) - reflectingSurfaceViewDepth); - if (rayTravelDistance > rangeEnd) { - hit = false; - break; - } - delta = abs(marchingPosition.z) - depthFromScreen; - if (checkSelfIntersection(reflectingSurfaceViewDepth, depthFromScreen, epsilon)) { - edgeFade = calculateEdgeFade(screenPosition); - hit = false; - break; - } - if (abs(delta) < passDistanceBias) { - if (isWithinReflectionRange(reflectingSurfaceViewDepth, depthFromScreen, rangeStart, rangeEnd)) { - processRayIntersection(screenPosition, depthFromScreen, delta, source, roughness, - hitColor, hitDepth, edgeFade); - if (hitDepth > furthestValidDepth) { - furthestValidDepth = hitDepth; - } - hit = true; - break; - } - } - // --- Detect sign change for binary search --- - if (i > 0 && sign(prevDelta) != sign(delta)) { - crossedSurface = true; - break; - } - prevDelta = delta; - prevPosition = marchingPosition; - advanceRayMarch(delta, marchingPosition, baseStepVector, - passRayStep, isAdaptiveStepEnabled, isExponentialStepEnabled, passAdaptiveStepMultiplier); - + return binarySearch(dir, hitCoord, dDepth); } - // --- Binary Search Refinement Loop --- - // Perform binary search if enabled, the main loop overshot (delta > 0), and no hit was found yet. - if (isBinarySearchEnabled && crossedSurface && !hit) { - vec3 a = prevPosition; - vec3 b = marchingPosition; - float prevDeltaBinary = prevDelta; - for (int j = 0; j < 5; ++j) { // 5 steps is usually enough - vec3 mid = mix(a, b, 0.5); - vec2 midScreen = generateProjectedPosition(mid); - float midEdgeFade = edgeFade; - if (checkAndUpdateOffScreen(midScreen, midEdgeFade)) { - b = mid; - continue; - } - float midDepth = getLinearDepth(midScreen); - float midDelta = abs(mid.z) - midDepth; - if (checkSelfIntersection(reflectingSurfaceViewDepth, midDepth, epsilon)) { - a = mid; - prevDeltaBinary = midDelta; - continue; - } - if (!isWithinReflectionRange(reflectingSurfaceViewDepth, midDepth, rangeStart, rangeEnd)) { - b = mid; - continue; - } - if (abs(midDelta) < passDistanceBias) { - processRayIntersection(midScreen, midDepth, midDelta, source, roughness, hitColor, hitDepth, midEdgeFade); - if (hitDepth > furthestValidDepth) { - furthestValidDepth = hitDepth; - } - edgeFade = midEdgeFade; - hit = true; - break; - } - if (sign(midDelta) == sign(prevDeltaBinary)) { - a = mid; - prevDeltaBinary = midDelta; - } else { - b = mid; - } - } - } // End of binary search refinement loop - } - - // Do a bit of distance fading if we have a hit. Use it to fade out far off objects that just don't look right. - if (hit) { - float zFadeStart = maxZDepth * 0.8; - float zFade = 1.0 - smoothstep(zFadeStart, maxZDepth, furthestValidDepth); - - // Combine both fades (multiply for stronger effect) - edgeFade *= zFade; - + // Grow step but cap at max to avoid skipping geometry + dir = normalize(dir) * min(length(dir) * STEP_GROWTH, MAX_STEP_SIZE); } - return hit; // Return whether a valid intersection was found. + return vec3(-1.0); } -/** - * @brief Handles the actual tracing logic for screen space reflections, including multi-sample glossy reflections. - * - * @param viewPos The view space position of the current pixel/surface. - * @param rayDirection The perfect reflection direction (already calculated). - * @param tc The texture coordinates of the current pixel being processed (screen space, 0-1). - * @param tracedColor Output: The accumulated reflection color. Alpha component is used for final fade/intensity. - * @param source The sampler2D (e.g., sceneMap) from which to sample reflection colors. - * @param roughness The roughness of the surface (0.0 for smooth, 1.0 for rough). - * @param iterations The number of ray marching iterations to perform. - * @param passRayStep The step size for ray marching. - * @param passDistanceBias The distance bias for intersection detection. - * @param passDepthRejectBias The depth rejection bias. - * @param passAdaptiveStepMultiplier The multiplier for adaptive stepping. - * @return bool True if at least one ray hit was successful, false otherwise. - */ -bool tracePass(vec3 viewPos, vec3 rayDirection, vec2 tc, inout vec4 tracedColor, sampler2D source, - float roughness, int iterations, float passRayStep, float passDistanceBias, - float passDepthRejectBias, float passAdaptiveStepMultiplier, float passDepthScaleExponent, vec2 distanceParams) +float calculateEdgeFade(vec2 screenPos) { - tracedColor = vec4(0.0); // Initialize accumulated color. - int hits = 0; // Counter for successful ray hits. - float cumulativeFade = 0.0; // Accumulator for edge fade factors from successful rays. - float averageHitDepth = 0.0; // Accumulator for hit depths. - - // Adjust the number of samples based on roughness. - // Smoother surfaces get more samples. - int totalSamples = int(max(float(glossySampleCount), float(glossySampleCount) * (1.0 - roughness))); - totalSamples = max(totalSamples, 1); // Ensure at least one sample. - vec2 blurOffset = vec2(0); - int pixelSeed = int(mod(dot(tc * screen_res, vec2(37.0, 17.0)), 128.0)); - for (int i = 0; i < totalSamples; i++) { - float temporalJitter = noiseSine; // Arbitrary multiplier for decorrelation - - float u1 = random(tc + vec2(i, 0.123) + temporalJitter); - float u2 = random(tc + vec2(0.456, i) + temporalJitter); - float jitter = random(tc + vec2(i * 0.777, 0.888) + temporalJitter); // Additional random jitter - - float alpha = max(roughness * roughness, 0.0001); // Already using alpha^2, clamp to avoid div by zero - float theta = atan(alpha * sqrt(u1) / sqrt(1.0 - u1)); - // Slightly jitter phi with a random offset, scaled by roughness for more effect on rough surfaces - float phi = 2.0 * 3.14159265 * (u2 + jitter * 0.15 * roughness); - - // Build tangent space - vec3 up = abs(rayDirection.y) < 0.999 ? vec3(0,1,0) : vec3(1,0,0); - vec3 tangent = normalize(cross(up, rayDirection)); - vec3 bitangent = cross(rayDirection, tangent); - - // GGX half-vector in tangent space - vec3 h = normalize( - sin(theta) * cos(phi) * tangent + - sin(theta) * sin(phi) * bitangent + - cos(theta) * rayDirection - ); - - // Reflect view vector about GGX half-vector to get the final sample direction - vec3 reflectionDirectionRandomized = normalize(reflect(-rayDirection, h)); - - float hitDepthVal; // Stores depth of the hit for this ray. - vec4 hitpointColor; // Stores color of the hit for this ray. - float rayEdgeFadeVal; // Stores edge fade for this ray. - - bool hit = traceScreenRay(viewPos, reflectionDirectionRandomized, hitpointColor, - hitDepthVal, source, rayEdgeFadeVal, roughness * 2.0, - iterations, passRayStep, passDistanceBias, passDepthRejectBias, passAdaptiveStepMultiplier, passDepthScaleExponent, distanceParams); - - if (hit) { - ++hits; - tracedColor.rgb += hitpointColor.rgb; // Accumulate color. - tracedColor.a += 1.0; // Using alpha to count hits for averaging, or for intensity. - cumulativeFade += rayEdgeFadeVal; // Accumulate edge fade. - averageHitDepth += hitDepthVal; // Accumulate hit depth. - } - } - - if (hits > 0) { - // Average the accumulated values if any rays hit. - tracedColor /= float(hits); // Average color. Note: alpha also gets divided. - cumulativeFade /= float(hits); // Average edge fade. - averageHitDepth /= float(hits); // Average hit depth. - // Store the average edge fade in the alpha channel for the caller to use. - tracedColor.a = cumulativeFade; - } else { - // No hits, result is black and no fade. - tracedColor = vec4(0.0); - } - - return hits > 0; // Return true if at least one ray hit was successful. + vec2 distFromCenter = abs(screenPos * 2.0 - 1.0); + vec2 fade = smoothstep(0.85, 1.0, distFromCenter); + return 1.0 - max(fade.x, fade.y); } -/** - * @brief Main function to compute screen space reflections for a given screen pixel. - * It traces multiple rays for glossy reflections, accumulates results, and applies various fading effects. - * - * @param totalSamples The desired number of samples for glossy reflections (can be adjusted internally). - * @param tc The texture coordinates of the current pixel being processed (screen space, 0-1). - * @param viewPos The view space position of the current pixel/surface. - * @param n The view space normal of the current pixel/surface. - * @param collectedColor Output: The accumulated reflection color. Alpha component is used for final fade/intensity. - * @param source The sampler2D (e.g., sceneMap) from which to sample reflection colors. - * @param glossiness The glossiness of the surface (0.0 for rough, 1.0 for perfectly smooth/mirror). Gets converted to roughness internally. - * @return float The number of rays that successfully hit a surface. - */ float tapScreenSpaceReflection( int totalSamples, vec2 tc, @@ -652,88 +176,133 @@ float tapScreenSpaceReflection( float roughness = 1.0 - glossiness; - if (roughness < maxRoughness) { + if (roughness >= maxRoughness) + return 0.0; - float viewDotNormal = dot(normalize(-viewPos), normalize(n)); - if (viewDotNormal <= 0.0) { - collectedColor = vec4(0.0); - return 0.0; - } + vec3 viewDir = normalize(viewPos); + vec3 normal = normalize(n); - float remappedRoughness = clamp((roughness - (maxRoughness * 0.6)) / (maxRoughness - (maxRoughness * 0.6)), 0.0, 1.0); - float roughnessIntensityFade = 1.0 - remappedRoughness; - - float roughnessFade = roughnessIntensityFade; - float currentPixelViewDepth = -viewPos.z; - - vec2 distFromCenter = abs(tc * 2.0 - 1.0); - float baseEdgeFade = 1.0 - smoothstep(0.85, 1.0, max(distFromCenter.x, distFromCenter.y)); - - if (baseEdgeFade > 0.001) { - vec3 rayDirection = normalize(reflect(normalize(viewPos), normalize(n))); - - float angleFactor = viewDotNormal; - float angleFactorSq = angleFactor * angleFactor; - - float combinedFade = roughnessFade;// distanceFactor; - combinedFade *= min(1, (1 -angleFactorSq) * 2); - - vec4 nearColor = vec4(0.0); - vec4 midColor = vec4(0.0); - vec4 farColor = vec4(0.0); - bool hasNearHits = false; - bool hasMidHits = false; - bool hasFarHits = false; - - float stepRoughnesMultiplier = mix(0.0, 8.5, roughness); - - // Near pass - vec2 distanceParams = vec2(splitParamsStart.x, splitParamsEnd.x); - hasNearHits = tracePass(viewPos, rayDirection, tc, nearColor, source, roughness, - int(iterationCount.x), rayStep.x, distanceBias.x, - depthRejectBias.x, adaptiveStepMultiplier.x, mix(1.0, 20.0, roughness * roughness), distanceParams); - - // Mid pass - distanceParams = vec2(splitParamsStart.y, splitParamsEnd.y); - hasMidHits = tracePass(viewPos, rayDirection, tc, midColor, source, roughness, - int(iterationCount.y), rayStep.y, distanceBias.y, - depthRejectBias.y, adaptiveStepMultiplier.y, mix(0.8, 12.0, roughness * roughness), distanceParams); - - // Far pass - distanceParams = vec2(splitParamsStart.z, splitParamsEnd.z); - hasFarHits = tracePass(viewPos, rayDirection, tc, farColor, source, roughness, - int(iterationCount.z), rayStep.z, distanceBias.z, - depthRejectBias.z, adaptiveStepMultiplier.z, max(0.5, stepRoughnesMultiplier), distanceParams); - - // Combine results from all three passes - collectedColor = vec4(0.0); - float totalWeight = 0.0; - - // Use a priority system: prefer near hits, then mid, then far - if (hasNearHits) { - collectedColor = nearColor; - totalWeight = 1.0; - } else if (hasMidHits) { - collectedColor = midColor; - totalWeight = 1.0; - } else if (hasFarHits) { - collectedColor = farColor; - totalWeight = 1.0; - } + float viewDotNormal = dot(-viewDir, normal); + if (viewDotNormal <= 0.0) + { + collectedColor = vec4(0.0); + return 0.0; + } - if (totalWeight > 0.0) { - float finalEdgeFade = collectedColor.a * combinedFade * baseEdgeFade; - collectedColor.a = finalEdgeFade; - return 1.0; - } else { - collectedColor = vec4(0.0); - return 0.0; - } - } else { - collectedColor = vec4(0.0); - return 0.0; + vec2 distFromCenter = abs(tc * 2.0 - 1.0); + float baseEdgeFade = 1.0 - smoothstep(0.85, 1.0, max(distFromCenter.x, distFromCenter.y)); + if (baseEdgeFade <= 0.001) + { + collectedColor = vec4(0.0); + return 0.0; + } + + // Bias the ray origin along the normal, scaled by distance. + // Prevents grazing-angle rays from scraping the originating surface + // at distance where depth precision breaks down. + float depthBias = max(0.01, -viewPos.z * DEPTH_BIAS); + vec3 biasedPos = viewPos - normal * depthBias; + + vec3 transformedPos = (inv_modelview_delta * vec4(biasedPos, 1.0)).xyz; + float startDepth = -transformedPos.z; + + if (startDepth > maxZDepth) + { + collectedColor = vec4(0.0); + return 0.0; + } + + vec3 perfectReflDir = normalize(reflect(viewDir, normal)); + + int numSamples = max(1, int(glossySampleCount)); + vec3 accumColor = vec3(0.0); + float accumFade = 0.0; + int hits = 0; + + for (int s = 0; s < numSamples; s++) + { + vec3 reflectDir = perfectReflDir; + + // Jitter reflection direction based on roughness (importance-sampled GGX) + if (roughness > 0.001) + { + float alpha = roughness * roughness; + float u1 = random(tc * screen_res + noiseSine + float(s) * 0.123); + float u2 = random(tc * screen_res * 1.7 + noiseSine + float(s) * 0.456 + 0.5); + + float theta = atan(alpha * sqrt(u1) / sqrt(1.0 - u1)); + float phi = 2.0 * 3.14159265 * u2; + + vec3 up = abs(reflectDir.y) < 0.999 ? vec3(0, 1, 0) : vec3(1, 0, 0); + vec3 tangent = normalize(cross(up, reflectDir)); + vec3 bitangent = cross(reflectDir, tangent); + + vec3 h = normalize( + sin(theta) * cos(phi) * tangent + + sin(theta) * sin(phi) * bitangent + + cos(theta) * reflectDir + ); + + reflectDir = normalize(reflect(-reflectDir, h)); } + + vec3 reflTarget = viewPos + reflectDir; + vec3 transformedTarget = (inv_modelview_delta * vec4(reflTarget, 1.0)).xyz; + vec3 transformedReflDir = normalize(transformedTarget - transformedPos); + + if (transformedReflDir.z >= 0.5) + continue; + + // Jitter ray origin along the surface normal (outward only) to break up step-boundary striations. + // Each pixel gets a different offset, so concentric banding from discrete steps dissolves into noise. + float normalJitter = random(tc * screen_res + float(s) * 0.789) * max(STEP_SIZE, -viewPos.z * 0.005); + vec3 jitteredPos = biasedPos + normal * normalJitter; + vec3 transformedJitteredPos = (inv_modelview_delta * vec4(jitteredPos, 1.0)).xyz; + vec3 hitCoord = transformedJitteredPos; + float dDepth; + + vec3 result = rayMarch(transformedReflDir, hitCoord, dDepth, startDepth); + + if (result.x < 0.0) + continue; + + vec2 hitTC = result.xy; + float hitDepth = result.z; + + float edgeFade = calculateEdgeFade(hitTC); + + float zFadeStart = maxZDepth * 0.8; + float zFade = 1.0 - smoothstep(zFadeStart, maxZDepth, hitDepth); + + float rayLength = length(hitCoord - transformedJitteredPos); + float maxMipLevels = floor(log2(max(screen_res.x, screen_res.y))); + float distanceFactor = clamp(rayLength / maxZDepth, 0.0, 1.0); + float effectiveRoughness = clamp(roughness + distanceFactor * roughness, 0.0, 1.0); + float mipLevel = maxMipLevels * effectiveRoughness; + vec4 sampledColor = textureLod(source, hitTC, mipLevel); + + float rayFade = 1.0 - smoothstep(maxZDepth * 0.6, maxZDepth, rayLength); + float sampleFade = edgeFade * zFade * rayFade; + + accumColor += sampledColor.rgb; + accumFade += sampleFade; + hits++; } - return 0; + if (hits == 0) + { + collectedColor = vec4(0.0); + return 0.0; + } + + accumColor /= float(numSamples); + accumFade /= float(numSamples); + + float remappedRoughness = clamp((roughness - (maxRoughness * 0.6)) / (maxRoughness - (maxRoughness * 0.6)), 0.0, 1.0); + float roughnessFade = 1.0 - remappedRoughness; + + float combinedFade = accumFade * roughnessFade * baseEdgeFade; + + collectedColor = vec4(accumColor, combinedFade); + return 1.0; } diff --git a/indra/newview/app_settings/shaders/class3/environment/waterF.glsl b/indra/newview/app_settings/shaders/class3/environment/waterF.glsl index da711299d7..34ea8e0a00 100644 --- a/indra/newview/app_settings/shaders/class3/environment/waterF.glsl +++ b/indra/newview/app_settings/shaders/class3/environment/waterF.glsl @@ -292,7 +292,7 @@ void main() #endif float metallic = 1.0; - float perceptualRoughness = blurMultiplier; + float perceptualRoughness = blurMultiplier * blurMultiplier; float gloss = 1 - perceptualRoughness; vec3 irradiance = vec3(0); diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 35ac7919ac..e21eaf6035 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -1035,6 +1035,10 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) if (LLPipeline::sRenderDeferred) { gPipeline.renderDeferredLighting(); + + // Copy screen to scene map for SSR to trace against next frame/pass. + // Must happen after deferred lighting so the lit scene is available. + gPipeline.copyScreenSpaceReflections(&gPipeline.mRT->screen, &gPipeline.mSceneMap); } LLPipeline::sUnderWaterRender = false; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 8695b96952..dfaf3cb907 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -5202,6 +5202,15 @@ bool LLViewerWindow::rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_hei F32 depth_conversion_factor_1 = (LLViewerCamera::getInstance()->getFar() + LLViewerCamera::getInstance()->getNear()) / (2.f * LLViewerCamera::getInstance()->getFar() * LLViewerCamera::getInstance()->getNear()); F32 depth_conversion_factor_2 = (LLViewerCamera::getInstance()->getFar() - LLViewerCamera::getInstance()->getNear()) / (2.f * LLViewerCamera::getInstance()->getFar() * LLViewerCamera::getInstance()->getNear()); + // Warmup pass: render the scene once to populate mSceneMap for SSR. + // Without this, SSR traces against an empty scene map on the first capture pass. + if (LLPipeline::sRenderDeferred && gPipeline.RenderScreenSpaceReflections) + { + gDisplaySwapBuffers = false; + gDepthDirty = true; + display(do_rebuild, scale_factor, 0, true); + } + // Subimages are in fact partial rendering of the final view. This happens when the final view is bigger than the screen. // In most common cases, scale_factor is 1 and there's no more than 1 iteration on x and y for (int subimage_y = 0; subimage_y < scale_factor; ++subimage_y) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index d04c0a7cb4..e1ebd5c107 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -8027,8 +8027,6 @@ void LLPipeline::renderFinalize() bool hdr = gGLManager.mGLVersion > 4.05f && has_hdr(); if (hdr) { - copyScreenSpaceReflections(&mRT->screen, &mSceneMap); - generateLuminance(&mRT->screen, &mLuminanceMap); generateExposure(&mLuminanceMap, &mExposureMap);