Hi everyone.
In a desperate attempt to get this thing working i am trying to get help here (since no one @ stackExchange was able to help). My task is simple (at least i think it should be), i want to add screen space reflections to my water shader. Its a thing that exists longer than my Grandma but it seems that its still secret NSA technology, considering how poor the documentation about it is. I have a working shader that creates nice reflections on a planar water surface as long as the camera is very close to the water surface but the reflection starts to fail as soon as the camera moves anywhere. I dont know exactly whats wrong and i have no idea how to fix it, i guess its either the GetUV function that gets screen coordinates instead of coordinates that fit on the water texture (my shader is applied to the water surface, its not a post-process effect) or the reflection direction is wrong for whatever reason.
Here are screenshots, illustrating how it looks and whats wrong:
https://imgur.com/a/5kIGikU
Here is the full shader code, the only variable is "screenInput", which is simply the whole screen rendered by the game that is used for reflection.
//-- Include some common stuff
#include "mta-helper.fx"
texture screenInput;
texture gDepthBuffer : DEPTHBUFFER;
///////////////////
// SAMPLE STATES //
///////////////////
sampler2D screenSampler = sampler_state
{
Texture = <screenInput>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Mirror;
AddressV = Mirror;
};
sampler SamplerDepth = sampler_state
{
Texture = (gDepthBuffer);
MinFilter = Point;
MagFilter = Point;
MipFilter = None;
AddressU = Clamp;
AddressV = Clamp;
};
// code from https://habrahabr.ru/post/244367/
float3 GetUV(float3 position)
{
float4 pVP = mul(float4(position, 1), gViewProjection);
pVP.xy = float2(0.5f, 0.5f) + float2(0.5f, -0.5f) * pVP.xy / pVP.w;
return float3(pVP.xy, pVP.z / pVP.w);
}
struct VertexInputType
{
float4 position : POSITION;
float3 normal : NORMAL0;
float2 textureCoords : TEXCOORD0;
};
struct PixelInputType
{
float4 position : POSITION;
float2 textureCoords : TEXCOORD0;
float4 reflectionPosition : TEXCOORD1;
float Depth : TEXCOORD2;
float3 worldPosition : TEXCOORD3;
float3 worldNormal : TEXCOORD4;
float4 vposition : TEXCOORD5;
float3 Normal : TEXCOORD6;
};
////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType WaterVertexShader(VertexInputType input)
{
PixelInputType output;
// Create the view projection world matrix for reflection.
matrix projection = mul(gWorldViewProjection, gWorld);
projection = mul(gWorld, projection);
// Calculate the position of the vertex against the world, view, and projection matrices.
output.position = MTACalcScreenPosition(input.position);
output.worldPosition = MTACalcWorldPosition(input.position);
output.worldNormal = MTACalcWorldNormal(input.normal);
// Store the texture coordinates for the pixel shader.
output.textureCoords = input.textureCoords;
// compute the eye vector
float4 vertexPosition = mul(input.position, gWorld);
float4 vPos = mul(vertexPosition, gView);
float4 pPos = mul(vPos, gProjection);
output.reflectionPosition.x = 0.5 * (pPos.w + pPos.x);
output.reflectionPosition.y = 0.5 * (pPos.w - pPos.y);
output.reflectionPosition.z = pPos.w;
output.reflectionPosition.w = vPos.z / vPos.w;
output.Depth = output.position.z;
output.vposition = mul(input.position, gWorldViewProjection);
output.Normal = input.normal;
return output;
}
////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 WaterPixelShader(PixelInputType input) : COLOR0
{
float2 textureCoords = input.reflectionPosition.xy / input.reflectionPosition.z;
// get to pixel view direction
float3 viewDir = normalize(input.worldPosition - gCameraPosition);
float3 reflectDir = normalize(reflect(viewDir, float3(0, 0, -1)));
// cast rays
float L = 0.01 * input.reflectionPosition.w;
float3 currentRay;
float3 nuv;
for(int i = 0; i < 10; i++)
{
currentRay = input.worldPosition + reflectDir * L;
nuv = GetUV(currentRay);
L = distance(currentRay, gCameraPosition);
}
float4 reflectionColor = tex2D(screenSampler, nuv.xy);
return saturate(reflectionColor);
}
////////////////////////////////////////////////////////////////////////////////
// Technique
////////////////////////////////////////////////////////////////////////////////
technique WaterTechnique
{
pass pass0
{
ZEnable = true;
ZWriteEnable = true;
ZFunc = 2;
VertexShader = compile vs_3_0 WaterVertexShader();
PixelShader = compile ps_3_0 WaterPixelShader();
}
}
// Fallback
technique fallback
{
pass P0
{
// Just draw normally
}
}
Maybe some genius here knows what to do and maybe there is a better way to do it than cast some rays because i have seen some shaders that do very cryptic things but still get a nice result.