Skip to content

Commit

Permalink
Far Cry Primal: Fix reflections via stereo2mono technique
Browse files Browse the repository at this point in the history
The reflections in Far Cry Primal follow the same pattern as Far Cry 4,
but fixing those required changes to every single vertex shader that
might ever be used when creating the reflection, and there were a lot of
these and I still find more.

Instead, take a different approach of using 3DMigoto's stereo2mono
support to reverse the eyes of the reflection (similar to the mirror in
MGSVTPP), combined with the same world space correction and forced
surface creation mode result in accurate 3D reflections.

It seems that there are some issues with the reverse stereo blit and
resources with mip-maps - it seems that the reverse stereo blit only
takes effect on the most detailed mip map, and the rest just end up with
a copy in one eye. The reflection seems to combine several mip-maps
together, and if they are not present it will be much too dark.

To work around this, use a custom shader that reverses the reflection to
a custom resource with only a single mip-map level so that there will
only be one to sample. This makes the ripples more detailed than they
are supposed to be (looks fine to me), so I might revisit it later and
see what we can do to solve the reverse stereo blit more generally.
  • Loading branch information
DarkStarSword committed Mar 3, 2016
1 parent 07784ee commit 594d528
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 2 deletions.
51 changes: 49 additions & 2 deletions Far Cry Primal/ShaderFixes/2b06acf00b51d62e-ps.txt
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ dcl_resource_texture3d (float,float,float,float) t13
dcl_resource_texture2darray (float,float,float,float) t14
dcl_resource_texture2d (float,float,float,float) t15
dcl_resource_texture2d (float,float,float,float) t16
dcl_resource_texture2d (float,float,float,float) t100
dcl_input_ps_siv linear noperspective centroid v0.xy, position
dcl_input_ps linear v1.xyzw
dcl_input_ps linear centroid v2.xyzw
Expand All @@ -315,7 +316,12 @@ dcl_input_ps linear v5.xyz
dcl_input_ps linear v6.xyz
dcl_input_ps linear v7.xyzw
dcl_output o0.xyzw
dcl_temps 23
dcl_temps 26

dcl_resource_texture2d (float,float,float,float) t125

ld_indexable(texture2d)(float,float,float,float) r23.xyzw, l(0, 0, 0, 0), t125.xyzw

dp3 r0.x, v3.xyzx, v3.xyzx
sqrt r0.x, r0.x
add r0.yzw, -v4.xxzy, cb0[42].xxzy
Expand Down Expand Up @@ -913,14 +919,55 @@ mul r12.xy, r3.wyww, cb6[12].zzzz
mad r12.xy, r12.xyxx, r0.xxxx, v5.xyxx
mov r12.z, v5.z
mov r12.w, l(1.000000)

// ViewProjectionMatrix:
dp4 r24.w, r12.xyzw, cb0[19].xyzw

// Correction:
add r23.w, r24.w, -r23.y
mul r24.x, r23.w, r23.x
mov r24.yzw, l(0.0)

// InvProjectionMatrix:
dp4 r25.x, r24.xyzw, cb0[0].xyzw
dp4 r25.y, r24.xyzw, cb0[1].xyzw
dp4 r25.z, r24.xyzw, cb0[2].xyzw
dp4 r25.w, r24.xyzw, cb0[3].xyzw
// InvViewMatrix, use output components with order matching above so the below
// add is slightly less brainfuck:
dp4 r24.x, r25.xyzw, cb0[4].xyzw
dp4 r24.y, r25.xyzw, cb0[5].xyzw
dp4 r24.z, r25.xyzw, cb0[6].xyzw
dp4 r24.w, r25.xyzw, cb0[7].xyzw

// Adjust coord:
add r12.xyz, r12.xyz, r24.xyzw

//WaterReflectionTransform:
dp4 r13.x, r12.xyzw, cb1[0].xyzw
dp4 r13.y, r12.xyzw, cb1[1].xyzw
dp4 r6.w, r12.xyzw, cb1[3].xyzw
div r12.xy, r13.xyxx, r6.wwww
mad r11.zw, -v0.xxxx, cb0[40].zzzz, l(0.000000, 0.000000, 1.000000, 1.000000)
add r11.xz, -r11.zzyz, r12.xxyx
mad_sat r2.xw, r11.xxxz, r2.xxxw, r11.wwwy
sample_l_indexable(texture2d)(float,float,float,float) r11.xyz, r2.xwxx, t16.xyzw, s11, r7.y

// Sample from the reversed buffer:
//sample_l_indexable(texture2d)(float,float,float,float) r11.xyz, r2.xwxx, t16.xyzw, s11, r7.y
sample_l_indexable(texture2d)(float,float,float,float) r11.xyz, r2.xwxx, t100.xyzw, s11, r7.y

// If we can sort out the problem with mip-maps not being copied during the
// reverse stereo blit we could switch the eyes with this code instead of the
// custom shader:
//// Swap reflection eyes
//mov r24.xyzw, r2.xyzw
//mul r24.x, r24.x, l(0.5)
//eq r23.w, r23.z, l(-1.0)
//if_nz r23.w
// add r24.x, r24.x, l(0.5)
//endif
//sample_l_indexable(texture2d)(float,float,float,float) r11.xyz, r24.xwxx, t100.xyzw, s11, r7.y

