100 Commits

Author SHA1 Message Date
D4rkl1ght3r 3e61200f44 Prepare Planet Pittsburgh for THN Script 2026-09-15 21:15:03 +02:00
D4rkl1ght3r d193692923 Fit FX texture to 16:9 cutscene ratio 2026-09-15 19:18:01 +02:00
D4rkl1ght3r c0a699c068 Add Ancient Character Props 2026-09-14 22:50:20 +02:00
D4rkl1ght3r 28f23a96fb Enhance crimson veins FX texture 2026-09-14 18:54:20 +02:00
D4rkl1ght3r 0ccceb96f3 adjust arc sound 2026-09-13 22:18:57 +02:00
D4rkl1ght3r f71de8f2cb Add Image Proteus Tome 2026-09-13 21:05:29 +02:00
D4rkl1ght3r d6a7c00066 Add proteus_tome & crimson_veins FX's 2026-09-13 18:52:30 +02:00
D4rkl1ght3r f53f9cd7cd Add cutscene artifacts FX's 2026-09-13 16:52:12 +02:00
D4rkl1ght3r 22acf17ff3 adjust volume & whispering voice to arc sound 2026-09-13 13:05:03 +02:00
D4rkl1ght3r ba5c857b05 Update Arc Sounds 2026-09-12 23:19:27 +02:00
D4rkl1ght3r 747c56281f Finalize REM Credits for Patch 0.4.6.0 2026-09-05 12:08:33 +02:00
D4rkl1ght3r 2e8e459df6 Create Li_alcatraz_deck_land_01.thn 2026-09-04 20:54:45 +02:00
D4rkl1ght3r aaf3bee0d0 Prepare Alcatraz Base for THN Script 2026-09-04 20:50:48 +02:00
D4rkl1ght3r 9b3b41bb08 Add PDA Background 2026-09-04 20:45:25 +02:00
D4rkl1ght3r 190dfba867 Add Liberty Rogues Arc Sound 2026-09-04 20:41:21 +02:00
D4rkl1ght3r c105e73c74 Add RenoDX DLSS5 Addon for OpenGL Reshade 2026-09-02 22:07:05 +02:00
D4rkl1ght3r fc30dc8aaa Fix Costume Entry 2026-09-02 21:34:00 +02:00
D4rkl1ght3r 0bee93e844 Update Reshade OpenGL to Version 6.8.0 with full add on support 2026-09-01 21:13:23 +02:00
D4rkl1ght3r 5cb1b8825b Add Alcatraz to Lua Config & Start parameters 2026-09-01 21:03:54 +02:00
D4rkl1ght3r 672dd09f93 Add Liberty Rogues Costume & PDA Screen for Cutscene 2026-09-01 20:40:15 +02:00
Darklighter f3002e0d9e Merge pull request 'Rem sp' (#53) from REM-SP into main
Reviewed-on: #53
2026-08-29 16:53:56 +00:00
D4rkl1ght3r 967787faff Update arc_sounds.ini 2026-08-29 18:50:04 +02:00
D4rkl1ght3r 99cf90f9c7 Update Infocards for SP 2026-08-29 18:48:07 +02:00
D4rkl1ght3r 686e6fad26 Create m13_REM-SP.ini 2026-08-29 18:48:07 +02:00
D4rkl1ght3r ea3bd49d3a Move Infocards & m13 from Main to REM-SP Branch 2026-08-29 18:48:07 +02:00
D4rkl1ght3r c461c3a642 Add Buffalo Triggers (not finished) 2026-08-29 18:48:06 +02:00
D4rkl1ght3r 39aec06623 Update Audio & Waypoint Trigger 2026-08-29 18:48:06 +02:00
D4rkl1ght3r 1df810e20c Update Jacobi Voice 2026-08-29 18:48:06 +02:00
D4rkl1ght3r 181917c7f2 Add Walker Vignette 2026-08-29 18:48:05 +02:00
D4rkl1ght3r e0f7918f3c Add more Waypoints to Li01 & Li05 2026-08-29 18:44:42 +02:00
D4rkl1ght3r 8b71d62270 Add Cnd to Cruiser Trigger 2026-08-29 18:44:41 +02:00
D4rkl1ght3r e03c154200 Add M13 Clearings & Westpoint Waypoints 2026-08-29 18:44:41 +02:00
D4rkl1ght3r b8547656b5 Fix Buffalo THN & Trigger 2026-08-29 18:44:41 +02:00
D4rkl1ght3r e413290c44 Add RTC Buffalo Start 2026-08-29 18:44:41 +02:00
D4rkl1ght3r 5ff7deebd4 Create buffalo_story_start.thn 2026-08-29 18:44:41 +02:00
D4rkl1ght3r cb8bc1f3d3 Add Loot Acquired to Rogues 2026-08-29 18:44:40 +02:00
D4rkl1ght3r ab66c99c32 Fix Cargo Drop 2026-08-29 18:44:40 +02:00
D4rkl1ght3r 294d22719c Change ShipSpawn Position 2026-08-29 18:44:40 +02:00
D4rkl1ght3r 854552c979 Adjust Voice COMM 2026-08-29 18:44:40 +02:00
D4rkl1ght3r cab452e737 Fix Alaska Trigger 2026-08-29 18:44:40 +02:00
D4rkl1ght3r c6ae7dae62 Add Jacobi Comm Voice Template 2026-08-29 18:44:40 +02:00
D4rkl1ght3r 587a5c49b8 Change Rochester undock_trigger 2026-08-29 18:44:39 +02:00
D4rkl1ght3r 016f2fcf30 Add Rochester Comm Voice Template 2026-08-29 18:44:39 +02:00
D4rkl1ght3r b2f64b18dc Add Trigger rochester_dock_permission1 2026-08-29 18:44:39 +02:00
D4rkl1ght3r a7144670e6 Change xenos_encounter1 2026-08-29 18:44:39 +02:00
D4rkl1ght3r ab396bf4fe Add Xenos NPC 2026-08-29 18:44:39 +02:00
D4rkl1ght3r 0a7400811f Add Xenos Wave 2026-08-29 18:44:39 +02:00
D4rkl1ght3r 387db719f3 Add Voice Entries 2026-08-29 18:44:38 +02:00
D4rkl1ght3r 102cc2479c Add Voice Entries 2026-08-29 18:44:38 +02:00
D4rkl1ght3r 592d2ffbd0 Add Trent Comm Voice Template 2026-08-29 18:44:38 +02:00
D4rkl1ght3r 1e93fa13ea Adjust Voice COMM 2026-08-29 18:44:38 +02:00
D4rkl1ght3r d0d84018f0 Add Rebus Comm Voice Template 2026-08-29 18:44:37 +02:00
D4rkl1ght3r 959e4f3a60 Adjust Voice COMM 2026-08-29 18:44:37 +02:00
D4rkl1ght3r 370c39a32e Add Harrison Comm Voice Template 2026-08-29 18:44:37 +02:00
D4rkl1ght3r 1f695308bd Add RTC Files 2026-08-29 18:44:37 +02:00
D4rkl1ght3r e3de8aa8ef Add Mission Triggers 2026-08-29 18:44:37 +02:00
D4rkl1ght3r a79b606d99 Upload THNs 2026-08-29 18:44:37 +02:00
D4rkl1ght3r c53f1902bd Add Reshade Shaders from former REM Repository 2026-08-29 18:40:16 +02:00
D4rkl1ght3r eb71e60c36 Update Li_mactan_deck_land_01.thn 2026-08-29 18:33:12 +02:00
D4rkl1ght3r 94eea6bc41 Add equinox weapon FX 2026-08-29 18:03:59 +02:00
D4rkl1ght3r 5037492722 Add Nebula FX & Solar 2026-08-29 18:01:41 +02:00
D4rkl1ght3r 25e3284493 Add Comment to Arc Sounds 2026-08-29 01:14:05 +02:00
D4rkl1ght3r 44dfd0bea9 Restructure Story Arc Images 2026-08-29 01:12:57 +02:00
D4rkl1ght3r cc4836cbe2 Add Arc Sound Trent 2026-08-29 01:01:01 +02:00
D4rkl1ght3r 057e5c2fb3 Update arc_sounds.ini 2026-08-29 01:00:30 +02:00
D4rkl1ght3r 324350ee9e Update Infocards for SP 2026-08-15 12:48:38 +02:00
D4rkl1ght3r 602a3b1d06 Create m13_REM-SP.ini 2026-08-15 12:48:06 +02:00
D4rkl1ght3r 0b995e3829 Move Infocards & m13 from Main to REM-SP Branch 2026-08-15 12:23:20 +02:00
D4rkl1ght3r c4ccf4f28a Add Buffalo Triggers (not finished) 2026-08-15 12:19:14 +02:00
D4rkl1ght3r 52150aba9f Update Audio & Waypoint Trigger 2026-08-15 12:19:13 +02:00
D4rkl1ght3r 0b10f8800a Update Jacobi Voice 2026-08-15 12:19:13 +02:00
D4rkl1ght3r 786198d14c Add Walker Vignette 2026-08-15 12:19:13 +02:00
D4rkl1ght3r da805fece2 Add more Waypoints to Li01 & Li05 2026-08-15 12:19:12 +02:00
D4rkl1ght3r cffe07850a Add Cnd to Cruiser Trigger 2026-08-15 12:19:12 +02:00
D4rkl1ght3r cce56a7198 Add M13 Clearings & Westpoint Waypoints 2026-08-15 12:19:12 +02:00
D4rkl1ght3r 69c0e1449d Fix Buffalo THN & Trigger 2026-08-15 12:18:53 +02:00
D4rkl1ght3r 6fb6a79849 Add RTC Buffalo Start 2026-08-15 12:18:52 +02:00
D4rkl1ght3r b0c80ed91a Create buffalo_story_start.thn 2026-08-15 12:18:52 +02:00
D4rkl1ght3r c913bc677b Add Loot Acquired to Rogues 2026-08-15 12:18:52 +02:00
D4rkl1ght3r 5b8fceb848 Fix Cargo Drop 2026-08-15 12:18:52 +02:00
D4rkl1ght3r 3323b2ba40 Change ShipSpawn Position 2026-08-15 12:16:58 +02:00
D4rkl1ght3r ac3a2d0353 Adjust Voice COMM 2026-08-15 12:15:21 +02:00
D4rkl1ght3r e78f34c228 Fix Alaska Trigger 2026-08-15 12:15:20 +02:00
D4rkl1ght3r 6719f711f8 Add Jacobi Comm Voice Template 2026-08-15 12:09:23 +02:00
D4rkl1ght3r 94bdee39fc Change Rochester undock_trigger 2026-08-15 12:09:23 +02:00
D4rkl1ght3r 898874b3b1 Add Rochester Comm Voice Template 2026-08-15 12:09:23 +02:00
D4rkl1ght3r 8feddc3d7e Add Trigger rochester_dock_permission1 2026-08-15 12:09:23 +02:00
D4rkl1ght3r d0cf157010 Change xenos_encounter1 2026-08-15 12:09:23 +02:00
D4rkl1ght3r 74347d77c1 Add Xenos NPC 2026-08-15 12:09:22 +02:00
D4rkl1ght3r 21089c15d3 Add Xenos Wave 2026-08-15 12:09:22 +02:00
D4rkl1ght3r 603b1e25d5 Add Voice Entries 2026-08-15 12:09:22 +02:00
D4rkl1ght3r a5fed8bd7e Add Voice Entries 2026-08-15 12:09:22 +02:00
D4rkl1ght3r d1c2077316 Add Trent Comm Voice Template 2026-08-15 12:09:22 +02:00
D4rkl1ght3r c605342cfa Adjust Voice COMM 2026-08-15 12:09:21 +02:00
D4rkl1ght3r 76bf4171f2 Add Rebus Comm Voice Template 2026-08-15 12:09:21 +02:00
D4rkl1ght3r 8c8d2d2bcd Adjust Voice COMM 2026-08-15 12:09:21 +02:00
D4rkl1ght3r 7c542d9753 Add Harrison Comm Voice Template 2026-08-15 12:09:21 +02:00
D4rkl1ght3r 32e459eaa1 Add RTC Files 2026-08-15 12:09:21 +02:00
D4rkl1ght3r 0052304608 Add Mission Triggers 2026-08-15 12:09:21 +02:00
D4rkl1ght3r 4a1e346a51 Upload THNs 2026-08-15 12:09:20 +02:00
88 changed files with 10121 additions and 19 deletions
Binary file not shown.
Binary file not shown.
+37 -10
View File
@@ -886,8 +886,6 @@ is_2d = true
attenuation = 0 attenuation = 0
file = Audio\Rem_Audio\Jacobi\dx_exp_0204_jacobi.wav file = Audio\Rem_Audio\Jacobi\dx_exp_0204_jacobi.wav
; Lane Hacker ; Lane Hacker
[Sound] [Sound]
@@ -946,16 +944,45 @@ is_2d = true
attenuation = 0 attenuation = 0
file = Audio\Rem_Audio\RVP140_BETA\dx_GCS_F10aR02_RVP140.wav file = Audio\Rem_Audio\RVP140_BETA\dx_GCS_F10aR02_RVP140.wav
[Sound]
nickname = dx_lanehacker_0204_rvp140
type = voice
is_2d = true
attenuation = 0
file = Audio\Rem_Audio\RVP140_BETA\dx_GCS_F10aR02_RVP140.wav
[Sound] [Sound]
nickname = dx_lanehacker_0205_rvp140 nickname = dx_lanehacker_0205_rvp140
type = voice type = voice
is_2d = true is_2d = true
attenuation = 0 attenuation = 0
file = Audio\Rem_Audio\RVP140_BETA\dx_GCS_V06aV04_RVP140.wav file = Audio\Rem_Audio\RVP140_BETA\dx_GCS_V06aV04_RVP140.wav
; Trent + 5 Db
[Sound]
nickname = dx_lanehacker_0201_Trent
type = voice
is_2d = true
attenuation = 0
file = Audio\Rem_Audio\Trent\dx_lanehacker_0201_trent.wav
; Liberty Rogues Pilot
[Sound]
nickname = dx_rogues_0401_comm
type = voice
is_2d = true
attenuation = 0
file = Audio\Rem_Audio\ROGUES\dx_rogues_0401_comm.wav
; Marcus Walker Viginette
[Sound]
nickname = dx_exp_vignette_walker_sp
type = voice
is_2d = true
attenuation = 0
file = Audio\Rem_Audio\viginettes\DX_EXP_VIGNETTE_WALKER_SP.wav
; Rebus Flashback
[Sound]
nickname = dx_flashback_rebus_sp
type = voice
is_2d = true
attenuation = 0
file = Audio\Rem_Audio\Story_Arcs\rebus_flashback.wav
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+151
View File
@@ -0,0 +1,151 @@
[Voice]
nickname = harrison_voice_m14
;extend = harrison_voice_m14
script = SC_MLHEAD_MOTION_WALLA_CASL_000LV_XA_%
script = SC_MLBODY_CHRB_IDLE_SMALL_000LV_XA_07
[Sound]
msg = DX_M14_0000_HARRISON
attenuation = -6
[Sound]
msg = DX_M14_0001_HARRISON
attenuation = -6
[Sound]
msg = DX_M14_0002_HARRISON
attenuation = -6
[Sound]
msg = DX_M14_0003_HARRISON
attenuation = -6
[Sound]
msg = DX_M14_0004_HARRISON
attenuation = -6
[Sound]
msg = DX_M14_0005_HARRISON
attenuation = -6
[Sound]
msg = DX_M14_0006_HARRISON
attenuation = -6
[Voice]
nickname = rebus_voice_m14
;extend = rebus_voice_m14
script = SC_FMHEAD_MOTION_WALLA_CASL_000LV_XA_%
script = SC_FMBODY_CHRB_IDLE_SMALL_000LV_XA_05
[Sound]
msg = DX_M14_0000_REBUS
attenuation = -6
[Sound]
msg = DX_M14_0001_REBUS
attenuation = -6
[Sound]
msg = DX_M14_0100_REBUS
attenuation = -6
[Sound]
msg = DX_M14_0101_REBUS
attenuation = -6
[Sound]
msg = DX_M14_0102_REBUS
attenuation = -6
[Voice]
nickname = trent_voice_m14
;extend = trent_voice_m14
script = SC_MLHEAD_MOTION_WALLA_CASL_000LV_XA_%
script = SC_MLBODY_CHRB_IDLE_SMALL_000LV_XA_07
[Sound]
msg = DX_M14_0010_TRENT
attenuation = -6
[Sound]
msg = DX_M14_0011_TRENT
attenuation = -6
[Sound]
msg = DX_M14_0012_TRENT
attenuation = -6
[Voice]
nickname = rochester_voice_m14
;extend = rochester_voice_m14
script = SC_MLHEAD_MOTION_WALLA_CASL_000LV_XA_%
script = SC_MLBODY_CHRB_IDLE_SMALL_000LV_XA_07
[Sound]
msg = DX_M14_0000_Junkers
attenuation = -6
[Sound]
msg = DX_M14_0001_Junkers
attenuation = -6
[Sound]
msg = DX_M14_0002_Junkers
attenuation = -6
[Sound]
msg = DX_M14_0003_Junkers
attenuation = -6
[Voice]
nickname = jacobi_voice_m14
;extend = jacobi_voice_m14
script = SC_FMHEAD_MOTION_WALLA_CASL_000LV_XA_%
script = SC_FMBODY_CHRB_IDLE_SMALL_000LV_XA_05
[Sound]
msg = DX_M14_0000_JACOBI
attenuation = -6
[Sound]
msg = DX_M14_0001_JACOBI
attenuation = -6
[Sound]
msg = DX_M14_0002_JACOBI
attenuation = -6
[Sound]
msg = DX_M14_0003_JACOBI
attenuation = -6
[Sound]
msg = DX_M14_0004_JACOBI
attenuation = -6
[Sound]
msg = DX_M14_0005_JACOBI
attenuation = -6
[Sound]
msg = DX_M14_0006_JACOBI
attenuation = -6
[Sound]
msg = DX_M14_0007_JACOBI
attenuation = -6
[Sound]
msg = DX_M14_0008_JACOBI
attenuation = -6
[Sound]
msg = DX_M14_0009_JACOBI
attenuation = -6
[Sound]
msg = DX_M14_0010_JACOBI
attenuation = -6
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
View File
@@ -1074,3 +1074,13 @@ head = sh_female2_rebus_head
body = pl_female1_rebus_peasant_body_bust body = pl_female1_rebus_peasant_body_bust
righthand = rebus_female_hand_right righthand = rebus_female_hand_right
lefthand = rebus_female_hand_left lefthand = rebus_female_hand_left
; Liberty Rogues Pilot
[Costume]
nickname = li_rogues_pilot
body = pi_pirate8_body
head = br_quigly_head
lefthand = benchmark_male_hand_left
righthand = benchmark_male_hand_right
accessory = comm_pi_pirate
+46
View File
@@ -1554,6 +1554,12 @@ textures = fx\planetflare.txm
textures = fx\sarma.txm textures = fx\sarma.txm
textures = fx\hexagon.txm textures = fx\hexagon.txm
[VisEffect]
nickname = rem_sphere_neb01
alchemy = fx\rem_misc\rem_neb01.ale
effect_crc = 313485249
textures = fx\nebuale.txm
[VisEffect] [VisEffect]
nickname = rtc_helium nickname = rtc_helium
alchemy = fx\rem_misc\rtc_helium.ale alchemy = fx\rem_misc\rtc_helium.ale
@@ -2616,3 +2622,43 @@ effect_crc = -255315402
textures = fx\efx.txm textures = fx\efx.txm
textures = fx\standardeffects.txm textures = fx\standardeffects.txm
; REM CUTSCENES
[VisEffect]
nickname = rtc_chronos_relic
alchemy = fx\rem_misc\rtc_chronos_relic.ale
effect_crc = 219533643
textures = fx\rem_cutscenes_relics.txm
[VisEffect]
nickname = rtc_artifact_crystal
alchemy = fx\rem_misc\rtc_artifact_crystal.ale
effect_crc = 330610581
textures = fx\rem_cutscenes_relics.txm
[VisEffect]
nickname = rtc_helios_shard
alchemy = fx\rem_misc\rtc_helios_shard.ale
effect_crc = -304427461
textures = fx\rem_cutscenes_relics.txm
[VisEffect]
nickname = rtc_athena_orb
alchemy = fx\rem_misc\rtc_athena_orb.ale
effect_crc = 338121621
textures = fx\rem_cutscenes_relics.txm
[VisEffect]
nickname = rtc_proteus_tome
alchemy = fx\rem_misc\rtc_proteus_tome.ale
effect_crc = -189157699
textures = fx\rem_cutscenes_relics.txm
[VisEffect]
nickname = rtc_ancient_veins
alchemy = fx\rem_misc\rtc_ancient_veins.ale
effect_crc = 201434746
textures = fx\rem_cutscenes_relics.txm
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
View File
@@ -3044,6 +3044,11 @@ nickname = rtc_helium
effect_type = EFT_EQUIP_ATTACHED_LARGE effect_type = EFT_EQUIP_ATTACHED_LARGE
vis_effect = rtc_helium vis_effect = rtc_helium
[Effect]
nickname = rem_sphere_neb01
effect_type = EFT_EQUIP_ATTACHED_LARGE
vis_effect = rem_sphere_neb01
[Effect] [Effect]
nickname = gf_blhazard_01 nickname = gf_blhazard_01
effect_type = EFT_EQUIP_ATTACHED_LARGE effect_type = EFT_EQUIP_ATTACHED_LARGE
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
+11
View File
@@ -160,3 +160,14 @@ pilot = MSN13_Osirus
state_graph = CRUISER state_graph = CRUISER
npc_class = lawful, CRUISER npc_class = lawful, CRUISER
; REM NPCs
[NPCShipArch]
nickname = MSN14_xenos
loadout = fc_x_ge_fighter4_loadout01
level = d11
ship_archetype = ge_fighter4
pilot = pilot_xenos_ace
state_graph = FIGHTER
npc_class = unlawful, elite_fighter
@@ -0,0 +1,10 @@
[CharacterEncounter]
Location = Li01_09_base, Deck
start_room = Deck
action = scripts\rem_arcs\westpoint_exploration_start.thn
autoplay
[Char]
npc = Jacobi
actor = jacobi
fidget = scripts\extras\fidget_stand_male_NULL.thn
@@ -0,0 +1,10 @@
[CharacterEncounter]
Location = Li01_09_base, Deck
start_room = Deck
action = scripts\rem_arcs\westpoint_exploration_end.thn
autoplay
[Char]
npc = Jacobi
actor = jacobi
fidget = scripts\extras\fidget_stand_male_NULL.thn
@@ -0,0 +1,10 @@
[CharacterEncounter]
Location = Li01_01_base, Cityscape
start_room = Cityscape
action = scripts\rem_arcs\manhattan_story_start01.thn
autoplay
[Char]
npc = King
actor = king
fidget = scripts\extras\fidget_stand_male_NULL.thn
@@ -0,0 +1,10 @@
[CharacterEncounter]
Location = Li01_13_base, Deck
start_room = Deck
action = scripts\rem_arcs\rochester_story_end01.thn
autoplay
[Char]
npc = King
actor = king
fidget = scripts\extras\fidget_stand_male_NULL.thn
@@ -0,0 +1,10 @@
[CharacterEncounter]
Location = Li01_12_base, Deck
start_room = Deck
action = scripts\rem_arcs\buffalo_story_start.thn
autoplay
[Char]
npc = sh_female2_rebus
actor = juni
fidget = scripts\extras\fidget_stand_female_NULL.thn
+2
View File
@@ -2859,3 +2859,5 @@ permutation_count = gcs_combat_inflicting_damage_worst, 6
permutation_count = gcs_combat_comingin_p, 3 permutation_count = gcs_combat_comingin_p, 3
permutation_count = gcs_combat_fleereason_noweapons, 3 permutation_count = gcs_combat_fleereason_noweapons, 3
[mVoiceProp]
voice = trent_voice_m14
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5 -1
View File
@@ -109,6 +109,10 @@ equip = rtc_mis5view_effect, HpFX01
nickname = dmkv_sphere_shield_load nickname = dmkv_sphere_shield_load
equip = rtc_helium_effect, HpFX01 equip = rtc_helium_effect, HpFX01
[Loadout]
nickname = dmkv_sphere_nebula_load
equip = rem_neb01_effect, HpFX01
[Loadout] [Loadout]
nickname = track_ring nickname = track_ring
equip = TrackGreen, HpRunningLight01 equip = TrackGreen, HpRunningLight01
@@ -13331,7 +13335,7 @@ cargo = emergency_action_documents_liberty, 1
[Loadout] [Loadout]
nickname = navy_satellite_sm_load nickname = navy_satellite_sm_load
archetype = navy_satellite_relay_sm archetype = navy_satellite
cargo = emergency_action_documents_liberty, 1 cargo = emergency_action_documents_liberty, 1
; pirate_transmitter_liberty_sm_load ; pirate_transmitter_liberty_sm_load
+31 -2
View File
@@ -2266,6 +2266,17 @@ mass = 10000
loadout = dmkv_sphere_shield_load loadout = dmkv_sphere_shield_load
hit_pts = 1e36 hit_pts = 1e36
[Solar]
nickname = dmkv_nebula
ids_name = 60239
ids_info = 60240
LODranges = 0, 999999
type = NON_TARGETABLE
DA_archetype = solar\REM_MISC\dmkv_sphere.3db
mass = 10000
loadout = dmkv_sphere_nebula_load
hit_pts = 1e36
[Solar] [Solar]
nickname = gravity_buoy nickname = gravity_buoy
type = SATELLITE type = SATELLITE
@@ -9533,7 +9544,7 @@ fuse = intermed_damage_transport01, 0, 750
fuse = intermed_damage_transport02, 0, 375 fuse = intermed_damage_transport02, 0, 375
[Solar] [Solar]
nickname = navy_satellite nickname = navy_satellite_test
ids_name = 217116 ids_name = 217116
type = MISSION_SATELLITE type = MISSION_SATELLITE
DA_archetype = solar\misc\gas_collector.cmp DA_archetype = solar\misc\gas_collector.cmp
@@ -9542,7 +9553,7 @@ material_library = fx\envmapbasic.mat
envmap_material = envmapbasic envmap_material = envmapbasic
LODranges = 0, 999999 LODranges = 0, 999999
mass = 10000 mass = 10000
loadout = gas_collector ;loadout = navy_satellite_sm_load
solar_radius = 300 solar_radius = 300
shape_name = NNM_SM_MINING shape_name = NNM_SM_MINING
hit_pts = 15000 hit_pts = 15000
@@ -9551,6 +9562,24 @@ fuse = fuse_gas_collector_burning, 0, 30000
explosion_arch = explosion_docking_ring explosion_arch = explosion_docking_ring
destructible = true destructible = true
[Solar]
nickname = navy_satellite
type = MISSION_SATELLITE
;ids_name = 237031
;ids_info = 300101
DA_archetype = solar\misc\gas_collector.cmp
material_library = solar\Solar_mat_misc01.mat
material_library = fx\envmapbasic.mat
envmap_material = envmapbasic
LODranges = 0, 999999
mass = 10000
;loadout = li_cruiser_debris_front_wal_sm_load
solar_radius = 1000
destructible = true
shape_name = NNM_SM_MINING
explosion_arch = explosion_wplatform
hit_pts = 2000
; Gravity Buoy Big ; Gravity Buoy Big
[Solar] [Solar]
@@ -17,7 +17,7 @@ name = Zg/PC/Player/01/A/Stand
[PlayerShipPlacement] [PlayerShipPlacement]
name = X/Shipcentre/01 name = X/Shipcentre/01
landing_script = scripts\bases\Pl_01_pad_land_01.thn landing_script = scripts\bases\pl_pitsburgh_pad_land_01.thn
launching_script = scripts\bases\Pl_01_pad_launch_01.thn launching_script = scripts\bases\Pl_01_pad_launch_01.thn
[ForSaleShipPlacement] [ForSaleShipPlacement]
@@ -8,6 +8,7 @@ ambient = ambience_deck_space_larger
[PlayerShipPlacement] [PlayerShipPlacement]
name = X/Shipcentre/01 name = X/Shipcentre/01
landing_script = Scripts\Bases\Li_alcatraz_deck_land_01.thn
[Camera] [Camera]
name = Camera_0 name = Camera_0
+8 -2
View File
@@ -545,7 +545,13 @@ prop = pumpkin, bases\REM_MISC\pumpkin.3db
room = bw_hallway01, bases\REM_MISC\bw_hallway01.cmp room = bw_hallway01, bases\REM_MISC\bw_hallway01.cmp
room = st_02_lab_doors, bases\REM_MISC\cv_lab_doors.cmp room = st_02_lab_doors, bases\REM_MISC\cv_lab_doors.cmp
room = bw_hallway01_connection, bases\REM_MISC\bw_hallway_connection.3db room = bw_hallway01_connection, bases\REM_MISC\bw_hallway_connection.3db
prop = chronos_relic_prop, Characters\props\chronos_relic_prop.3db prop = chronos_relic_prop, Characters\props2\chronos_relic_prop.3db
prop = chronos_relic_prop, Characters\props2\athena_orb_prop.3db
prop = chronos_relic_prop, Characters\props2\helios_shard_prop.3db
room = newsroom, bases\generic\newsroom.3db room = newsroom, bases\generic\newsroom.3db
room = celebration_hall, bases\REM_MISC\celebration_hall.3db room = celebration_hall, bases\REM_MISC\celebration_hall.3db
prop = celebration_hall_chairs, bases\REM_MISC\celebration_hall_chairs.3db prop = celebration_hall_chairs, bases\REM_MISC\celebration_hall_chairs.3db
prop = pda_screen01, bases\REM_MISC\pda_screen01.3db
prop = pda_background01, bases\REM_MISC\pda_background01.3db
prop = comm_pi_pirate01, characters\commhelmets\comm_br_darcy.3db
;prop = comm_pi_pirate02, characters\commhelmets\comm_pi_pirate.3db
+2
View File
@@ -21,10 +21,12 @@ setglobal('anm_gen', "stand")
-- MISSIONS Settings -- MISSIONS Settings
setglobal('manhattan_gen', "origin") setglobal('manhattan_gen', "origin")
setglobal('pittsburgh_gen', "origin")
setglobal('rochester_gen', "origin") setglobal('rochester_gen', "origin")
setglobal('westpoint_gen', "origin") setglobal('westpoint_gen', "origin")
setglobal('buffalo_gen', "origin") setglobal('buffalo_gen', "origin")
setglobal('mactan_gen', "origin") setglobal('mactan_gen', "origin")
setglobal('alcatraz_gen', "origin")
setglobal('kyoto_gen', "origin") setglobal('kyoto_gen', "origin")
setglobal('ainu_gen', "origin") setglobal('ainu_gen', "origin")
setglobal('arranmore_gen', "origin") setglobal('arranmore_gen', "origin")
+47
View File
@@ -471,3 +471,50 @@ rem.AddInfoCard(500005, "Hokkaido->Chugoku")
rem.AddInfoCard(500010, "Chugoku->Hokkaido") rem.AddInfoCard(500010, "Chugoku->Hokkaido")
-- Single Player
rem.AddInfoCard(600000, "Launch your Ship")
rem.AddInfoCard(600001, "Dock on Tradelane")
rem.AddInfoCard(600002, "Fly to Westpoint Military Academy")
rem.AddInfoCard(600003, "Dock on Westpoint Military Academy")
rem.AddInfoCard(600004, "Dock on Tradelane")
rem.AddInfoCard(600005, "Fly to the Destination Point")
rem.AddInfoCard(600006, "Dock on Alaska Jumpgate")
rem.AddInfoCard(600007, "Fly to the Destination Point")
rem.AddInfoCard(600017, "Junkers")
rem.AddInfoCard(600018, "President Jacobi")
rem.AddInfoCard(600019, "The Rebus")
-- Exploration Arc I - West Point (West Point - Li01_09_Base)
rem.AddInfoCard(600020, "Exploration Arc - West Point ")
rem.AddInfoCard(600021, [[<?xml version="1.0" encoding="UTF-16"?><RDL><PUSH/><JUST loc="left"/><TEXT>Mission 01: Walker's Remembrence</TEXT><PARA/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>Marcus Walker died when the Liberty Cruiser LNS Utah breaks a blockade at Zone 21. He sacrificed himself to help Jun'ko Zane and Edison Trent escape from the Alaska System.</TEXT><PARA/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>President Jacobi says that she has a reliable source saying that an audio-log from Markus Walker exists. The task is to find the cruiser debris on Zone 21 and extract the audio-log and transmit it to West Point academy.</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT></TEXT><PARA/><JUST loc="left"/><TEXT>Objectives:</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>- Inverstigate the LNS Utah wreckage.</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>- Find and transmit the audio log to West Point.</TEXT><PARA/><JUST loc="left"/><POP/></RDL>]])
rem.AddInfoCard(600022, "Find Walkers Audio Log")
rem.AddInfoCard(600023, "Fly to Zone 21 in the Alaska System")
rem.AddInfoCard(600024, "Destroy the Cruiser's debris")
rem.AddInfoCard(600025, "Tractor in the Audio Log")
rem.AddInfoCard(600026, "Deliver the log to West Point Academy")
rem.AddInfoCard(600027, [[<?xml version="1.0" encoding="UTF-16"?><RDL><PUSH/><JUST loc="left"/><TEXT>Mission 01: Walker's Remembrence</TEXT><PARA/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>Status: Mission accomplished</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT></TEXT><PARA/><JUST loc="left"/><TEXT>The success of this mission and the sourcing of informations, was probably more important than it appeared. It's an unusal behaviour that the Liberty Rogues engaged on that mission because of this insignificant audio-log.</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>An investigation with the Neural-Net found some encrypted files hidden in some trace loops. There's no way to get on these informations, unless you have the key to unlock the files.</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>The current situation is a dead-end, so let's get into space for another mission.</TEXT><PARA/><JUST loc="left"/><POP/></RDL>]])
rem.AddInfoCard(600028, "Enter the West Point Academy Bar")
rem.AddInfoCard(600029, "Launch into Space")
-- Story Arc I - Tour of Duty (Planet Manhattan - Li01_01_Base)
rem.AddInfoCard(600030, "Story Arc I - Manhattan")
rem.AddInfoCard(600031, [[<?xml version="1.0" encoding="UTF-16"?><RDL><PUSH/><JUST loc="left"/><TEXT>Mission 02: Tour of Duty</TEXT><PARA/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>Harrison explains that the nomad incident changed the initial situation of the colonies. Political and military permissions are in transition, but also new conflicts arise. Only the highest government authorities know more about the reasons for these structural changes.</TEXT><PARA/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>Liberty's government was considering a new taxation scheme aimed at the Junkers, that would impos tariffs on their scrap businesses in exchange for protection and infrastructure support. Many in the Junker community saw this as another attempt to restrict their hard fought independence.</TEXT><PARA/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>Harrison is asking giving him an escort to Rochester Base in sector D3 in the New York System. As a new diplomat he needs to arrange an agreement between Liberty and the Junkers..</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT></TEXT><PARA/><JUST loc="left"/><TEXT>Objectives:</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>- Fly to Rochester Base.</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>- Negotiate an agreement with the Junkers.</TEXT><PARA/><JUST loc="left"/><POP/></RDL>]])
rem.AddInfoCard(600032, "Negotiate an agreement with the Junkers")
rem.AddInfoCard(600033, "Fly to Rochester Base")
rem.AddInfoCard(600034, "Eliminate all Xenos")
rem.AddInfoCard(600035, "Land on Rochester Base")
rem.AddInfoCard(600036, [[<?xml version="1.0" encoding="UTF-16"?><RDL><PUSH/><JUST loc="left"/><TEXT>When we arrived sector D3 a few Xenos attacked Rochester Base. We joined the fight to support the Junkers. After the threat has been eliminated, we landed on Rochster Base to start the negotiations.</TEXT><PARA/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>Harrison seems to have a good knack for it. The negotiations seems to went well.</TEXT><PARA/><JUST loc="left"/><POP/></RDL>]])
-- Story Arc Buffalo - Inner Corruption (Buffalo Station - Li01_12_Base)
rem.AddInfoCard(600040, "Story Arc - Buffalo")
rem.AddInfoCard(600041, [[<?xml version="1.0" encoding="UTF-16"?><RDL><PUSH/><JUST loc="left"/><TEXT>Mission 01: Inner Corruption</TEXT><PARA/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>After arriving at Buffalo Station, a female artificial intelligence unit linked with an underground news channel called the Rebus has been introduced: Revealing the truth about Liberty's intention, it's inner corruption and the lie to cover-up of the impacts of the nomad incident.</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>Through the complex security communication structure of the Rebus, independent freelancers are always required to gain information. Liberty transmitted their planned emergency action documents to their intel Navy Satellite near Maine. Based on our intelligence, this Navy Satellite near the icy moon Maine functions as a connection hub for communication networks used in covert operations and discreet messaging. Getting these documents will certainly have an impact to the further political events and the revelation of Liberty's political agendas.</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>Objectives:</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>- Destroy the Navy Satellite</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>- Obtain the latest emergency action documents from Liberty</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>- Deliver the files to the Rebus on Buffalo Base</TEXT><PARA/><JUST loc="left"/><POP/></RDL>]])
rem.AddInfoCard(600042, "Obtain the Emergency Action Documents")
rem.AddInfoCard(600043, "Destroy the Navy Satellite")
rem.AddInfoCard(600044, "Tractor in the Emergency Action Documents")
rem.AddInfoCard(600045, "Deliver the Documents to Buffalo")
rem.AddInfoCard(600046, [[<?xml version="1.0" encoding="UTF-16"?><RDL><PUSH/><JUST loc="left"/><TEXT>>Mission 01: Inner Corruption</TEXT><PARA/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>Status: Mission accomplished</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT></TEXT><PARA/><JUST loc="left"/><TEXT>The Rebus was right. By obtaining the latest emergency action documents, there are some delicate information what hardliners of the Liberty authorities have planned as a reaction for the destruction of Freeport 7 and the Nomad incident.</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>These actions are primarely focussing to restrict the scope in what the The Rebus is acting. The political pressure is forcing President Jacobi to take action accordingly.</TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT></TEXT><PARA/><JUST loc="left"/><JUST loc="left"/><TEXT>It will take some time to evalute all captured documents. In the meantime the Lane Hackers are requesting some assistance with one of their patrols. Fly to Mactan Base in the Magellan System to start the mission.</TEXT><PARA/><JUST loc="left"/><POP/></RDL>]])
rem.AddInfoCard(600047, "Fly to Mactan Base")
rem.AddInfoCard(600048, "Land on Mactan Base")
+4
View File
@@ -78,6 +78,8 @@ function modifyLuaConfig(filePath)
elseif line:find("setglobal%('manhattan_gen',") then elseif line:find("setglobal%('manhattan_gen',") then
lines[i] = "setglobal('manhattan_gen', \"origin\")" lines[i] = "setglobal('manhattan_gen', \"origin\")"
elseif line:find("setglobal%('pittsburgh_gen',") then
lines[i] = "setglobal('pittsburgh_gen', \"origin\")"
elseif line:find("setglobal%('rochester_gen',") then elseif line:find("setglobal%('rochester_gen',") then
lines[i] = "setglobal('rochester_gen', \"origin\")" lines[i] = "setglobal('rochester_gen', \"origin\")"
elseif line:find("setglobal%('westpoint_gen',") then elseif line:find("setglobal%('westpoint_gen',") then
@@ -86,6 +88,8 @@ function modifyLuaConfig(filePath)
lines[i] = "setglobal('buffalo_gen', \"origin\")" lines[i] = "setglobal('buffalo_gen', \"origin\")"
elseif line:find("setglobal%('mactan_gen',") then elseif line:find("setglobal%('mactan_gen',") then
lines[i] = "setglobal('mactan_gen', \"origin\")" lines[i] = "setglobal('mactan_gen', \"origin\")"
elseif line:find("setglobal%('alcatraz_gen',") then
lines[i] = "setglobal('alcatraz_gen', \"origin\")"
elseif line:find("setglobal%('kyoto_gen',") then elseif line:find("setglobal%('kyoto_gen',") then
lines[i] = "setglobal('kyoto_gen', \"origin\")" lines[i] = "setglobal('kyoto_gen', \"origin\")"
elseif line:find("setglobal%('ainu_gen',") then elseif line:find("setglobal%('ainu_gen',") then
Binary file not shown.

Before

Width:  |  Height:  |  Size: 869 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 KiB

After

Width:  |  Height:  |  Size: 869 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

+4
View File
@@ -44,6 +44,10 @@ TutorialProgress=2
VariableListHeight=300.000000 VariableListHeight=300.000000
VariableListUseTabs=0 VariableListUseTabs=0
[PROXY]
EnableProxyLibrary=0
ProxyLibrary=
[SCREENSHOT] [SCREENSHOT]
ClearAlpha=1 ClearAlpha=1
FileFormat=1 FileFormat=1
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
enabled=1
mode=2
hdr=-1
depth_inverted=-1
flags=-1
reset_every=1
log_frames=3
host_window=0
work_resolution=100
work_upscale=0
work_sharpness=0.30
async_home=1
mv_scale_x=1.000
mv_scale_y=1.000
cast_key=0
cast_scale=100
cast_mode=0
+1
View File
@@ -110,6 +110,7 @@ DLL = remres.dll ; Dynamic Infocards
voices = audio\voices_mission11.ini ;dialogue specific to mission 11 (story & special supporting characters only - no reusable dialogue) voices = audio\voices_mission11.ini ;dialogue specific to mission 11 (story & special supporting characters only - no reusable dialogue)
voices = audio\voices_mission12.ini ;dialogue specific to mission 12 (story & special supporting characters only - no reusable dialogue) voices = audio\voices_mission12.ini ;dialogue specific to mission 12 (story & special supporting characters only - no reusable dialogue)
voices = audio\voices_mission13.ini ;dialogue specific to mission 13 (story & special supporting characters only - no reusable dialogue) voices = audio\voices_mission13.ini ;dialogue specific to mission 13 (story & special supporting characters only - no reusable dialogue)
voices = audio\voices_mission14.ini ;dialogue specific to mission 14 (story & special supporting characters only - no reusable dialogue)
; voices = audio\voices_tutorial.ini ;dialogue specific to tutorial CUT on 20021211 MG ; voices = audio\voices_tutorial.ini ;dialogue specific to tutorial CUT on 20021211 MG
explosions = fx\explosions.ini explosions = fx\explosions.ini
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+182
View File
@@ -0,0 +1,182 @@
// LICENSE
// =======
// Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved.
// -------
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
// -------
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
// Software.
// -------
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
//Initial port to ReShade: SLSNe https://gist.github.com/SLSNe/bbaf2d77db0b2a2a0755df581b3cf00c
//Optimizations by Marty McFly:
// vectorized math, even with scalar gcn hardware this should work
// out the same, order of operations has not changed
// For some reason, it went from 64 to 48 instructions, a lot of MOV gone
// Also modified the way the final window is calculated
//
// reordered min() and max() operations, from 11 down to 9 registers
//
// restructured final weighting, 49 -> 48 instructions
//
// delayed RCP to replace SQRT with RSQRT
//
// removed the saturate() from the control var as it is clamped
// by UI manager already, 48 -> 47 instructions
//
// replaced tex2D with tex2Doffset intrinsic (address offset by immediate integer)
// 47 -> 43 instructions
// 9 -> 8 registers
//Further modified by OopyDoopy and Lord of Lunacy:
// Changed wording in the UI for the existing variable and added a new variable and relevant code to adjust sharpening strength.
//Fix by Lord of Lunacy:
// Made the shader use a linear colorspace rather than sRGB, as recommended by the original AMD documentation from FidelityFX.
//Modified by CeeJay.dk:
// Included a label and tooltip description. I followed AMDs official naming guidelines for FidelityFX.
//
// Used gather trick to reduce the number of texture operations by one (9 -> 8). It's now 42 -> 51 instructions but still faster
// because of the texture operation that was optimized away.
//Fix by CeeJay.dk
// Fixed precision issues with the gather at super high resolutions
// Also tried to refactor the samples so more work can be done while they are being sampled, but it's not so easy and the gains
// I'm seeing are so small they might be statistical noise. So it MIGHT be faster - no promises.
//Fix by BC46
// Removed the "0.5 * pixel" expressions in the DX10 or higher check.
// With the use of dgVoodoo at least, the previous code caused the entire image to be blurry and slightly mispositioned.
uniform float Contrast <
ui_type = "drag";
ui_label = "Contrast Adaptation";
ui_tooltip = "Adjusts the range the shader adapts to high contrast (0 is not all the way off). Higher values = more high contrast sharpening.";
ui_min = 0.0; ui_max = 1.0;
> = 0.0;
uniform float Sharpening <
ui_type = "drag";
ui_label = "Sharpening intensity";
ui_tooltip = "Adjusts sharpening intensity by averaging the original pixels to the sharpened result. 1.0 is the unmodified default.";
ui_min = 0.0; ui_max = 1.0;
> = 1.0;
#include "ReShade.fxh"
#define pixel float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT)
texture TexColor : COLOR;
sampler sTexColor {Texture = TexColor; SRGBTexture = true;};
float3 CASPass(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target
{
// fetch a 3x3 neighborhood around the pixel 'e',
// a b c
// d(e)f
// g h i
float3 b = tex2Doffset(sTexColor, texcoord, int2(0, -1)).rgb;
float3 d = tex2Doffset(sTexColor, texcoord, int2(-1, 0)).rgb;
#if __RENDERER__ >= 0xa000 // If DX10 or higher
float4 red_efhi = tex2DgatherR(sTexColor, texcoord);
float3 e = float3( red_efhi.w, red_efhi.w, red_efhi.w);
float3 f = float3( red_efhi.z, red_efhi.z, red_efhi.z);
float3 h = float3( red_efhi.x, red_efhi.x, red_efhi.x);
float3 i = float3( red_efhi.y, red_efhi.y, red_efhi.y);
float4 green_efhi = tex2DgatherG(sTexColor, texcoord);
e.g = green_efhi.w;
f.g = green_efhi.z;
h.g = green_efhi.x;
i.g = green_efhi.y;
float4 blue_efhi = tex2DgatherB(sTexColor, texcoord);
e.b = blue_efhi.w;
f.b = blue_efhi.z;
h.b = blue_efhi.x;
i.b = blue_efhi.y;
#else // If DX9
float3 e = tex2D(sTexColor, texcoord).rgb;
float3 f = tex2Doffset(sTexColor, texcoord, int2(1, 0)).rgb;
float3 h = tex2Doffset(sTexColor, texcoord, int2(0, 1)).rgb;
float3 i = tex2Doffset(sTexColor, texcoord, int2(1, 1)).rgb;
#endif
float3 g = tex2Doffset(sTexColor, texcoord, int2(-1, 1)).rgb;
float3 a = tex2Doffset(sTexColor, texcoord, int2(-1, -1)).rgb;
float3 c = tex2Doffset(sTexColor, texcoord, int2(1, -1)).rgb;
// Soft min and max.
// a b c b
// d e f * 0.5 + d e f * 0.5
// g h i h
// These are 2.0x bigger (factored out the extra multiply).
float3 mnRGB = min(min(min(d, e), min(f, b)), h);
float3 mnRGB2 = min(mnRGB, min(min(a, c), min(g, i)));
mnRGB += mnRGB2;
float3 mxRGB = max(max(max(d, e), max(f, b)), h);
float3 mxRGB2 = max(mxRGB, max(max(a, c), max(g, i)));
mxRGB += mxRGB2;
// Smooth minimum distance to signal limit divided by smooth max.
float3 rcpMRGB = rcp(mxRGB);
float3 ampRGB = saturate(min(mnRGB, 2.0 - mxRGB) * rcpMRGB);
// Shaping amount of sharpening.
ampRGB = rsqrt(ampRGB);
float peak = -3.0 * Contrast + 8.0;
float3 wRGB = -rcp(ampRGB * peak);
float3 rcpWeightRGB = rcp(4.0 * wRGB + 1.0);
// 0 w 0
// Filter shape: w 1 w
// 0 w 0
float3 window = (b + d) + (f + h);
float3 outColor = saturate((window * wRGB + e) * rcpWeightRGB);
return lerp(e, outColor, Sharpening);
}
technique ContrastAdaptiveSharpen
<
ui_label = "AMD FidelityFX Contrast Adaptive Sharpening";
ui_tooltip =
"CAS is a low overhead adaptive sharpening algorithm that AMD includes with their drivers.\n"
"This port to ReShade works with all cards from all vendors,\n"
"but cannot do the optional scaling that CAS is normally also capable of when activated in the AMD drivers.\n"
"\n"
"The algorithm adjusts the amount of sharpening per pixel to target an even level of sharpness across the image.\n"
"Areas of the input image that are already sharp are sharpened less, while areas that lack detail are sharpened more.\n"
"This allows for higher overall natural visual sharpness with fewer artifacts.";
>
{
pass
{
VertexShader = PostProcessVS;
PixelShader = CASPass;
SRGBWriteEnable = true;
}
}
@@ -0,0 +1,140 @@
// Copyright (c) 2016-2018, bacondither
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer
// in this position and unchanged.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Colourfulness - version 2018-11-12
// EXPECTS FULL RANGE GAMMA LIGHT
#include "ReShadeUI.fxh"
uniform float colourfulness < __UNIFORM_SLIDER_FLOAT1
ui_min = -1.0; ui_max = 2.0;
ui_tooltip = "Degree of colourfulness, 0 = neutral";
ui_step = 0.01;
> = 0.4;
uniform float lim_luma < __UNIFORM_SLIDER_FLOAT1
ui_min = 0.1; ui_max = 1.0;
ui_tooltip = "Lower values allows for more change near clipping";
ui_step = 0.01;
> = 0.7;
uniform bool enable_dither <
ui_tooltip = "Enables dithering, avoids introducing banding in gradients";
ui_category = "Dither";
> = false;
uniform bool col_noise <
ui_tooltip = "Coloured dither noise, lower subjective noise level";
ui_category = "Dither";
> = true;
uniform float backbuffer_bits <
ui_min = 1.0; ui_max = 32.0;
ui_tooltip = "Backbuffer bith depth, most likely 8 or 10 bits";
ui_category = "Dither";
> = 8.0;
//-------------------------------------------------------------------------------------------------
#ifndef fast_luma
#define fast_luma 1 // Rapid approx of sRGB gamma, small difference in quality
#endif
#ifndef temporal_dither
#define temporal_dither 0 // Dither changes with every frame
#endif
//-------------------------------------------------------------------------------------------------
#include "ReShade.fxh"
#if (temporal_dither == 1)
uniform int rnd < source = "random"; min = 0; max = 1000; >;
#endif
// Sigmoid function, sign(v)*pow(pow(abs(v), -2) + pow(s, -2), 1.0/-2)
#define soft_lim(v,s) ( (v*s)*rcp(sqrt(s*s + v*v)) )
// Weighted power mean, p = 0.5
#define wpmean(a,b,w) ( pow(abs(w)*sqrt(abs(a)) + abs(1-w)*sqrt(abs(b)), 2) )
// Max/Min RGB components
#define maxRGB(c) ( max((c).r, max((c).g, (c).b)) )
#define minRGB(c) ( min((c).r, min((c).g, (c).b)) )
// Mean of Rec. 709 & 601 luma coefficients
#define lumacoeff float3(0.2558, 0.6511, 0.0931)
float3 Colourfulness(float4 vpos : SV_Position, float2 tex : TEXCOORD) : SV_Target
{
#if (fast_luma == 1)
float3 c0 = tex2D(ReShade::BackBuffer, tex).rgb;
float luma = sqrt(dot(saturate(c0*abs(c0)), lumacoeff));
c0 = saturate(c0);
#else // Better approx of sRGB gamma
float3 c0 = saturate(tex2D(ReShade::BackBuffer, tex).rgb);
float luma = pow(dot(pow(c0 + 0.06, 2.4), lumacoeff), 1.0/2.4) - 0.06;
#endif
// Calc colour saturation change
float3 diff_luma = c0 - luma;
float3 c_diff = diff_luma*(colourfulness + 1) - diff_luma;
if (colourfulness > 0.0)
{
// 120% of c_diff clamped to max visible range + overshoot
float3 rlc_diff = clamp((c_diff*1.2) + c0, -0.0001, 1.0001) - c0;
// Calc max saturation-increase without altering RGB ratios
float poslim = (1.0002 - luma)/(abs(maxRGB(diff_luma)) + 0.0001);
float neglim = (luma + 0.0002)/(abs(minRGB(diff_luma)) + 0.0001);
float3 diffmax = diff_luma*min(min(poslim, neglim), 32) - diff_luma;
// Soft limit diff
c_diff = soft_lim( c_diff, max(wpmean(diffmax, rlc_diff, lim_luma), 1e-7) );
}
if (enable_dither == true)
{
// Interleaved gradient noise by Jorge Jimenez
const float3 magic = float3(0.06711056, 0.00583715, 52.9829189);
#if (temporal_dither == 1)
float xy_magic = (vpos.x + rnd)*magic.x + (vpos.y + rnd)*magic.y;
#else
float xy_magic = vpos.x*magic.x + vpos.y*magic.y;
#endif
float noise = (frac(magic.z*frac(xy_magic)) - 0.5)/(exp2(backbuffer_bits) - 1);
c_diff += col_noise == true ? float3(-noise, noise, -noise) : noise;
}
return saturate(c0 + c_diff);
}
technique Colourfulness
{
pass
{
VertexShader = PostProcessVS;
PixelShader = Colourfulness;
}
}
+817
View File
@@ -0,0 +1,817 @@
/*
DLSS5_Feed.fx - companion effect for the "DLSS 5 Feed" ReShade add-on (dlss5-feed.addon64/32).
It turns what ReShade already has into the guide textures DLSS needs, in the exact layout
the add-on expects:
DLSS5_MV RG16F motion vectors in PIXELS, pointing from the current pixel to where it was
in the previous frame (DLSS convention). Vectors that fail validation
(below) are zeroed.
DLSS5_Depth R32F the game's raw hardware depth (not linearised), sampled at backbuffer size,
with ReShade's RESHADE_DEPTH_INPUT_* orientation fixes applied.
DLSS5_Mask R8 "bias current colour" mask for DLSS: 1 where the motion vector could not
be trusted, so DLSS leans on the current frame there instead of warping
history in. Optional -- an add-on that does not know it ignores it.
MOTION VECTOR PROVIDER -- set the DLSS5_MV_PROVIDER preprocessor definition (ReShade overlay:
this effect's "Preprocessor definitions", or the global list) and enable that provider's
technique ABOVE this one in the effect list:
0 texMotionVectors the community-standard shared texture: qUINT_motionvectors,
dh_uber_motion, ReshadeMotionEstimation (DRME -- NOTE: DRME does not
compile on ReShade 6.8, "cannot sample from texture that is also used
as render target"; it then silently writes nothing) [default]
1 Launchpad iMMERSE Launchpad (MartysMods_LAUNCHPAD.fx): Deferred::MotionVectorsTex.
Launchpad only runs its optical flow when asked to, so this mode also
files that per-frame request (Launchpad's IPC buffer, see below).
2 VORT vort_Motion.fx (MIT): MotVectTexVort -- the recommended provider
3 LumeniteFX Kernel lumenite_Kernel.fx ("LUMENITE: Kernel"): Kernel::tFlow -- pyramidal
optical flow with per-level median + a-trous filtering and previous-
frame seeding. 1/8 resolution, upsampled here. Needs no depth buffer.
4 LumeniteFX QuantMotion
lumenite_QuantMotion.fx: QuantMotion::tFlow -- the light cut of 3.
This is the same mechanism dh_uber_rt (USE_MARTY_LAUNCHPAD_MOTION / USE_VORT_MOTION) and
vort (V_MV_MODE) use: the selected provider's OUTPUT texture is declared here exactly as the
provider declares it, so ReShade binds the same resource, and only that one is allocated.
Every provider above hands out delta UV with prev_uv = uv + mv. Nothing of any provider is
included or bundled: this file contains no third-party code and includes no third-party
files beyond ReShade's own headers.
VALIDATION -- why it exists. A game's motion vectors are geometric: a static wall under a
flickering light has vectors of exactly zero. Every provider above is OPTICAL FLOW: it
matches pixels, so a lighting change (flicker, flames, particles) is answered with a vector
that points at whatever happened to match -- confidently wrong, and DLSS then warps its
history in from there. That is the "warping around flames" and the "bad dither when the
light flickers". The fix is the one every production TAA uses: reproject and CHECK.
For each pixel, three tests against the previous frame at uv + mv:
- luma: the previous luma must fall inside the current 3x3 neighbourhood's range
(flicker moves the whole range, so a stale match falls outside);
- depth: the previous linear depth must match the current one (disocclusions);
- consistency: the previous frame's vector at that spot must resemble this one
(real motion is smooth frame to frame; flow on fire is erratic).
A vector failing any test is zeroed (the surface is treated as static -- the right answer
for a lit wall) and the pixel is flagged in DLSS5_Mask so DLSS trusts the current frame there.
The add-on runs DLSS + DLSS 5 neural rendering right after the "DLSS5_Feed" technique has
rendered, so anything placed below it in the list is applied on top of the neural output.
*/
#include "ReShade.fxh"
// Expose ReShade's completed frame to the add-on as an SRV. The 64-bit D3D11 path
// uses this only when its work-resolution control is below 100%; no extra pass or
// copy is introduced by this declaration.
texture DLSS5_ColorInput : COLOR;
sampler sDLSS5_ColorInput { Texture = DLSS5_ColorInput; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
#ifndef DLSS5_MV_PROVIDER
#define DLSS5_MV_PROVIDER 0
#endif
// ---------------------------------------------------------------------------------------------
// The selected provider's output, declared byte for byte like the provider itself does.
// ---------------------------------------------------------------------------------------------
#if DLSS5_MV_PROVIDER == 1
// iMMERSE Launchpad (MartysMods/mmx_deferred.fxh)
namespace Deferred {
texture MotionVectorsTex { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RG16F; };
// Launchpad's request buffer. Launchpad only computes optical flow when a consumer asked
// for it during the previous frame (it reads this 1x1 RGBA8 at the top of its technique
// and clears it at the bottom; bit 4 = optical flow, written through the render-target
// write mask). Being below Launchpad in the list, our request lands for the next frame.
// Declared like Launchpad declares it; the two shaders that write it below are ours.
namespace IPC {
texture2D PredicationBuffer { Format = RGBA8; };
}
}
sampler sDLSS5_ProviderMV { Texture = Deferred::MotionVectorsTex; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
float4 DLSS5_IpcRequestVS(in uint id : SV_VertexID) : SV_Position { return float4(0.0, 0.0, 0.0, 1.0); }
float4 DLSS5_IpcRequestPS(in float4 vpos : SV_Position) : SV_Target0 { return 1.0; }
#define DLSS5_MV_PROVIDER_NAME "Launchpad (Deferred::MotionVectorsTex)"
#define DLSS5_MV_REQUEST_PASS pass IpcRequestOpticalFlow { PrimitiveTopology = POINTLIST; VertexCount = 1; VertexShader = DLSS5_IpcRequestVS; PixelShader = DLSS5_IpcRequestPS; RenderTarget = Deferred::IPC::PredicationBuffer; RenderTargetWriteMask = 4; }
#elif DLSS5_MV_PROVIDER == 2
// VORT (Includes/vort_MotionUtils.fxh, V_MV_MODE 1)
texture2D MotVectTexVort { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RG16F; };
sampler sDLSS5_ProviderMV { Texture = MotVectTexVort; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
#define DLSS5_MV_PROVIDER_NAME "VORT (MotVectTexVort)"
#elif DLSS5_MV_PROVIDER == 3
// LumeniteFX Kernel (lumenite_Kernel.fx), as lumenite_RTAO/TRAA re-declare it. 1/8 resolution.
namespace Kernel {
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
}
sampler sDLSS5_ProviderMV { Texture = Kernel::tFlow; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Linear; MagFilter = Linear; };
sampler sDLSS5_ProviderMVPoint { Texture = Kernel::tFlow; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
sampler sDLSS5_ProviderConfidence{ Texture = Kernel::tConfidence; AddressU = Clamp; AddressV = Clamp; };
#define DLSS5_MV_PROVIDER_NAME "LumeniteFX Kernel (Kernel::tFlow, 1/8 res)"
#define DLSS5_MV_LOWRES 1
#elif DLSS5_MV_PROVIDER == 4
// LumeniteFX QuantMotion (lumenite_QuantMotion.fx), as lumenite_QuantAO re-declares it. 1/8 resolution.
namespace QuantMotion {
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
}
sampler sDLSS5_ProviderMV { Texture = QuantMotion::tFlow; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Linear; MagFilter = Linear; };
sampler sDLSS5_ProviderMVPoint { Texture = QuantMotion::tFlow; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
sampler sDLSS5_ProviderConfidence{ Texture = QuantMotion::tConfidence; AddressU = Clamp; AddressV = Clamp; };
#define DLSS5_MV_PROVIDER_NAME "LumeniteFX QuantMotion (QuantMotion::tFlow, 1/8 res)"
#define DLSS5_MV_LOWRES 1
#else
// The community-standard shared texture (ReshadeMotionEstimation, qUINT, dh_uber_motion, ...)
texture texMotionVectors < pooled = false; > { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RG16F; };
sampler sDLSS5_ProviderMV { Texture = texMotionVectors; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
#define DLSS5_MV_PROVIDER_NAME "texMotionVectors (DRME, qUINT, dh_uber_motion, ...)"
#endif
#ifndef DLSS5_MV_LOWRES
#define DLSS5_MV_LOWRES 0
#endif
#ifndef DLSS5_MV_REQUEST_PASS
#define DLSS5_MV_REQUEST_PASS
#endif
// ---------------------------------------------------------------------------------------------
uniform int MV_PROVIDER_INFO <
ui_type = "radio";
ui_label = " ";
ui_text = "Motion vector provider: " DLSS5_MV_PROVIDER_NAME "\n"
"Change it with the DLSS5_MV_PROVIDER preprocessor definition:\n"
" 0 texMotionVectors (DRME, qUINT, dh_uber_motion) 1 Launchpad 2 VORT\n"
" 3 LumeniteFX Kernel 4 LumeniteFX QuantMotion\n"
"Enable that provider's technique ABOVE DLSS 5 Feed.";
>;
#if DLSS5_MV_LOWRES
uniform int MV_LOWRES_FILTER <
ui_type = "combo";
ui_items = "Bilinear\0Point (nearest)\0";
ui_label = "Low-res provider filter";
ui_tooltip = "How the provider's 1/8-resolution flow is brought up to full resolution.\n"
"Bilinear smooths across flow cells; point keeps each 8x8 cell's vector as-is.";
> = 0;
#endif
// ---------------------------------------------------------------------------------------------
// Geometry vectors. A game's motion vectors for static geometry come from camera motion and
// depth, not from pixels. We have depth; the camera motion is fitted each frame from the
// provider's flow over a sparse grid (robust two-pass least squares on a 9-term screen-space
// model: affine + quadratic rotation terms + inverse-depth parallax terms), and every pixel
// then gets the vector that model predicts from its depth -- correct under flicker, correct
// while moving. The provider's flow is only used where it disagrees with the model AND wins a
// structure test: a genuinely moving object. Flames and flicker lose that test and keep the
// geometric vector, so nothing warps.
// ---------------------------------------------------------------------------------------------
uniform bool GEOM_ENABLE <
ui_category = "Geometry vectors (camera model + depth) -- EXPERIMENTAL";
ui_label = "Use geometry vectors (experimental, off by default)";
ui_tooltip = "Fit the camera motion from the provider's flow + depth each frame and derive every static\n"
"pixel's vector from it. The provider is then only consulted for moving objects.\n"
"EXPERIMENTAL: the per-frame fit is still noisy, and anything not part of the 3D world\n"
"(the HUD) gets camera vectors it should not have -- expect jitter there.\n"
"Off = the per-pixel validation below is applied to the provider's flow directly.";
> = false;
uniform float GEOM_PARALLAX <
ui_category = "Geometry vectors (camera model + depth)";
ui_type = "drag"; ui_min = 0.001; ui_max = 0.5; ui_step = 0.001;
ui_label = "Parallax depth scale";
ui_tooltip = "The model's inverse-depth term is s / (depth + s) with linear depth in 0..1. Smaller = more\n"
"parallax resolution near the camera. Usually fine as is.";
> = 0.02;
uniform float GEOM_OUTLIER_PX <
ui_category = "Geometry vectors (camera model + depth)";
ui_type = "drag"; ui_min = 0.5; ui_max = 32.0; ui_step = 0.5;
ui_label = "Fit: outlier rejection (px)";
ui_tooltip = "Second fitting pass ignores samples whose flow is further than this from the first pass's\n"
"prediction -- moving objects, flames, the first-person weapon.";
> = 4.0;
uniform float GEOM_AGREE_PX <
ui_category = "Geometry vectors (camera model + depth)";
ui_type = "drag"; ui_min = 0.0; ui_max = 16.0; ui_step = 0.1;
ui_label = "Agreement (px)";
ui_tooltip = "If the provider's flow is within this many pixels (+10% of the vector) of the model, the\n"
"model's vector is used as-is. Beyond it, the structure test decides moving object vs junk.";
> = 1.5;
uniform float GEOM_DYNAMIC_MARGIN <
ui_category = "Geometry vectors (camera model + depth)";
ui_type = "drag"; ui_min = 0.0; ui_max = 0.9; ui_step = 0.01;
ui_label = "Moving-object margin";
ui_tooltip = "For the provider's flow to override the model on a disagreeing pixel, its reprojection must\n"
"explain the pixel's structure at least this much (relative) better than the model's does.\n"
"Higher = more conservative (fewer things count as moving objects).";
> = 0.25;
uniform float GEOM_MASK_REJECTED <
ui_category = "Geometry vectors (camera model + depth)";
ui_type = "drag"; ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
ui_label = "Mask strength on rejected flow";
ui_tooltip = "Where the provider disagreed with the model but did not win the structure test (fire, smoke,\n"
"flicker), the geometric vector is used; this is how strongly DLSS is additionally asked to\n"
"favour the current frame there. 0 = pure history (smoothest), 1 = mostly current frame.";
> = 0.35;
uniform bool MV_VALIDATE <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_label = "Validate motion vectors against the previous frame";
ui_tooltip = "Optical-flow providers answer a lighting change (flicker, flames) with a vector that\n"
"points at whatever happened to match. Reprojecting and checking catches those:\n"
"the vector is zeroed and DLSS is told to trust the current frame there (DLSS5_Mask).";
> = true;
uniform bool VALIDATE_STATIC <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_label = "Static-hypothesis test (zeroes the vector, keeps history)";
ui_tooltip = "For each pixel, asks which explains it better: 'did not move' or the provider's vector.\n"
"Both are scored on illumination-normalised 3x3 structure (local mean removed), so a\n"
"flickering light does not count as motion. When 'did not move' wins, the vector is zeroed\n"
"and the pixel is NOT masked -- a static wall wants its full history, which is what smooths\n"
"the flicker. This is the test for the flickering-wall case.";
> = true;
uniform float STATIC_BIAS <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_type = "drag"; ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
ui_label = "Static bias";
ui_tooltip = "How much worse (relative) the static explanation may score than the vector's and still win.\n"
"0 = the vector must strictly beat 'did not move'. Higher favours zero vectors.";
> = 0.15;
uniform float STATIC_MIN_CONTRAST <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_type = "drag"; ui_min = 0.0; ui_max = 0.1; ui_step = 0.001;
ui_label = "Static test: minimum patch contrast";
ui_tooltip = "Below this 3x3 contrast (mean absolute deviation of luma) a patch has no structure to judge\n"
"motion by, and the test abstains -- the provider's vector stands. Raise it if flat surfaces\n"
"trail while moving (yellow on plain motion in the debug view); lower it if the\n"
"flickering wall stops being caught.";
> = 0.012;
uniform bool VALIDATE_LUMA <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_label = "Luma test (mask only)";
ui_tooltip = "The reprojected previous luma must fall inside the current 3x3 neighbourhood's luma range.\n"
"A failure only raises the mask (DLSS leans on the current frame); it never zeroes the vector,\n"
"because a lighting change does not prove the surface did not move. Off by default: on a\n"
"flickering surface it asks DLSS to drop exactly the history that would smooth the flicker.";
> = false;
uniform float LUMA_TOLERANCE <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_type = "drag"; ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
ui_label = "Luma tolerance";
ui_tooltip = "How far outside the current 3x3 neighbourhood's luma range the reprojected previous luma\n"
"may fall (relative to that range's maximum). Lower = stricter.";
> = 0.25;
uniform bool VALIDATE_DEPTH <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_label = "Depth test (zeroes the vector)";
ui_tooltip = "The reprojected previous linear depth must match the current one: a mismatch means the vector\n"
"points at a different surface (disocclusion), so it is zeroed and masked. Sky is exempt.";
> = true;
uniform float DEPTH_TOLERANCE <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_type = "drag"; ui_min = 0.0; ui_max = 0.5; ui_step = 0.005;
ui_label = "Depth tolerance";
ui_tooltip = "Allowed relative difference between the reprojected previous linear depth and the current one.";
> = 0.10;
uniform bool VALIDATE_MV <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_label = "Consistency test (zeroes the vector)";
ui_tooltip = "This frame's vector must resemble the previous frame's vector at the spot it points to.\n"
"Real motion is smooth frame to frame; optical flow on fire, smoke or a flickering wall is not.\n"
"A failure zeroes the vector and masks the pixel.";
> = true;
uniform float MV_CONSISTENCY <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_type = "drag"; ui_min = 0.0; ui_max = 16.0; ui_step = 0.1;
ui_label = "Vector consistency (px)";
ui_tooltip = "Allowed change, in pixels, between this frame's vector and the previous frame's vector at\n"
"the reprojected spot, plus 50% of the vector length. Raise it if plain camera motion\n"
"shows blue in the 'Validation tests' debug view.";
> = 1.4;
uniform float MASK_STRENGTH <
ui_category = "Validation (flicker / flames / disocclusion)";
ui_type = "drag"; ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
ui_label = "Bias-current-colour mask strength";
ui_tooltip = "How strongly a distrusted pixel asks DLSS to favour the current frame (DLSS5_Mask).\n"
"1 = fully; 0 = only zero the vector, do not mask.";
> = 1.0;
uniform float2 MV_SIGN <
ui_type = "drag";
ui_min = -1.0; ui_max = 1.0; ui_step = 2.0;
ui_label = "Motion vector sign (x, y)";
ui_tooltip = "Flip a component if the DLAA output doubles/smears in that direction while moving.\n"
"Default (1, 1) matches the convention every supported provider uses (prev_uv = uv + mv).";
> = float2(1.0, 1.0);
uniform float MV_SCALE <
ui_type = "drag";
ui_min = 0.0; ui_max = 4.0; ui_step = 0.01;
ui_label = "Motion vector scale";
ui_tooltip = "1.0 = the provider's estimate as-is. Diagnostic only.";
> = 1.0;
uniform int DEBUG_VIEW <
ui_type = "combo";
ui_items = "Motion vectors (colour = direction, brightness = speed)\0"
"Raw depth\0"
"Provider confidence (LumeniteFX only; white = confident)\0"
"Validation mask (white = vector distrusted, DLSS uses current frame)\0"
"Validation mask over the image\0"
"Validation tests over the image (red = luma, green = depth, blue = consistency, yellow = static wins)\0"
"Geometry model vectors (colour = direction, brightness = speed)\0"
"Geometry decision over the image (green = model, red = provider won as moving object, blue = provider rejected)\0"
"Geometry fit quality (grey = inlier share; top strip = fit error, black 0 px .. white 8 px)\0";
ui_label = "Debug view (DLSS5_Feed_Debug technique)";
> = 0;
// Outputs for the add-on
texture DLSS5_MV { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RG16F; };
texture DLSS5_Depth { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R32F; };
texture DLSS5_Mask { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R8; };
sampler sDLSS5_MV { Texture = DLSS5_MV; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
sampler sDLSS5_Depth { Texture = DLSS5_Depth; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
sampler sDLSS5_Mask { Texture = DLSS5_Mask; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
// Previous-frame history for validation (written at the end of the technique)
texture DLSS5_PrevLuma { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; };
texture DLSS5_PrevDepth { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; };
texture DLSS5_PrevMV { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RG16F; };
// Luma may be interpolated (a smooth quantity); depth and vectors must NOT be -- bilinear
// across an object edge mixes two surfaces' values and fails the test on every edge in motion.
sampler sDLSS5_PrevLuma { Texture = DLSS5_PrevLuma; AddressU = Clamp; AddressV = Clamp; MinFilter = LINEAR; MagFilter = LINEAR; MipFilter = POINT; };
sampler sDLSS5_PrevDepth { Texture = DLSS5_PrevDepth; AddressU = Clamp; AddressV = Clamp; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
sampler sDLSS5_PrevMV { Texture = DLSS5_PrevMV; AddressU = Clamp; AddressV = Clamp; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
// Camera-model fit: a sparse sample grid of (x, y, w, valid | u, v), and the solved model as
// six 1x1 RGBA32F texels (18 parameters + fit statistics).
#define DLSS5_FIT_W 40
#define DLSS5_FIT_H 23
texture DLSS5_FitA { Width = DLSS5_FIT_W; Height = DLSS5_FIT_H; Format = RGBA32F; };
texture DLSS5_FitB { Width = DLSS5_FIT_W; Height = DLSS5_FIT_H; Format = RGBA32F; };
sampler sDLSS5_FitA { Texture = DLSS5_FitA; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
sampler sDLSS5_FitB { Texture = DLSS5_FitB; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
texture DLSS5_Cam0 { Width = 1; Height = 1; Format = RGBA32F; };
texture DLSS5_Cam1 { Width = 1; Height = 1; Format = RGBA32F; };
texture DLSS5_Cam2 { Width = 1; Height = 1; Format = RGBA32F; };
texture DLSS5_Cam3 { Width = 1; Height = 1; Format = RGBA32F; };
texture DLSS5_Cam4 { Width = 1; Height = 1; Format = RGBA32F; };
texture DLSS5_Cam5 { Width = 1; Height = 1; Format = RGBA32F; };
sampler sDLSS5_Cam0 { Texture = DLSS5_Cam0; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
sampler sDLSS5_Cam1 { Texture = DLSS5_Cam1; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
sampler sDLSS5_Cam2 { Texture = DLSS5_Cam2; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
sampler sDLSS5_Cam3 { Texture = DLSS5_Cam3; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
sampler sDLSS5_Cam4 { Texture = DLSS5_Cam4; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
sampler sDLSS5_Cam5 { Texture = DLSS5_Cam5; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
// ---------------------------------------------------------------------------------------------
// The selected provider's vector at uv, as delta UV (prev_uv = uv + mv).
float2 ProviderMV(float2 uv)
{
float4 c = float4(uv, 0.0, 0.0);
#if DLSS5_MV_LOWRES
return MV_LOWRES_FILTER == 0 ? tex2Dlod(sDLSS5_ProviderMV, c).xy : tex2Dlod(sDLSS5_ProviderMVPoint, c).xy;
#else
return tex2Dlod(sDLSS5_ProviderMV, c).xy;
#endif
}
float Luma(float2 uv)
{
return dot(tex2Dlod(sDLSS5_ColorInput, float4(uv, 0.0, 0.0)).rgb, float3(0.299, 0.587, 0.114));
}
// Illumination-normalised 3x3 structure difference between the current frame at uv_cur and
// the previous frame at uv_prev: each patch has its own mean removed first, so a brightness
// change (flicker) contributes nothing and only the pattern is compared.
// Also returns the current patch's contrast (mean absolute deviation): a patch with no
// structure cannot decide anything, and the caller must not pretend it can.
float PatchError(float2 uv_cur, float2 uv_prev, out float contrast)
{
const float2 px = BUFFER_PIXEL_SIZE;
float c[9], p[9];
float mc = 0.0, mp = 0.0;
[unroll] for (int i = 0; i < 9; ++i)
{
const float2 o = float2(i % 3 - 1, i / 3 - 1) * px;
c[i] = Luma(uv_cur + o);
p[i] = tex2Dlod(sDLSS5_PrevLuma, float4(uv_prev + o, 0.0, 0.0)).x;
mc += c[i]; mp += p[i];
}
mc /= 9.0; mp /= 9.0;
float err = 0.0;
contrast = 0.0;
[unroll] for (int j = 0; j < 9; ++j)
{
err += abs((c[j] - mc) - (p[j] - mp));
contrast += abs(c[j] - mc);
}
contrast /= 9.0;
return err / 9.0;
}
// Per-test failure (0 = fine, 1 = failed, soft in between): x = luma, y = depth, z = consistency,
// w = the static hypothesis won. Luma failing says "this pixel's appearance changed"; depth or
// consistency failing says "this vector points at the wrong thing"; static winning says "no
// vector explains this pixel better than zero". Only y, z and w justify zeroing the vector, and
// only x, y, z justify asking DLSS to distrust history.
float4 ValidateTests(float2 uv, float2 mv)
{
const float2 puv = uv + mv;
float4 bad = 0.0;
// Reprojecting off-screen: nothing to compare against. Keep the vector (DLSS handles
// it) and let the mask lean on the current frame.
if (any(puv < 0.0) || any(puv > 1.0)) return float4(1.0, 0.0, 0.0, 0.0);
// 0. Static hypothesis: does "did not move" explain this pixel at least as well as the
// vector does? Scored on mean-removed structure, so flicker is not motion. Skipped for
// vectors under half a pixel (nothing to decide).
if (VALIDATE_STATIC && length(mv * BUFFER_SCREEN_SIZE) > 0.5)
{
float sc, unused;
const float es = PatchError(uv, uv, sc);
const float ef = PatchError(uv, puv, unused);
// Only a patch with structure can tell the two apart. Below the contrast floor the
// scores tie for lack of evidence, and a tie must go to the provider (its flow is
// propagated from textured neighbours -- the right guess for a moving flat wall).
// With structure, static wins only if it beats the vector by a share of that contrast.
if (sc >= STATIC_MIN_CONTRAST)
bad.w = es + 0.25 * sc <= ef * (1.0 + STATIC_BIAS) ? 1.0 : 0.0;
}
// 1. Luma: current 3x3 range vs the previous luma at the reprojected spot.
if (VALIDATE_LUMA)
{
const float2 px = BUFFER_PIXEL_SIZE;
float lc = Luma(uv), lmin = lc, lmax = lc;
[unroll] for (int y = -1; y <= 1; ++y)
[unroll] for (int x = -1; x <= 1; ++x)
{
const float l = Luma(uv + float2(x, y) * px);
lmin = min(lmin, l); lmax = max(lmax, l);
}
const float lp = tex2Dlod(sDLSS5_PrevLuma, float4(puv, 0.0, 0.0)).x;
const float margin = LUMA_TOLERANCE * max(lmax, 0.05) + 2.0 / 255.0;
bad.x = saturate(max(lmin - lp, lp - lmax) / margin);
}
// 2. Depth: previous linear depth at the reprojected spot vs the current one (sky exempt).
const float dc = ReShade::GetLinearizedDepth(uv);
if (VALIDATE_DEPTH && dc < 0.999)
{
const float dp = tex2Dlod(sDLSS5_PrevDepth, float4(puv, 0.0, 0.0)).x;
const float tol = DEPTH_TOLERANCE * max(dc, 1e-3);
bad.y = saturate((abs(dp - dc) - tol) / (tol + 1e-5));
}
// 3. Consistency: the previous frame's vector where this pixel came from vs this one.
if (VALIDATE_MV && MV_CONSISTENCY > 0.0)
{
const float2 pmv = tex2Dlod(sDLSS5_PrevMV, float4(puv, 0.0, 0.0)).xy;
const float diff = length((mv - pmv) * BUFFER_SCREEN_SIZE);
const float allow = MV_CONSISTENCY + 0.5 * length(mv * BUFFER_SCREEN_SIZE);
bad.z = saturate((diff - allow) / allow);
}
return bad;
}
// ---------------------------------------------------------------------------------------------
// Camera model. Screen position x, y in -0.5..0.5, inverse-depth term w = s / (depth + s).
// Basis (9 terms): 1, x, y, x^2, xy, y^2, w, xw, yw -- the small-rotation flow field of a
// pinhole camera is quadratic in the image position, and translation adds terms in 1/Z.
// Both flow components share the basis; the fit solves them together (two right-hand sides).
// ---------------------------------------------------------------------------------------------
#define DLSS5_BASIS(B, x, y, w) \
B[0] = 1.0; B[1] = x; B[2] = y; B[3] = x * x; B[4] = x * y; B[5] = y * y; B[6] = w; B[7] = x * w; B[8] = y * w;
float ParallaxW(float d) { return GEOM_PARALLAX / (d + GEOM_PARALLAX); }
// The model's predicted delta-UV at uv for linear depth d.
float2 PredictMV(float2 uv, float d)
{
const float4 c = float4(0.5, 0.5, 0.0, 0.0);
const float4 p0 = tex2Dlod(sDLSS5_Cam0, c), p1 = tex2Dlod(sDLSS5_Cam1, c), p2 = tex2Dlod(sDLSS5_Cam2, c);
const float4 p3 = tex2Dlod(sDLSS5_Cam3, c), p4 = tex2Dlod(sDLSS5_Cam4, c);
const float x = uv.x - 0.5, y = uv.y - 0.5, w = ParallaxW(d);
float B[9]; DLSS5_BASIS(B, x, y, w)
// u: p0.xyzw p1.xyzw p2.x v: p2.yzw p3.xyzw p4.xy
const float u = p0.x * B[0] + p0.y * B[1] + p0.z * B[2] + p0.w * B[3] + p1.x * B[4] + p1.y * B[5] + p1.z * B[6] + p1.w * B[7] + p2.x * B[8];
const float v = p2.y * B[0] + p2.z * B[1] + p2.w * B[2] + p3.x * B[3] + p3.y * B[4] + p3.z * B[5] + p3.w * B[6] + p4.x * B[7] + p4.y * B[8];
return float2(u, v);
}
bool FitIsUsable()
{
const float4 s = tex2Dlod(sDLSS5_Cam5, float4(0.5, 0.5, 0.0, 0.0)); // x = inlier share, y = rms px, z = samples used
return s.z >= 40.0 && s.x >= 0.25;
}
// Pass 1: sample the provider's flow and the depth on a sparse grid.
void PS_FitSamples(float4 vpos : SV_Position, float2 uv : TEXCOORD, out float4 A : SV_Target0, out float4 B : SV_Target1)
{
const float2 suv = (floor(vpos.xy) + 0.5) / float2(DLSS5_FIT_W, DLSS5_FIT_H);
const float d = ReShade::GetLinearizedDepth(suv);
const float2 mv = ProviderMV(suv);
const bool valid = d > 0.001 && all(abs(mv * BUFFER_SCREEN_SIZE) < 512.0);
A = float4(suv.x - 0.5, suv.y - 0.5, ParallaxW(d), valid ? 1.0 : 0.0);
B = float4(mv, 0.0, 0.0);
}
// Pass 2 (one pixel): robust least squares. Pass one fits everything; pass two refits on the
// samples the first fit explains to within GEOM_OUTLIER_PX, which drops moving objects,
// flames and the weapon from the camera estimate.
void PS_FitSolve(float4 vpos : SV_Position, float2 uv : TEXCOORD,
out float4 P0 : SV_Target0, out float4 P1 : SV_Target1, out float4 P2 : SV_Target2,
out float4 P3 : SV_Target3, out float4 P4 : SV_Target4, out float4 P5 : SV_Target5)
{
float p[18];
[unroll] for (int z = 0; z < 18; ++z) p[z] = 0.0;
float inlier = 0.0, rms = 0.0, used = 0.0;
const int total = DLSS5_FIT_W * DLSS5_FIT_H;
[loop] for (int it = 0; it < 2; ++it)
{
float M[45]; // upper triangle of the 9x9 normal matrix
float ru[9], rv[9];
[unroll] for (int z0 = 0; z0 < 45; ++z0) M[z0] = 0.0;
[unroll] for (int z1 = 0; z1 < 9; ++z1) { ru[z1] = 0.0; rv[z1] = 0.0; }
int n = 0;
float se = 0.0;
[loop] for (int s = 0; s < total; ++s)
{
const int2 cell = int2(s % DLSS5_FIT_W, s / DLSS5_FIT_W);
const float4 a = tex2Dfetch(sDLSS5_FitA, cell);
const float4 b = tex2Dfetch(sDLSS5_FitB, cell);
if (a.w < 0.5) continue;
float B[9]; DLSS5_BASIS(B, a.x, a.y, a.z)
if (it > 0)
{
float pu = 0.0, pv = 0.0;
[unroll] for (int i0 = 0; i0 < 9; ++i0) { pu += p[i0] * B[i0]; pv += p[9 + i0] * B[i0]; }
const float r = length((float2(pu, pv) - b.xy) * BUFFER_SCREEN_SIZE);
if (r > GEOM_OUTLIER_PX) continue;
se += r * r;
}
++n;
int k = 0;
[unroll] for (int i = 0; i < 9; ++i)
{
ru[i] += B[i] * b.x;
rv[i] += B[i] * b.y;
[unroll] for (int j = i; j < 9; ++j) { M[k] += B[i] * B[j]; ++k; }
}
}
if (n < 40) break; // not enough evidence: keep whatever the previous pass produced
// Augmented 9 x (9 + 2) system, Gauss-Jordan with partial pivoting, tiny ridge for
// the degenerate cases (flat depth makes w collinear with 1; a still camera makes
// everything zero).
float G[99];
{
int k2 = 0;
[unroll] for (int i = 0; i < 9; ++i)
{
[unroll] for (int j = i; j < 9; ++j) { G[i * 11 + j] = M[k2]; G[j * 11 + i] = M[k2]; ++k2; }
G[i * 11 + i] += 1e-5 * n;
G[i * 11 + 9] = ru[i];
G[i * 11 + 10] = rv[i];
}
}
bool singular = false;
[loop] for (int col = 0; col < 9; ++col)
{
int piv = col;
float best = abs(G[col * 11 + col]);
[loop] for (int r0 = col + 1; r0 < 9; ++r0)
{
const float v0 = abs(G[r0 * 11 + col]);
if (v0 > best) { best = v0; piv = r0; }
}
if (best < 1e-12) { singular = true; break; }
if (piv != col)
[unroll] for (int c0 = 0; c0 < 11; ++c0) { const float t = G[col * 11 + c0]; G[col * 11 + c0] = G[piv * 11 + c0]; G[piv * 11 + c0] = t; }
const float inv = 1.0 / G[col * 11 + col];
[unroll] for (int c1 = 0; c1 < 11; ++c1) G[col * 11 + c1] *= inv;
[loop] for (int r1 = 0; r1 < 9; ++r1)
{
if (r1 == col) continue;
const float f = G[r1 * 11 + col];
if (f == 0.0) continue;
[unroll] for (int c2 = 0; c2 < 11; ++c2) G[r1 * 11 + c2] -= f * G[col * 11 + c2];
}
}
if (singular) break;
[unroll] for (int i2 = 0; i2 < 9; ++i2) { p[i2] = G[i2 * 11 + 9]; p[9 + i2] = G[i2 * 11 + 10]; }
used = n;
inlier = float(n) / float(total);
if (it > 0) rms = sqrt(se / max(n, 1));
}
P0 = float4(p[0], p[1], p[2], p[3]);
P1 = float4(p[4], p[5], p[6], p[7]);
P2 = float4(p[8], p[9], p[10], p[11]);
P3 = float4(p[12], p[13], p[14], p[15]);
P4 = float4(p[16], p[17], 0.0, 0.0);
P5 = float4(inlier, rms, used, 0.0);
}
// Per-pixel decision: x = final delta-UV vector, .z = 0 model / 1 provider (moving object) /
// 2 provider rejected, .w = mask contribution from that decision.
float4 GeometryDecide(float2 uv, float d, float2 flow)
{
const float2 pred = PredictMV(uv, d);
const float r = length((flow - pred) * BUFFER_SCREEN_SIZE);
const float agree = GEOM_AGREE_PX + 0.1 * length(pred * BUFFER_SCREEN_SIZE);
if (r <= agree) return float4(pred, 0.0, 0.0);
float cp, cf;
const float ep = PatchError(uv, uv + pred, cp);
const float ef = PatchError(uv, uv + flow, cf);
const bool dynamic = cp >= STATIC_MIN_CONTRAST && ef <= ep * (1.0 - GEOM_DYNAMIC_MARGIN) - 1.0 / 255.0;
if (dynamic) return float4(flow, 1.0, 0.0);
return float4(pred, 2.0, GEOM_MASK_REJECTED * saturate((r - agree) / (4.0 * agree)));
}
float RawDepth(float2 uv)
{
// Raw hardware depth, exactly as the game wrote it -- the same orientation/offset
// corrections ReShade.fxh applies in GetLinearizedDepth(), minus the linearisation
// (DLSS must receive the raw values; the add-on tells it whether the range is reversed).
float2 t = uv;
#if RESHADE_DEPTH_INPUT_IS_UPSIDE_DOWN
t.y = 1.0 - t.y;
#endif
t.x /= RESHADE_DEPTH_INPUT_X_SCALE;
t.y /= RESHADE_DEPTH_INPUT_Y_SCALE;
#if RESHADE_DEPTH_INPUT_X_PIXEL_OFFSET
t.x -= RESHADE_DEPTH_INPUT_X_PIXEL_OFFSET * BUFFER_RCP_WIDTH;
#else
t.x -= RESHADE_DEPTH_INPUT_X_OFFSET / 2.000000001;
#endif
#if RESHADE_DEPTH_INPUT_Y_PIXEL_OFFSET
t.y += RESHADE_DEPTH_INPUT_Y_PIXEL_OFFSET * BUFFER_RCP_HEIGHT;
#else
t.y += RESHADE_DEPTH_INPUT_Y_OFFSET / 2.000000001;
#endif
return tex2Dlod(ReShade::DepthBuffer, float4(t, 0.0, 0.0)).x;
}
void PS_MotionVectors(float4 vpos : SV_Position, float2 uv : TEXCOORD,
out float2 mv_out : SV_Target0, out float mask : SV_Target1,
out float depth : SV_Target2)
{
// Providers hand out "delta UV": previous position = uv + mv. DLSS wants the same
// direction, in pixels.
const float2 flow = ProviderMV(uv);
float2 mv = flow;
float distrust = 0.0;
if (GEOM_ENABLE && FitIsUsable())
{
const float d = ReShade::GetLinearizedDepth(uv);
const float4 g = GeometryDecide(uv, d, flow);
mv = g.xy;
distrust = g.w;
// Disocclusion: the geometric vector on a newly revealed pixel points into the
// occluder's old position; the depth test catches that and asks for the current frame.
if (VALIDATE_DEPTH && d < 0.999)
{
const float2 puv = uv + mv;
if (all(puv >= 0.0) && all(puv <= 1.0))
{
const float dp = tex2Dlod(sDLSS5_PrevDepth, float4(puv, 0.0, 0.0)).x;
const float tol = DEPTH_TOLERANCE * max(d, 1e-3);
distrust = max(distrust, saturate((abs(dp - d) - tol) / (tol + 1e-5)));
}
}
}
else if (MV_VALIDATE)
{
const float4 bad = ValidateTests(uv, flow);
const float zero_vector = max(bad.y, max(bad.z, bad.w)); // wrong target, or static explains it: treat as static
distrust = max(bad.x, max(bad.y, bad.z)); // appearance changed / wrong target: favour the current frame
mv = flow * (1.0 - zero_vector);
}
mv_out = mv * float2(BUFFER_WIDTH, BUFFER_HEIGHT) * MV_SIGN * MV_SCALE;
mask = distrust * MASK_STRENGTH;
depth = RawDepth(uv);
}
// End of the technique: this frame becomes next frame's history. The raw provider vector is
// stored (not the validated one), so one distrusted frame does not poison the next test.
void PS_StoreHistory(float4 vpos : SV_Position, float2 uv : TEXCOORD,
out float luma : SV_Target0, out float depth : SV_Target1, out float2 mv : SV_Target2)
{
luma = Luma(uv);
depth = ReShade::GetLinearizedDepth(uv);
mv = ProviderMV(uv);
}
float3 PS_Debug(float4 vpos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
if (DEBUG_VIEW == 1)
{
const float raw_depth = tex2Dlod(sDLSS5_Depth, float4(uv, 0.0, 0.0)).x;
#if RESHADE_DEPTH_INPUT_IS_REVERSED
const float proximity = raw_depth;
#else
const float proximity = 1.0 - raw_depth;
#endif
// Display-only contrast curve: DLSS5_Depth itself remains raw and untouched.
return pow(saturate(proximity), 0.125).xxx;
}
if (DEBUG_VIEW == 2)
{
#if DLSS5_MV_LOWRES
return saturate(tex2Dlod(sDLSS5_ProviderConfidence, float4(uv, 0.0, 0.0)).x).xxx;
#else
return (0.25).xxx; // this provider publishes no confidence map
#endif
}
if (DEBUG_VIEW == 3)
return tex2Dlod(sDLSS5_Mask, float4(uv, 0.0, 0.0)).xxx;
if (DEBUG_VIEW == 4)
{
const float m = tex2Dlod(sDLSS5_Mask, float4(uv, 0.0, 0.0)).x;
const float3 img = tex2Dlod(sDLSS5_ColorInput, float4(uv, 0.0, 0.0)).rgb;
return lerp(img, float3(1.0, 0.2, 0.1), m * 0.75);
}
if (DEBUG_VIEW == 5)
{
// Recomputed here against the same history the feed pass used this frame.
const float4 bad = ValidateTests(uv, ProviderMV(uv));
const float3 img = tex2Dlod(sDLSS5_ColorInput, float4(uv, 0.0, 0.0)).rgb * 0.5;
return saturate(img + bad.xyz * 0.9 + bad.w * float3(0.6, 0.6, 0.0));
}
if (DEBUG_VIEW == 6)
{
const float2 pv = PredictMV(uv, ReShade::GetLinearizedDepth(uv)) * BUFFER_SCREEN_SIZE;
const float angle = atan2(pv.y, pv.x), speed = length(pv);
const float3 rgb = saturate(3.0 * abs(2.0 * frac(angle / 6.283185 + float3(0.0, -1.0 / 3.0, 1.0 / 3.0)) - 1.0) - 1.0);
return lerp(0.5, rgb, saturate(speed / 16.0));
}
if (DEBUG_VIEW == 7)
{
const float3 img = tex2Dlod(sDLSS5_ColorInput, float4(uv, 0.0, 0.0)).rgb * 0.5;
if (!FitIsUsable()) return img; // no usable fit this frame: nothing to show
const float4 g = GeometryDecide(uv, ReShade::GetLinearizedDepth(uv), ProviderMV(uv));
const float3 tint = g.z < 0.5 ? float3(0.0, 0.5, 0.0) : g.z < 1.5 ? float3(0.9, 0.0, 0.0) : float3(0.0, 0.2, 0.9);
return saturate(img + tint);
}
if (DEBUG_VIEW == 8)
{
const float4 s = tex2Dlod(sDLSS5_Cam5, float4(0.5, 0.5, 0.0, 0.0));
if (uv.y < 0.05) return saturate(s.y / 8.0).xxx; // fit error strip
return s.x.xxx; // inlier share
}
float2 mv = tex2Dlod(sDLSS5_MV, float4(uv, 0.0, 0.0)).xy; // pixels
float angle = atan2(mv.y, mv.x);
float speed = length(mv);
float3 rgb = saturate(3.0 * abs(2.0 * frac(angle / 6.283185 + float3(0.0, -1.0 / 3.0, 1.0 / 3.0)) - 1.0) - 1.0);
return lerp(0.5, rgb, saturate(speed / 16.0)); // 16 px/frame saturates the colour
}
// ---------------------------------------------------------------------------------------------
technique DLSS5_Feed
<
ui_label = "DLSS 5 Feed (place below your motion-vector provider)";
ui_tooltip = "Prepares motion vectors + depth (+ a trust mask) for the DLSS 5 Feed add-on.\n\n"
"Provider: " DLSS5_MV_PROVIDER_NAME "\n"
"Change it with the DLSS5_MV_PROVIDER preprocessor definition (0 texMotionVectors,\n"
"1 Launchpad, 2 VORT, 3 LumeniteFX Kernel, 4 LumeniteFX QuantMotion) and enable\n"
"that provider's technique ABOVE this one.";
>
{
pass FitSamples { VertexShader = PostProcessVS; PixelShader = PS_FitSamples; RenderTarget0 = DLSS5_FitA; RenderTarget1 = DLSS5_FitB; }
pass FitSolve { VertexShader = PostProcessVS; PixelShader = PS_FitSolve; RenderTarget0 = DLSS5_Cam0; RenderTarget1 = DLSS5_Cam1; RenderTarget2 = DLSS5_Cam2; RenderTarget3 = DLSS5_Cam3; RenderTarget4 = DLSS5_Cam4; RenderTarget5 = DLSS5_Cam5; }
pass Guides { VertexShader = PostProcessVS; PixelShader = PS_MotionVectors; RenderTarget0 = DLSS5_MV; RenderTarget1 = DLSS5_Mask; RenderTarget2 = DLSS5_Depth; }
pass History { VertexShader = PostProcessVS; PixelShader = PS_StoreHistory; RenderTarget0 = DLSS5_PrevLuma; RenderTarget1 = DLSS5_PrevDepth; RenderTarget2 = DLSS5_PrevMV; }
DLSS5_MV_REQUEST_PASS // Launchpad only: ask it to compute optical flow again next frame
}
technique DLSS5_Feed_Debug
<
ui_label = "DLSS 5 Feed - debug view";
ui_tooltip = "Shows the motion vectors / depth / mask the add-on will send to DLSS. Enable only for checking.";
>
{
pass { VertexShader = PostProcessVS; PixelShader = PS_Debug; }
}
+67
View File
@@ -0,0 +1,67 @@
/**
* HDR
* by Christian Cann Schuldt Jensen ~ CeeJay.dk
*
* Not actual HDR - It just tries to mimic an HDR look (relatively high performance cost)
*/
#include "ReShadeUI.fxh"
uniform float HDRPower < __UNIFORM_SLIDER_FLOAT1
ui_min = 0.0; ui_max = 8.0;
ui_label = "Power";
> = 1.30;
uniform float radius1 < __UNIFORM_SLIDER_FLOAT1
ui_min = 0.0; ui_max = 8.0;
ui_label = "Radius 1";
> = 0.793;
uniform float radius2 < __UNIFORM_SLIDER_FLOAT1
ui_min = 0.0; ui_max = 8.0;
ui_label = "Radius 2";
ui_tooltip = "Raising this seems to make the effect stronger and also brighter.";
> = 0.87;
#include "ReShade.fxh"
float3 HDRPass(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target
{
float3 color = tex2D(ReShade::BackBuffer, texcoord).rgb;
float3 bloom_sum1 = tex2D(ReShade::BackBuffer, texcoord + float2(1.5, -1.5) * radius1 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum1 += tex2D(ReShade::BackBuffer, texcoord + float2(-1.5, -1.5) * radius1 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum1 += tex2D(ReShade::BackBuffer, texcoord + float2( 1.5, 1.5) * radius1 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum1 += tex2D(ReShade::BackBuffer, texcoord + float2(-1.5, 1.5) * radius1 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum1 += tex2D(ReShade::BackBuffer, texcoord + float2( 0.0, -2.5) * radius1 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum1 += tex2D(ReShade::BackBuffer, texcoord + float2( 0.0, 2.5) * radius1 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum1 += tex2D(ReShade::BackBuffer, texcoord + float2(-2.5, 0.0) * radius1 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum1 += tex2D(ReShade::BackBuffer, texcoord + float2( 2.5, 0.0) * radius1 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum1 *= 0.005;
float3 bloom_sum2 = tex2D(ReShade::BackBuffer, texcoord + float2(1.5, -1.5) * radius2 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum2 += tex2D(ReShade::BackBuffer, texcoord + float2(-1.5, -1.5) * radius2 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum2 += tex2D(ReShade::BackBuffer, texcoord + float2( 1.5, 1.5) * radius2 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum2 += tex2D(ReShade::BackBuffer, texcoord + float2(-1.5, 1.5) * radius2 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum2 += tex2D(ReShade::BackBuffer, texcoord + float2( 0.0, -2.5) * radius2 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum2 += tex2D(ReShade::BackBuffer, texcoord + float2( 0.0, 2.5) * radius2 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum2 += tex2D(ReShade::BackBuffer, texcoord + float2(-2.5, 0.0) * radius2 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum2 += tex2D(ReShade::BackBuffer, texcoord + float2( 2.5, 0.0) * radius2 * BUFFER_PIXEL_SIZE).rgb;
bloom_sum2 *= 0.010;
float dist = radius2 - radius1;
float3 HDR = (color + (bloom_sum2 - bloom_sum1)) * dist;
float3 blend = HDR + color;
color = pow(abs(blend), abs(HDRPower)) + HDR; // pow - don't use fractions for HDRpower
return saturate(color);
}
technique HDR
{
pass
{
VertexShader = PostProcessVS;
PixelShader = HDRPass;
}
}
+492
View File
@@ -0,0 +1,492 @@
/*
Magic Bloom by luluco250
Attempts to simulate a natural-looking bloom.
Features:
--Wide bloom blurring, derived from the gaussian function
defined here: https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics
--Eye adaptation, decreases or increases the brightness
of bloom according to the overall image luminance.
--Lens dirt, as standard I suppose. Really not much here.
It uses an image named "MagicBloom_Dirt.png" so make
sure you have one in your textures directory.
--Unwanted features can be disabled through
preprocessor definitions, saving performance.
Preprocessor definitions:
--MAGICBLOOM_ADAPT_RESOLUTION:
Determines the width/height of the texture used for adaptation.
It is recommended to use 256, but you can use as far as 1024 without issues.
Too low resolutions will make adaptation seem "unstable".
Must be a power of two value: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 etc.
--MAGICBLOOM_BLUR_PRECALCULATED:
If set to 0 the gaussian blur will be calculated inside the shader.
Otherwise, it uses a pre-calculated kernel (array).
--MAGICBLOOM_NODIRT:
If set to 1 all lens dirt related features are disabled.
Beneficial for performance if you don't wish to use lens dirt.
--MAGICBLOOM_NOADAPT:
If set to 1 all adaptation related features are disabled.
Beneficial for performance if you don't wish to use adaptation.
MIT Licensed:
Copyright (c) 2017 luluco250
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "ReShade.fxh"
//Statics
#ifndef MAGICBLOOM_ADAPT_RESOLUTION
#define MAGICBLOOM_ADAPT_RESOLUTION 256
#endif
#ifndef MAGICBLOOM_BLUR_PRECALCULATED
#define MAGICBLOOM_BLUR_PRECALCULATED 1
#endif
#ifndef MAGICBLOOM_NODIRT
#define MAGICBLOOM_NODIRT 0
#endif
#ifndef MAGICBLOOM_NOADAPT
#define MAGICBLOOM_NOADAPT 0
#endif
static const int iBlurSamples = 4;
static const int iAdaptResolution = MAGICBLOOM_ADAPT_RESOLUTION;
#define CONST_LOG2(v) (((v) & 0xAAAAAAAA) != 0) | ((((v) & 0xFFFF0000) != 0) << 4) | ((((v) & 0xFF00FF00) != 0) << 3) | ((((v) & 0xF0F0F0F0) != 0) << 2) | ((((v) & 0xCCCCCCCC) != 0) << 1)
static const float sigma = float(iBlurSamples) / 2.0;
static const float double_pi = 6.283185307179586476925286766559;
static const int lowest_mip = CONST_LOG2(iAdaptResolution) + 1;
static const float3 luma_value = float3(0.2126, 0.7152, 0.0722);
//Uniforms
#include "ReShadeUI.fxh"
uniform float fBloom_Intensity < __UNIFORM_SLIDER_FLOAT1
ui_label = "Bloom Intensity";
ui_tooltip = "Amount of bloom applied to the image.";
ui_min = 0.0;
ui_max = 10.0;
ui_step = 0.001;
> = 1.0;
uniform float fBloom_Threshold <
ui_label = "Bloom Threshold";
ui_tooltip =
"Thresholds (limits) dark pixels from being accounted for bloom.\n"
"Essentially, it increases the contrast in bloom and blackens darker pixels.\n"
"At 1.0 all pixels are used in bloom.\n"
"This value is not normalized, it is exponential, therefore changes in lower values are more noticeable than at higher values.";
ui_type = "drag";
ui_min = 1.0;
ui_max = 10.0;
ui_step = 0.1;
> = 2.0;
#if !MAGICBLOOM_NODIRT
uniform float fDirt_Intensity < __UNIFORM_SLIDER_FLOAT1
ui_label = "Dirt Intensity";
ui_tooltip =
"Amount of lens dirt applied to bloom.\n"
"Uses a texture called \"MagicBloom_Dirt.png\" from your textures directory(ies).";
ui_min = 0.0;
ui_max = 1.0;
ui_step = 0.001;
> = 0.0;
#endif
#if !MAGICBLOOM_NOADAPT
uniform float fExposure < __UNIFORM_SLIDER_FLOAT1
ui_label = "Exposure";
ui_tooltip =
"The target exposure that bloom adapts to.\n"
"It is recommended to just leave it at 0.5, unless you wish for a brighter (1.0) or darker (0.0) image.";
ui_min = 0.0;
ui_max = 1.0;
ui_step = 0.001;
> = 0.5;
uniform float fAdapt_Speed <
ui_label = "Adaptation Speed";
ui_tooltip =
"How quick bloom adapts to changes in the image brightness.\n"
"At 1.0, changes are instantaneous.\n"
"It is recommended to use low values, between 0.01 and 0.1.\n"
"0.1 will provide a quick but natural adaptation.\n"
"0.01 will provide a slow form of adaptation.";
ui_type = "drag";
ui_min = 0.001;
ui_max = 1.0;
ui_step = 0.001;
> = 0.1;
uniform float fAdapt_Sensitivity < __UNIFORM_SLIDER_FLOAT1
ui_label = "Adapt Sensitivity";
ui_tooltip =
"How sensitive adaptation is towards brightness.\n"
"At higher values bloom can get darkened at the slightest amount of brightness.\n"
"At lower values bloom will require a lot of image brightness before it's fully darkened."
"1.0 will not modify the amount of brightness that is accounted for adaptation.\n"
"0.5 is a good value, but may miss certain bright spots.";
ui_min = 0.0;
ui_max = 3.0;
ui_step = 0.001;
> = 1.0;
uniform float2 f2Adapt_Clip < __UNIFORM_SLIDER_FLOAT2
ui_label = "Adaptation Min/Max";
ui_tooltip =
"Determines the minimum and maximum values that adaptation can determine to ajust bloom.\n"
"Reducing the maximum would cause bloom to be brighter (as it is less adapted).\n"
"Increasing the minimum would cause bloom to be darker (as it is more adapted).\n"
"Keep the maximum above or equal to the minium and vice-versa.";
ui_min = 0.0;
ui_max = 1.0;
ui_step = 0.001;
> = float2(0.0, 1.0);
uniform int iAdapt_Precision < __UNIFORM_SLIDER_INT1
ui_label = "Adaptation Precision";
ui_tooltip =
"Determins how accurately bloom adapts to the center of image.\n"
"At 0 the adaptation is calculated from the average of the whole image.\n"
"At the highest value (which may vary) adaptation focuses solely on the center pixel(s) of the screen.\n"
"Values closer to 0 are recommended.";
ui_min = 0;
ui_max = lowest_mip;
ui_step = 0.1;
> = lowest_mip * 0.3;
#endif
uniform uint iDebug <
ui_label = "Debug Options";
ui_tooltip = "Contains debugging options like displaying the bloom texture.";
ui_type = "combo";
ui_items = "None\0Display Bloom Texture\0";
> = 0;
//Textures
texture tMagicBloom_1 < pooled = true; > { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RGBA16F; };
texture tMagicBloom_2 < pooled = true; > { Width = BUFFER_WIDTH / 4; Height = BUFFER_HEIGHT / 4; Format = RGBA16F; };
texture tMagicBloom_3 < pooled = true; > { Width = BUFFER_WIDTH / 8; Height = BUFFER_HEIGHT / 8; Format = RGBA16F; };
texture tMagicBloom_4 < pooled = true; > { Width = BUFFER_WIDTH / 16; Height = BUFFER_HEIGHT / 16; Format = RGBA16F; };
texture tMagicBloom_5 < pooled = true; > { Width = BUFFER_WIDTH / 32; Height = BUFFER_HEIGHT / 32; Format = RGBA16F; };
texture tMagicBloom_6 < pooled = true; > { Width = BUFFER_WIDTH / 64; Height = BUFFER_HEIGHT / 64; Format = RGBA16F; };
texture tMagicBloom_7 < pooled = true; > { Width = BUFFER_WIDTH / 128; Height = BUFFER_HEIGHT / 128; Format = RGBA16F; };
texture tMagicBloom_8 < pooled = true; > { Width = BUFFER_WIDTH / 256; Height = BUFFER_HEIGHT / 256; Format = RGBA16F; };
#if !MAGICBLOOM_NOADAPT
texture tMagicBloom_Small { Width = iAdaptResolution; Height = iAdaptResolution; Format = R32F; MipLevels = lowest_mip; };
texture tMagicBloom_Adapt { Format = R32F; };
texture tMagicBloom_LastAdapt { Format = R32F; };
#endif
#if !MAGICBLOOM_NODIRT
texture tMagicBloom_Dirt <source="MagicBloom_Dirt.png";> { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; };
#endif
//Samplers
sampler sMagicBloom_1 { Texture = tMagicBloom_1; };
sampler sMagicBloom_2 { Texture = tMagicBloom_2; };
sampler sMagicBloom_3 { Texture = tMagicBloom_3; };
sampler sMagicBloom_4 { Texture = tMagicBloom_4; };
sampler sMagicBloom_5 { Texture = tMagicBloom_5; };
sampler sMagicBloom_6 { Texture = tMagicBloom_6; };
sampler sMagicBloom_7 { Texture = tMagicBloom_7; };
sampler sMagicBloom_8 { Texture = tMagicBloom_8; };
#if !MAGICBLOOM_NOADAPT
sampler sMagicBloom_Small { Texture = tMagicBloom_Small; };
sampler sMagicBloom_Adapt { Texture = tMagicBloom_Adapt; MinFilter = POINT; MagFilter = POINT; };
sampler sMagicBloom_LastAdapt { Texture = tMagicBloom_LastAdapt; MinFilter = POINT; MagFilter = POINT; };
#endif
#if !MAGICBLOOM_NODIRT
sampler sMagicBloom_Dirt { Texture = tMagicBloom_Dirt; };
#endif
//Functions
#if !MAGICBLOOM_BLUR_PRECALCULATED
float gaussian_function(float2 i) {
static const float first_part = 1.0 / (double_pi * pow(sigma, 2.0));
static const float second_part_a = 1.0 / (2.0 * pow(sigma, 2.0));
float second_part_b = (pow(i.x, 2.0) + pow(i.y, 2.0)) * second_part_a;
return first_part * exp(-second_part_b);
}
#endif
//Why use a single-pass blur? To reduce the amount of textures used in half.
//Scale should be the original resolution divided by target resolution.
float3 blur(sampler sp, float2 uv, float scale) {
float2 ps = BUFFER_PIXEL_SIZE * scale;
#if MAGICBLOOM_BLUR_PRECALCULATED
static const float kernel[9] = {
0.0269955, 0.0647588, 0.120985, 0.176033, 0.199471, 0.176033, 0.120985, 0.0647588, 0.0269955
};
static const float accum = 1.02352;
#else
float accum = 0.0;
#endif
float gaussian_weight = 0.0;
float3 col = 0.0;
[loop]
for (int x = -iBlurSamples; x <= iBlurSamples; ++x) {
for (int y = -iBlurSamples; y <= iBlurSamples; ++y) {
#if MAGICBLOOM_BLUR_PRECALCULATED
gaussian_weight = kernel[x + iBlurSamples] * kernel[y + iBlurSamples];
#else
gaussian_weight = gaussian_function(float2(x, y));
accum += gaussian_weight;
#endif
col += tex2D(sp, uv + ps * float2(x, y)).rgb * gaussian_weight;
}
}
#if MAGICBLOOM_BLUR_PRECALCULATED
return col * accum;
#else
return col / accum;
#endif
}
/*
Uncharted 2 Tonemapper
Thanks John Hable and Naughty Dog.
*/
float3 tonemap(float3 col, float exposure) {
static const float A = 0.15; //shoulder strength
static const float B = 0.50; //linear strength
static const float C = 0.10; //linear angle
static const float D = 0.20; //toe strength
static const float E = 0.02; //toe numerator
static const float F = 0.30; //toe denominator
static const float W = 11.2; //linear white point value
col *= exposure;
col = ((col * (A * col + C * B) + D * E) / (col * (A * col + B) + D * F)) - E / F;
static const float white = 1.0 / (((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F);
col *= white;
return col;
}
float3 blend_screen(float3 a, float3 b) {
return 1.0 - (1.0 - a) * (1.0 - b);
}
/*
The function below is a leftover from debugging.
It just draws a line on the screen, it's horizontal position being
the value you specify (from 0.0-1.0, becoming left-right).
No use now but might be useful later on so I just left it here.
*/
/*void debug_value(inout float3 col, float2 uv, float value, float3 needle_color) {
static const float2 ps = BUFFER_PIXEL_SIZE;
col = (uv.x + ps.x > value && uv.x - ps.x < value) ? needle_color : col;
}*/
//Shaders
/*
Thresholding is performed on the first blur for two reasons:
--Save an entire texture from being used to threshold.
--Being the smallest blur it also results in the least amount of artifacts.
*/
float4 PS_Blur1(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
float3 col = blur(ReShade::BackBuffer, uv, 2.0);
col = pow(abs(col), fBloom_Threshold);
col *= fBloom_Intensity;
return float4(col, 1.0);
}
float4 PS_Blur2(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
return float4(blur(sMagicBloom_1, uv, 4.0), 1.0);
}
float4 PS_Blur3(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
return float4(blur(sMagicBloom_2, uv, 8.0), 1.0);
}
float4 PS_Blur4(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
return float4(blur(sMagicBloom_3, uv, 8.0), 1.0);
}
float4 PS_Blur5(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
return float4(blur(sMagicBloom_4, uv, 16.0), 1.0);
}
float4 PS_Blur6(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
return float4(blur(sMagicBloom_5, uv, 32.0), 1.0);
}
float4 PS_Blur7(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
return float4(blur(sMagicBloom_6, uv, 64.0), 1.0);
}
float4 PS_Blur8(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
return float4(blur(sMagicBloom_7, uv, 128.0), 1.0);
}
//Final blend shader
float4 PS_Blend(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
float3 col = tex2D(ReShade::BackBuffer, uv).rgb;
float3 bloom = tex2D(sMagicBloom_1, uv).rgb
+ tex2D(sMagicBloom_2, uv).rgb
+ tex2D(sMagicBloom_3, uv).rgb
+ tex2D(sMagicBloom_4, uv).rgb
+ tex2D(sMagicBloom_5, uv).rgb
+ tex2D(sMagicBloom_6, uv).rgb
+ tex2D(sMagicBloom_7, uv).rgb
+ tex2D(sMagicBloom_8, uv).rgb;
//Dunno if making the division by 8 a static multiplication helps, but whatever.
static const float bloom_accum = 1.0 / 8.0;
bloom *= bloom_accum;
#if !MAGICBLOOM_NOADAPT
float exposure = fExposure / max(tex2D(sMagicBloom_Adapt, 0.0).x, 0.00001);
bloom = tonemap(bloom, exposure);
#else
//Without adaptation it seems 100.0 exposure is needed for bloom to look bright enough.
bloom = tonemap(bloom, 100.0);
#endif
#if !MAGICBLOOM_NODIRT
float3 dirt = tex2D(sMagicBloom_Dirt, uv).rgb;
dirt *= fDirt_Intensity;
bloom = blend_screen(bloom, dirt * bloom);
#endif
col = blend_screen(col, bloom);
//If we're to display the bloom texture, we replace col with it.
col = iDebug == 1 ? bloom : col;
return float4(col, 1.0);
}
#if !MAGICBLOOM_NOADAPT
/*
How adaptation works:
--Calculate image luminance.
--Save it to a smaller, mipmapped texture.
--Mipmaps require a power of 2 texture.
--Fetch a mipmap level according to a specfied amount of precision.
--The lowest mipmap is simply an average of the entire image.
*/
float PS_GetSmall(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
return dot(tex2D(ReShade::BackBuffer, uv).rgb, luma_value);
}
float PS_GetAdapt(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
float curr = tex2Dlod(sMagicBloom_Small, float4(0.5, 0.5, 0, lowest_mip - iAdapt_Precision)).x;
curr *= fAdapt_Sensitivity;
curr = clamp(curr, f2Adapt_Clip.x, f2Adapt_Clip.y);
float last = tex2D(sMagicBloom_LastAdapt, 0.0).x;
//Using the frametime/delta here would actually scale adaptation with the framerate.
//We don't want that, so we don't even bother with it.
return lerp(last, curr, fAdapt_Speed);
}
float PS_SaveAdapt(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target {
return tex2D(sMagicBloom_Adapt, 0.0).x;
}
#endif
technique MagicBloom {
pass Blur1 {
VertexShader = PostProcessVS;
PixelShader = PS_Blur1;
RenderTarget = tMagicBloom_1;
}
pass Blur2 {
VertexShader = PostProcessVS;
PixelShader = PS_Blur2;
RenderTarget = tMagicBloom_2;
}
pass Blur3 {
VertexShader = PostProcessVS;
PixelShader = PS_Blur3;
RenderTarget = tMagicBloom_3;
}
pass Blur4 {
VertexShader = PostProcessVS;
PixelShader = PS_Blur4;
RenderTarget = tMagicBloom_4;
}
pass Blur5 {
VertexShader = PostProcessVS;
PixelShader = PS_Blur5;
RenderTarget = tMagicBloom_5;
}
pass Blur6 {
VertexShader = PostProcessVS;
PixelShader = PS_Blur6;
RenderTarget = tMagicBloom_6;
}
pass Blur7 {
VertexShader = PostProcessVS;
PixelShader = PS_Blur7;
RenderTarget = tMagicBloom_7;
}
pass Blur8 {
VertexShader = PostProcessVS;
PixelShader = PS_Blur8;
RenderTarget = tMagicBloom_8;
}
pass Blend {
VertexShader = PostProcessVS;
PixelShader = PS_Blend;
}
#if !MAGICBLOOM_NOADAPT
pass GetSmall {
VertexShader = PostProcessVS;
PixelShader = PS_GetSmall;
RenderTarget = tMagicBloom_Small;
}
pass GetAdapt {
VertexShader = PostProcessVS;
PixelShader = PS_GetAdapt;
RenderTarget = tMagicBloom_Adapt;
}
pass SaveAdapt {
VertexShader = PostProcessVS;
PixelShader = PS_SaveAdapt;
RenderTarget = tMagicBloom_LastAdapt;
}
#endif
}
+72
View File
@@ -0,0 +1,72 @@
/**
* Tonemap version 1.1
* by Christian Cann Schuldt Jensen ~ CeeJay.dk
*/
#include "ReShadeUI.fxh"
uniform float Gamma < __UNIFORM_SLIDER_FLOAT1
ui_min = 0.0; ui_max = 2.0;
ui_tooltip = "Adjust midtones. 1.0 is neutral. This setting does exactly the same as the one in Lift Gamma Gain, only with less control.";
> = 1.0;
uniform float Exposure < __UNIFORM_SLIDER_FLOAT1
ui_min = -1.0; ui_max = 1.0;
ui_tooltip = "Adjust exposure";
> = 0.0;
uniform float Saturation < __UNIFORM_SLIDER_FLOAT1
ui_min = -1.0; ui_max = 1.0;
ui_tooltip = "Adjust saturation";
> = 0.0;
uniform float Bleach < __UNIFORM_SLIDER_FLOAT1
ui_min = 0.0; ui_max = 1.0;
ui_tooltip = "Brightens the shadows and fades the colors";
> = 0.0;
uniform float Defog < __UNIFORM_SLIDER_FLOAT1
ui_min = 0.0; ui_max = 1.0;
ui_tooltip = "How much of the color tint to remove";
> = 0.0;
uniform float3 FogColor < __UNIFORM_COLOR_FLOAT3
ui_label = "Defog Color";
ui_tooltip = "Which color tint to remove";
> = float3(0.0, 0.0, 1.0);
#include "ReShade.fxh"
float3 TonemapPass(float4 position : SV_Position, float2 texcoord : TexCoord) : SV_Target
{
float3 color = tex2D(ReShade::BackBuffer, texcoord).rgb;
color = saturate(color - Defog * FogColor * 2.55); // Defog
color *= pow(2.0f, Exposure); // Exposure
color = pow(color, Gamma); // Gamma
const float3 coefLuma = float3(0.2126, 0.7152, 0.0722);
float lum = dot(coefLuma, color);
float L = saturate(10.0 * (lum - 0.45));
float3 A2 = Bleach * color;
float3 result1 = 2.0f * color * lum;
float3 result2 = 1.0f - 2.0f * (1.0f - lum) * (1.0f - color);
float3 newColor = lerp(result1, result2, L);
float3 mixRGB = A2 * newColor;
color += ((1.0f - A2) * mixRGB);
float3 middlegray = dot(color, (1.0 / 3.0));
float3 diffcolor = color - middlegray;
color = (color + diffcolor * Saturation) / (1 + (diffcolor * Saturation)); // Saturation
return color;
}
technique Tonemap
{
pass
{
VertexShader = PostProcessVS;
PixelShader = TonemapPass;
}
}
@@ -0,0 +1,252 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_ColorManagement.fxh
Version : 2026.05.05
Author : Afzaal (Kaidō)
Description: Provides color management including color space detection,
color space transfers and tonemapping.
Supported colorbuffers:
- SDR (sRGB)
- HDR (scRGB / Linear)
- HDR (PQ / ST.2084)
- HDR (HLG)
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
#pragma once
/*-------------------.
| :: PREPROCESSOR :: |
'-------------------*/
#ifndef HDR_WHITELEVEL
#define HDR_WHITELEVEL 203
#endif
#if BUFFER_COLOR_SPACE > 0
//already defined by ReShade
#else
#if BUFFER_COLOR_BIT_DEPTH == 8
#undef BUFFER_COLOR_SPACE
#define BUFFER_COLOR_SPACE 1 //sRGB
#elif BUFFER_COLOR_BIT_DEPTH == 16
#undef BUFFER_COLOR_SPACE
#define BUFFER_COLOR_SPACE 2 //scRGB
#elif __RENDERER__ < 0xb000
#undef BUFFER_COLOR_SPACE
#define BUFFER_COLOR_SPACE 1 //D3D9/10 usually SDR
#endif
#endif
/*------------------.
| :: UI UNIFORMS :: |
'------------------*/
// uniform int SHOW_COLOR_SPACE <
// ui_category = "Color Management";
// ui_type = "combo";
// ui_label = "Colorspace";
// ui_tooltip = "Shows the detected color space.\n1=sRGB, 2=scRGB, 3=PQ, 4=HLG";
// hidden = true;
// #if BUFFER_COLOR_SPACE == 1
// ui_items = "sRGB (Detected)\0";
// #elif BUFFER_COLOR_SPACE == 2
// ui_items = "scRGB (Detected)\0";
// #elif BUFFER_COLOR_SPACE == 3
// ui_items = "PQ / ST.2084 (Detected)\0";
// #elif BUFFER_COLOR_SPACE == 4
// ui_items = "HLG (Detected)\0";
// #else
// ui_items = "Unknown (Defaulting to sRGB)\0";
// #endif
// > = 0;
#if BUFFER_COLOR_BIT_DEPTH > 8 || BUFFER_COLOR_SPACE > 1
#define COLORSPACE_CONVERSION 1 //use approx. transfer function; 0 for accurate
#else
#define COLORSPACE_CONVERSION 2 //N/A for 8-bit
#endif
#if BUFFER_COLOR_SPACE == 1
#define TONEMAPPER 1 //reinhard tonemapper workflow for SDR (sRGB) colorbuffer; 0 for None
#else
#define TONEMAPPER 0
#endif
/*-------------------------.
| :: TRANSFER FUNCTIONS :: |
'-------------------------*/
//sRGB
float3 sRGBtoLinearAccurate(float3 r) {
return (r <= 0.04045) ? (r / 12.92) : pow(abs(r + 0.055) / 1.055, 2.4);
}
float3 sRGBtoLinearFast(float3 r) {
return max(r / 12.92, r * r); //gamma 2.0 approx
}
float3 sRGBtoLinear(float3 r) {
if (COLORSPACE_CONVERSION == 1) return sRGBtoLinearFast(r);
else return sRGBtoLinearAccurate(r);
}
float3 linearToSRGBAccurate(float3 r) {
return (r <= 0.0031308) ? (r * 12.92) : (1.055 * pow(abs(r), 1.0 / 2.4) - 0.055);
}
float3 linearToSRGBFast(float3 r) {
return min(r * 12.92, sqrt(r)); //gamma 2.0 approx
}
float3 linearToSRGB(float3 r) {
if (COLORSPACE_CONVERSION == 1) return linearToSRGBFast(r);
else return linearToSRGBAccurate(r);
}
//PQ (ST.2084)
float3 PQtoLinearAccurate(float3 r) {
const float m1 = 1305.0/8192.0;
const float m2 = 2523.0/32.0;
const float c1 = 107.0/128.0;
const float c2 = 2413.0/128.0;
const float c3 = 2392.0/128.0;
float3 powr = pow(max(r, 0), 1.0/m2);
r = pow(max(max(powr - c1, 0) / (c2 - c3 * powr), 0), 1.0/m1);
//scale 10,000 nits down so Paper White (HDR_WHITELEVEL) maps to 1.0
return r * 10000.0 / HDR_WHITELEVEL;
}
float3 PQtoLinearFast(float3 r) {
float3 square = r * r;
float3 quad = square * square;
float3 oct = quad * quad;
r = max(max(square / 340.0, quad / 6.0), oct);
return r * 10000.0 / HDR_WHITELEVEL;
}
float3 PQtoLinear(float3 r) {
if (COLORSPACE_CONVERSION == 1) return PQtoLinearFast(r);
else return PQtoLinearAccurate(r);
}
float3 linearToPQAccurate(float3 r) {
const float m1 = 1305.0/8192.0;
const float m2 = 2523.0/32.0;
const float c1 = 107.0/128.0;
const float c2 = 2413.0/128.0;
const float c3 = 2392.0/128.0;
r = r * (HDR_WHITELEVEL / 10000.0); //rescale 1.0 back to nits
float3 powr = pow(max(r, 0), m1);
r = pow(max((c1 + c2 * powr) / (1 + c3 * powr), 0), m2);
return r;
}
float3 linearToPQFast(float3 r) {
r = r * (HDR_WHITELEVEL / 10000.0);
float3 squareroot = sqrt(r);
float3 quadroot = sqrt(squareroot);
float3 octroot = sqrt(quadroot);
r = min(octroot, min(sqrt(sqrt(6.0))*quadroot, sqrt(340.0)*squareroot));
return r;
}
float3 linearToPQ(float3 r) {
if (COLORSPACE_CONVERSION == 1) return linearToPQFast(r);
else return linearToPQAccurate(r);
}
//HLG (Hybrid Log Gamma)
float3 linearToHLG(float3 r) {
r = r * HDR_WHITELEVEL / 1000.0;
const float a = 0.17883277;
const float b = 0.28466892;
const float c = 0.55991073;
float3 s = sqrt(3 * r);
return (s < 0.5) ? s : (log(12 * r - b) * a + c);
}
float3 HLGtoLinear(float3 r) {
const float a = 0.17883277;
const float b = 0.28466892;
const float c = 0.55991073;
r = (r < 0.5) ? (r * r / 3.0) : ((exp((r - c) / a) + b) / 12.0);
return r * 1000.0 / HDR_WHITELEVEL;
}
//YCoCg
float3 linearToYCoCg(float3 r) {
float y = (r.r + 2.0 * r.g + r.b) * 0.25;
float co = (r.r - r.b) * 0.5;
float cg = (r.g - (r.r + r.b) * 0.5) * 0.5;
return float3(y, co, cg);
}
float3 YCoCgToLinear(float3 r) {
float y = r.x;
float co = r.y;
float cg = r.z;
float g = y + cg;
float rOut = y + co - cg;
float b = y - co - cg;
return float3(rOut, g, b);
}
/*--------------.
| :: HELPERS :: |
'--------------*/
float3 ToLinearColorspace(float3 r, bool tonemap) {
if (BUFFER_COLOR_SPACE == 2) r = r * (80.0 / HDR_WHITELEVEL); //scRGB
else if (BUFFER_COLOR_SPACE == 3) r = PQtoLinear(r);
else if (BUFFER_COLOR_SPACE == 4) r = HLGtoLinear(r);
else {
r = sRGBtoLinear(r);
if (TONEMAPPER == 1 && tonemap) r = r / max(1.0 - r, 0.001); //inverse reinhard
}
return r;
}
float3 ToOutputColorspace(float3 r, bool tonemap) {
if (BUFFER_COLOR_SPACE == 2) r = r * (HDR_WHITELEVEL / 80.0); //scRGB
else if (BUFFER_COLOR_SPACE == 3) r = linearToPQ(r);
else if (BUFFER_COLOR_SPACE == 4) r = linearToHLG(r);
else {
if (TONEMAPPER == 1 && tonemap) r = r / (1.0 + r); //forward reinhard
r = linearToSRGB(r);
}
return r;
}
//read the theoretical max value of the buffer (in linear scale)
float GetMaxColorValue() {
if (BUFFER_COLOR_SPACE == 4) return 1000.0 / HDR_WHITELEVEL;
if (BUFFER_COLOR_SPACE >= 2) return 10000.0 / HDR_WHITELEVEL;
return 1.0;
}
float GetLuminance(float3 color)
{
return dot(color, float3(0.2126, 0.7152, 0.0722));
}
float3 GetLinearColor(float2 uv, bool tonemap)
{
float3 color = tex2Dlod(ReShade::BackBuffer, float4(uv, 0, 0)).rgb;
return ToLinearColorspace(color, tonemap);
}
@@ -0,0 +1,54 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_Compute.fxh
Version : 2026.05.09
Author : Afzaal (Kaidō)
Description: Header file for supporting compute enabled platforms.
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
#pragma once
#include "ReShade.fxh"
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#define D3D9 0x9000
#define D3D10 0xa000
#define D3D11 0xb000
#define D3D12 0xc000
#define OPENGL 0x10000
#define VULKAN 0x20000
#if __RENDERER__ >= D3D11
#define _COMPUTE_ENABLED_ 1
#else
#define _COMPUTE_ENABLED_ 0
#endif
struct CSInput
{
uint3 dispatchID : SV_DispatchThreadID; //global pixel coord (x, y, 0)
uint3 groupID : SV_GroupID; //which tile/group in grid
uint3 localID : SV_GroupThreadID; //thread inside group [0..CS_W-1]
uint flatIndex : SV_GroupIndex; //localID flattened: y*CS_W + x
};
@@ -0,0 +1,94 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_Helpers.fxh
Version : 2026.05.30
Author : Afzaal (Kaidō)
Description: Helper functions for Lumenite shaders.
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
#pragma once
#include "ReShade.fxh"
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#define PI 3.14159265359
#define EPSILON 1e-6
//R2 sequence constants
static const float PHI_2 = 1.324717957244746;
static const float2 R2_CONSTANT = float2(1.0/PHI_2, 1.0/(PHI_2*PHI_2));
/*--------------.
| :: UNIFORMS ::|
'--------------*/
uniform float TIMER < source = "timer"; >; //ms since launch
uniform float FRAME_TIME < source = "frametime"; >; //ms last frame
uniform uint FRAME_COUNT < source = "framecount"; >;
uniform float2 MOUSE_POS < source = "mousepoint"; >; //in screen px
uniform bool MOUSE_DOWN < source = "mousebutton"; min = 0; max = 0; >;
/*--------------.
| :: HELPERS :: |
'--------------*/
bool CheckerboardSkip(uint2 currentPos, float scale)
{
//map current buffer pixel to full screen pixel.
//floor() to ensure we snap to the integer grid of the full screen
uint2 fullScreenPos = uint2(floor(currentPos.x * scale), floor(currentPos.y * scale));
return (((fullScreenPos.x + fullScreenPos.y + (FRAME_COUNT & 1)) & 1) == 1);
}
float GetDepth(float2 uv)
{
return ReShade::GetLinearizedDepth(uv);
}
bool IsOOB(float2 uv) {
return any(uv < 0.0) || any(uv > 1.0);
}
//QUASI-MONTE CARLO SEQUENCE
//fast Hilbert curve math (a 1D index from 2D coords)
uint HilbertIndex(uint x, uint y) {
uint index = 0;
[unroll] for (uint s = 64 / 2; s > 0; s /= 2) {
uint rx = (x & s) > 0;
uint ry = (y & s) > 0;
index += s * s * ((3 * rx) ^ ry);
if (ry == 0) {
if (rx == 1) {
x = 64 - 1 - x;
y = 64 - 1 - y;
}
uint t = x; x = y; y = t;
}
}
return index;
}
float2 GetStratifiedNoise(float2 vpos) {
uint2 screenPos = uint2(vpos.xy) % 64; //64x64 tiled pixel coords
uint hIndex = HilbertIndex(screenPos.x, screenPos.y); //Hilbert index (spatial)
uint totalIndex = hIndex + (uint(FRAME_COUNT % 64) * 288); //temporal offset: 288 (same as Intel XeGTAO implementation)
return frac(float(totalIndex) * R2_CONSTANT);
}
@@ -0,0 +1,96 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_Projections.fxh
Version : 2026.04.11
Author : Afzaal (Kaidō)
Description: Camera projection functions for Lumenite shaders.
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
#pragma once
#include "ReShade.fxh"
/*--------------.
| :: HELPERS :: |
'--------------*/
//VERTEX SHADER
struct VSOUT
{
float4 vpos : SV_Position;
float2 uv : TEXCOORD0;
float tan_half_fov_x : TEXCOORD1;
float tan_half_fov_y : TEXCOORD2;
float inv_tan_half_fov_x : TEXCOORD3;
float inv_tan_half_fov_y : TEXCOORD4;
float near_ratio : TEXCOORD5;
float diff_ratio : TEXCOORD6;
};
#define TAN_HALF_FOV_Y tan(radians(FOV * 0.5))
#define ASPECT_RATIO_X_OVER_Y ((float)BUFFER_WIDTH / (float)BUFFER_HEIGHT)
#define TAN_HALF_FOV_X TAN_HALF_FOV_Y * ASPECT_RATIO_X_OVER_Y
#define INV_TAN_HALF_FOV_X rcp(TAN_HALF_FOV_X)
#define INV_TAN_HALF_FOV_Y rcp(TAN_HALF_FOV_Y)
VSOUT VS(uint id : SV_VertexID)
{
VSOUT o;
o.uv.x = (id == 2) ? 2.0 : 0.0;
o.uv.y = (id == 1) ? 2.0 : 0.0;
o.vpos = float4(mad(o.uv.x, 2.0, -1.0), mad(o.uv.y, -2.0, 1.0), 0.0, 1.0);
o.tan_half_fov_x = TAN_HALF_FOV_X;
o.tan_half_fov_y = TAN_HALF_FOV_Y;
o.inv_tan_half_fov_x = INV_TAN_HALF_FOV_X;
o.inv_tan_half_fov_y = INV_TAN_HALF_FOV_Y;
o.near_ratio = NEAR_PLANE / RESHADE_DEPTH_LINEARIZATION_FAR_PLANE;
o.diff_ratio = 1.0 - o.near_ratio; //lerp(a,b,t) = (a+t * (b-a)), precompute (b-a) or (1.0-near_ratio) here
return o;
}
//PROJECTION FUNCTIONS
//normalized frustum
//left-handed viewspace
//normals point outwards
//Z+ goes into the screen
float3 UVToViewSpace(float2 uv, float linear_depth_vs, VSOUT ps_input)
{
float projection_scale = mad(linear_depth_vs, ps_input.diff_ratio, ps_input.near_ratio); //faster lerp: a+t * diff
float3 view_pos;
float ndc_x = mad(uv.x, 2.0, -1.0);
float ndc_y = mad(uv.y, -2.0, 1.0);
view_pos.x = ndc_x * ps_input.tan_half_fov_x * projection_scale;
view_pos.y = ndc_y * ps_input.tan_half_fov_y * projection_scale;
view_pos.z = linear_depth_vs;
return view_pos;
}
float2 ViewSpaceToUV(float3 view_pos, VSOUT ps_input)
{
float inv_projection_scale = rcp(mad(view_pos.z, ps_input.diff_ratio, ps_input.near_ratio));
float2 ndc;
ndc.x = view_pos.x * ps_input.inv_tan_half_fov_x * inv_projection_scale;
ndc.y = view_pos.y * ps_input.inv_tan_half_fov_y * inv_projection_scale;
float2 uv;
uv.x = mad(ndc.x, 0.5, 0.5);
uv.y = mad(ndc.y, -0.5, 0.5);
return uv;
}
@@ -0,0 +1,511 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_AnamorphicBloom.fx
Version : 2026.06.09
Author : Afzaal (Kaidō)
Description: Artistic bloom approximating the Anamorphic lens aesthetic.
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#ifndef ANAMORPHIC_BLOOM
#define ANAMORPHIC_BLOOM 1
#endif
#ifndef ANAMORPHIC_STREAKS
#define ANAMORPHIC_STREAKS 0
#endif
#ifndef COLOR_FRINGING
#define COLOR_FRINGING 0
#endif
#define BLOOM_THRESHOLD_SCALER 10.0
/*--------------.
| :: HEADERS :: |
'--------------*/
#include "ReShade.fxh"
#include "./include/lumenite_ColorManagement.fxh"
#include "./include/lumenite_Helpers.fxh"
/*---------------.
| :: UNIFORMS :: |
'---------------*/
#if ANAMORPHIC_BLOOM
uniform bool BLOOM_SKIP_SKYBOX <
ui_type = "radio";
ui_label = "Exclude Skybox (Bloom)";
ui_tooltip = "Prevents sky pixels from contributing to bloom.";
ui_category = "Anamorphic Bloom";
> = false;
uniform bool BLOOM_SHARP <
ui_type = "radio";
ui_label = "Add More Definition to Bloom Shape (Experimental)";
ui_tooltip = "Enables a sharper 1D horizontal kernel. May flicker with camera movement.";
ui_category = "Anamorphic Bloom";
> = false;
uniform float BLOOM_INTENSITY <
ui_type = "drag";
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
ui_label = "Bloom Intensity";
ui_tooltip = "Scales the intensity of the Bloom effect.";
ui_category = "Anamorphic Bloom";
> = 1.0;
uniform float BLOOM_THRESHOLD <
ui_type = "drag";
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
ui_label = "Bloom Threshold";
ui_tooltip = "Higher values bloom more of the scene.";
ui_category = "Anamorphic Bloom";
> = 0.7;
uniform float BLOOM_STRETCH <
ui_type = "drag";
ui_min = 0.0; ui_max = 7.5; ui_step = 0.01;
ui_label = "Bloom Stretch";
ui_tooltip = "Adjusts the horizontal elongation of the Bloom effect.";
ui_category = "Anamorphic Bloom";
> = 7.5;
#if COLOR_FRINGING
uniform float BLOOM_CA <
ui_type = "drag";
ui_min = 0.0; ui_max = 10.0; ui_step = 0.01;
ui_label = "Bloom Chromatic Shift";
ui_tooltip = "Shifts R/B channels within the bloom passes.";
ui_category = "Anamorphic Bloom";
> = 10.0;
#endif
#endif
#if ANAMORPHIC_STREAKS
uniform bool STREAK_SKIP_SKYBOX <
ui_type = "radio";
ui_label = "Exclude Skybox (Streaks)";
ui_tooltip = "Prevents sky pixels from contributing to light streaks.";
ui_category = "Anamorphic Streaks";
> = false;
uniform float STREAK_INTENSITY <
ui_type = "drag";
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
ui_label = "Streak Intensity";
ui_tooltip = "Scales the intensity of the light streaks.";
ui_category = "Anamorphic Streaks";
> = 1.0;
uniform float STREAK_THRESHOLD <
ui_type = "drag";
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
ui_label = "Streak Threshold";
ui_tooltip = "Higher values considers more of the scene.";
ui_category = "Anamorphic Streaks";
> = 0.5;
uniform float STREAK_STRETCH <
ui_type = "drag";
ui_min = 0.0; ui_max = 10.0; ui_step = 0.01;
ui_label = "Streak Stretch";
ui_tooltip = "Adjusts the horizontal elongation of the light streaks.";
ui_category = "Anamorphic Streaks";
> = 10.0;
uniform float3 STREAK_TINT <
ui_type = "color";
ui_label = "Tint";
ui_tooltip = "Tints the light streaks with chosen color. Set to white (1, 1, 1) for pass-through.";
ui_category = "Anamorphic Streaks";
> = float3(0.55, 0.55, 1.0);
#if COLOR_FRINGING
uniform float STREAK_CA <
ui_type = "drag";
ui_min = 0.0; ui_max = 10.0; ui_step = 0.01;
ui_label = "Streak Chromatic Shift";
ui_tooltip = "Shifts R/B channels of the light streaks.";
ui_category = "Anamorphic Streaks";
> = 10.0;
#endif
#endif
uniform int USER_GUIDE <
ui_type = "radio";
ui_category = "";
ui_label = " ";
ui_text = "Exclude Skybox: Requires access to properly configured depth buffer.";
>;
namespace LumeniteAnamorphicBloom {
/*-------------.
| :: MACROS :: |
'-------------*/
#if ANAMORPHIC_BLOOM
#define BLOOM_SHIFT (float2(BLOOM_CA * BUFFER_PIXEL_SIZE.x, 0.0)) //once per pass
#if COLOR_FRINGING
#define SAMPLE_BLOOM_TEX(s, uv) float3( \
tex2D(s, (uv) - BLOOM_SHIFT).r, \
tex2D(s, (uv)).g, \
tex2D(s, (uv) + BLOOM_SHIFT).b \
)
#else
#define SAMPLE_BLOOM_TEX(s, uv) tex2D(s, uv).rgb
#endif
#endif
#if ANAMORPHIC_STREAKS
#define STREAK_SHIFT (STREAK_CA * BUFFER_PIXEL_SIZE.x)
#if COLOR_FRINGING
#define SAMPLE_STREAK_TEX(s, uv, o) float3( \
tex2D(s, uv + float2(o - STREAK_SHIFT, 0.0)).r, \
tex2D(s, uv + float2(o, 0.0)).g, \
tex2D(s, uv + float2(o + STREAK_SHIFT, 0.0)).b \
)
#else
#define SAMPLE_STREAK_TEX(s, uv, o) tex2D(s, uv + float2(o, 0.0)).rgb
#endif
#endif
/*---------------------.
| :: RENDER TARGETS :: |
'---------------------*/
texture2D tUnpackedColor { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
sampler2D sUnpackedColor { Texture = tUnpackedColor; };
#if ANAMORPHIC_BLOOM
texture2D tBloomDown0 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sBloomDown0 { Texture = tBloomDown0; };
texture2D tBloomDown1 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/4; Format = RGBA16F; };
sampler2D sBloomDown1 { Texture = tBloomDown1; };
texture2D tBloomDown2 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RGBA16F; };
sampler2D sBloomDown2 { Texture = tBloomDown2; };
texture2D tBloomDown3 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = RGBA16F; };
sampler2D sBloomDown3 { Texture = tBloomDown3; };
texture2D tBloomDown4 { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = RGBA16F; };
sampler2D sBloomDown4 { Texture = tBloomDown4; };
texture2D tBloomUp3 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = RGBA16F; };
sampler2D sBloomUp3 { Texture = tBloomUp3; };
texture2D tBloomUp2 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RGBA16F; };
sampler2D sBloomUp2 { Texture = tBloomUp2; };
texture2D tBloomUp1 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/4; Format = RGBA16F; };
sampler2D sBloomUp1 { Texture = tBloomUp1; };
texture2D tBloomUp0 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sBloomUp0 { Texture = tBloomUp0; };
texture2D tBloomUp4 { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
sampler2D sBloomUp4 { Texture = tBloomUp4; };
#endif
#if ANAMORPHIC_STREAKS
texture2D tStreakDown0 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sStreakDown0 { Texture = tStreakDown0; };
texture2D tStreakDown1 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sStreakDown1 { Texture = tStreakDown1; };
texture2D tStreakDown2 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sStreakDown2 { Texture = tStreakDown2; };
texture2D tStreakDown3 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sStreakDown3 { Texture = tStreakDown3; };
texture2D tStreakDown4 { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sStreakDown4 { Texture = tStreakDown4; };
texture2D tStreakUp3 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sStreakUp3 { Texture = tStreakUp3; };
texture2D tStreakUp2 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sStreakUp2 { Texture = tStreakUp2; };
texture2D tStreakUp1 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sStreakUp1 { Texture = tStreakUp1; };
texture2D tStreakUp0 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
sampler2D sStreakUp0 { Texture = tStreakUp0; };
#endif
/*--------------.
| :: HELPERS :: |
'--------------*/
#if ANAMORPHIC_BLOOM
float3 TentFilter13Anisotropic(sampler2D src, float2 uv, float2 radius)
{
float dx = radius.x;
float dy = radius.y;
[branch] if (BLOOM_SHARP)
{
float3 center = SAMPLE_BLOOM_TEX(src, uv);
float3 innerLeft = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, 0));
float3 innerRight = SAMPLE_BLOOM_TEX(src, uv + float2( dx, 0));
float3 outerLeft = SAMPLE_BLOOM_TEX(src, uv + float2(-2*dx, 0));
float3 outerRight = SAMPLE_BLOOM_TEX(src, uv + float2( 2*dx, 0));
return center * 0.25 + (innerLeft + innerRight) * 0.25 + (outerLeft + outerRight) * 0.125;
}
float3 a = SAMPLE_BLOOM_TEX(src, uv + float2(-2*dx, 2*dy)).rgb;
float3 b = SAMPLE_BLOOM_TEX(src, uv + float2( 0, 2*dy)).rgb;
float3 c = SAMPLE_BLOOM_TEX(src, uv + float2( 2*dx, 2*dy)).rgb;
float3 d = SAMPLE_BLOOM_TEX(src, uv + float2(-2*dx, 0)).rgb;
float3 e = SAMPLE_BLOOM_TEX(src, uv + float2( 0, 0)).rgb;
float3 f = SAMPLE_BLOOM_TEX(src, uv + float2( 2*dx, 0)).rgb;
float3 g = SAMPLE_BLOOM_TEX(src, uv + float2(-2*dx, -2*dy)).rgb;
float3 h = SAMPLE_BLOOM_TEX(src, uv + float2( 0, -2*dy)).rgb;
float3 i = SAMPLE_BLOOM_TEX(src, uv + float2( 2*dx, -2*dy)).rgb;
float3 j = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, dy)).rgb;
float3 k = SAMPLE_BLOOM_TEX(src, uv + float2( dx, dy)).rgb;
float3 l = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, -dy)).rgb;
float3 m = SAMPLE_BLOOM_TEX(src, uv + float2( dx, -dy)).rgb;
return e * 0.125 + (a + c + g + i) * 0.03125 + (b + d + f + h) * 0.0625 + (j + k + l + m) * 0.125;
}
float3 TentFilter9Anisotropic(sampler2D src, float2 uv, float2 radius)
{
float dx = radius.x;
float dy = radius.y;
[branch] if (BLOOM_SHARP)
{
float3 center = SAMPLE_BLOOM_TEX(src, uv);
float3 left = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, 0));
float3 right = SAMPLE_BLOOM_TEX(src, uv + float2( dx, 0));
return center * 0.5 + (left + right) * 0.25;
}
float3 a = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, dy)).rgb;
float3 b = SAMPLE_BLOOM_TEX(src, uv + float2( 0, dy)).rgb;
float3 c = SAMPLE_BLOOM_TEX(src, uv + float2( dx, dy)).rgb;
float3 d = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, 0)).rgb;
float3 e = SAMPLE_BLOOM_TEX(src, uv + float2( 0, 0)).rgb;
float3 f = SAMPLE_BLOOM_TEX(src, uv + float2( dx, 0)).rgb;
float3 g = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, -dy)).rgb;
float3 h = SAMPLE_BLOOM_TEX(src, uv + float2( 0, -dy)).rgb;
float3 i = SAMPLE_BLOOM_TEX(src, uv + float2( dx, -dy)).rgb;
return (e * 4.0 + (b + d + f + h) * 2.0 + (a + c + g + i)) * 0.0625;
}
#endif
#if ANAMORPHIC_STREAKS
float3 StreakFilter(sampler2D src, float2 uv, float radius)
{
float dx = BUFFER_PIXEL_SIZE.x * radius;
return SAMPLE_STREAK_TEX(src, uv, -dx * 2.0) * 0.1 +
SAMPLE_STREAK_TEX(src, uv, -dx) * 0.25 +
SAMPLE_STREAK_TEX(src, uv, 0.0) * 0.3 +
SAMPLE_STREAK_TEX(src, uv, dx) * 0.25 +
SAMPLE_STREAK_TEX(src, uv, dx * 2.0) * 0.1;
}
#endif
/*--------------.
| :: SHADERS :: |
'--------------*/
float4 PS_StoreUnpackedColor(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return float4(GetLinearColor(uv, false), 1);
}
#if ANAMORPHIC_BLOOM
//downsample with anisotropic blur (13-tap)
float4 PS_BloomDownsample0(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 radius = float2(BLOOM_STRETCH, 1.0) * BUFFER_PIXEL_SIZE;
float3 downsample = TentFilter13Anisotropic(sUnpackedColor, uv, radius);
if (BLOOM_SKIP_SKYBOX) downsample *= (GetDepth(uv) < 1.0);
downsample = downsample * smoothstep(0.0, max(1.0 - BLOOM_THRESHOLD, 0.07)*BLOOM_THRESHOLD_SCALER, GetLuminance(downsample));
return float4(downsample, 1);
}
float4 PS_BloomDownsample1(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 radius = float2(BLOOM_STRETCH, 1.0) * BUFFER_PIXEL_SIZE * 2.0;
return float4(TentFilter13Anisotropic(sBloomDown0, uv, radius), 1);
}
float4 PS_BloomDownsample2(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 radius = float2(BLOOM_STRETCH, 1.0) * BUFFER_PIXEL_SIZE * 4.0;
return float4(TentFilter13Anisotropic(sBloomDown1, uv, radius), 1);
}
float4 PS_BloomDownsample3(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 radius = float2(BLOOM_STRETCH, 1.0) * BUFFER_PIXEL_SIZE * 8.0;
return float4(TentFilter13Anisotropic(sBloomDown2, uv, radius), 1);
}
float4 PS_BloomDownsample4(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 radius = float2(BLOOM_STRETCH, 1.0) * BUFFER_PIXEL_SIZE * 16.0;
return float4(TentFilter13Anisotropic(sBloomDown3, uv, radius), 1);
}
//upsample with anisotropic blur (9-tap)
float4 PS_BloomUpsample0(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 radius = float2(BLOOM_STRETCH, 0.0) * BUFFER_PIXEL_SIZE * 32.0 * float2(1.0, rcp(BUFFER_ASPECT_RATIO));
float3 upsample = TentFilter9Anisotropic(sBloomDown4, uv, radius);
float3 previous = tex2D(sBloomDown3, uv).rgb;
return float4(upsample + previous, 1);
}
float4 PS_BloomUpsample1(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 radius = float2(BLOOM_STRETCH, 0.0) * BUFFER_PIXEL_SIZE * 16.0 * float2(1.0, rcp(BUFFER_ASPECT_RATIO));
float3 upsample = TentFilter9Anisotropic(sBloomUp3, uv, radius);
float3 previous = tex2D(sBloomDown2, uv).rgb;
return float4(upsample + previous, 1);
}
float4 PS_BloomUpsample2(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 radius = float2(BLOOM_STRETCH, 0.0) * BUFFER_PIXEL_SIZE * 8.0 * float2(1.0, rcp(BUFFER_ASPECT_RATIO));
float3 upsample = TentFilter9Anisotropic(sBloomUp2, uv, radius);
float3 previous = tex2D(sBloomDown1, uv).rgb;
return float4(upsample + previous, 1);
}
float4 PS_BloomUpsample3(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 radius = float2(BLOOM_STRETCH, 0.0) * BUFFER_PIXEL_SIZE * 4.0 * float2(1.0, rcp(BUFFER_ASPECT_RATIO));
float3 upsample = TentFilter9Anisotropic(sBloomUp1, uv, radius);
float3 previous = tex2D(sBloomDown0, uv).rgb;
return float4(upsample + previous, 1);
}
float4 PS_BloomUpsample4(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 radius = float2(BLOOM_STRETCH, 0.0) * BUFFER_PIXEL_SIZE * 2.0 * float2(1.0, rcp(BUFFER_ASPECT_RATIO));
float3 upsample = TentFilter9Anisotropic(sBloomUp0, uv, radius);
float3 previous = tex2D(sBloomDown0, uv).rgb;
return float4(upsample + previous, 1);
}
#endif
#if ANAMORPHIC_STREAKS
//thresholding pass
float4 PS_Prefilter(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float3 color = tex2D(sUnpackedColor, uv).rgb;
if (STREAK_SKIP_SKYBOX) color *= (GetDepth(uv) < 1.0);
float br = max(color.r, max(color.g, color.b));
float nm = max(0.0, br - (1.0 - STREAK_THRESHOLD));
return float4(color * (nm / max(br, 0.0001)), 1.0);
}
float4 PS_StreakDownsample0(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakDown0, uv, 1.0 * STREAK_STRETCH), 1); }
float4 PS_StreakDownsample1(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakDown1, uv, 2.0 * STREAK_STRETCH), 1); }
float4 PS_StreakDownsample2(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakDown2, uv, 4.0 * STREAK_STRETCH), 1); }
float4 PS_StreakDownsample3(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakDown3, uv, 8.0 * STREAK_STRETCH), 1); }
float4 PS_StreakUpsample0(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakUp3, uv, 8.0 * STREAK_STRETCH) + tex2D(sStreakDown2, uv).rgb, 1); }
float4 PS_StreakUpsample1(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakUp2, uv, 4.0 * STREAK_STRETCH) + tex2D(sStreakDown1, uv).rgb, 1); }
float4 PS_StreakUpsample2(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakUp1, uv, 2.0 * STREAK_STRETCH) + tex2D(sStreakDown0, uv).rgb, 1); }
float4 PS_StreakUpsample3(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakDown4, uv, 16.0 * STREAK_STRETCH) + tex2D(sStreakDown3, uv).rgb, 1); }
#endif
float4 PS_ToDisplay(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float3 unpackedColor = tex2D(sUnpackedColor, uv).rgb;
float3 light = 0;
#if ANAMORPHIC_BLOOM
light = tex2D(sBloomUp4, uv).rgb * BLOOM_INTENSITY;
#endif
#if ANAMORPHIC_STREAKS
light = max(light, tex2D(sStreakUp0, uv).rgb * STREAK_TINT * STREAK_INTENSITY);
#endif
float3 toDisplay;
#if (BUFFER_COLOR_SPACE == 1)
//sRGB colorspace
toDisplay = 1.0 - (1.0 - unpackedColor) * (1.0 - light);
#else
toDisplay = unpackedColor + light;
#endif
toDisplay = ToOutputColorspace(toDisplay, false);
return float4(toDisplay, 1);
}
/*----------------.
| :: TECHNIQUE :: |
'----------------*/
technique Lumenite_AnamorphicBloom <
ui_label = "LUMENITE: AnamorphicBloom";
ui_tooltip = "Artistic bloom & Lens Flare approximating the Anamorphic lens aesthetic.";
>
{
pass { VertexShader = PostProcessVS; PixelShader = PS_StoreUnpackedColor; RenderTarget = tUnpackedColor; }
//bloom pyramid
#if ANAMORPHIC_BLOOM
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomDownsample0; RenderTarget = tBloomDown0; }
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomDownsample1; RenderTarget = tBloomDown1; }
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomDownsample2; RenderTarget = tBloomDown2; }
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomDownsample3; RenderTarget = tBloomDown3; }
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomDownsample4; RenderTarget = tBloomDown4; }
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomUpsample0; RenderTarget = tBloomUp3; }
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomUpsample1; RenderTarget = tBloomUp2; }
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomUpsample2; RenderTarget = tBloomUp1; }
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomUpsample3; RenderTarget = tBloomUp0; }
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomUpsample4; RenderTarget = tBloomUp4; }
#endif
//streak pyramid
#if ANAMORPHIC_STREAKS
pass { VertexShader = PostProcessVS; PixelShader = PS_Prefilter; RenderTarget = tStreakDown0; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakDownsample0; RenderTarget = tStreakDown1; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakDownsample1; RenderTarget = tStreakDown2; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakDownsample2; RenderTarget = tStreakDown3; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakDownsample3; RenderTarget = tStreakDown4; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakUpsample3; RenderTarget = tStreakUp3; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakUpsample0; RenderTarget = tStreakUp2; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakUpsample1; RenderTarget = tStreakUp1; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakUpsample2; RenderTarget = tStreakUp0; }
#endif
pass { VertexShader = PostProcessVS; PixelShader = PS_ToDisplay; }
}
}
@@ -0,0 +1,753 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_Kernel.fx
Version : 2026.07.28
Author : Afzaal (Kaidō)
Description: Pre-effect for various LumeniteFX shaders.
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#define FOV 60.0
#define NEAR_PLANE 0.01
#ifndef IMAGE_SPACE
#define IMAGE_SPACE 0
#endif
#ifndef DEBUG_KERNEL
#define DEBUG_KERNEL 0
#endif
/*--------------.
| :: HEADERS :: |
'--------------*/
#include "ReShade.fxh"
#if DEBUG_KERNEL
#include "DrawText.fxh"
#endif
#include "./include/lumenite_Projections.fxh"
#include "./include/lumenite_Helpers.fxh"
#include "./include/lumenite_Compute.fxh"
/*---------------.
| :: UNIFORMS :: |
'---------------*/
#if DEBUG_KERNEL
uniform int DEBUG_VIEW <
ui_type = "combo";
ui_items = "Split View\0"
"Normals/Depth\0"
"Optical Flow\0"
"Motion Vectors\0"
"Motion Confidence\0"
;
ui_label = "Debug View";
ui_category = "Kernel";
> = 0;
#endif
namespace Kernel {
/*---------------------.
| :: RENDER TARGETS :: |
'---------------------*/
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sFlow { Texture = tFlow; MagFilter = POINT; MinFilter = POINT; };
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
sampler2D sConfidence { Texture = tConfidence; };
texture tNormals { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; MipLevels = 4; };
sampler sNormals { Texture = tNormals; };
texture2D tDepth { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 4; };
sampler2D sDepth { Texture = tDepth; };
texture2D tCurrLuma { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 8; };
sampler2D sCurrLuma { Texture = tCurrLuma; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tPrevLuma { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 8; };
sampler2D sPrevLuma { Texture = tPrevLuma; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow128 { Width = BUFFER_WIDTH/128; Height = BUFFER_HEIGHT/128; Format = RG16F; };
sampler2D sFlow128 { Texture = tFlow128; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow64A { Width = BUFFER_WIDTH/64; Height = BUFFER_HEIGHT/64; Format = RG16F; };
sampler2D sFlow64A { Texture = tFlow64A; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow64B { Width = BUFFER_WIDTH/64; Height = BUFFER_HEIGHT/64; Format = RG16F; };
sampler2D sFlow64B { Texture = tFlow64B; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow32A { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = RG16F; };
sampler2D sFlow32A { Texture = tFlow32A; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow32B { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = RG16F; };
sampler2D sFlow32B { Texture = tFlow32B; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow16A { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = RG16F; };
sampler2D sFlow16A { Texture = tFlow16A; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow16B { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = RG16F; };
sampler2D sFlow16B { Texture = tFlow16B; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow8 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sFlow8 { Texture = tFlow8; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tPrevFrameFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sPrevFrameFlow { Texture = tPrevFrameFlow; MagFilter = POINT; MinFilter = POINT; };
texture2D tPrevConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
sampler2D sPrevConfidence { Texture = tPrevConfidence; };
/*--------------.
| :: HELPERS :: |
'--------------*/
float3 GetColor(float2 uv)
{
return tex2Dlod(ReShade::BackBuffer, float4(uv, 0, 0)).rgb;
}
float3 DepthGradient(float t, float2 uv)
{
//grayscale: close=dark, far=bright
float3 depth = saturate(t).xxx;
const float ditherBit = 8.0;
float gridPos = frac(dot(uv, (BUFFER_SCREEN_SIZE * float2(1.0 / 16.0, 10.0 / 36.0)) + 0.25));
float ditherShift = 0.25 * (1.0 / (pow(2.0, ditherBit) - 1.0));
float3 ditherShiftRGB = float3(ditherShift, -ditherShift, ditherShift); //subpixel dithering
ditherShiftRGB = lerp(2.0 * ditherShiftRGB, -2.0 * ditherShiftRGB, gridPos);
return depth + ditherShiftRGB;
}
float3 MotionToColor(float2 motion)
{
float angle = atan2(-motion.y, -motion.x) / 6.283 + 0.5;
float rawLength = length(motion) / (15.0 * BUFFER_PIXEL_SIZE.x);
float compressed = rawLength / (1.0 + rawLength * 1.4); //asymptotic squash
float boosted = pow(compressed, 0.5); //lift shadows
float magnitude = saturate(lerp(compressed, boosted, saturate(rawLength * 3.0)));
float3 hsv = float3(angle, 1, magnitude);
float4 K = float4(1, 2/3.0, 1/3.0, 3);
float3 p = abs(frac(hsv.xxx + K.xyz) * 6 - K.www);
return hsv.z * lerp(K.xxx, clamp(p - K.xxx, 0, 1), hsv.y) + 0.1;
}
float SegmentDist(float2 p, float2 a, float2 b) //anti-aliased distance from point p to segment a-b
{
float2 pa = p - a;
float2 ba = b - a;
float h = saturate(dot(pa, ba) / (dot(ba, ba) + EPSILON));
return length(pa - ba * h);
}
float4 DrawMotionVectors(float2 uv)
{
static const int GATHER = 2; //cell radius searched (5x5); always MAX_LENGTH <= GATHER*GRID_SPACING
static const float GRID_SPACING = 16.0; //px between grid nodes
static const float DOT_RADIUS = 2.0; //px radius of node dots
static const float GRID_OPACITY = 0.20; //0..1 lattice visibility
static const float3 GRID_TINT = float3(0.55, 0.55, 0.60);
static const float SHAFT_THICKNESS = 1.5; //px half-width of shaft (larger)
static const float HEAD_LENGTH = 6.0; //px length of arrowhead (larger)
static const float HEAD_HALF_WIDTH = 4.0; //px half-width of head base (larger)
static const float MIN_LENGTH = 7.0; //px shortest arrow
static const float MAX_LENGTH = 30.0; //px longest arrow (<= GATHER*GRID_SPACING)
static const float LENGTH_SCALE = 2.5; //arrow px per motion px (elongation gain)
static const float AA = 0.9; //px edge softness
float3 baseColor = GetColor(uv);
float2 pixelPos = uv * BUFFER_SCREEN_SIZE;
//dotted grid
float2 g = pixelPos / GRID_SPACING;
float2 nearest = round(g) * GRID_SPACING; //nearest node centre, px
float dDot = length(pixelPos - nearest); //px distance to that node
float gridCov = (1.0 - smoothstep(DOT_RADIUS - AA, DOT_RADIUS + AA, dDot)) * GRID_OPACITY;
float bestCov = 0.0;
float3 bestColor = float3(0.0, 0.0, 0.0);
//union of arrows from the (2*GATHER+1)^2 nearest nodes (roots on grid crossings)
float2 baseNode = round(g);
[unroll] for (int ny = -GATHER; ny <= GATHER; ny++)
[unroll] for (int nx = -GATHER; nx <= GATHER; nx++)
{
float2 rootPx = (baseNode + float2(nx, ny)) * GRID_SPACING; //node sits on a crossing
float2 rootUV = rootPx * BUFFER_PIXEL_SIZE;
float2 motion = tex2Dlod(sFlow, float4(rootUV, 0, 0)).xy;
float2 motionPx = motion * BUFFER_SCREEN_SIZE;
float magPx = length(motionPx);
bool valid = (magPx >= 0.4) && (tex2Dlod(sDepth, float4(rootUV, 0, 0)).r < 0.999);
float len = clamp(magPx * LENGTH_SCALE, MIN_LENGTH, MAX_LENGTH); //elongates with this node's motion
float2 fwd = -motionPx / (magPx + EPSILON); //negate for forward motion
float2 tip = rootPx + fwd * len;
float2 perp = float2(-fwd.y, fwd.x);
//shaft
float2 shaftEnd = rootPx + fwd * max(len - HEAD_LENGTH, 0.0);
float dShaft = SegmentDist(pixelPos, rootPx, shaftEnd);
float covShaft = 1.0 - smoothstep(SHAFT_THICKNESS - AA, SHAFT_THICKNESS + AA, dShaft);
//head
float2 toTip = pixelPos - tip;
float along = dot(toTip, -fwd);
float side = abs(dot(toTip, perp));
float halfW = HEAD_HALF_WIDTH * saturate(along / HEAD_LENGTH);
float covAlong = smoothstep(-AA, AA, along) * (1.0 - smoothstep(HEAD_LENGTH - AA, HEAD_LENGTH + AA, along));
float covHead = covAlong * (1.0 - smoothstep(halfW - AA, halfW + AA, side));
float cov = max(covShaft, covHead) * (valid ? 1.0 : 0.0);
if (cov > bestCov) { bestCov = cov; bestColor = MotionToColor(motion); }
}
float3 outColor = lerp(baseColor, GRID_TINT, gridCov); //lattice underneath
outColor = lerp(outColor, bestColor, bestCov); //arrows on top
return float4(outColor, 1.0);
}
float ZMSAD(sampler2D currLumaSrc, sampler2D prevLumaSrc, float2 posA, float2 posB, float2 texelSize, uint mip)
{
static const int2 offsets[9] = {
int2(0, 3),
int2(0, 1),
int2(-3,0), int2(-1,0), int2(0, 0), int2(1,0), int2(3,0),
int2(0,-1),
int2(0,-3)
};
//gather samples and calculate the mean for each patch
float samplesA[9], samplesB[9];
float meanA = 0.0, meanB = 0.0;
[unroll] for(int i = 0; i < 9; i++) {
float2 offset = float2(offsets[i]) * texelSize;
samplesA[i] = tex2Dlod(currLumaSrc, float4(posA + offset, 0, mip)).r;
samplesB[i] = tex2Dlod(prevLumaSrc, float4(posB + offset, 0, mip)).r;
meanA += samplesA[i];
meanB += samplesB[i];
}
meanA /= 9.0;
meanB /= 9.0;
//SAD on the normalized samples
float err = 0.0;
[unroll] for(int i = 0; i < 9; i++)
err += abs((samplesA[i] - meanA) - (samplesB[i] - meanB));
return ((err / 9.0) + EPSILON);
}
float2 Median9(sampler2D flowSrc, float2 uv, float2 texelSize, uint mip)
{
float2 v[9];
int idx = 0;
[unroll] for(int dy = -1; dy <= 1; dy++) for(int dx = -1; dx <= 1; dx++)
v[idx++] = tex2Dlod(flowSrc, float4(uv + float2(dx, dy) * texelSize, 0, mip)).xy;
//bubble sort ensures the Median lands in v[4], only needs 5 passes
//indices 4,5,6,7,8 contain the 5 largest items, so v[4] is the median
[unroll] for(int k = 0; k < 5; k++) for(int i = 0; i < 8 - k; i++) { //checks decrease as right side gets sorted
float2 a = v[i];
float2 b = v[i+1];
v[i] = min(a, b);
v[i+1] = max(a, b);
}
return v[4];
}
float2 BilateralMedian9(sampler2D flowSrc, float2 uv, float2 texelSize, uint mip)
{
static const int2 DENSE_3X3[9] = {
int2(-1,-1), int2(0,-1), int2(1,-1),
int2(-1, 0), int2(0, 0), int2(1, 0),
int2(-1, 1), int2(0, 1), int2(1, 1)
};
float lumaC = tex2Dlod(sCurrLuma, float4(uv, 0, mip)).x;
float lumaW = tex2Dlod(sCurrLuma, float4(uv + float2(-1.0, 0.0) * texelSize, 0, mip)).x;
float lumaE = tex2Dlod(sCurrLuma, float4(uv + float2( 1.0, 0.0) * texelSize, 0, mip)).x;
float lumaN = tex2Dlod(sCurrLuma, float4(uv + float2( 0.0,-1.0) * texelSize, 0, mip)).x;
float lumaS = tex2Dlod(sCurrLuma, float4(uv + float2( 0.0, 1.0) * texelSize, 0, mip)).x;
//central-difference gradient, wider baseline than quad ddx/ddy, derived from real samples
float dxLuma = (lumaE - lumaW) * 0.5;
float dyLuma = (lumaS - lumaN) * 0.5;
float2 v[9];
uint validCount = 0;
[unroll] for (int i = 0; i < 9; i++) {
int2 off = DENSE_3X3[i];
float2 sampleUV = uv + float2(off) * texelSize;
//cardinals + center use sampled luma; diagonals get linear prediction
float sampleLuma = lumaC; //covers (0,0)
if (off.x == -1 && off.y == 0) sampleLuma = lumaW;
else if (off.x == 1 && off.y == 0) sampleLuma = lumaE;
else if (off.x == 0 && off.y == -1) sampleLuma = lumaN;
else if (off.x == 0 && off.y == 1) sampleLuma = lumaS;
else if (off.x != 0 && off.y != 0) sampleLuma = lumaC + float(off.x) * dxLuma + float(off.y) * dyLuma;
bool isValid = abs(lumaC - sampleLuma) <= 0.05;
v[i] = isValid ? tex2Dlod(flowSrc, float4(sampleUV, 0, 0)).xy : float2(1e38, 1e38);
validCount += uint(isValid);
}
if(validCount < 3u) return v[4];
//right-to-left bubble: smallest reaches v[0] per pass; after 5 passes, v[0..4] sorted ascending
[unroll] for(int k = 0; k < 5; k++) for(int j = 7; j >= k; j--) {
float2 a = v[j];
float2 b = v[j+1];
v[j] = min(a, b);
v[j+1] = max(a, b);
}
uint medianIdx = validCount / 2u;
float2 result = v[1]; //fallback for validCount == 3 (medianIdx 1)
if (medianIdx == 2u) result = v[2];
if (medianIdx == 3u) result = v[3];
if (medianIdx == 4u) result = v[4];
return result;
}
float2 ATrousFilter(sampler2D motionSrc, float2 uv, uint dilation, uint mip)
{
static const int2 offsets[8] = { int2(-1,-1), int2(0,-1), int2(1,-1),
int2(-1, 0), int2(1, 0),
int2(-1, 1), int2(0, 1), int2(1, 1) };
float centerLuma = tex2Dlod(sCurrLuma, float4(uv, 0, mip)).r;
#if IMAGE_SPACE == 0
float centerDepth = tex2Dlod(sDepth, float4(uv, 0, mip)).r;
#endif
float2 centerFlow = tex2Dlod(motionSrc, float4(uv, 0, 0)).xy;
float centerConf = max(tex2Dlod(sConfidence, float4(uv, 0, 0)).r, 0.01); //0.01 floor prevents NaN if conf hits 0
float2 sum = centerFlow * centerConf;
float totalWeight = centerConf;
[unroll] for (int i = 0; i < 8; i++) {
float2 sampleUV = uv + float2(offsets[i]) * dilation * BUFFER_PIXEL_SIZE * 8.0; //*8 = stride of flow grid
float2 sampleFlow = tex2Dlod(motionSrc, float4(sampleUV, 0, 0)).xy;
float sampleConf = tex2Dlod(sConfidence, float4(sampleUV, 0, 0)).r;
float confWeight = pow(sampleConf, 3.0);
float discontinuityGate;
#if IMAGE_SPACE == 0
float sampleDepth = tex2Dlod(sDepth, float4(sampleUV, 0, mip)).r;
float absDepthDiff = abs(centerDepth - sampleDepth);
float depthWeight = (absDepthDiff < 0.003) ? 1.0 : 0.0;
discontinuityGate = depthWeight;
#else
float2 flowDeltaPx = (sampleFlow - centerFlow) * BUFFER_SCREEN_SIZE; //measure flow disagreement in full-res px
float rawMotionGate = exp2(-dot(flowDeltaPx, flowDeltaPx) / (0.01 + EPSILON));
float motionGate = lerp(1.0, rawMotionGate, saturate(centerConf)); //if center flow is unreliable; relax gate so confident neighbors repair it
discontinuityGate = motionGate;
#endif
float sampleLuma = tex2Dlod(sCurrLuma, float4(sampleUV, 0, mip)).r;
float absLumaDiff = abs(centerLuma - sampleLuma);
float lumaWeight = saturate(1.0 - absLumaDiff * 10.0); //10.0: scale, 4.0: sharpness
float weight = confWeight * lumaWeight * discontinuityGate;
sum += sampleFlow * weight;
totalWeight += weight;
}
return sum / (totalWeight + EPSILON);
}
float2 UpscaleFlow(sampler2D coarseSrc, sampler2D currLumaSrc, sampler2D prevLumaSrc, float2 uv, float2 texelSize, uint mip)
{
if(FRAME_COUNT == 0) return float2(0, 0);
float2 coarseTexelSize = rcp(float2(tex2Dsize(coarseSrc, 0)));
//pool candidates for tournament selection. order matters here
float2 candidates[10];
candidates[0] = tex2D(coarseSrc, uv).xy ;
candidates[1] = tex2D(coarseSrc, uv + float2(0, -coarseTexelSize.y)).xy ;
candidates[2] = tex2D(coarseSrc, uv + float2(0, coarseTexelSize.y)).xy ;
candidates[3] = tex2D(coarseSrc, uv - float2(coarseTexelSize.x, 0)).xy ;
candidates[4] = tex2D(coarseSrc, uv + float2(coarseTexelSize.x, 0)).xy ;
candidates[5] = tex2D(coarseSrc, uv + float2(-coarseTexelSize.x, -coarseTexelSize.y)).xy ;
candidates[6] = tex2D(coarseSrc, uv + float2( coarseTexelSize.x, -coarseTexelSize.y)).xy ;
candidates[7] = tex2D(coarseSrc, uv + float2(-coarseTexelSize.x, coarseTexelSize.y)).xy ;
candidates[8] = tex2D(coarseSrc, uv + float2(coarseTexelSize.x, coarseTexelSize.y)).xy ;
candidates[9] = tex2D(sPrevFrameFlow, uv).xy;
float minCost = 1e6;
float2 prediction = candidates[0];
[loop] for (int i = 0; i < 10; i++) {
float cost = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + candidates[i], texelSize, mip);
if (cost < minCost) {
minCost = cost;
prediction = candidates[i];
}
}
//refinement with parabolic fitting
float costLeft = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction - float2(texelSize.x, 0), texelSize, mip);
float costRight = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction + float2(texelSize.x, 0), texelSize, mip);
float costDown = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction - float2(0, texelSize.y), texelSize, mip);
float costUp = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction + float2(0, texelSize.y), texelSize, mip);
//sub-pixel offset (parabolic fitting)
float2 subpixelOffset;
subpixelOffset.x = (costLeft - costRight) / (4.0 * (costLeft + costRight - 2.0 * minCost) + EPSILON); //EPSILON for flat surface handling
subpixelOffset.y = (costDown - costUp) / (4.0 * (costDown + costUp - 2.0 * minCost) + EPSILON);
//clamp offset to a reasonable range
subpixelOffset = clamp(subpixelOffset, -0.5, 0.5);
return (prediction+subpixelOffset*texelSize);
}
/*--------------.
| :: SHADERS :: |
'--------------*/
void PS_ReconstructNormals(VSOUT input, out float4 gbuffer : SV_Target0, out float depthC : SV_Target1)
{
depthC = GetDepth(input.uv);
const float2 offsetX = float2(BUFFER_PIXEL_SIZE.x, 0);
const float2 offsetY = float2(0, BUFFER_PIXEL_SIZE.y);
float3 pC = UVToViewSpace(input.uv, depthC, input);
float3 pL = UVToViewSpace(input.uv - offsetX, GetDepth(input.uv - offsetX), input);
float3 pR = UVToViewSpace(input.uv + offsetX, GetDepth(input.uv + offsetX), input);
float3 pT = UVToViewSpace(input.uv - offsetY, GetDepth(input.uv - offsetY), input);
float3 pB = UVToViewSpace(input.uv + offsetY, GetDepth(input.uv + offsetY), input);
float3 diffX2 = pR - pC;
float3 diffX1 = pC - pL;
float3 diffY2 = pB - pC;
float3 diffY1 = pC - pT;
float lenSqX2 = dot(diffX2, diffX2);
float lenSqX1 = dot(diffX1, diffX1);
float lenSqY2 = dot(diffY2, diffY2);
float lenSqY1 = dot(diffY1, diffY1);
float3 ddx = lenSqX2 < lenSqX1 ? diffX2 : diffX1;
float3 ddy = lenSqY2 < lenSqY1 ? diffY2 : diffY1;
float3 geoNormal = normalize(cross(ddx, ddy));
gbuffer = float4(geoNormal, depthC);
}
float PS_PackFeatures(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float3 color = GetColor(uv);
float luma = dot(color, float3(0.2126, 0.7152, 0.0722));
return luma * rcp(1.0 + luma);
}
float2 PS_ComputeFlow128(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
if(FRAME_COUNT == 0) return float2(0, 0);
static const int SEARCH_RADIUS = 3;
static const uint mip = 5;
float2 texelSize = BUFFER_PIXEL_SIZE * exp2(mip);
//candidate seeds for the coarsest level for tournament selection
float2 prevSeed = tex2D(sPrevFrameFlow, uv).xy;
float2 zeroSeed = float2(0, 0);
float prevCost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv + prevSeed, texelSize, mip);
float zeroCost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv + zeroSeed, texelSize, mip);
float2 seed = (zeroCost < prevCost) ? zeroSeed : prevSeed; //pick better candidate as seed
float2 bestFlow = seed;
float minCost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv+seed, texelSize, mip);
//search in a grid AROUND the seed
for (int y = -SEARCH_RADIUS; y <= SEARCH_RADIUS; ++y) for (int x = -SEARCH_RADIUS; x <= SEARCH_RADIUS; ++x) {
if (x == 0 && y == 0) continue;
float2 candidateFlow = seed + float2(x, y) * texelSize;
float cost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv + candidateFlow, texelSize, mip);
if (cost < minCost) {
minCost = cost;
bestFlow = candidateFlow;
if (minCost < 0.01) //near-perfect match found
return bestFlow;
}
}
return bestFlow;
}
float2 PS_UpscaleFlow64(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow128, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*16.0, 4);
}
float2 PS_MedianPass64(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return Median9(sFlow64A, uv, BUFFER_PIXEL_SIZE*64.0, 6);
}
float2 PS_UpscaleFlow32(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow64B, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*8.0, 3);
}
float2 PS_MedianPass32(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return Median9(sFlow32A, uv, BUFFER_PIXEL_SIZE*32.0, 5);
}
float2 PS_UpscaleFlow16(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow32B, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*4.0, 2);
}
float2 PS_MedianPass16(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return Median9(sFlow16A, uv, BUFFER_PIXEL_SIZE*16.0, 4);
}
float2 PS_UpscaleFlow8(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow16B, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*2.0, 1);
}
float2 PS_MedianPass8A(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return BilateralMedian9(sFlow, uv, BUFFER_PIXEL_SIZE*8.0, 3);
}
float2 PS_MedianPass8B(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return BilateralMedian9(sFlow8, uv, BUFFER_PIXEL_SIZE*8.0, 3);
}
float2 PS_ATrousPassA(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target //stride 1
{
return ATrousFilter(sFlow, uv, 2, 3);
}
float2 PS_ATrousPassB(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target //stride 2
{
float2 flow = ATrousFilter(sFlow8, uv, 4, 1);
//kill sub-pixel noise
float flowPixelMag = length(flow / BUFFER_PIXEL_SIZE);
float gate = saturate(1.0 - pow(1.0 - saturate(saturate(flowPixelMag) - 0.2), 10.0)); //SNAP TO REALITY
return flow*gate;
}
float PS_Confidence(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
if(FRAME_COUNT == 0) return 0.0; //no confidence
float2 flow = tex2D(sFlow, uv).xy;
float2 prevUV = uv + flow; //warp prev frame forward
if(IsOOB(prevUV)) return 0.0;
//look at local contrast for pattern confidence
float sumX = 0, sumX2 = 0, sumY = 0, sumY2 = 0;
float2 lumaTexSize = BUFFER_PIXEL_SIZE * 4.0;
static const float2 offsets[5] = {
float2(0, 1),
float2(-1,0), float2(0, 0), float2(1,0),
float2(0,-1)
};
[unroll] for(int i = 0; i < 5; i++) {
float valCurr = tex2Dlod(sCurrLuma, float4(uv + offsets[i] * lumaTexSize, 0, 2)).r;
float valPrev = tex2Dlod(sPrevLuma, float4(prevUV + offsets[i] * lumaTexSize, 0, 2)).r;
sumX += valCurr; sumX2 += valCurr * valCurr;
sumY += valPrev; sumY2 += valPrev * valPrev;
}
float varCurr = max(0.0, (sumX2 / 5.0) - (sumX / 5.0 * sumX / 5.0));
float varPrev = max(0.0, (sumY2 / 5.0) - (sumY / 5.0 * sumY / 5.0));
float patternConf = 1.0 - saturate(abs(sqrt(varCurr) - sqrt(varPrev)) / (sqrt(varCurr) + 0.01));
//look at neighborhood for flow consistency
float flowMagnitude = length(flow);
float2 flowTexelSize = BUFFER_PIXEL_SIZE * 8.0;
float2 flowN = tex2Dlod(sFlow, float4(uv + float2(0, -flowTexelSize.y), 0, 0)).xy;
float2 flowS = tex2Dlod(sFlow, float4(uv + float2(0, flowTexelSize.y), 0, 0)).xy;
float2 flowE = tex2Dlod(sFlow, float4(uv + float2( flowTexelSize.x, 0), 0, 0)).xy;
float2 flowW = tex2Dlod(sFlow, float4(uv + float2(-flowTexelSize.x, 0), 0, 0)).xy;
float2 avgNeighborFlow = (flowN + flowS + flowE + flowW) * 0.25;
float spatialDiff = distance(flow, avgNeighborFlow);
float spatialThreshold = flowMagnitude * 0.5 + BUFFER_PIXEL_SIZE.x;
float spatialConfidence = saturate(1.0 - (spatialDiff / (spatialThreshold + EPSILON)));
//motion length penalty
float subpixelThreshold = length(BUFFER_PIXEL_SIZE);
float lengthConfidence = (flowMagnitude <= subpixelThreshold) ? 1.0 : rcp((flowMagnitude / subpixelThreshold) * 0.05 + 1.0);
//float panThreshold = BUFFER_PIXEL_SIZE.x * 30.0;
//float lengthConfidence = (flowMagnitude <= panThreshold) ? 1.0 : rcp(((flowMagnitude - panThreshold) / panThreshold) * 0.1 + 1.0);
//current frame final confidence
float currentConf = spatialConfidence * lengthConfidence * patternConf;
//temporal filter
float historyConf = tex2D(sPrevConfidence, prevUV).r;
//DEPRECATED: linear EMA (a=0.15) 15% new + 85% history every frame
//unbiased (settles at the true mean), very stable but distrusts a real drop only as slowly as it trusts a rise
//return lerp(historyConf, currentConf, 0.15); //higher makes it react to changes quickly
//Asymmetric EMA; a=0.5 only on a genuine drop (>0.05 below history) fast distrust, else a reasonable a=0.1
//0.05 deadband keeps calm-region jitter on 0.1; only true occlusion/disocclusion bleeds confidence fast
float alpha = (currentConf < historyConf - 0.05) ? 0.5 : 0.1;
return lerp(historyConf, currentConf, alpha);
}
void PS_StoreFlow(float4 pos : SV_Position, float2 uv : TEXCOORD, out float2 flow : SV_Target0, out float confidence : SV_Target1)
{
flow = tex2D(sFlow, uv).xy;
confidence = tex2D(sConfidence, uv).r;
}
float PS_StoreLuma(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return tex2D(sCurrLuma, uv).r;
}
#if DEBUG_KERNEL
float4 PS_Debug(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float3 sceneColor = GetColor(uv);
switch(DEBUG_VIEW)
{
case 0: {
static const float LINE_PX = 1.5; //divider half-width, px
static const float3 LINE_TINT = float3(0.0, 0.0, 0.0);
static const float2 BOX_HALF = float2(0.16, 0.18); //centre inset half-extents, uv
float2 pixelPos = uv * BUFFER_SCREEN_SIZE;
float2 centrePx = BUFFER_SCREEN_SIZE * 0.5;
float2 boxHalfPx = BOX_HALF * BUFFER_SCREEN_SIZE;
//axis-aligned box distance
float2 dd = abs(pixelPos - centrePx) - boxHalfPx;
float boxSDF = length(max(dd, 0.0)) + min(max(dd.x, dd.y), 0.0);
float3 view;
if (boxSDF < 0.0)
{
float2 boxUV = (uv - (0.5 - BOX_HALF)) / (2.0 * BOX_HALF); //full frame mapped into inset
view = DrawMotionVectors(boxUV).rgb; //centre: motion vectors
}
else
{
float2 quadUV = frac(uv * 2.0); //flow/confidence remap to full [0,1] frame
if (uv.y < 0.5)
view = (uv.x < 0.5)
? tex2Dlod(sNormals, float4(uv, 0, 0)).rgb * 0.5 + 0.5 //TL: normals (spatial, raw uv)
: DepthGradient(tex2Dlod(sDepth, float4(uv, 0, 0)).r, uv); //TR: depth (spatial, raw uv)
else if (uv.x < 0.5)
view = MotionToColor(tex2Dlod(sFlow, float4(quadUV, 0, 0)).xy); //BL: optical flow field
else
{
float confidence = tex2Dlod(sConfidence, float4(quadUV, 0, 0)).x; //BR: motion confidence field
float3 confidenceColor = (confidence < 0.5)
? lerp(float3(1.0, 0.0, 0.0), float3(1.0, 1.0, 0.0), confidence * 2.0)
: lerp(float3(1.0, 1.0, 0.0), float3(0.0, 1.0, 0.0), (confidence - 0.5) * 2.0);
view = lerp(GetColor(quadUV), confidenceColor, 0.9);
}
//black dividers
float dCross = min(abs(pixelPos.x - centrePx.x), abs(pixelPos.y - centrePx.y));
view = lerp(view, LINE_TINT, 1.0 - smoothstep(LINE_PX - 0.9, LINE_PX + 0.9, dCross));
}
//centre inset border
view = lerp(view, LINE_TINT, 1.0 - smoothstep(LINE_PX - 0.9, LINE_PX + 0.9, abs(boxSDF)));
//window labels
float2 texcoord = uv; //alias: the DrawText macro declares its own internal 'uv'
float labelMask = 0.0;
float labelSize = max(BUFFER_HEIGHT * 0.025, 12.0); //label height, px
int lblNormals[21] = { __R, __e, __c, __o, __n, __s, __t, __r, __u, __c, __t, __e, __d, __Space, __N, __o, __r, __m, __a, __l, __s };
int lblDepth[16] = { __L, __i, __n, __e, __a, __r, __i, __z, __e, __d, __Space, __D, __e, __p, __t, __h };
int lblFlow[10] = { __F, __l, __o, __w, __Space, __F, __i, __e, __l, __d };
int lblConfidence[16] = { __C, __o, __n, __f, __i, __d, __e, __n, __c, __e, __Space, __F, __i, __e, __l, __d };
int lblVectors[14] = { __M, __o, __t, __i, __o, __n, __Space, __V, __e, __c, __t, __o, __r, __s };
labelMask = 0.0; DrawText_String(float2(BUFFER_WIDTH * 0.25 - 21.0 * labelSize * 0.25, BUFFER_HEIGHT * 0.03), labelSize, 1.0, texcoord, lblNormals, 21, labelMask); view = lerp(view, float3(1.00, 1.00, 1.00), saturate(labelMask)); //TL white
labelMask = 0.0; DrawText_String(float2(BUFFER_WIDTH * 0.75 - 16.0 * labelSize * 0.25, BUFFER_HEIGHT * 0.03), labelSize, 1.0, texcoord, lblDepth, 16, labelMask); view = lerp(view, float3(0.55, 0.85, 1.00), saturate(labelMask)); //TR blue
labelMask = 0.0; DrawText_String(float2(BUFFER_WIDTH * 0.25 - 10.0 * labelSize * 0.25, BUFFER_HEIGHT * 0.53), labelSize, 1.0, texcoord, lblFlow, 10, labelMask); view = lerp(view, float3(1.00, 1.00, 1.00), saturate(labelMask)); //BL white
labelMask = 0.0; DrawText_String(float2(BUFFER_WIDTH * 0.75 - 16.0 * labelSize * 0.25, BUFFER_HEIGHT * 0.53), labelSize, 1.0, texcoord, lblConfidence, 16, labelMask); view = lerp(view, float3(1.00, 1.00, 1.00), saturate(labelMask)); //BR white
labelMask = 0.0; DrawText_String(float2(BUFFER_WIDTH * 0.50 - 14.0 * labelSize * 0.25, BUFFER_HEIGHT * (0.5 - BOX_HALF.y) + 8.0), labelSize, 1.0, texcoord, lblVectors, 14, labelMask); view = lerp(view, float3(1.00, 1.00, 1.00), saturate(labelMask)); //centre white
view = lerp(view, float3(1.0, 1.0, 1.0), saturate(labelMask)); //white labels
return float4(view, 1.0);
}
case 1: {
float4 gbuffer = tex2D(sNormals, uv);
float3 normal = gbuffer.rgb;
float depth = gbuffer.a;
bool isLeftHalf = uv.x < 0.5;
float4 dbg;
if (isLeftHalf)
dbg = float4(normal * 0.5 + 0.5, 1.0); //left: normals
else
dbg = float4(DepthGradient(depth, uv), 1.0); //right: depth gradient
return dbg;
}
case 2: return float4(MotionToColor(tex2D(sFlow, uv).xy), 1);
case 3: return DrawMotionVectors(uv);
case 4:
{
float confidence = tex2D(sConfidence, uv).x;
float3 confidenceColor;
if (confidence < 0.5)
confidenceColor = lerp(float3(1.0, 0.0, 0.0), float3(1.0, 1.0, 0.0), confidence * 2.0);
else
confidenceColor = lerp(float3(1.0, 1.0, 0.0), float3(0.0, 1.0, 0.0), (confidence - 0.5) * 2.0);
return float4(lerp(sceneColor, confidenceColor, 0.9), 1.0);
}
default: return float4(sceneColor, 1.0);
}
}
#endif
/*----------------.
| :: TECHNIQUE :: |
'----------------*/
technique Lumenite_Kernel <
ui_label = "LUMENITE: Kernel 2.0";
ui_tooltip = "Pre-effect for LumeniteFX shaders.";
>
{
//normals
#if IMAGE_SPACE == 0
pass { VertexShader = VS; PixelShader = PS_ReconstructNormals; RenderTarget0 = tNormals; RenderTarget1 = tDepth; }
#endif
//optical flow
pass { VertexShader = PostProcessVS; PixelShader = PS_PackFeatures; RenderTarget = tCurrLuma; }
pass { VertexShader = PostProcessVS; PixelShader = PS_ComputeFlow128; RenderTarget = tFlow128; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow64; RenderTarget = tFlow64A; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass64; RenderTarget = tFlow64B; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow32; RenderTarget = tFlow32A; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass32; RenderTarget = tFlow32B; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow16; RenderTarget = tFlow16A; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass16; RenderTarget = tFlow16B; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow8; RenderTarget = tFlow; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass8A; RenderTarget = tFlow8; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass8B; RenderTarget = tFlow; }
pass { VertexShader = PostProcessVS; PixelShader = PS_Confidence; RenderTarget = tConfidence; }
pass { VertexShader = PostProcessVS; PixelShader = PS_ATrousPassA; RenderTarget = tFlow8; }
pass { VertexShader = PostProcessVS; PixelShader = PS_ATrousPassB; RenderTarget = tFlow; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StoreFlow; RenderTarget0 = tPrevFrameFlow; RenderTarget1 = tPrevConfidence; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StoreLuma; RenderTarget = tPrevLuma; }
//debug views
#if DEBUG_KERNEL
pass { VertexShader = PostProcessVS; PixelShader = PS_Debug; }
#endif
}
}
@@ -0,0 +1,368 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_LSAO.fx
Version : 2026.06.09
Author : Afzaal (Kaidō)
Description: Large-Scale Ray Traced Ambient Occlusion (Screen Space).
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#define FOV 60.0
#define NEAR_PLANE 0.01
#define AO_MAX_MARCH_STEPS 100
/*--------------.
| :: HEADERS :: |
'--------------*/
#include "ReShade.fxh"
#include "./include/lumenite_Projections.fxh"
#include "./include/lumenite_Helpers.fxh"
#include "./include/lumenite_ColorManagement.fxh"
/*---------------.
| :: UNIFORMS :: |
'---------------*/
uniform bool DEBUG_VIEW <
ui_label = "Show AO Mask";
ui_tooltip = "Debug view for the AO. Shows raw AO.";
ui_category = "Ambient Occlusion";
> = 0;
uniform float DEPTH_BOUNDARY <
ui_type = "slider";
ui_min = 0.001; ui_max = 0.999; ui_step = 0.001;
ui_label = "AO Range";
ui_tooltip = "The Z+ range/depth in which the effect is applied.";
ui_category = "Ambient Occlusion";
hidden = false;
> = 0.6;
uniform float DEPTH_FADE_START <
ui_type = "slider";
ui_min = 0.1; ui_max = 1.0; ui_step = 0.01;
ui_label = "Z+ Fade Start (%)";
ui_tooltip = "Z+ fraction where effect starts fading out (relative to AO Range)";
ui_category = "Ambient Occlusion";
hidden = true;
> = 0.75;
uniform float AO_INTENSITY <
ui_type = "drag";
ui_min = 0.0; ui_max = 1.0;
ui_label = "AO Strength";
ui_tooltip = "Controls the intensity of the ambient occlusion effect.";
ui_category = "Ambient Occlusion";
> = 1.0;
/*--------------.
| :: IMPORTS :: |
'--------------*/
namespace Kernel {
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sFlow { Texture = tFlow; MagFilter = POINT; MinFilter = POINT; };
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
sampler2D sConfidence { Texture = tConfidence; };
texture tNormals { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; MipLevels = 4; };
sampler sNormals { Texture = tNormals; };
texture2D tDepth { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 4; };
sampler2D sDepth { Texture = tDepth; };
}
namespace LumeniteLSAO {
/*---------------------.
| :: RENDER TARGETS :: |
'---------------------*/
texture tAOTrace { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = R16F; };
sampler sAOTrace { Texture = tAOTrace; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
texture tAO1 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
sampler sAO1 { Texture = tAO1; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
texture tAO2 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
sampler sAO2 { Texture = tAO2; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
sampler sAO2Linear { Texture = tAO2; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
texture tPrevAO { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
sampler sPrevAO { Texture = tPrevAO; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
//HiZ mipchain
texture tHiZMip0 { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; };
texture tHiZMip1 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = R16F; };
texture tHiZMip2 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/4; Format = R16F; };
texture tHiZMip3 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
texture tHiZMip4 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = R16F; };
texture tHiZMip5 { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = R16F; };
sampler sHiZMip0 { Texture = tHiZMip0; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
sampler sHiZMip1 { Texture = tHiZMip1; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
sampler sHiZMip2 { Texture = tHiZMip2; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
sampler sHiZMip3 { Texture = tHiZMip3; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
sampler sHiZMip4 { Texture = tHiZMip4; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
sampler sHiZMip5 { Texture = tHiZMip5; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
/*--------------.
| :: HELPERS :: |
'--------------*/
void BuildOrthonormalBasis(float3 n, out float3 b1, out float3 b2)
{
if (n.z < -0.9999999) {
b1 = float3(0.0, -1.0, 0.0);
b2 = float3(-1.0, 0.0, 0.0);
} else {
float a = rcp(1.0 + n.z);
float b = -n.x * n.y * a;
b1 = float3(mad(-n.x * n.x, a, 1.0), b, -n.x);
b2 = float3(b, mad(-n.y * n.y, a, 1.0), -n.y);
}
}
float3 GenerateHemisphereDirection(float3 normal, float2 rand, float3 tangent, float3 bitangent)
{
float phi = rand.x * 6.28318530718; //2.0*PI as constant
float sinPhi, cosPhi;
sincos(phi, sinPhi, cosPhi);
float cosTheta = sqrt(1.0 - rand.y);
float sinTheta = sqrt(rand.y);
float3 result = normal * cosTheta;
result = mad(bitangent, sinTheta * sinPhi, result);
result = mad(tangent, sinTheta * cosPhi, result);
return result;
}
float CalculateDepthFade(float depth)
{
float fadeStartDepth = DEPTH_BOUNDARY * DEPTH_FADE_START;
float fadeRange = DEPTH_BOUNDARY - fadeStartDepth;
return 1.0 - saturate((depth - fadeStartDepth) / fadeRange);
}
float2 ATrousFilter(sampler SourceSampler, float2 uv, uint dilation, bool adaptiveDilation)
{
float4 gbuffer = tex2D(Kernel::sNormals, uv);
if (gbuffer.a == 0 || gbuffer.a >= DEPTH_BOUNDARY) return float2(1.0, 0.0);
[branch] if (adaptiveDilation) {
float confidence = tex2Dlod(Kernel::sConfidence, float4(uv, 0, 0)).r;
dilation += uint(round((1.0 - confidence) * 2.0)); //scale filter kernel w. motion by up to a factor of 2
}
float2 centerData = tex2Dlod(SourceSampler, float4(uv, 0, 0)).rg;
float variance = max(0.0, centerData.g - (centerData.r * centerData.r)); //Moment - AO^2
variance = max(variance, 0.0001);
float2 sum = centerData;
float totalWeight = 1.0;
for (int y = -1; y <= 1; y++) for (int x = -1; x <= 1; x++) {
if (x == 0 && y == 0) continue;
float2 sampleUV = uv + float2(x, y) * dilation * (BUFFER_PIXEL_SIZE * 2.0); //don't forget the x2.0 to properly step half-res grid!
float2 sampleData = tex2Dlod(SourceSampler, float4(sampleUV, 0, 0)).rg;
float4 sampleGeo = tex2Dlod(Kernel::sNormals, float4(sampleUV, 0, 0));
float depthWeight = exp(-abs(gbuffer.a - sampleGeo.a) / (gbuffer.a * 0.02 + 0.001));
float normalWeight = pow(saturate(dot(gbuffer.rgb, sampleGeo.rgb)), 50.0);
float aoDiff = centerData.r - sampleData.r;
float aoWeight = exp(-(aoDiff * aoDiff) / (variance + 0.0001));
float weight = depthWeight * normalWeight * aoWeight;
sum += sampleData * weight;
totalWeight += weight;
}
return sum / (totalWeight + EPSILON);
}
float SamplePrevHiZ(float2 centerUV, sampler srcSampler, int srcMipLvl) {
float2 srcTexelSize = BUFFER_PIXEL_SIZE * pow(2, srcMipLvl);
float2 off[4] = { float2(-0.5, -0.5), float2(0.5, -0.5), float2(-0.5, 0.5), float2(0.5, 0.5) };
float minDepth = 1.0;
[unroll] for(int i=0; i<4; i++)
minDepth = min(minDepth, tex2D(srcSampler, centerUV + off[i] * srcTexelSize).r);
return minDepth;
}
/*--------------.
| :: SHADERS :: |
'--------------*/
float PS_GenerateMip0(VSOUT input) : SV_Target
{
float2 blockOriginUV = floor(input.uv / (BUFFER_PIXEL_SIZE * 2.0)) * (BUFFER_PIXEL_SIZE * 2.0);
float2 uvs[4] = { blockOriginUV + BUFFER_PIXEL_SIZE * float2(0.5, 0.5),
blockOriginUV + BUFFER_PIXEL_SIZE * float2(1.5, 0.5),
blockOriginUV + BUFFER_PIXEL_SIZE * float2(0.5, 1.5),
blockOriginUV + BUFFER_PIXEL_SIZE * float2(1.5, 1.5) };
float d0 = tex2D(Kernel::sDepth, uvs[0]).r;
float d1 = tex2D(Kernel::sDepth, uvs[1]).r;
float d2 = tex2D(Kernel::sDepth, uvs[2]).r;
float d3 = tex2D(Kernel::sDepth, uvs[3]).r;
return min(min(d0, d1), min(d2, d3));
}
float PS_ReduceMip1 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip0, 0); }
float PS_ReduceMip2 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip1, 1); }
float PS_ReduceMip3 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip2, 2); }
float PS_ReduceMip4 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip3, 3); }
float PS_ReduceMip5 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip4, 4); }
float PS_TraceAO(VSOUT input) : SV_Target
{
float4 gbuffer = tex2D(Kernel::sNormals, input.uv);
float3 normal = gbuffer.rgb;
float depth = gbuffer.a;
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
float3 startPos = UVToViewSpace(input.uv, depth, input);
float3 tangent, bitangent;
BuildOrthonormalBasis(normal, tangent, bitangent);
float2 noise = GetStratifiedNoise(input.vpos.xy);
float3 rayDir = GenerateHemisphereDirection(normal, noise, tangent, bitangent);
float totalRayLength = 0.7 * depth;
float baseStepSize = totalRayLength / (float)AO_MAX_MARCH_STEPS;
float stepSize = baseStepSize;
float3 currentPos = startPos + rayDir * stepSize;
float occlusion = 0.0;
float t = stepSize;
[loop]
for (int step = 0; step < AO_MAX_MARCH_STEPS; step++) {
if (t >= totalRayLength) break;
float2 hitPos = ViewSpaceToUV(currentPos, input);
if (IsOOB(hitPos)) break;
//select the appropriate Mip Level
float2 ray_screen_velocity = abs(rayDir.xy / currentPos.z) * float2(BUFFER_WIDTH, BUFFER_HEIGHT);
float footprint = max(ray_screen_velocity.x, ray_screen_velocity.y) * max(stepSize / currentPos.z, 1.0);
int mip = clamp(int(log2(max(footprint, 1.0))), 0, 5);
float HiZDepth;
if (mip==5) HiZDepth = tex2Dlod(sHiZMip5, float4(hitPos,0,0)).r;
else if (mip==4) HiZDepth = tex2Dlod(sHiZMip4, float4(hitPos,0,0)).r;
else if (mip==3) HiZDepth = tex2Dlod(sHiZMip3, float4(hitPos,0,0)).r;
else if (mip==2) HiZDepth = tex2Dlod(sHiZMip2, float4(hitPos,0,0)).r;
else if (mip==1) HiZDepth = tex2Dlod(sHiZMip1, float4(hitPos,0,0)).r;
else HiZDepth = tex2Dlod(sHiZMip0, float4(hitPos,0,0)).r;
//skip some empty space
float currentStepSize = stepSize * max(1.0, float(mip) * 0.5); //stepsize mip scaling
if (currentPos.z < HiZDepth && (HiZDepth - currentPos.z) > currentStepSize) {
float leap = max(currentStepSize, (HiZDepth - currentPos.z) * 0.065);
currentPos += rayDir * leap;
t += leap;
continue;
}
//hit test
float sceneDepth = tex2Dlod(Kernel::sDepth, float4(hitPos, 0, 0)).r;
float depthDiff = currentPos.z - sceneDepth;
float maxThickness = currentPos.z * 0.6;
if (depthDiff > (currentPos.z * 0.0001) && depthDiff < maxThickness) {
float3 scenePos = UVToViewSpace(hitPos, sceneDepth, input);
float hitDistance = length(scenePos - startPos);
float normalizedDist = hitDistance / totalRayLength;
occlusion = 1.0 - saturate(depthDiff / maxThickness);
occlusion = occlusion * occlusion;
occlusion = saturate(pow(saturate(1.0 - normalizedDist), 1.2) * occlusion * 1.4);
break;
}
currentPos += rayDir * stepSize;
t += stepSize;
}
float aoFactor = 1.0 - saturate(occlusion * AO_INTENSITY);
return aoFactor;
}
float2 PS_TemporalFilter(VSOUT input) : SV_Target
{
float depth = tex2D(Kernel::sDepth, input.uv).r;
//overwrite noise at boundary with clean White, prevents gaps
if (depth >= DEPTH_BOUNDARY) return float2(1.0, 1.0); //1.0 AO, 1.0 Moment
if (depth == 0) discard;
float ao = tex2D(sAOTrace, input.uv).r;
ao = lerp(1.0, ao, CalculateDepthFade(depth));
float moment = ao * ao;
float2 flow = tex2D(Kernel::sFlow, input.uv).xy;
float confidence = tex2D(Kernel::sConfidence, input.uv).x;
confidence = saturate(confidence + log2(2.0 - confidence) * 0.6); //boost confidence
float2 rawHistory = tex2D(sPrevAO, input.uv + flow).rg; //history stores "1.0 - AO". 0.0 (Black Texture) -> Reads as 1.0 (White)
float prevAO = 1.0 - rawHistory.r;
float prevMoment = 1.0 - rawHistory.g;
float alpha = confidence * 0.98;
ao = lerp(ao, prevAO, alpha);
moment = lerp(moment, prevMoment, alpha);
//max(..., 0.001) to ensure we NEVER write exactly 0.0 again
//this tells the next frame "I contain data"
return float2(max(ao, 0.001), max(moment, 0.001));
}
float2 PS_StoreAO(VSOUT input) : SV_Target
{
//must prevent history collision here
//if we store exactly 0.0 (means White), the next frame's blend pass thinks
//history is empty and resets it, causing shimmer
//so clamp to 0.0001 so the system knows "This is valid history data"
float2 data = tex2D(sAO1, input.uv).rg;
return float2(max(1.0 - data.r, 0.0001), max(1.0 - data.g, 0.0001)); //store inverted
}
float2 PS_ATrousPass1(VSOUT input) : SV_Target { return ATrousFilter(sAO1, input.uv, 2, false); }
float4 PS_ToDisplay(VSOUT input) : SV_Target
{
float depth = tex2D(Kernel::sDepth, input.uv).r;
float ao = ATrousFilter(sAO2Linear, input.uv, 4, true).r; //stable AO mask (fades to 1.0)
if (DEBUG_VIEW) {
#if BUFFER_COLOR_SPACE > 1
return float4(ToOutputColorspace(ao.xxx, true), 1.0);
#else
return float4(ao.xxx, 1.0);
#endif
}
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
float3 base = GetLinearColor(input.uv, true);
base *= ao;
return float4(ToOutputColorspace(base, true), 1.0);
}
/*----------------.
| :: TECHNIQUE :: |
'----------------*/
technique Lumenite_LSAO <
ui_label = "LUMENITE: LSAO";
ui_tooltip = "Large-Scale Ray Traced Ambient Occlusion (Screen Space).";
>
{
pass { VertexShader = VS; PixelShader = PS_GenerateMip0; RenderTarget = tHiZMip0; }
pass { VertexShader = VS; PixelShader = PS_ReduceMip1; RenderTarget = tHiZMip1; }
pass { VertexShader = VS; PixelShader = PS_ReduceMip2; RenderTarget = tHiZMip2; }
pass { VertexShader = VS; PixelShader = PS_ReduceMip3; RenderTarget = tHiZMip3; }
pass { VertexShader = VS; PixelShader = PS_ReduceMip4; RenderTarget = tHiZMip4; }
pass { VertexShader = VS; PixelShader = PS_ReduceMip5; RenderTarget = tHiZMip5; }
pass { VertexShader = VS; PixelShader = PS_TraceAO; RenderTarget = tAOTrace; }
pass { VertexShader = VS; PixelShader = PS_TemporalFilter; RenderTarget = tAO1; }
pass { VertexShader = VS; PixelShader = PS_StoreAO; RenderTarget = tPrevAO; }
pass { VertexShader = VS; PixelShader = PS_ATrousPass1; RenderTarget = tAO2; }
pass { VertexShader = VS; PixelShader = PS_ToDisplay; }
}
}
@@ -0,0 +1,403 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_QuantAO.fx
Version : 2026.06.09
Author : Afzaal (Kaidō)
Description: Fast Ambient Occlusion (Screen Space).
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#define FOV 60.0
#define NEAR_PLANE 0.01
#define AO_MAX_MARCH_STEPS 48
/*--------------.
| :: HEADERS :: |
'--------------*/
#include "ReShade.fxh"
#include "./include/lumenite_Projections.fxh"
#include "./include/lumenite_Helpers.fxh"
#include "./include/lumenite_ColorManagement.fxh"
/*---------------.
| :: UNIFORMS :: |
'---------------*/
uniform bool DEBUG_VIEW <
ui_label = "Show AO Mask";
ui_tooltip = "Debug view for the AO. Shows raw AO.";
ui_category = "Ambient Occlusion";
> = 0;
uniform float DEPTH_BOUNDARY <
ui_type = "slider";
ui_min = 0.001; ui_max = 0.999; ui_step = 0.001;
ui_label = "AO Range";
ui_tooltip = "The Z+ range/depth in which the effect is applied.";
ui_category = "Ambient Occlusion";
hidden = false;
> = 0.6;
uniform float DEPTH_FADE_START <
ui_type = "slider";
ui_min = 0.1; ui_max = 1.0; ui_step = 0.01;
ui_label = "Z+ Fade Start (%)";
ui_tooltip = "Z+ fraction where effect starts fading out (relative to AO Range)";
ui_category = "Ambient Occlusion";
hidden = true;
> = 0.75;
uniform float AO_INTENSITY <
ui_type = "drag";
ui_min = 0.0; ui_max = 1.0;
ui_label = "AO Strength";
ui_tooltip = "Controls the intensity of the ambient occlusion effect.";
ui_category = "Ambient Occlusion";
> = 1.0;
/*--------------.
| :: IMPORTS :: |
'--------------*/
namespace QuantMotion {
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sFlow { Texture = tFlow; MagFilter = POINT; MinFilter = POINT; };
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
sampler2D sConfidence { Texture = tConfidence; };
}
namespace LumeniteQuantAO {
/*---------------------.
| :: RENDER TARGETS :: |
'---------------------*/
texture tNormals { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RGBA16F; };
sampler sNormals { Texture = tNormals; };
texture2D tDepth { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = R16F; };
sampler2D sDepth { Texture = tDepth; };
texture tAOTrace { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = R16F; };
sampler sAOTrace { Texture = tAOTrace; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
texture tAO1 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
sampler sAO1 { Texture = tAO1; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
texture tAO2 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
sampler sAO2 { Texture = tAO2; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
texture tAO3 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
sampler sAO3Linear { Texture = tAO3; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; };
texture tPrevAO { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
sampler sPrevAO { Texture = tPrevAO; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
//HiZ mipchain
texture tHiZMip0 { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; };
texture tHiZMip1 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = R16F; };
texture tHiZMip2 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/4; Format = R16F; };
texture tHiZMip3 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
texture tHiZMip4 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = R16F; };
texture tHiZMip5 { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = R16F; };
sampler sHiZMip0 { Texture = tHiZMip0; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
sampler sHiZMip1 { Texture = tHiZMip1; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
sampler sHiZMip2 { Texture = tHiZMip2; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
sampler sHiZMip3 { Texture = tHiZMip3; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
sampler sHiZMip4 { Texture = tHiZMip4; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
sampler sHiZMip5 { Texture = tHiZMip5; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
/*--------------.
| :: HELPERS :: |
'--------------*/
void BuildOrthonormalBasis(float3 n, out float3 b1, out float3 b2)
{
if (n.z < -0.9999999) {
b1 = float3(0.0, -1.0, 0.0);
b2 = float3(-1.0, 0.0, 0.0);
} else {
float a = rcp(1.0 + n.z);
float b = -n.x * n.y * a;
b1 = float3(mad(-n.x * n.x, a, 1.0), b, -n.x);
b2 = float3(b, mad(-n.y * n.y, a, 1.0), -n.y);
}
}
float3 GenerateHemisphereDirection(float3 normal, float2 rand, float3 tangent, float3 bitangent)
{
float phi = rand.x * 6.28318530718; //2.0*PI as constant
float sinPhi, cosPhi;
sincos(phi, sinPhi, cosPhi);
float cosTheta = sqrt(1.0 - rand.y);
float sinTheta = sqrt(rand.y);
float3 result = normal * cosTheta;
result = mad(bitangent, sinTheta * sinPhi, result);
result = mad(tangent, sinTheta * cosPhi, result);
return result;
}
float CalculateDepthFade(float depth)
{
float fadeStartDepth = DEPTH_BOUNDARY * DEPTH_FADE_START;
float fadeRange = DEPTH_BOUNDARY - fadeStartDepth;
return 1.0 - saturate((depth - fadeStartDepth) / fadeRange);
}
float2 ATrousFilter(sampler SourceSampler, float2 uv, uint dilation, bool adaptiveDilation)
{
float4 gbuffer = tex2D(sNormals, uv);
if (gbuffer.a == 0 || gbuffer.a >= DEPTH_BOUNDARY) return float2(1.0, 0.0);
[branch] if (adaptiveDilation) {
float confidence = tex2Dlod(QuantMotion::sConfidence, float4(uv, 0, 0)).r;
dilation += uint(round((1.0 - confidence) * 2.0)); //scale filter kernel w. motion by up to a factor of 2
}
float2 centerData = tex2Dlod(SourceSampler, float4(uv, 0, 0)).rg;
float variance = max(0.0, centerData.g - (centerData.r * centerData.r)); //Moment - AO^2
variance = max(variance, 0.0001);
float2 sum = centerData;
float totalWeight = 1.0;
for (int y = -1; y <= 1; y++) for (int x = -1; x <= 1; x++) {
if (x == 0 && y == 0) continue;
float2 sampleUV = uv + float2(x, y) * dilation * (BUFFER_PIXEL_SIZE * 2.0); //don't forget the x2.0 to properly step half-res grid!
float2 sampleData = tex2Dlod(SourceSampler, float4(sampleUV, 0, 0)).rg;
float4 sampleGeo = tex2Dlod(sNormals, float4(sampleUV, 0, 0));
float depthWeight = exp(-abs(gbuffer.a - sampleGeo.a) / (gbuffer.a * 0.02 + 0.001));
float normalWeight = pow(saturate(dot(gbuffer.rgb, sampleGeo.rgb)), 50.0);
float aoDiff = centerData.r - sampleData.r;
float aoWeight = exp(-(aoDiff * aoDiff) / (variance + 0.0001));
float weight = depthWeight * normalWeight * aoWeight;
sum += sampleData * weight;
totalWeight += weight;
}
return sum / (totalWeight + EPSILON);
}
float SamplePrevHiZ(float2 centerUV, sampler srcSampler, int srcMipLvl) {
float2 srcTexelSize = BUFFER_PIXEL_SIZE * pow(2, srcMipLvl);
float2 off[4] = { float2(-0.5, -0.5), float2(0.5, -0.5), float2(-0.5, 0.5), float2(0.5, 0.5) };
float minDepth = 1.0;
[unroll] for(int i=0; i<4; i++)
minDepth = min(minDepth, tex2D(srcSampler, centerUV + off[i] * srcTexelSize).r);
return minDepth;
}
/*--------------.
| :: SHADERS :: |
'--------------*/
void PS_ReconstructNormals(VSOUT input, out float4 gbuffer : SV_Target0, out float depthC : SV_Target1)
{
depthC = GetDepth(input.uv);
const float2 offsetX = float2(BUFFER_PIXEL_SIZE.x, 0);
const float2 offsetY = float2(0, BUFFER_PIXEL_SIZE.y);
float3 pC = UVToViewSpace(input.uv, depthC, input);
float3 pL = UVToViewSpace(input.uv - offsetX, GetDepth(input.uv - offsetX), input);
float3 pR = UVToViewSpace(input.uv + offsetX, GetDepth(input.uv + offsetX), input);
float3 pT = UVToViewSpace(input.uv - offsetY, GetDepth(input.uv - offsetY), input);
float3 pB = UVToViewSpace(input.uv + offsetY, GetDepth(input.uv + offsetY), input);
float3 diffX2 = pR - pC;
float3 diffX1 = pC - pL;
float3 diffY2 = pB - pC;
float3 diffY1 = pC - pT;
float lenSqX2 = dot(diffX2, diffX2);
float lenSqX1 = dot(diffX1, diffX1);
float lenSqY2 = dot(diffY2, diffY2);
float lenSqY1 = dot(diffY1, diffY1);
float3 ddx = lenSqX2 < lenSqX1 ? diffX2 : diffX1;
float3 ddy = lenSqY2 < lenSqY1 ? diffY2 : diffY1;
float3 geoNormal = normalize(cross(ddx, ddy));
gbuffer = float4(geoNormal, depthC);
}
float PS_GenerateMip0(VSOUT input) : SV_Target
{
float2 blockOriginUV = floor(input.uv / (BUFFER_PIXEL_SIZE * 2.0)) * (BUFFER_PIXEL_SIZE * 2.0);
float2 uvs[4] = { blockOriginUV + BUFFER_PIXEL_SIZE * float2(0.5, 0.5),
blockOriginUV + BUFFER_PIXEL_SIZE * float2(1.5, 0.5),
blockOriginUV + BUFFER_PIXEL_SIZE * float2(0.5, 1.5),
blockOriginUV + BUFFER_PIXEL_SIZE * float2(1.5, 1.5) };
float d0 = tex2D(sDepth, uvs[0]).r;
float d1 = tex2D(sDepth, uvs[1]).r;
float d2 = tex2D(sDepth, uvs[2]).r;
float d3 = tex2D(sDepth, uvs[3]).r;
return min(min(d0, d1), min(d2, d3));
}
float PS_ReduceMip1 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip0, 0); }
float PS_ReduceMip2 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip1, 1); }
float PS_ReduceMip3 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip2, 2); }
float PS_ReduceMip4 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip3, 3); }
float PS_ReduceMip5 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip4, 4); }
float PS_TraceAO(VSOUT input) : SV_Target
{
float4 gbuffer = tex2D(sNormals, input.uv);
float3 normal = gbuffer.rgb;
float depth = gbuffer.a;
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
float3 startPos = UVToViewSpace(input.uv, depth, input);
float3 tangent, bitangent;
BuildOrthonormalBasis(normal, tangent, bitangent);
float2 noise = GetStratifiedNoise(input.vpos.xy);
float3 rayDir = GenerateHemisphereDirection(normal, noise, tangent, bitangent);
float totalRayLength = 0.7 * depth;
float baseStepSize = totalRayLength / (float)AO_MAX_MARCH_STEPS;
float stepSize = baseStepSize;
float3 currentPos = startPos + rayDir * stepSize;
float occlusion = 0.0;
float t = stepSize;
[loop]
for (int step = 0; step < AO_MAX_MARCH_STEPS; step++) {
if (t >= totalRayLength) break;
float2 hitPos = ViewSpaceToUV(currentPos, input);
if (IsOOB(hitPos)) break;
//select the appropriate Mip Level
float2 ray_screen_velocity = abs(rayDir.xy / currentPos.z) * float2(BUFFER_WIDTH, BUFFER_HEIGHT);
float footprint = max(ray_screen_velocity.x, ray_screen_velocity.y) * max(stepSize / currentPos.z, 1.0);
int mip = clamp(int(log2(max(footprint, 1.0))), 0, 5);
float HiZDepth;
if (mip==5) HiZDepth = tex2Dlod(sHiZMip5, float4(hitPos,0,0)).r;
else if (mip==4) HiZDepth = tex2Dlod(sHiZMip4, float4(hitPos,0,0)).r;
else if (mip==3) HiZDepth = tex2Dlod(sHiZMip3, float4(hitPos,0,0)).r;
else if (mip==2) HiZDepth = tex2Dlod(sHiZMip2, float4(hitPos,0,0)).r;
else if (mip==1) HiZDepth = tex2Dlod(sHiZMip1, float4(hitPos,0,0)).r;
else HiZDepth = tex2Dlod(sHiZMip0, float4(hitPos,0,0)).r;
//skip some empty space
float currentStepSize = stepSize * max(1.0, float(mip) * 0.5); //stepsize mip scaling
if (currentPos.z < HiZDepth && (HiZDepth - currentPos.z) > currentStepSize) {
float leap = max(currentStepSize, (HiZDepth - currentPos.z) * 0.065);
currentPos += rayDir * leap;
t += leap;
continue;
}
//hit test
float sceneDepth = tex2Dlod(sDepth, float4(hitPos, 0, 0)).r;
float depthDiff = currentPos.z - sceneDepth;
float maxThickness = currentPos.z * 0.6;
if (depthDiff > (currentPos.z * 0.0001) && depthDiff < maxThickness) {
float3 scenePos = UVToViewSpace(hitPos, sceneDepth, input);
float hitDistance = length(scenePos - startPos);
float normalizedDist = hitDistance / totalRayLength;
occlusion = 1.0 - saturate(depthDiff / maxThickness);
occlusion = occlusion * occlusion;
occlusion = saturate(pow(saturate(1.0 - normalizedDist), 1.2) * occlusion * 1.4);
break;
}
currentPos += rayDir * stepSize;
t += stepSize;
}
float aoFactor = 1.0 - saturate(occlusion * AO_INTENSITY);
return aoFactor;
}
float2 PS_TemporalFilter(VSOUT input) : SV_Target
{
float depth = tex2D(sDepth, input.uv).r;
//overwrite noise at boundary with clean White, prevents gaps
if (depth >= DEPTH_BOUNDARY) return float2(1.0, 1.0); //1.0 AO, 1.0 Moment
if (depth == 0) discard;
float ao = tex2D(sAOTrace, input.uv).r;
ao = lerp(1.0, ao, CalculateDepthFade(depth));
float moment = ao * ao;
float2 flow = tex2D(QuantMotion::sFlow, input.uv).xy;
float confidence = tex2D(QuantMotion::sConfidence, input.uv).x;
confidence = saturate(confidence + log2(2.0 - confidence) * 0.55); //boost confidence
float2 rawHistory = tex2D(sPrevAO, input.uv + flow).rg; //history stores "1.0 - AO". 0.0 (Black Texture) -> Reads as 1.0 (White)
float prevAO = 1.0 - rawHistory.r;
float prevMoment = 1.0 - rawHistory.g;
float alpha = confidence * 0.98;
ao = lerp(ao, prevAO, alpha);
moment = lerp(moment, prevMoment, alpha);
//max(..., 0.001) to ensure we NEVER write exactly 0.0 again
//this tells the next frame "I contain data"
return float2(max(ao, 0.001), max(moment, 0.001));
}
float2 PS_StoreAO(VSOUT input) : SV_Target
{
//must prevent history collision here
//if we store exactly 0.0 (means White), the next frame's blend pass thinks
//history is empty and resets it, causing shimmer
//so clamp to 0.0001 so the system knows "This is valid history data"
float2 data = tex2D(sAO1, input.uv).rg;
return float2(max(1.0 - data.r, 0.0001), max(1.0 - data.g, 0.0001)); //store inverted
}
float2 PS_ATrousPass1(VSOUT input) : SV_Target { return ATrousFilter(sAO1, input.uv, 2, false); }
float2 PS_ATrousPass2(VSOUT input) : SV_Target { return ATrousFilter(sAO2, input.uv, 4, true); }
float4 PS_ToDisplay(VSOUT input) : SV_Target
{
float depth = tex2D(sDepth, input.uv).r;
float ao = tex2D(sAO3Linear, input.uv).r;
if (DEBUG_VIEW) {
#if BUFFER_COLOR_SPACE > 1
return float4(ToOutputColorspace(ao.xxx, true), 1.0);
#else
return float4(ao.xxx, 1.0);
#endif
}
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
float3 base = GetLinearColor(input.uv, true);
base *= ao;
return float4(ToOutputColorspace(base, true), 1.0);
}
/*----------------.
| :: TECHNIQUE :: |
'----------------*/
technique Lumenite_QuantAO <
ui_label = "LUMENITE: QuantAO";
ui_tooltip = "Fast Ambient Occlusion (Screen Space).";
>
{
pass { VertexShader = VS; PixelShader = PS_ReconstructNormals; RenderTarget0 = tNormals; RenderTarget1 = tDepth; }
pass { VertexShader = VS; PixelShader = PS_GenerateMip0; RenderTarget = tHiZMip0; }
pass { VertexShader = VS; PixelShader = PS_ReduceMip1; RenderTarget = tHiZMip1; }
pass { VertexShader = VS; PixelShader = PS_ReduceMip2; RenderTarget = tHiZMip2; }
pass { VertexShader = VS; PixelShader = PS_ReduceMip3; RenderTarget = tHiZMip3; }
pass { VertexShader = VS; PixelShader = PS_ReduceMip4; RenderTarget = tHiZMip4; }
pass { VertexShader = VS; PixelShader = PS_ReduceMip5; RenderTarget = tHiZMip5; }
pass { VertexShader = VS; PixelShader = PS_TraceAO; RenderTarget = tAOTrace; }
pass { VertexShader = VS; PixelShader = PS_TemporalFilter; RenderTarget = tAO1; }
pass { VertexShader = VS; PixelShader = PS_StoreAO; RenderTarget = tPrevAO; }
pass { VertexShader = VS; PixelShader = PS_ATrousPass1; RenderTarget = tAO2; }
pass { VertexShader = VS; PixelShader = PS_ATrousPass2; RenderTarget = tAO3; }
pass { VertexShader = VS; PixelShader = PS_ToDisplay; }
}
}
@@ -0,0 +1,444 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : QuantMotion.fx
Version : 2026.06.16
Author : Afzaal (Kaidō)
Description: Superfast motion vectors for low-end hardware.
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#define EPSILON 1e-6
#ifndef DEBUG_FLOW
#define DEBUG_FLOW 0
#endif
/*--------------.
| :: HEADERS :: |
'--------------*/
#include "ReShade.fxh"
/*---------------.
| :: UNIFORMS :: |
'---------------*/
uniform uint FRAME_COUNT < source = "framecount"; >;
namespace QuantMotion {
/*---------------------.
| :: RENDER TARGETS :: |
'---------------------*/
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sFlow { Texture = tFlow; MagFilter = POINT; MinFilter = POINT; };
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
sampler2D sConfidence { Texture = tConfidence; };
texture2D tCurrLuma { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 8; };
sampler2D sCurrLuma { Texture = tCurrLuma; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tPrevLuma { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 8; };
sampler2D sPrevLuma { Texture = tPrevLuma; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow128 { Width = BUFFER_WIDTH/128; Height = BUFFER_HEIGHT/128; Format = RG16F; };
sampler2D sFlow128 { Texture = tFlow128; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow64A { Width = BUFFER_WIDTH/64; Height = BUFFER_HEIGHT/64; Format = RG16F; };
sampler2D sFlow64A { Texture = tFlow64A; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow64B { Width = BUFFER_WIDTH/64; Height = BUFFER_HEIGHT/64; Format = RG16F; };
sampler2D sFlow64B { Texture = tFlow64B; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow32A { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = RG16F; };
sampler2D sFlow32A { Texture = tFlow32A; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow32B { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = RG16F; };
sampler2D sFlow32B { Texture = tFlow32B; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow16A { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = RG16F; };
sampler2D sFlow16A { Texture = tFlow16A; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow16B { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = RG16F; };
sampler2D sFlow16B { Texture = tFlow16B; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow8 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sFlow8 { Texture = tFlow8; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tPrevFrameFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sPrevFrameFlow { Texture = tPrevFrameFlow; MagFilter = POINT; MinFilter = POINT; };
texture2D tPrevConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
sampler2D sPrevConfidence { Texture = tPrevConfidence; };
/*--------------.
| :: HELPERS :: |
'--------------*/
bool IsOOB(float2 uv) {
return any(uv < 0.0) || any(uv > 1.0);
}
float3 GetColor(float2 uv)
{
return tex2Dlod(ReShade::BackBuffer, float4(uv, 0, 0)).rgb;
}
float3 MotionToColor(float2 motion)
{
float angle = atan2(-motion.y, -motion.x) / 6.283 + 0.5;
float rawLength = length(motion) / (15.0 * BUFFER_PIXEL_SIZE.x);
float compressed = rawLength / (1.0 + rawLength * 1.4); //asymptotic squash
float boosted = pow(compressed, 0.5); //lift shadows
float magnitude = saturate(lerp(compressed, boosted, saturate(rawLength * 3.0)));
float3 hsv = float3(angle, 1, magnitude);
float4 K = float4(1, 2/3.0, 1/3.0, 3);
float3 p = abs(frac(hsv.xxx + K.xyz) * 6 - K.www);
return hsv.z * lerp(K.xxx, clamp(p - K.xxx, 0, 1), hsv.y) + 0.1;
}
float ZMSAD(sampler2D currLumaSrc, sampler2D prevLumaSrc, float2 posA, float2 posB, float2 texelSize, uint mip)
{
static const int2 offsets[9] = {
int2(0, 3),
int2(0, 1),
int2(-3,0), int2(-1,0), int2(0, 0), int2(1,0), int2(3,0),
int2(0,-1),
int2(0,-3)
};
//gather samples and calculate the mean for each patch
float samplesA[9], samplesB[9];
float meanA = 0.0, meanB = 0.0;
[unroll] for(int i = 0; i < 9; i++) {
float2 offset = float2(offsets[i]) * texelSize;
samplesA[i] = tex2Dlod(currLumaSrc, float4(posA + offset, 0, mip)).r;
samplesB[i] = tex2Dlod(prevLumaSrc, float4(posB + offset, 0, mip)).r;
meanA += samplesA[i];
meanB += samplesB[i];
}
meanA /= 9.0;
meanB /= 9.0;
//SAD on the normalized samples
float err = 0.0;
[unroll] for(int i = 0; i < 9; i++)
err += abs((samplesA[i] - meanA) - (samplesB[i] - meanB));
return ((err / 9.0) + EPSILON);
}
float2 Median9(sampler2D flowSrc, float2 uv, float2 texelSize, uint mip)
{
float2 v[9];
int idx = 0;
[unroll] for(int dy = -1; dy <= 1; dy++) for(int dx = -1; dx <= 1; dx++)
v[idx++] = tex2Dlod(flowSrc, float4(uv + float2(dx, dy) * texelSize, 0, mip)).xy;
//bubble sort ensures the Median lands in v[4], only needs 5 passes
//indices 4,5,6,7,8 contain the 5 largest items, so v[4] is the median
[unroll] for(int k = 0; k < 5; k++) for(int i = 0; i < 8 - k; i++) { //checks decrease as right side gets sorted
float2 a = v[i];
float2 b = v[i+1];
v[i] = min(a, b);
v[i+1] = max(a, b);
}
return v[4];
}
float2 BilateralMedian9(sampler2D flowSrc, float2 uv, float2 texelSize, uint mip)
{
static const int2 DENSE_3X3[9] = {
int2(-1,-1), int2(0,-1), int2(1,-1),
int2(-1, 0), int2(0, 0), int2(1, 0),
int2(-1, 1), int2(0, 1), int2(1, 1)
};
float lumaC = tex2Dlod(sCurrLuma, float4(uv, 0, 0)).x;
float lumaW = tex2Dlod(sCurrLuma, float4(uv + float2(-1.0, 0.0) * texelSize, 0, 0)).x;
float lumaE = tex2Dlod(sCurrLuma, float4(uv + float2( 1.0, 0.0) * texelSize, 0, 0)).x;
float lumaN = tex2Dlod(sCurrLuma, float4(uv + float2( 0.0,-1.0) * texelSize, 0, 0)).x;
float lumaS = tex2Dlod(sCurrLuma, float4(uv + float2( 0.0, 1.0) * texelSize, 0, 0)).x;
//central-difference gradient, wider baseline than quad ddx/ddy, derived from real samples
float dxLuma = (lumaE - lumaW) * 0.5;
float dyLuma = (lumaS - lumaN) * 0.5;
float2 v[9];
uint validCount = 0;
[unroll] for (int i = 0; i < 9; i++) {
int2 off = DENSE_3X3[i];
float2 sampleUV = uv + float2(off) * texelSize;
//cardinals + center use sampled luma; diagonals get linear prediction
float sampleLuma = lumaC; //covers (0,0)
if (off.x == -1 && off.y == 0) sampleLuma = lumaW;
else if (off.x == 1 && off.y == 0) sampleLuma = lumaE;
else if (off.x == 0 && off.y == -1) sampleLuma = lumaN;
else if (off.x == 0 && off.y == 1) sampleLuma = lumaS;
else if (off.x != 0 && off.y != 0) sampleLuma = lumaC + float(off.x) * dxLuma + float(off.y) * dyLuma;
bool isValid = abs(lumaC - sampleLuma) <= 0.05;
v[i] = isValid ? tex2Dlod(flowSrc, float4(sampleUV, 0, mip)).xy : float2(1e38, 1e38);
validCount += uint(isValid);
}
if(validCount < 3u) return v[4];
//right-to-left bubble: smallest reaches v[0] per pass; after 5 passes, v[0..4] sorted ascending
[unroll] for(int k = 0; k < 5; k++) for(int j = 7; j >= k; j--) {
float2 a = v[j];
float2 b = v[j+1];
v[j] = min(a, b);
v[j+1] = max(a, b);
}
uint medianIdx = validCount / 2u;
float2 result = v[1]; //fallback for validCount == 3 (medianIdx 1)
if (medianIdx == 2u) result = v[2];
if (medianIdx == 3u) result = v[3];
if (medianIdx == 4u) result = v[4];
return result;
}
float2 ATrousFilter(sampler2D motionSrc, float2 uv, uint dilation, uint mip)
{
static const int2 offsets[8] = { int2(-1,-1), int2(0,-1), int2(1,-1),
int2(-1, 0), int2(1, 0),
int2(-1, 1), int2(0, 1), int2(1, 1) };
float2 centerFlow = tex2Dlod(motionSrc, float4(uv, 0, 0)).xy;
float centerConf = max(tex2Dlod(sConfidence, float4(uv, 0, 0)).r, 0.01); //0.01 floor prevents NaN if conf hits 0
float2 sum = centerFlow * centerConf;
float totalWeight = centerConf;
[unroll] for (int i = 0; i < 8; i++) {
float2 sampleUV = uv + float2(offsets[i]) * dilation * BUFFER_PIXEL_SIZE * 8.0; //*8 = stride of flow grid
float2 sampleFlow = tex2Dlod(motionSrc, float4(sampleUV, 0, 0)).xy;
float2 flowDelta = (sampleFlow - centerFlow) / BUFFER_PIXEL_SIZE * 8.0;
float flowWeight = exp(-dot(flowDelta, flowDelta) * 0.125);
float weight = flowWeight;
sum += sampleFlow * weight;
totalWeight += weight;
}
return sum / (totalWeight + EPSILON);
}
float2 UpscaleFlow(sampler2D coarseSrc, sampler2D currLumaSrc, sampler2D prevLumaSrc, float2 uv, float2 texelSize, uint mip)
{
if(FRAME_COUNT == 0) return float2(0, 0);
float2 coarseTexelSize = rcp(float2(tex2Dsize(coarseSrc, 0)));
//pool candidates for tournament selection. order matters here
float2 candidates[6];
candidates[0] = tex2D(coarseSrc, uv).xy ;
candidates[1] = tex2D(coarseSrc, uv + float2(0, -coarseTexelSize.y)).xy ;
candidates[2] = tex2D(coarseSrc, uv + float2(0, coarseTexelSize.y)).xy ;
candidates[3] = tex2D(coarseSrc, uv - float2(coarseTexelSize.x, 0)).xy ;
candidates[4] = tex2D(coarseSrc, uv + float2(coarseTexelSize.x, 0)).xy ;
candidates[5] = tex2D(sPrevFrameFlow, uv).xy;
float minCost = 1e6;
float2 prediction = candidates[0];
[loop] for (int i = 0; i < 6; i++) {
float cost = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + candidates[i], texelSize, mip);
if (cost < minCost) {
minCost = cost;
prediction = candidates[i];
}
}
//refinement with parabolic fitting
float costLeft = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction - float2(texelSize.x, 0), texelSize, mip);
float costRight = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction + float2(texelSize.x, 0), texelSize, mip);
float costDown = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction - float2(0, texelSize.y), texelSize, mip);
float costUp = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction + float2(0, texelSize.y), texelSize, mip);
//sub-pixel offset (parabolic fitting)
float2 subpixelOffset;
subpixelOffset.x = (costLeft - costRight) / (4.0 * (costLeft + costRight - 2.0 * minCost) + EPSILON); //EPSILON for flat surface handling
subpixelOffset.y = (costDown - costUp) / (4.0 * (costDown + costUp - 2.0 * minCost) + EPSILON);
//clamp offset to a reasonable range
subpixelOffset = clamp(subpixelOffset, -0.5, 0.5);
return (prediction+subpixelOffset*texelSize);
}
/*--------------.
| :: SHADERS :: |
'--------------*/
float PS_PackFeatures(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float luma = dot(GetColor(uv), float3(0.2126, 0.7152, 0.0722));
return luma * rcp(1.0 + luma);
}
float2 PS_ComputeFlow128(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
if(FRAME_COUNT == 0) return float2(0, 0);
static const int SEARCH_RADIUS = 3;
static const uint mip = 5;
float2 texelSize = BUFFER_PIXEL_SIZE * exp2(mip);
//candidate seeds for the coarsest level for tournament selection
float2 prevSeed = tex2D(sPrevFrameFlow, uv).xy;
float2 zeroSeed = float2(0, 0);
float prevCost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv + prevSeed, texelSize, mip);
float zeroCost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv + zeroSeed, texelSize, mip);
float2 seed = (zeroCost < prevCost) ? zeroSeed : prevSeed; //pick better candidate as seed
float2 bestFlow = seed;
float minCost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv+seed, texelSize, mip);
//search in a grid AROUND the seed
for (int y = -SEARCH_RADIUS; y <= SEARCH_RADIUS; ++y) for (int x = -SEARCH_RADIUS; x <= SEARCH_RADIUS; ++x) {
if (x == 0 && y == 0) continue;
float2 candidateFlow = seed + float2(x, y) * texelSize;
float cost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv + candidateFlow, texelSize, mip);
if (cost < minCost) {
minCost = cost;
bestFlow = candidateFlow;
if (minCost < 0.01) //near-perfect match found
return bestFlow;
}
}
return bestFlow;
}
float2 PS_UpscaleFlow64(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow128, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*16.0, 4);
}
float2 PS_MedianPass64(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return Median9(sFlow64A, uv, BUFFER_PIXEL_SIZE*64.0, 6);
}
float2 PS_UpscaleFlow32(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow64B, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*8.0, 3);
}
float2 PS_MedianPass32(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return Median9(sFlow32A, uv, BUFFER_PIXEL_SIZE*32.0, 5);
}
float2 PS_UpscaleFlow16(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow32B, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*4.0, 2);
}
float2 PS_MedianPass16(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return Median9(sFlow16A, uv, BUFFER_PIXEL_SIZE*16.0, 4);
}
float2 PS_UpscaleFlow8(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow16B, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*2.0, 1);
}
float2 PS_MedianPass8(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return BilateralMedian9(sFlow8, uv, BUFFER_PIXEL_SIZE*8.0, 3);
}
float2 PS_ATrousPassA(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target //stride 1
{
return ATrousFilter(sFlow, uv, 2, 3);
}
float2 PS_ATrousPassB(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target //stride 2
{
float2 flow = ATrousFilter(sFlow8, uv, 4, 1);
//kill sub-pixel noise
float flowPixelMag = length(flow / BUFFER_PIXEL_SIZE);
float gate = saturate(1.0 - pow(1.0 - saturate(saturate(flowPixelMag) - 0.2), 10.0)); //SNAP TO REALITY
return flow*gate;
}
float PS_Confidence(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
if(FRAME_COUNT == 0) return 0.0; //no confidence
float2 flow = tex2D(sFlow, uv).xy;
float2 prevUV = uv + flow; //warp prev frame forward
if(IsOOB(prevUV)) return 0.0;
float currLuma = tex2Dlod(sCurrLuma, float4(uv, 0, 3)).r;
float prevLuma = tex2Dlod(sPrevLuma, float4(prevUV, 0, 3)).r;
float lumaError = abs(currLuma - prevLuma);
if(lumaError > 0.1) return 0.0; //no confidence
float subpixelThreshold = length(BUFFER_PIXEL_SIZE);
float flowMagnitude = length(flow);
if (flowMagnitude <= subpixelThreshold) return 0.9; //if flow is subpixel, high confidence
float motionPenalty = flowMagnitude / subpixelThreshold;
float lengthConfidence = rcp(motionPenalty * 0.07 + 1.0);
float photometricConfidence = exp(-lumaError * 8.0 * lengthConfidence);
//current frame final confidence
float currentConf = lengthConfidence * photometricConfidence;
//temporal filter
float historyConf = tex2D(sPrevConfidence, prevUV).r;
float alpha = (currentConf < historyConf - 0.05) ? 0.5 : 0.1; //drop fast (kill speckles promptly), regain slowly (stay stable)
return lerp(historyConf, currentConf, alpha);
}
void PS_StoreFlow(float4 pos : SV_Position, float2 uv : TEXCOORD, out float2 flow : SV_Target0, out float confidence : SV_Target1)
{
flow = tex2D(sFlow, uv).xy;
confidence = tex2D(sConfidence, uv).r;
}
float PS_StoreLuma(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return tex2D(sCurrLuma, uv).r;
}
#if DEBUG_FLOW
float4 PS_Debug(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return float4(MotionToColor(tex2D(sFlow, uv).xy), 1);
}
#endif
/*----------------.
| :: TECHNIQUE :: |
'----------------*/
technique Lumenite_QuantMotion <
ui_label = "LUMENITE: QuantMotion";
ui_tooltip = "Superfast motion vectors for ReShade.";
>
{
//optical flow
pass { VertexShader = PostProcessVS; PixelShader = PS_PackFeatures; RenderTarget = tCurrLuma; }
pass { VertexShader = PostProcessVS; PixelShader = PS_ComputeFlow128; RenderTarget = tFlow128; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow64; RenderTarget = tFlow64A; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass64; RenderTarget = tFlow64B; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow32; RenderTarget = tFlow32A; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass32; RenderTarget = tFlow32B; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow16; RenderTarget = tFlow16A; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass16; RenderTarget = tFlow16B; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow8; RenderTarget = tFlow8; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass8; RenderTarget = tFlow; }
pass { VertexShader = PostProcessVS; PixelShader = PS_Confidence; RenderTarget = tConfidence; }
pass { VertexShader = PostProcessVS; PixelShader = PS_ATrousPassA; RenderTarget = tFlow8; }
pass { VertexShader = PostProcessVS; PixelShader = PS_ATrousPassB; RenderTarget = tFlow; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StoreFlow; RenderTarget0 = tPrevFrameFlow; RenderTarget1 = tPrevConfidence; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StoreLuma; RenderTarget = tPrevLuma; }
//debug views
#if DEBUG_FLOW
pass { VertexShader = PostProcessVS; PixelShader = PS_Debug; }
#endif
}
}
@@ -0,0 +1,300 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_RTAO.fx
Version : 2026.05.30
Author : Afzaal (Kaidō)
Description: Ray Traced Ambient Occlusion (Screen Space).
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#define FOV 60.0
#define NEAR_PLANE 0.01
#define INITIAL_STEP_SCALE 0.9
#define STEP_GROWTH_FACTOR 1.2
#define AO_MAX_MARCH_STEPS 15
/*--------------.
| :: HEADERS :: |
'--------------*/
#include "ReShade.fxh"
#include "./include/lumenite_Projections.fxh"
#include "./include/lumenite_Helpers.fxh"
#include "./include/lumenite_ColorManagement.fxh"
/*---------------.
| :: UNIFORMS :: |
'---------------*/
uniform bool DEBUG_VIEW <
ui_label = "Show AO Mask";
ui_tooltip = "Debug view for the AO. Shows raw AO.";
ui_category = "Ambient Occlusion";
> = 0;
uniform float DEPTH_BOUNDARY <
ui_type = "slider";
ui_min = 0.001; ui_max = 0.999; ui_step = 0.001;
ui_label = "AO Range";
ui_tooltip = "The Z+ range/depth in which the effect is applied.";
ui_category = "Ambient Occlusion";
hidden = false;
> = 0.6;
uniform float DEPTH_FADE_START <
ui_type = "slider";
ui_min = 0.1; ui_max = 1.0; ui_step = 0.01;
ui_label = "Z+ Fade Start (%)";
ui_tooltip = "Z+ fraction where effect starts fading out (relative to AO Range)";
ui_category = "Ambient Occlusion";
hidden = true;
> = 0.75;
uniform float AO_INTENSITY <
ui_type = "drag";
ui_min = 0.0; ui_max = 1.0;
ui_label = "AO Strength";
ui_tooltip = "Controls the intensity of the ambient occlusion effect.";
ui_category = "Ambient Occlusion";
> = 1.0;
//deprecated
// uniform int USER_GUIDE <
// ui_type = "radio";
// ui_category = "";
// ui_label = " ";
// ui_text = "RESOLUTION_SCALING:\n0: Renders AO at full-resolution.\n1: Renders AO at half-resolution.";
// >;
/*--------------.
| :: IMPORTS :: |
'--------------*/
namespace Kernel {
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sFlow { Texture = tFlow; MagFilter = POINT; MinFilter = POINT; };
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
sampler2D sConfidence { Texture = tConfidence; };
texture tNormals { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; MipLevels = 4; };
sampler sNormals { Texture = tNormals; };
texture2D tDepth { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 4; };
sampler2D sDepth { Texture = tDepth; };
}
namespace LumeniteRTAO {
/*---------------------.
| :: RENDER TARGETS :: |
'---------------------*/
texture tAOTrace { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = R16F; };
sampler sAOTrace { Texture = tAOTrace; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
texture tAO1 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
sampler sAO1 { Texture = tAO1; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
sampler sAO1Linear { Texture = tAO1; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
texture tPrevAO { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
sampler sPrevAO { Texture = tPrevAO; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
/*--------------.
| :: HELPERS :: |
'--------------*/
void BuildOrthonormalBasis(float3 n, out float3 b1, out float3 b2)
{
if (n.z < -0.9999999) {
b1 = float3(0.0, -1.0, 0.0);
b2 = float3(-1.0, 0.0, 0.0);
} else {
float a = rcp(1.0 + n.z);
float b = -n.x * n.y * a;
b1 = float3(mad(-n.x * n.x, a, 1.0), b, -n.x);
b2 = float3(b, mad(-n.y * n.y, a, 1.0), -n.y);
}
}
float3 GenerateHemisphereDirection(float3 normal, float2 rand, float3 tangent, float3 bitangent)
{
float phi = rand.x * 6.28318530718; //2.0*PI as constant
float sinPhi, cosPhi;
sincos(phi, sinPhi, cosPhi);
float cosTheta = sqrt(1.0 - rand.y);
float sinTheta = sqrt(rand.y);
float3 result = normal * cosTheta;
result = mad(bitangent, sinTheta * sinPhi, result);
result = mad(tangent, sinTheta * cosPhi, result);
return result;
}
float CalculateDepthFade(float depth)
{
float fadeStartDepth = DEPTH_BOUNDARY * DEPTH_FADE_START;
float fadeRange = DEPTH_BOUNDARY - fadeStartDepth;
return 1.0 - saturate((depth - fadeStartDepth) / fadeRange);
}
float2 ATrousFilter(sampler SourceSampler, float2 uv, uint dilation)
{
float4 gbuffer = tex2D(Kernel::sNormals, uv);
if (gbuffer.a == 0 || gbuffer.a >= DEPTH_BOUNDARY) return float2(1.0, 0.0);
float confidence = tex2Dlod(Kernel::sConfidence, float4(uv, 0, 0)).r;
dilation += uint(round((1.0 - confidence))); //scale filter radius with motion
float2 centerData = tex2Dlod(SourceSampler, float4(uv, 0, 0)).rg;
float variance = max(0.0, centerData.g - (centerData.r * centerData.r)); //Moment - AO^2
variance = max(variance, 0.0001);
float2 sum = centerData;
float totalWeight = 1.0;
for (int y = -1; y <= 1; y++) for (int x = -1; x <= 1; x++) {
if (x == 0 && y == 0) continue;
float2 sampleUV = uv + float2(x, y) * dilation * (BUFFER_PIXEL_SIZE * 2.0); //don't forget the x2.0 to properly step half-res grid!
float2 sampleData = tex2Dlod(SourceSampler, float4(sampleUV, 0, 0)).rg;
float4 sampleGeo = tex2Dlod(Kernel::sNormals, float4(sampleUV, 0, 0));
float depthWeight = exp(-abs(gbuffer.a - sampleGeo.a) / (gbuffer.a * 0.02 + 0.001));
float normalWeight = pow(saturate(dot(gbuffer.rgb, sampleGeo.rgb)), 50.0);
float aoDiff = centerData.r - sampleData.r;
float aoWeight = exp(-(aoDiff * aoDiff) / (variance + 0.0001));
float weight = depthWeight * normalWeight * aoWeight;
sum += sampleData * weight;
totalWeight += weight;
}
return sum / (totalWeight + EPSILON);
}
/*--------------.
| :: SHADERS :: |
'--------------*/
float PS_TraceAO(VSOUT input) : SV_Target
{
//deprecated
// if (CHECKERBOARD_RENDERING) {
// #if RESOLUTION_SCALING
// if(CheckerboardSkip(uint2(input.vpos.xy), 2.0)) discard;
// #else
// if(CheckerboardSkip(uint2(input.vpos.xy), 1.0)) discard;
// #endif
// }
float4 gbuffer = tex2D(Kernel::sNormals, input.uv);
float3 normal = gbuffer.rgb;
float depth = gbuffer.a;
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
float3 startPos = UVToViewSpace(input.uv, depth, input);
float3 tangent, bitangent;
BuildOrthonormalBasis(normal, tangent, bitangent);
float2 noise = GetStratifiedNoise(input.vpos.xy);
float3 rayDir = GenerateHemisphereDirection(normal, noise, tangent, bitangent);
float invDepth = rcp(depth);
float totalRayLength = 0.02 * depth;
float initialStepScale = INITIAL_STEP_SCALE * rcp((float)AO_MAX_MARCH_STEPS);
float stepSize = totalRayLength * initialStepScale;
float3 rayPos = mad(rayDir, stepSize * 0.5, startPos);
rayPos += normal * depth * 0.0005; //push ray slightly OUTWARD along the normal; clears staircase artifacts
float occlusion = 0.0;
[loop]
for (int step = 0; step < AO_MAX_MARCH_STEPS; step++) {
float2 sampleUV = ViewSpaceToUV(rayPos, input);
float sceneDepth = GetDepth(sampleUV);
float depthDiff = rayPos.z - sceneDepth;
[branch]
if (depthDiff > 0.0 && depthDiff < rayPos.z) {
float3 scenePos = UVToViewSpace(sampleUV, sceneDepth, input);
float hitDistance = length(scenePos - startPos);
float normalizedDistance = hitDistance * invDepth;
occlusion = exp(-normalizedDistance * 15.0);
break;
}
stepSize *= STEP_GROWTH_FACTOR;
rayPos = mad(rayDir, stepSize, rayPos);
}
float aoFactor = 1.0 - saturate(occlusion * AO_INTENSITY);
return aoFactor;
}
float2 PS_TemporalFilter(VSOUT input) : SV_Target
{
float depth = tex2D(Kernel::sDepth, input.uv).r;
//overwrite noise at boundary with clean White, prevents gaps
if (depth >= DEPTH_BOUNDARY) return float2(1.0, 1.0); //1.0 AO, 1.0 Moment
if (depth == 0) discard;
float ao = tex2D(sAOTrace, input.uv).r;
ao = lerp(1.0, ao, CalculateDepthFade(depth));
float moment = ao * ao;
float2 flow = tex2D(Kernel::sFlow, input.uv).xy;
float confidence = tex2D(Kernel::sConfidence, input.uv).x;
confidence = saturate(confidence + log2(2.0 - confidence) * 0.6); //boost confidence
float2 rawHistory = tex2D(sPrevAO, input.uv + flow).rg; //history stores "1.0 - AO". 0.0 (Black Texture) -> Reads as 1.0 (White)
float prevAO = 1.0 - rawHistory.r;
float prevMoment = 1.0 - rawHistory.g;
float alpha = confidence * 0.98;
ao = lerp(ao, prevAO, alpha);
moment = lerp(moment, prevMoment, alpha);
//max(..., 0.001) to ensure we NEVER write exactly 0.0 again
//this tells the next frame "I contain data"
return float2(max(ao, 0.001), max(moment, 0.001));
}
float2 PS_StoreAO(VSOUT input) : SV_Target
{
//must prevent history collision here
//if we store exactly 0.0 (means White), the next frame's blend pass thinks
//history is empty and resets it, causing shimmer
//so clamp to 0.0001 so the system knows "This is valid history data"
float2 data = tex2D(sAO1, input.uv).rg;
return float2(max(1.0 - data.r, 0.0001), max(1.0 - data.g, 0.0001)); //store inverted
}
float4 PS_ToDisplay(VSOUT input) : SV_Target
{
float depth = tex2D(Kernel::sDepth, input.uv).r;
float ao = ATrousFilter(sAO1Linear, input.uv, 2).r; //stable AO mask (fades to 1.0)
if (DEBUG_VIEW) {
#if BUFFER_COLOR_SPACE > 1
return float4(ToOutputColorspace(ao.xxx, true), 1.0);
#else
return float4(ao.xxx, 1.0);
#endif
}
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
float3 base = GetLinearColor(input.uv, true);
base *= ao;
return float4(ToOutputColorspace(base, true), 1.0);
}
/*----------------.
| :: TECHNIQUE :: |
'----------------*/
technique Lumenite_RTAO <
ui_label = "LUMENITE: RTAO";
ui_tooltip = "Ray Traced Ambient Occlusion (Screen Space).";
>
{
pass { VertexShader = VS; PixelShader = PS_TraceAO; RenderTarget = tAOTrace; }
pass { VertexShader = VS; PixelShader = PS_TemporalFilter; RenderTarget = tAO1; }
pass { VertexShader = VS; PixelShader = PS_StoreAO; RenderTarget = tPrevAO; }
pass { VertexShader = VS; PixelShader = PS_ToDisplay; }
}
}
@@ -0,0 +1,356 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_SSSR.fx
Version : 2026.05.30
Author : Afzaal (Kaidō)
Description: Stochastic Screen Space Reflections.
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#define FOV 70.0
#define NEAR_PLANE 0.5
#define RAY_LENGTH_SCALE 9.0
#define RAY_ORIGIN_BIAS -0.0004
/*--------------.
| :: HEADERS :: |
'--------------*/
#include "ReShade.fxh"
#include "./include/lumenite_Projections.fxh"
#include "./include/lumenite_Helpers.fxh"
#include "./include/lumenite_ColorManagement.fxh"
/*---------------.
| :: UNIFORMS :: |
'---------------*/
uniform float DEPTH_BOUNDARY <
ui_type = "slider";
ui_min = 0.001; ui_max = 0.999; ui_step = 0.001;
ui_label = "SSSR Range";
ui_tooltip = "The Z+ range/depth in which the effect is applied.";
ui_category = "";
hidden = false;
> = 0.85;
uniform float DEPTH_FADE_START <
ui_type = "slider";
ui_min = 0.1; ui_max = 1.0; ui_step = 0.01;
ui_label = "Z+ Fade Start (%)";
ui_tooltip = "Z+ fraction where effect starts fading out (relative to Z+ boundary)";
ui_category = "";
hidden = true;
> = 0.75;
uniform int MAX_STEPS <
ui_type = "drag";
ui_min = 1; ui_max = 32; ui_step = 1;
ui_label = "Ray Resolution";
ui_category = "";
ui_tooltip = "";
> = 32;
uniform int BINARY_SEARCH_STEPS <
ui_type = "drag";
ui_min = 1; ui_max = 8; ui_step = 1;
ui_label = "Hit Refinement";
ui_category = "";
ui_tooltip = "";
> = 4;
uniform float F0 <
ui_type = "drag";
ui_min = 0.0; ui_max = 2.0; ui_step = 0.001;
ui_label = "Base Reflectivity (F0)";
ui_category = "";
ui_tooltip = "";
> = 1.0;
uniform float ROUGHNESS <
ui_type = "drag";
ui_min = 0.0; ui_max = 0.3; ui_step = 0.001;
ui_label = "Roughness";
ui_category = "";
ui_tooltip = "";
> = 0.1;
uniform float BUMP_SCALE <
ui_type = "drag";
ui_min = 0.0; ui_max = 1.0; ui_step = 0.001;
ui_label = "Bump Detail";
ui_tooltip = "Scale of the extracted bump details. Lower = finer bumps.";
> = 0.5;
uniform float TAIL_FEATHERING <
ui_type = "drag";
ui_min = 0.0; ui_max = 5.0; ui_step = 0.001;
ui_label = "Tail Feathering";
ui_category = "";
ui_tooltip = "";
> = 0.0;
/*--------------.
| :: IMPORTS :: |
'--------------*/
namespace Kernel {
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sFlow { Texture = tFlow; MagFilter = POINT; MinFilter = POINT; };
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
sampler2D sConfidence { Texture = tConfidence; };
texture tNormals { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; MipLevels = 4; };
sampler sNormals { Texture = tNormals; };
texture2D tDepth { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 4; };
sampler2D sDepth { Texture = tDepth; };
}
namespace LumeniteSSSR {
/*---------------------.
| :: RENDER TARGETS :: |
'---------------------*/
texture tSpec1 { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
sampler sSpec1 { Texture = tSpec1; AddressU = CLAMP; AddressV = CLAMP; };
texture tSpec2 { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
sampler sSpec2 { Texture = tSpec2; AddressU = CLAMP; AddressV = CLAMP; };
texture tPrevSpec { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
sampler sPrevSpec { Texture = tPrevSpec; AddressU = CLAMP; AddressV = CLAMP; };
/*--------------.
| :: HELPERS :: |
'--------------*/
float CalculateDepthFade(float depth)
{
float fadeStartDepth = DEPTH_BOUNDARY * DEPTH_FADE_START;
float fadeRange = DEPTH_BOUNDARY - fadeStartDepth;
return 1.0 - saturate((depth - fadeStartDepth) / fadeRange);
}
float3 CalculateSmoothNormal(float2 uv, float4 gbuffer, int dilation, sampler SrcSampler)
{
float3 normal = gbuffer.rgb;
float depth = gbuffer.a;
float3 normalSum = normal;
float weightSum = 1.0;
[unroll] for(int dy = -4; dy <= 4; dy++) for (int dx = -4; dx <= 4; dx++) {
float2 sampleUV = uv + float2(dx, dy) * ReShade::PixelSize * dilation;
float4 neighborData = tex2Dlod(SrcSampler, float4(sampleUV, 0, 0));
float depthDiff = abs(neighborData.a - depth);
float normalDot = max(dot(normal, neighborData.rgb), 0.0);
float weight = exp(-depthDiff * 300.0) * pow(normalDot, 20.0);
normalSum += neighborData.rgb * weight;
weightSum += weight;
}
return normalize(normalSum / weightSum);
}
float3 GetBackBuffer(float2 uv)
{
return tex2Dlod(ReShade::BackBuffer, float4(uv,0,0)).rgb;
}
float3 CalculateBumpyNormal(float2 uv, float3 geoNormal)
{
float2 texelSize = BUFFER_PIXEL_SIZE * BUMP_SCALE;
float3 lumaWeights = float3(0.299, 0.587, 0.114);
//use gamma space intentionally
float lumaCenter = dot(GetBackBuffer(uv), lumaWeights);
float lumaRight = dot(GetBackBuffer(uv + float2(texelSize.x, 0.0)), lumaWeights);
float lumaBottom = dot(GetBackBuffer(uv + float2(0.0, texelSize.y)), lumaWeights);
//luma gradients
float dx = (lumaRight - lumaCenter) * 2.5;
float dy = (lumaBottom - lumaCenter) * 2.5;
//orthogonal tangent basis around macro geometry normal
float3 up = abs(geoNormal.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0);
float3 tangent = normalize(cross(up, geoNormal));
float3 bitangent = cross(geoNormal, tangent);
//2D bump gradient to 3D tangent-space normal
float3 bumpVec = normalize(float3(-dx, -dy, 1.0));
return normalize(tangent * bumpVec.x + bitangent * bumpVec.y + geoNormal * bumpVec.z);
}
/*--------------.
| :: SHADERS :: |
'--------------*/
float4 PS_TraceSpecular(VSOUT input) : SV_Target
{
float4 gbuffer = tex2D(Kernel::sNormals, input.uv);
float3 normal = gbuffer.rgb;
float depth = gbuffer.a;
if (depth <= 0.0 || depth > DEPTH_BOUNDARY) return float4(0, 0, 0, 1);
//process normals
normal = CalculateSmoothNormal(input.uv, gbuffer, 3, Kernel::sNormals);
if (BUMP_SCALE > 0.0)
normal = CalculateBumpyNormal(input.uv, normal);
float3 StartPos = UVToViewSpace(input.uv, depth, input);
float3 viewDir = normalize(-StartPos);
float dynamicRayLengthNormalized = min(RAY_LENGTH_SCALE * depth, 1.0-depth);
float stepSize = dynamicRayLengthNormalized / float(MAX_STEPS);
float3 mirrorDir = reflect(-viewDir, normal);
if (dot(mirrorDir, mirrorDir) < 0.001) { //mirror reflection validation chck
return float4(0, 0, 0, 1);
}
float2 noise = GetStratifiedNoise(input.vpos.xy);
float3 jitterN = normalize(normal + float3((noise * 2.0 - 1.0) * ROUGHNESS * 0.2, 0.0));
float3 rayDir = reflect(-viewDir, jitterN);
if (dot(rayDir, normal) < 0.0) rayDir = mirrorDir; //prevent jitter from pushing ray inside the geometry
float biasedOffset = RAY_ORIGIN_BIAS + (depth * RAY_ORIGIN_BIAS * 0.01); //intentional -ve origin bias
float3 biasedStartPos = StartPos - (normal * biasedOffset); //deliberately pushes the ray slightly into the floor, makes it immediately collide with the floor's depth, killing "joined reflections"
float t = stepSize * noise.x;
float3 spec = float3(0.0, 0.0, 0.0);
bool hitFound = false;
float distanceRatio = 0.0;
float2 finalUV = 0.0;
for (int i = 0; i < MAX_STEPS; i++)
{
if (t >= dynamicRayLengthNormalized)
break;
float3 currentPos = biasedStartPos + rayDir * t;
float2 hitUV = ViewSpaceToUV(currentPos, input);
if (IsOOB(hitUV))
break;
float sceneDepth = tex2Dlod(Kernel::sDepth, float4(hitUV, 0, 0)).r;
if (sceneDepth > DEPTH_BOUNDARY) {
t += stepSize;
continue;
}
float3 scenePos = UVToViewSpace(hitUV, sceneDepth, input);
float depthDiff = currentPos.z - scenePos.z;
if (depthDiff > 0.0) //passed behind surface
{
float gateThreshold = dynamicRayLengthNormalized * 0.1; //initially, a broad thickness check
float extraThickness = (t > gateThreshold) ? 0.01 : 0.0; //if t is past the threshold, add extra thickness
float dynamicThickness = (currentPos.z * 0.05) + extraThickness;
if (depthDiff < dynamicThickness)
{
//binary search refinement
float binarySearchT = t;
float binarySearchStep = stepSize;
float3 binarySearchCurrentPos = currentPos;
float2 binarySearchUV = hitUV;
float3 binarySearchScenePos = scenePos;
for (int j = 0; j < BINARY_SEARCH_STEPS; j++) {
binarySearchStep *= 0.5;
binarySearchT += (binarySearchCurrentPos.z > binarySearchScenePos.z) ? -binarySearchStep : binarySearchStep; //move backwards if behind the surface, otherwise forwards
binarySearchCurrentPos = biasedStartPos + rayDir * binarySearchT;
binarySearchUV = ViewSpaceToUV(binarySearchCurrentPos, input);
float binarySearchSceneDepth = tex2Dlod(Kernel::sDepth, float4(binarySearchUV, 0, 0)).r;
binarySearchScenePos = UVToViewSpace(binarySearchUV, binarySearchSceneDepth, input);
}
float finalDepthDiff = binarySearchCurrentPos.z - binarySearchScenePos.z;
if (abs(finalDepthDiff) < (binarySearchCurrentPos.z * 0.01 + 0.01)) { //tighter thickness tolerance on the final refined hit to discard empty space behind thin grass
hitFound = true;
distanceRatio = binarySearchT / dynamicRayLengthNormalized;
finalUV = binarySearchUV;
break;
}
}
}
t += stepSize * noise.y;
}
if (hitFound) {
float3 hitColor = GetLinearColor(finalUV, false);
float2 edgeFadeUV = abs(finalUV * 2.0 - 1.0);
float edgeFade = saturate(1.0 - max(edgeFadeUV.x, edgeFadeUV.y));
edgeFade = smoothstep(0.0, 0.05, edgeFade);
float maxDistFade = pow(saturate(1.0 - distanceRatio), TAIL_FEATHERING + EPSILON);
spec = hitColor * maxDistFade * edgeFade;
}
return float4(spec, 1.0);
}
float4 PS_TemporalBlend(VSOUT input) : SV_Target
{
float depth = tex2D(Kernel::sDepth, input.uv).r;
if (depth >= DEPTH_BOUNDARY) return float4(0, 0, 0, 0);
float3 spec = tex2D(sSpec1, input.uv).rgb;
float2 flow = tex2D(Kernel::sFlow, input.uv).xy;
float confidence = tex2D(Kernel::sConfidence, input.uv).x;
confidence = saturate(confidence + log2(2.0 - confidence) * 0.5);
float3 prevSpec = tex2D(sPrevSpec, input.uv + flow).rgb;
float historyMax = max(prevSpec.r, max(prevSpec.g, prevSpec.b));
float blendWeight = (historyMax < 0.00001) ? 0.0 : (confidence * 0.98);
float3 blended = lerp(spec, prevSpec, blendWeight);
return float4(blended, 1.0);
}
float4 PS_StoreHistory(VSOUT input) : SV_Target
{
float depth = tex2D(Kernel::sDepth, input.uv).r;
if (depth >= DEPTH_BOUNDARY) return float4(0, 0, 0, 0); //if past boundary, store 0.0 to 'clear' history for next frame
return float4(max(tex2D(sSpec2, input.uv).rgb, 0.0001), 1.0); //clamp to 0.0001 so it knows 'valid hist data', prevents shimmer at depth boundary edges
}
float4 PS_ToDisplay(VSOUT input) : SV_Target
{
float3 base = GetLinearColor(input.uv, false);
float4 gbuffer = tex2D(Kernel::sNormals, input.uv);
float3 normal = gbuffer.rgb;
float depth = gbuffer.a;
float3 surfacePos = UVToViewSpace(input.uv, depth, input);
float depthFade = CalculateDepthFade(depth);
float3 viewDir = normalize(-surfacePos);
float NdotV = saturate(dot(normal, viewDir));
float fresnel = F0 + (1.0 - F0) * pow(1.0 - NdotV, 5.0); //schlick's approximation
float3 spec = tex2D(sSpec2, input.uv).rgb;
spec *= depthFade;
spec *= fresnel;
float reflectionMask = saturate(length(spec) + fresnel * 0.5);
float3 conservationBase = base * (1.0 - reflectionMask * 0.7 * depthFade);
return float4(ToOutputColorspace(conservationBase + spec, false), 1.0);
}
/*----------------.
| :: TECHNIQUE :: |
'----------------*/
technique LUMENITE_SSSR <
ui_label = "LUMENITE: SSSR";
ui_tooltip = "Stochastic Screen Space Reflections.";
>
{
pass { VertexShader = VS; PixelShader = PS_TraceSpecular; RenderTarget = tSpec1; }
pass { VertexShader = VS; PixelShader = PS_TemporalBlend; RenderTarget = tSpec2; }
pass { VertexShader = VS; PixelShader = PS_ToDisplay; }
pass { VertexShader = VS; PixelShader = PS_StoreHistory; RenderTarget = tPrevSpec; }
}
}
@@ -0,0 +1,525 @@
/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_TRAA.fx
Version : 2026.07.28
Author : Afzaal (Kaidō)
Description: Temporal Reprojection Anti-Aliasing
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#ifndef ENABLE_DLAA
#define ENABLE_DLAA 1
#endif
/*--------------.
| :: HEADERS :: |
'--------------*/
#include "ReShade.fxh"
#include "./include/lumenite_ColorManagement.fxh"
#include "./include/lumenite_Helpers.fxh"
/*---------------.
| :: UNIFORMS :: |
'---------------*/
uniform int SHOW_STATUS <
ui_type = "radio";
ui_label = " ";
#if ENABLE_DLAA
ui_text = "DLAA Prepass: Enabled.";
#else
ui_text = "DLAA Prepass: Disabled.";
#endif
>;
#if ENABLE_DLAA
uniform bool DEBUG_EDGES <
ui_label = "Show Edge Mask";
ui_tooltip = "Paints the detected edge mask over black background.";
> = false;
uniform int EDGE_MODE <
ui_type = "combo";
ui_label = "Edge Detection";
ui_items = "Luma\0Geometric\0";
ui_tooltip = "Luma: shading and texture edges as well; the classic DLAA mask.\n"
"Geometric: silhouettes only, ignores flat UI.";
> = 0;
#endif
uniform float HISTORY_BLEND <
ui_type = "slider";
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
ui_label = "Temporal Blend";
hidden = false;
> = 0.9;
uniform float SHARP_STRENGTH <
ui_type = "drag";
ui_min = 0; ui_max = 2.0; ui_step = 0.05;
ui_label = "Adaptive Sharpen";
hidden = false;
> = 1.0;
uniform float MAX_SHARP_DIFF <
ui_type = "drag";
ui_min = 0.05; ui_max = 0.25; ui_step = 0.01;
ui_label = "Sharpen Guard";
ui_tooltip = "Higher = more aggressive sharpening allowed.\nLower = tighter anti-ringing clamp.";
hidden = true;
> = 0.1;
uniform float HFI_INTENSITY <
ui_type = "drag";
ui_min = 0.0; ui_max = 0.1; ui_step = 0.001;
ui_label = "High-Frequency Injection";
ui_tooltip = "Re-injects detail lost during Temporal blend.";
> = 0.01;
/*--------------.
| :: IMPORTS :: |
'--------------*/
namespace Kernel {
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sFlow { Texture = tFlow; MagFilter = POINT; MinFilter = POINT; };
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
sampler2D sConfidence { Texture = tConfidence; };
texture tNormals { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; MipLevels = 4; };
sampler sNormals { Texture = tNormals; };
texture2D tDepth { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 4; };
sampler2D sDepth { Texture = tDepth; };
}
namespace LumeniteTRAA {
/*---------------------.
| :: RENDER TARGETS :: |
'---------------------*/
#if ENABLE_DLAA
texture tDLAAPreFilter { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
sampler sDLAAPreFilter { Texture = tDLAAPreFilter; MinFilter = LINEAR; MagFilter = LINEAR; MipFilter = LINEAR; };
texture tDLAAPrePass { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
sampler sDLAAPrePass { Texture = tDLAAPrePass; MagFilter = POINT; MinFilter = POINT; };
#endif
texture tCurrHistory { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
sampler sCurrHistory { Texture = tCurrHistory; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
texture tPrevHistory { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
sampler sPrevHistory { Texture = tPrevHistory; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
/*--------------.
| :: HELPERS :: |
'--------------*/
//5-Tap 2D Catmull-Rom Filter (Brian Karis / Unreal Engine)
float3 SampleCatmullRom5Tap(sampler tex, float2 uv) {
float2 pos = uv * float2(BUFFER_WIDTH, BUFFER_HEIGHT);
float2 centerPos = floor(pos - 0.5) + 0.5;
float2 f = pos - centerPos;
//1D Catmull-Rom weights
float2 f2 = f * f;
float2 f3 = f2 * f;
float2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
float2 w1 = 1.0 + f2 * (-2.5 + 1.5 * f);
float2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
float2 w3 = f2 * (-0.5 + 0.5 * f);
//group the inner positive lobes (w1, w2) for bilinear hardware
float2 w12 = w1 + w2;
float2 offset12 = w2 / (w12 + 0.00001); // Prevent div by zero
//5-tap texture fetch in a cross pattern
float2 texCoord0 = (centerPos - float2(1.0, 0.0) + float2(0.0, offset12.y)) * BUFFER_PIXEL_SIZE; //left
float2 texCoord1 = (centerPos + float2(2.0, 0.0) + float2(0.0, offset12.y)) * BUFFER_PIXEL_SIZE; //right
float2 texCoord2 = (centerPos + float2(offset12.x, -1.0)) * BUFFER_PIXEL_SIZE; //top
float2 texCoord3 = (centerPos + float2(offset12.x, 2.0)) * BUFFER_PIXEL_SIZE; //bottom
float2 texCoord4 = (centerPos + offset12) * BUFFER_PIXEL_SIZE; //center
//final 2D weights for the 5 taps
float weight0 = w0.x * w12.y; //left
float weight1 = w3.x * w12.y; //right
float weight2 = w12.x * w0.y; //top
float weight3 = w12.x * w3.y; //bottom
float weight4 = w12.x * w12.y; //center
//normalize weights because we dropped the 4 corner taps (sum would be slightly < 1.0)
float weightSum = weight0 + weight1 + weight2 + weight3 + weight4;
weightSum = max(weightSum, 0.0001);
weight0 /= weightSum;
weight1 /= weightSum;
weight2 /= weightSum;
weight3 /= weightSum;
weight4 /= weightSum;
//sample w. hw bilinear filtering (offsets take care of the interpolation)
float3 color0 = tex2Dlod(tex, float4(texCoord0, 0, 0)).rgb;
float3 color1 = tex2Dlod(tex, float4(texCoord1, 0, 0)).rgb;
float3 color2 = tex2Dlod(tex, float4(texCoord2, 0, 0)).rgb;
float3 color3 = tex2Dlod(tex, float4(texCoord3, 0, 0)).rgb;
float3 color4 = tex2Dlod(tex, float4(texCoord4, 0, 0)).rgb;
float3 result = color0 * weight0 + color1 * weight1 + color2 * weight2 + color3 * weight3 + color4 * weight4;
//anti-ringing clamp
float3 minColor = min(min(min(color0, color1), min(color2, color3)), color4);
float3 maxColor = max(max(max(color0, color1), max(color2, color3)), color4);
return clamp(result, minColor, maxColor);
}
//if ray misses the bounding box (tEnter > tExit), returning t = 1.0 is the safe fallback
float3 YCoCgLineBoxClip(float3 historyYCoCg, float3 meanYCoCg, float3 colorMin, float3 colorMax) {
float3 rayDir = meanYCoCg - historyYCoCg;
rayDir = abs(rayDir) < 0.0001 ? float3(0.0001, 0.0001, 0.0001) : rayDir; //avoid div by zero
//compute t for intersection with min and max bounds per-channel
float3 tMin = (colorMin - historyYCoCg) / rayDir;
float3 tMax = (colorMax - historyYCoCg) / rayDir;
float3 t1 = min(tMin, tMax);
float3 t2 = max(tMin, tMax);
tMin = t1;
tMax = t2;
//entry and exit points for ray-box intersection
float tEnter = max(max(tMin.x, tMin.y), tMin.z);
float tExit = min(min(tMax.x, tMax.y), tMax.z);
//if ray misses the box; fallback to 1.0 (mean) to discard history
//else, clamp the entry point to [0, 1] to clip exactly at the box edge
float t = tEnter > tExit ? 1.0 : clamp(tEnter, 0.0, 1.0);
return historyYCoCg + rayDir * t;
}
/*--------------.
| :: SHADERS :: |
'--------------*/
#if ENABLE_DLAA
float4 PS_DLAAPreFilter(float4 vpos : SV_Position, float2 uv : TexCoord) : SV_Target {
float3 center = sqrt(max(GetLinearColor(uv, false), 0.0));
float edge;
if (!EDGE_MODE) {
//luma edge in perceptual space; the extra sqrt fattens the mask
float2 px = float2(BUFFER_PIXEL_SIZE.x, 0.0);
float2 py = float2(0.0, BUFFER_PIXEL_SIZE.y);
float3 left = sqrt(max(GetLinearColor(uv - px, false), 0.0));
float3 right = sqrt(max(GetLinearColor(uv + px, false), 0.0));
float3 top = sqrt(max(GetLinearColor(uv - py, false), 0.0));
float3 bottom = sqrt(max(GetLinearColor(uv + py, false), 0.0));
float3 edges = 4.0 * abs((left + right + top + bottom) - 4.0 * center);
edge = GetLuminance(sqrt(max(edges, 0.0))); //recursive gamma compression: do another sqrt(), fattens the edge mask
} else {
float4 s0 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2(-1,-1), 0, 0));
float4 s1 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2( 0,-1), 0, 0));
float4 s2 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2( 1,-1), 0, 0));
float4 s3 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2(-1, 0), 0, 0));
float4 s4 = tex2Dlod(Kernel::sNormals, float4(uv, 0, 0));
float4 s5 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2( 1, 0), 0, 0));
float4 s6 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2(-1, 1), 0, 0));
float4 s7 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2( 0, 1), 0, 0));
float4 s8 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2( 1, 1), 0, 0));
//3x3 depth Sobel
float dC = s4.a;
float sxD = -s0.a + s2.a - 2.0 * s3.a + 2.0 * s5.a - s6.a + s8.a;
float syD = -s0.a - 2.0 * s1.a - s2.a + s6.a + 2.0 * s7.a + s8.a;
float depthEdge = saturate(sqrt(sxD * sxD + syD * syD) / (dC + 1e-5));
//3x3 normal Sobel
float3 sxN = -s0.xyz + s2.xyz - 2.0 * s3.xyz + 2.0 * s5.xyz - s6.xyz + s8.xyz;
float3 syN = -s0.xyz - 2.0 * s1.xyz - s2.xyz + s6.xyz + 2.0 * s7.xyz + s8.xyz;
float normalEdge = saturate(length(sxN) + length(syN));
edge = max(depthEdge, normalEdge);
}
return float4(center, edge);
}
#define SAMPLE_G(uv, dx, dy) tex2Dlod(sDLAAPreFilter, float4((uv) + float2(dx, dy) * BUFFER_PIXEL_SIZE, 0, 0))
float4 PS_DLAA(float4 vpos : SV_Position, float2 uv : TexCoord) : SV_Target {
float4 center = SAMPLE_G(uv, 0.0, 0.0);
float4 left01 = SAMPLE_G(uv, -1.5, 0.0);
float4 right01 = SAMPLE_G(uv, 1.5, 0.0);
float4 top01 = SAMPLE_G(uv, 0.0, -1.5);
float4 bottom01 = SAMPLE_G(uv, 0.0, 1.5);
//flat-region early exit
float localEdges = max(center.a, max(max(left01.a, right01.a), max(top01.a, bottom01.a)));
if (localEdges < 0.05) return float4(center.xyz * center.xyz, 1.0);
float4 wH = 2.0 * (left01 + right01);
float4 wV = 2.0 * (top01 + bottom01);
float4 edgeH = abs(wH - 4.0 * center) / 4.0;
float4 edgeV = abs(wV - 4.0 * center) / 4.0;
float4 blurredH = (wH + 2.0 * center) / 6.0;
float4 blurredV = (wV + 2.0 * center) / 6.0;
float edgeHLum = GetLuminance(edgeH.xyz);
float edgeVLum = GetLuminance(edgeV.xyz);
float blurredHLum = GetLuminance(blurredH.xyz);
float blurredVLum = GetLuminance(blurredV.xyz);
const float kLambda = 3.0;
const float kEpsilon = 0.1;
float edgeMaskH = saturate((kLambda * edgeHLum - kEpsilon) / (blurredVLum + 1e-5));
float edgeMaskV = saturate((kLambda * edgeVLum - kEpsilon) / (blurredHLum + 1e-5));
float gate = (!EDGE_MODE) ? 1.0 : center.a;
edgeMaskH *= gate;
edgeMaskV *= gate;
float4 clr = center;
clr = lerp(clr, blurredH, edgeMaskV);
clr = lerp(clr, blurredV, edgeMaskH * 0.5);
//skip unnecessary work on long-edges
if (localEdges > 0.5) {
float4 h0 = right01;
float4 h1 = SAMPLE_G(uv, 3.5, 0.0);
float4 h2 = SAMPLE_G(uv, 5.5, 0.0);
float4 h3 = SAMPLE_G(uv, 7.5, 0.0);
float4 h4 = left01;
float4 h5 = SAMPLE_G(uv, -3.5, 0.0);
float4 h6 = SAMPLE_G(uv, -5.5, 0.0);
float4 h7 = SAMPLE_G(uv, -7.5, 0.0);
float4 v0 = bottom01;
float4 v1 = SAMPLE_G(uv, 0.0, 3.5);
float4 v2 = SAMPLE_G(uv, 0.0, 5.5);
float4 v3 = SAMPLE_G(uv, 0.0, 7.5);
float4 v4 = top01;
float4 v5 = SAMPLE_G(uv, 0.0, -3.5);
float4 v6 = SAMPLE_G(uv, 0.0, -5.5);
float4 v7 = SAMPLE_G(uv, 0.0, -7.5);
float longEdgeMaskH = (h0.a + h1.a + h2.a + h3.a + h4.a + h5.a + h6.a + h7.a) / 8.0;
float longEdgeMaskV = (v0.a + v1.a + v2.a + v3.a + v4.a + v5.a + v6.a + v7.a) / 8.0;
longEdgeMaskH = saturate(longEdgeMaskH * 2.0 - 1.0);
longEdgeMaskV = saturate(longEdgeMaskV * 2.0 - 1.0);
if (abs(longEdgeMaskH - longEdgeMaskV) > 0.2) {
float4 left = SAMPLE_G(uv, -1.0, 0.0);
float4 right = SAMPLE_G(uv, 1.0, 0.0);
float4 top = SAMPLE_G(uv, 0.0, -1.0);
float4 bottom = SAMPLE_G(uv, 0.0, 1.0);
float4 longBlurredH = (h0 + h1 + h2 + h3 + h4 + h5 + h6 + h7) / 8.0;
float4 longBlurredV = (v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7) / 8.0;
float lbHLum = GetLuminance(longBlurredH.xyz);
float lbVLum = GetLuminance(longBlurredV.xyz);
float centerLum = GetLuminance(center.xyz);
float leftLum = GetLuminance(left.xyz);
float rightLum = GetLuminance(right.xyz);
float topLum = GetLuminance(top.xyz);
float bottomLum = GetLuminance(bottom.xyz);
float4 clrV = center;
float4 clrH = center;
float hx = saturate(0.0 + (lbHLum - topLum) / (centerLum - topLum + 1e-6));
float hy = saturate(1.0 + (lbHLum - centerLum) / (centerLum - bottomLum + 1e-6));
float vx = saturate(0.0 + (lbVLum - leftLum) / (centerLum - leftLum + 1e-6));
float vy = saturate(1.0 + (lbVLum - centerLum) / (centerLum - rightLum + 1e-6));
float4 vhxy = float4(vx, vy, hx, hy);
vhxy.x = (vhxy.x == 0.0) ? 1.0 : vhxy.x;
vhxy.y = (vhxy.y == 0.0) ? 1.0 : vhxy.y;
vhxy.z = (vhxy.z == 0.0) ? 1.0 : vhxy.z;
vhxy.w = (vhxy.w == 0.0) ? 1.0 : vhxy.w;
clrV = lerp(left, clrV, vhxy.x);
clrV = lerp(right, clrV, vhxy.y);
clrH = lerp(top, clrH, vhxy.z);
clrH = lerp(bottom, clrH, vhxy.w);
clr = lerp(clr, clrV, longEdgeMaskV);
clr = lerp(clr, clrH, longEdgeMaskH);
}
}
//highlight protection
float4 r0 = SAMPLE_G(uv, -1.5, -1.5);
float4 r1 = SAMPLE_G(uv, 1.5, -1.5);
float4 r2 = SAMPLE_G(uv, -1.5, 1.5);
float4 r3 = SAMPLE_G(uv, 1.5, 1.5);
float4 r = (4.0 * (r0 + r1 + r2 + r3) + center + top01 + bottom01 + left01 + right01) / 25.0;
float mask = saturate(r.a * 3.0 - 2.0);
clr = lerp(clr, center, mask);
return float4(clr.xyz * clr.xyz, 1.0); //store linear color here!
}
#endif
float4 PS_TRAA(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
//3x3 neighborhood from DLAA prepass
static const float2 offsets[9] = {
float2(-1, -1), float2(0, -1), float2(1, -1),
float2(-1, 0) , float2(0, 0) , float2(1, 0),
float2(-1, 1) , float2(0, 1) , float2(1, 1)
};
float3 samples[9];
float3 samplesYCoCg[9];
float3 meanYCoCg = float3(0, 0, 0);
for (int i = 0; i < 9; i++) {
float2 samplePos = texcoord + BUFFER_PIXEL_SIZE * offsets[i];
#if ENABLE_DLAA
samples[i] = tex2Dlod(sDLAAPrePass, float4(samplePos, 0, 0)).rgb;
#else
samples[i] = GetLinearColor(samplePos, false);
#endif
samplesYCoCg[i] = linearToYCoCg(samples[i]);
meanYCoCg += samplesYCoCg[i];
}
meanYCoCg /= 9.0;
//standard deviation per channel
float3 stddev = float3(0, 0, 0);
for (int i = 0; i < 9; i++) {
float3 diff = samplesYCoCg[i] - meanYCoCg;
stddev += diff * diff;
}
stddev = sqrt(stddev / 9.0);
//variance-scaled bounding box in YCoCg
float3 colorMin = meanYCoCg - stddev * 1.25;
float3 colorMax = meanYCoCg + stddev * 1.25;
float2 flow = tex2D(Kernel::sFlow, texcoord).xy;
float confidence = tex2D(Kernel::sConfidence, texcoord).x;
confidence = saturate(confidence + 0.11 * 4.0 * confidence * (1.0 - confidence));
float2 historyUV = texcoord + flow;
historyUV = clamp(historyUV, BUFFER_PIXEL_SIZE, 1.0 - BUFFER_PIXEL_SIZE);
float3 historyRGB = SampleCatmullRom5Tap(sPrevHistory, historyUV);
float3 historyYCoCg = linearToYCoCg(historyRGB);
//clip history to current neighborhood bounds via line-box intersection
float3 clippedHistoryYCoCg = YCoCgLineBoxClip(historyYCoCg, meanYCoCg, colorMin, colorMax);
//re-inject current pixel's detail into clipped history
float3 centerYCoCg = samplesYCoCg[4]; //blend against the center pixel (index 4 of the 3x3 grid)
float3 injectedHistory = clippedHistoryYCoCg + (centerYCoCg - meanYCoCg) * HFI_INTENSITY;
//blend clipped history with current in YCoCg space
float blendVal = min(0.98, HISTORY_BLEND);
float3 blendedYCoCg = lerp(centerYCoCg, injectedHistory, confidence * blendVal);
float3 output = YCoCgToLinear(blendedYCoCg);
return float4(output, 1.0);
}
float4 PS_ToDisplay(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
#if ENABLE_DLAA
if (DEBUG_EDGES) {
float edgeDbg = tex2D(sDLAAPreFilter, texcoord).a;
static const float3 edgeTint = float3(0.125, 0.698, 0.667) * float3(0.125, 0.698, 0.667); //target color squared so lands on the real hue
return float4(ToOutputColorspace(edgeTint * saturate(edgeDbg), false), 1.0);
}
#endif
float3 c = tex2D(sCurrHistory, texcoord).rgb;
float3 sharpened = c;
if (SHARP_STRENGTH > 0) {
float2 off = BUFFER_PIXEL_SIZE * 0.5;
float3 ne = tex2D(sCurrHistory, texcoord + float2( off.x, off.y)).rgb;
float3 sw = tex2D(sCurrHistory, texcoord + float2(-off.x, -off.y)).rgb;
float3 se = tex2D(sCurrHistory, texcoord + float2( off.x, -off.y)).rgb;
float3 nw = tex2D(sCurrHistory, texcoord + float2(-off.x, off.y)).rgb;
//bounds for the neighborhood
float3 local_min = min(min(min(ne, nw), min(se, sw)), c);
float3 local_max = max(max(max(ne, nw), max(se, sw)), c);
//high-pass
float3 diag_max = max(max(ne, nw), max(se, sw));
float3 diag_min = min(min(ne, nw), min(se, sw));
float3 diff_rgb = 2.0 * c + (ne + nw + se + sw) - 3.0 * (diag_max + diag_min);
static const float3 luma_weight = float3(0.2126, 0.7152, 0.0722);
float luma_c = dot(c, luma_weight);
float luma_diff = dot(diff_rgb, luma_weight);
//rational limit
float max_allowed = MAX_SHARP_DIFF * (luma_c + 0.1);
luma_diff = luma_diff / (rcp(SHARP_STRENGTH) + abs(luma_diff) / max(max_allowed, 0.001));
//lower epsilon (0.005) for more dark-area detail
float ratio = (luma_c + luma_diff) / max(luma_c, 0.005);
//allow up to 3x brightness for extreme highlights
ratio = clamp(ratio, 0.3, 3.0);
sharpened = c * ratio;
//anti-ringing
//instead of clamping strictly to min/max, we allow a 20% overshoot
//perceived "sharpness" while capping fireflies
float3 overshoot_min = local_min * 0.8;
float3 overshoot_max = local_max * 1.2;
sharpened = clamp(sharpened, overshoot_min, overshoot_max);
}
return float4(ToOutputColorspace(sharpened, false), 1.0);
}
float4 PS_StoreHistory(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
float3 taaResult = tex2D(sCurrHistory, texcoord).rgb;
return float4(taaResult, 1.0);
}
/*----------------.
| :: TECHNIQUE :: |
'----------------*/
technique Lumenite_TRAA <
ui_label = "LUMENITE: TRAA";
ui_tooltip = "Temporal Reprojection Anti-Aliasing.";
>
{
#if ENABLE_DLAA
pass { VertexShader = PostProcessVS; PixelShader = PS_DLAAPreFilter; RenderTarget = tDLAAPreFilter; }
pass { VertexShader = PostProcessVS; PixelShader = PS_DLAA; RenderTarget = tDLAAPrePass; } //spatial filter
#endif
pass { VertexShader = PostProcessVS; PixelShader = PS_TRAA; RenderTarget = tCurrHistory; } //temporal filter
pass { VertexShader = PostProcessVS; PixelShader = PS_ToDisplay; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StoreHistory; RenderTarget = tPrevHistory; }
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
# >REM<Freelancer # >REM<Freelancer
# 0.4.5.2 # 0.4.6.0
+2 -2
View File
@@ -13,8 +13,8 @@ Website : https://rem-freelancer.moe/
File Name : REM.zip File Name : REM.zip
File Size : 15,00 GB File Size : 15,00 GB
Mod Version : 0.4.5.2 Mod Version : 0.4.6.0
Date Released : 25/08/2026 Date Released : 05/09/2026
****************************************************** ******************************************************