OverviewFilesCommitsIssuesDocsReleasesPeers

nvidia-rtx-godot commits / 74e12c2

Add ray-differential pixel footprints for RT texture LOD
dFdx/dFdy are zero stubs in hit shaders -- there is no 2x2 quad to differentiate over -- so every textureGrad in a custom hit group was silently selecting LOD 0: full-resolution texels at every distance, aliasing on minified detail, and the sampler doing its worst-case work on incoherent rays. Compute the world-space footprint of one pixel at the hit instead: the camera's right/up axes scaled by the world size of a pixel at this distance (projection_matrix[1][1] is 1/tan(fovy/2)), then slid along the ray onto the surface plane so grazing angles stretch the footprint as real derivatives would. Exposed as RAY_FOOTPRINT_X / RAY_FOOTPRINT_Y. Deliberately NOT wired into dFdx() itself. dFdx of a world position is exactly this footprint, but dFdx of anything else is not, and a macro cannot tell the difference -- silently handing back a position footprint for an arbitrary expression would be wrong in a way nothing would catch. Shaders opt in and say what they mean; the zero stubs stay for shaders that merely need to compile. On secondary bounces gl_HitTEXT is that bounce's length rather than a camera distance, so the footprint is an approximation there. Still far better than the zero it replaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74e12c2f4d4b40597995d1b2c41228e87b92adf1
by plankatron <fleeky@gmail.com> · 2026-07-29T20:53:04.000Z

Changed files (3) +43 -0

servers/rendering/renderer_rd/forward_clustered/scene_shader_raytracing.cpp +5 -0
……@@ -1573,6 +1573,11 @@ void SceneShaderRaytracing::init(const String p_defines) {
15731573 // behind the RT define.
15741574 actions.renames["BOUNCE_INDEX"] = "rt_bounce_index";
15751575 actions.renames["PRIMARY_HIT"] = "rt_primary_hit";
1576 // Ray-differential stand-in for dFdx/dFdy(WORLD_POSITION), which cannot
1577 // exist in a hit shader. Feed these to textureGrad instead of taking
1578 // derivatives, or the sampler falls back to LOD 0 and aliases.
1579 actions.renames["RAY_FOOTPRINT_X"] = "rt_footprint_x";
1580 actions.renames["RAY_FOOTPRINT_Y"] = "rt_footprint_y";
15761581
15771582 actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz";
15781583 actions.renames["CAMERA_POSITION_WORLD"] = "inv_view_matrix[3].xyz";
servers/rendering/renderer_rd/shaders/raytracing/raytracing_custom_fragment_inc.glsl +26 -0
……@@ -62,6 +62,32 @@ rt_front_facing = rt_front_face;
6262rt_screen_uv = vec2(gl_LaunchIDEXT.xy) / vec2(gl_LaunchSizeEXT.xy);
6363rt_frag_coord = vec4(gl_LaunchIDEXT.xy, 0.0, 1.0);
6464
65// Per-pixel world-space footprint at the hit (ray differentials). The camera's
66// right/up axes, scaled by the world size of one pixel at this distance, then
67// slid along the ray onto the surface plane so grazing angles stretch the
68// footprint the way real derivatives would.
69//
70// projection_matrix[1][1] is 1/tan(fovy/2), so 2*t/(that * viewport_height) is the
71// world height of a pixel at distance t. On secondary bounces gl_HitTEXT is that
72// bounce's length rather than a camera distance, which makes this an
73// approximation there -- still far better than the zero derivative it replaces.
74{
75 float rt_t = gl_HitTEXT;
76 vec3 rt_dir = gl_WorldRayDirectionEXT;
77 float rt_px = 2.0 * rt_t / max(projection_matrix[1][1] * read_viewport_size.y, 1e-6);
78 vec3 rt_fx = inv_view_matrix[0].xyz * rt_px;
79 vec3 rt_fy = inv_view_matrix[1].xyz * rt_px;
80 vec3 rt_n = normalize(rt_normal);
81 float rt_ndotd = dot(rt_n, rt_dir);
82 if (abs(rt_ndotd) > 1e-4) {
83 rt_footprint_x = rt_fx - rt_dir * (dot(rt_fx, rt_n) / rt_ndotd);
84 rt_footprint_y = rt_fy - rt_dir * (dot(rt_fy, rt_n) / rt_ndotd);
85 } else {
86 rt_footprint_x = rt_fx;
87 rt_footprint_y = rt_fy;
88 }
89}
90
6591// Run vertex shader (computes varyings, may modify built-ins).
6692/* RT_CUSTOM_VERTEX_CALL */
6793
servers/rendering/renderer_rd/shaders/raytracing/raytracing_custom_globals_inc.glsl +12 -0
……@@ -30,6 +30,18 @@ vec4 color_interp = vec4(1.0);
3030uint rt_bounce_index = 0u;
3131bool rt_primary_hit = true;
3232
33// World-space size of one pixel's footprint at this hit, along screen X and Y,
34// projected onto the surface. This is the ray-differential replacement for
35// dFdx/dFdy(WORLD_POSITION) -- which cannot exist in a hit shader, there being no
36// 2x2 quad -- and it is what a textureGrad needs to pick a sane mip.
37//
38// Exposed to GDShader as RAY_FOOTPRINT_X / RAY_FOOTPRINT_Y. Deliberately NOT
39// wired into dFdx() itself: dFdx of a world position is exactly this, but dFdx of
40// anything else is not, and silently returning a position footprint for an
41// arbitrary expression would be wrong in a way nothing would catch.
42vec3 rt_footprint_x = vec3(0.0);
43vec3 rt_footprint_y = vec3(0.0);
44
3345vec4 custom0_attrib = vec4(0.0);
3446vec4 custom1_attrib = vec4(0.0);
3547vec4 custom2_attrib = vec4(0.0);