min r11.xyz, r11.xyzx, l(16.000000, 16.000000, 16.000000, 0.000000)
ld_structured_indexable(structured_buffer, stride=16)(mixed,mixed,mixed,mixed) r2.x, l(0), l(12), t3.xxxx
mul r11.xyz, r2.xxxx, r11.xyzx
Expand Down
27 changes: 27 additions & 0 deletions Far Cry Primal/ShaderFixes/full_screen.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Texture2D<float4> StereoParams : register(t125);
Texture1D<float4> IniParams : register(t120);

void main(
out float4 pos : SV_Position0,
uint vertex : SV_VertexID)
{
// Not using vertex buffers so manufacture our own coordinates.
switch(vertex) {
case 0:
pos.xy = float2(-1, -1);
break;
case 1:
pos.xy = float2(-1, 1);
break;
case 2:
pos.xy = float2(1, -1);
break;
case 3:
pos.xy = float2(1, 1);
break;
default:
pos.xy = 0;
break;
};
pos.zw = float2(0, 1);
}
16 changes: 16 additions & 0 deletions Far Cry Primal/ShaderFixes/reverset100.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Texture2D<float4> StereoParams : register(t125);
Texture2D<float4> t100 : register(t100);

void main(float4 pos : SV_Position0, out float4 result : SV_Target0)
{
float4 stereo = StereoParams.Load(0);

float width, height;

t100.GetDimensions(width, height);

if (stereo.z == -1)
pos.x += width / 2;

result = t100.Load(float3(pos.x, pos.y, 0));
}
58 changes: 58 additions & 0 deletions Far Cry Primal/d3dx.ini
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,64 @@ hash = 6a8b6667694bb2d4
vs-t110 = ResourceDepthBuffer
vs-cb13 = ResourceCViewportShaderParameterProvider

;;;;;;;;;;;;;;; REFLECTION ;;;;;;;;;;;;;;;;;;;
[TextureOverrideReflectionRGB]
; <Register orig_hash=3b1a8d48 type=Texture2D Width=512 Height=512 MipLevels=6 ArraySize=1 RawFormat=9 Format="R16G16B16A16_TYPELESS" SampleDesc.Count=1 SampleDesc.Quality=0 Usage=0 BindFlags=0x28 CPUAccessFlags=0x0 MiscFlags=0x0></Register>
hash = 3b1a8d48
StereoMode = 1
[TextureOverrideReflectionDepth]
; <DepthTarget orig_hash=b05c0230 type=Texture2D Width=512 Height=512 MipLevels=1 ArraySize=1 RawFormat=19 Format="R32G8X24_TYPELESS" SampleDesc.Count=1 SampleDesc.Quality=0 Usage=0 BindFlags=0x48 CPUAccessFlags=0x0 MiscFlags=0x0></DepthTarget>
hash = b05c0230
StereoMode = 1


; In FC4 we had to track down every single vertex shader that draws something
; in the reflection and reverse their position, which was easy to miss them.
; Here we are using a new strategy of using
[ShaderOverrideReflection]
hash = 2b06acf00b51d62e
; The reverse stereo blit does not seem to work on mip-maps, which results in
; the right eye being too dark with this approach:
;ps-t100 = stereo2mono ps-t16
; Instead, run a custom shader that can reverse the reflection and write to a
; custom resource that has been forced to 1 mip-map level. This effectively
; throws away the other mip-maps:
run = CustomShaderReverseReflection
; Assign the result of the custom shader as an input to replace the original
; reflection:
ps-t100 = ResourceReversedReflection
; Clear after the draw call for safety:
post ps-t100 = null

[ResourceReversedReflection]
; We have a couple of problems with mip-maps using this approach - the reverse
; stereo blit does not copy them, and our custom shader does not (yet) run on
; them. The way the game samples the texture seems to use several mip-maps and
; this results in the texture being too dark. Force to only use a single
; mip-map to recover the correct brightness, however this results in a minor
; difference as the ripples will have more detail than intended. Looks fine to
; me, but I might revisit this later.
mips = 1

[CustomShaderReverseReflection]
vs = ShaderFixes/full_screen.hlsl
ps = ShaderFixes/reverset100.hlsl
blend = disable
topology = triangle_strip
oD = null
; Important to specify 'stereo' here for the same reason we had to force the
; reflection creation mode in the first place:
ResourceReversedReflection = stereo copy_desc ps-t16
o0 = ResourceReversedReflection
; Mip-maps will only have data in one eye, but that's ok because this shader is
; here to throw the rest away (with some tweaks to 3DMigoto's resource copying
; we could possibly do the same by using subresource copies instead of a full
; copy):
ps-t100 = stereo2mono ps-t16
draw = 4, 0
post o0 = null


[Present]
; Clear an ini param at the start of each frame:
;x = 0
Expand Down

0 comments on commit 594d528

Please sign in to comment.