forked from aya/aya
311 lines
8.5 KiB
HLSL
311 lines
8.5 KiB
HLSL
#include "common.h"
|
|
|
|
// Tunables
|
|
#define CFG_TEXTURE_TILING 0.2
|
|
#define CFG_TEXTURE_DETILING 0.1
|
|
|
|
#define CFG_SPECULAR 2
|
|
#define CFG_GLOSS 900
|
|
|
|
#define CFG_NORMAL_STRENGTH 0.25
|
|
|
|
#define CFG_REFRACTION_STRENGTH 0.05
|
|
|
|
#define CFG_FRESNEL_OFFSET 0.3
|
|
|
|
#define CFG_SSR_STEPS 8
|
|
#define CFG_SSR_START_DISTANCE 1
|
|
#define CFG_SSR_STEP_CLAMP 0.2
|
|
#define CFG_SSR_DEPTH_CUTOFF 10
|
|
|
|
// Shader code
|
|
struct Appdata
|
|
{
|
|
ATTR_INT4 Position : POSITION;
|
|
|
|
ATTR_INT4 Normal : NORMAL;
|
|
|
|
ATTR_INT4 Material0 : TEXCOORD0;
|
|
ATTR_INT4 Material1 : TEXCOORD1;
|
|
};
|
|
|
|
struct VertexOutput
|
|
{
|
|
float4 HPosition : POSITION;
|
|
|
|
float4 Weights_Wave: COLOR0;
|
|
float3 Tangents: COLOR1;
|
|
|
|
float2 Uv0: TEXCOORD0;
|
|
float2 Uv1: TEXCOORD1;
|
|
float2 Uv2: TEXCOORD2;
|
|
|
|
float4 LightPosition_Fog : TEXCOORD3;
|
|
|
|
float3 Normal: TEXCOORD4;
|
|
float4 View_Depth: TEXCOORD5;
|
|
|
|
#ifdef PIN_HQ
|
|
float4 PositionScreen: TEXCOORD6;
|
|
#endif
|
|
};
|
|
|
|
WORLD_MATRIX_ARRAY(WorldMatrixArray, 72);
|
|
|
|
uniform float4 WaveParams; // .x = frequency .y = phase .z = height .w = lerp
|
|
uniform float4 WaterColor; // deep water color
|
|
uniform float4 WaterParams; // .x = refraction depth scale, .y = refraction depth offset
|
|
|
|
float3 displacePosition(float3 position, float waveFactor)
|
|
{
|
|
float x = sin((position.z - position.x) * WaveParams.x - WaveParams.y);
|
|
float z = sin((position.z + position.x) * WaveParams.x + WaveParams.y);
|
|
float p = (x + z) * WaveParams.z;
|
|
|
|
float3 result = position;
|
|
|
|
result.y += p * waveFactor;
|
|
|
|
return result;
|
|
}
|
|
|
|
float4 clipToScreen(float4 pos)
|
|
{
|
|
#ifdef GLSL
|
|
pos.xy = pos.xy * 0.5 + 0.5 * pos.w;
|
|
#else
|
|
pos.xy = pos.xy * float2(0.5, -0.5) + 0.5 * pos.w;
|
|
#endif
|
|
return pos;
|
|
}
|
|
|
|
float2 getUV(float3 position, ATTR_INT projection, float seed)
|
|
{
|
|
float3 u = WorldMatrixArray[1 + int(projection)].xyz;
|
|
float3 v = WorldMatrixArray[19 + int(projection)].xyz;
|
|
|
|
float2 uv = float2(dot(position, u), dot(position, v)) * (0.25 * CFG_TEXTURE_TILING) + CFG_TEXTURE_DETILING * float2(seed, floor(seed * 2.6651441f));
|
|
|
|
return uv;
|
|
}
|
|
|
|
VertexOutput WaterVS(Appdata IN)
|
|
{
|
|
VertexOutput OUT = (VertexOutput)0;
|
|
|
|
float3 posWorld = IN.Position.xyz * WorldMatrixArray[0].w + WorldMatrixArray[0].xyz;
|
|
float3 normalWorld = IN.Normal.xyz * (1.0 / 127.0) - 1.0;
|
|
|
|
#if defined(GLSLES) && !defined(GL3) // iPad2 workaround
|
|
float3 weights = abs(IN.Position.www - float3(0, 1, 2)) < 0.1;
|
|
#else
|
|
float3 weights = IN.Position.www == float3(0, 1, 2);
|
|
#endif
|
|
|
|
float waveFactor = dot(weights, IN.Material0.xyz) * (1.0 / 255.0);
|
|
|
|
#ifdef PIN_HQ
|
|
float fade = saturate0(1 - dot(posWorld - G(CameraPosition), -G(ViewDir).xyz) * G(FadeDistance_GlowFactor).y);
|
|
|
|
posWorld = displacePosition(posWorld, waveFactor * fade);
|
|
#endif
|
|
|
|
OUT.HPosition = mul(G(ViewProjection), float4(posWorld, 1));
|
|
|
|
OUT.LightPosition_Fog = float4(lgridPrepareSample(lgridOffset(posWorld, normalWorld)), (G(FogParams).z - OUT.HPosition.w) * G(FogParams).w);
|
|
|
|
OUT.Uv0 = getUV(posWorld, IN.Material1.x, IN.Normal.w);
|
|
OUT.Uv1 = getUV(posWorld, IN.Material1.y, IN.Material0.w);
|
|
OUT.Uv2 = getUV(posWorld, IN.Material1.z, IN.Material1.w);
|
|
|
|
OUT.Weights_Wave.xyz = weights;
|
|
OUT.Weights_Wave.w = waveFactor;
|
|
|
|
OUT.Normal = normalWorld;
|
|
OUT.View_Depth = float4(G(CameraPosition) - posWorld, OUT.HPosition.w);
|
|
OUT.Tangents = float3(IN.Material1.xyz) > 7.5; // side vs top
|
|
|
|
#ifdef PIN_HQ
|
|
OUT.PositionScreen = clipToScreen(OUT.HPosition);
|
|
#endif
|
|
|
|
return OUT;
|
|
}
|
|
|
|
TEX_DECLARE2D(NormalMap1, 0);
|
|
TEX_DECLARE2D(NormalMap2, 1);
|
|
TEX_DECLARECUBE(EnvMap, 2);
|
|
LGRID_SAMPLER(LightMap, 3);
|
|
TEX_DECLARE2D(LightMapLookup, 4);
|
|
TEX_DECLARE2D(GBufferColor, 5);
|
|
TEX_DECLARE2D(GBufferDepth, 6);
|
|
|
|
float fresnel(float ndotv)
|
|
{
|
|
return saturate(0.78 - 2.5 * abs(ndotv)) + CFG_FRESNEL_OFFSET;
|
|
}
|
|
|
|
float4 sampleMix(float2 uv)
|
|
{
|
|
#ifdef PIN_HQ
|
|
return lerp(tex2D(NormalMap1, uv), tex2D(NormalMap2, uv), WaveParams.w);
|
|
#else
|
|
return tex2D(NormalMap1, uv);
|
|
#endif
|
|
}
|
|
|
|
float3 sampleNormal(float2 uv0, float2 uv1, float2 uv2, float3 w, float3 normal, float3 tsel)
|
|
{
|
|
return terrainNormal(sampleMix(uv0), sampleMix(uv1), sampleMix(uv2), w, normal, tsel);
|
|
}
|
|
|
|
float3 sampleNormalSimple(float2 uv0, float2 uv1, float2 uv2, float3 w)
|
|
{
|
|
float4 data = sampleMix(uv0) * w.x + sampleMix(uv1) * w.y + sampleMix(uv2) * w.z;
|
|
|
|
return nmapUnpack(data).xzy;
|
|
}
|
|
|
|
float unpackDepth(float2 uv)
|
|
{
|
|
float4 geomTex = tex2D(GBufferDepth, uv);
|
|
float d = geomTex.z * (1.0f/256.0f) + geomTex.w;
|
|
return d * GBUFFER_MAX_DEPTH;
|
|
}
|
|
|
|
float3 getRefractedColor(float4 cpos, float3 N, float3 waterColor)
|
|
{
|
|
float2 refruv0 = cpos.xy / cpos.w;
|
|
float2 refruv1 = refruv0 + N.xz * CFG_REFRACTION_STRENGTH;
|
|
|
|
float4 refr0 = tex2D(GBufferColor, refruv0);
|
|
refr0.w = unpackDepth(refruv0);
|
|
|
|
float4 refr1 = tex2D(GBufferColor, refruv1);
|
|
refr1.w = unpackDepth(refruv1);
|
|
|
|
float4 result = lerp(refr0, refr1, saturate(refr1.w - cpos.w));
|
|
|
|
// Estimate water absorption by a scaled depth difference
|
|
float depthfade = saturate((result.w - cpos.w) * WaterParams.x + WaterParams.y);
|
|
|
|
// Since GBuffer depth is clamped we tone the refraction down after half of the range for a smooth fadeout
|
|
float gbuffade = saturate(cpos.w * (2.f / GBUFFER_MAX_DEPTH) - 1);
|
|
|
|
float fade = saturate(depthfade + gbuffade);
|
|
|
|
return lerp(result.rgb, waterColor, fade);
|
|
}
|
|
|
|
float3 getReflectedColor(float4 cpos, float3 wpos, float3 R)
|
|
{
|
|
float3 result = 0;
|
|
float inside = 0;
|
|
|
|
float distance = CFG_SSR_START_DISTANCE;
|
|
float diff = 0;
|
|
float diffclamp = cpos.w * CFG_SSR_STEP_CLAMP;
|
|
|
|
float4 Pproj = cpos;
|
|
float4 Rproj = clipToScreen(mul(G(ViewProjection), float4(R, 0)));
|
|
|
|
#ifndef GLSL
|
|
[unroll]
|
|
#endif
|
|
for (int i = 0; i < CFG_SSR_STEPS; ++i)
|
|
{
|
|
distance += clamp(diff, -diffclamp, diffclamp);
|
|
|
|
float4 cposi = Pproj + Rproj * distance;
|
|
float2 uv = cposi.xy / cposi.w;
|
|
float depth = unpackDepth(uv);
|
|
|
|
diff = depth - cposi.w;
|
|
}
|
|
|
|
float4 cposi = Pproj + Rproj * distance;
|
|
float2 uv = cposi.xy / cposi.w;
|
|
|
|
// Ray hit has to be inside the screen bounds
|
|
float ufade = abs(uv.x - 0.5) < 0.5;
|
|
float vfade = abs(uv.y - 0.5) < 0.5;
|
|
|
|
// Fade reflections out with distance; use max(ray hit, original depth) to discard hits that are too far
|
|
// 4 - depth * 4 would give us fade out from 0.75 to 1; 3.9 makes sure reflections go to 0 slightly before GBUFFER_MAX_DEPTH
|
|
float wfade = saturate((4 - 0.1) - max(cpos.w, cposi.w) * (4.f / GBUFFER_MAX_DEPTH));
|
|
|
|
// Ray hit has to be reasonably close to where we started
|
|
float dfade = abs(diff) < CFG_SSR_DEPTH_CUTOFF;
|
|
|
|
// Avoid back-projection
|
|
float Vfade = Rproj.w > 0;
|
|
|
|
float fade = ufade * vfade * wfade * dfade * Vfade;
|
|
|
|
return lerp(texCUBE(EnvMap, R).rgb, tex2D(GBufferColor, uv).rgb, fade);
|
|
}
|
|
|
|
float4 WaterPS(VertexOutput IN): COLOR0
|
|
{
|
|
float4 light = lgridSample(TEXTURE(LightMap), TEXTURE(LightMapLookup), IN.LightPosition_Fog.xyz);
|
|
float shadow = light.a;
|
|
|
|
float3 w = IN.Weights_Wave.xyz;
|
|
|
|
// Use simplified normal reconstruction for LQ mobile (assumes flat water surface)
|
|
#if defined(GLSLES) && !defined(PIN_HQ)
|
|
float3 normal = sampleNormalSimple(IN.Uv0, IN.Uv1, IN.Uv2, w);
|
|
#else
|
|
float3 normal = sampleNormal(IN.Uv0, IN.Uv1, IN.Uv2, w, IN.Normal, IN.Tangents);
|
|
#endif
|
|
|
|
// Flatten the normal for Fresnel and for reflections to make them less chaotic
|
|
float3 flatNormal = lerp(IN.Normal, normal, CFG_NORMAL_STRENGTH);
|
|
|
|
float3 waterColor = WaterColor.rgb;
|
|
|
|
#ifdef PIN_HQ
|
|
float fade = saturate0(1 - IN.View_Depth.w * G(FadeDistance_GlowFactor).y);
|
|
|
|
float3 view = normalize(IN.View_Depth.xyz);
|
|
|
|
float fre = fresnel(dot(flatNormal, view)) * IN.Weights_Wave.w;
|
|
|
|
float3 position = G(CameraPosition) - IN.View_Depth.xyz;
|
|
|
|
#ifdef PIN_GBUFFER
|
|
float3 refr = getRefractedColor(IN.PositionScreen, normal, waterColor);
|
|
float3 refl = getReflectedColor(IN.PositionScreen, position, reflect(-view, flatNormal));
|
|
#else
|
|
float3 refr = waterColor;
|
|
float3 refl = texCUBE(EnvMap, reflect(-view, flatNormal)).rgb;
|
|
#endif
|
|
|
|
float specularIntensity = CFG_SPECULAR * fade;
|
|
float specularPower = CFG_GLOSS;
|
|
|
|
float3 specular = G(Lamp0Color) * (specularIntensity * shadow * (float)(half)pow(saturate(dot(normal, normalize(-G(Lamp0Dir) + view))), specularPower));
|
|
#else
|
|
float3 view = normalize(IN.View_Depth.xyz);
|
|
|
|
float fre = fresnel(dot(flatNormal, view));
|
|
|
|
float3 refr = waterColor;
|
|
|
|
float3 refl = texCUBE(EnvMap, reflect(-IN.View_Depth.xyz, flatNormal)).rgb;
|
|
|
|
float3 specular = 0;
|
|
#endif
|
|
|
|
// Combine
|
|
float4 result;
|
|
result.rgb = lerp(refr, refl, fre) * (G(AmbientColor).rgb + G(Lamp0Color).rgb * shadow + light.rgb) + specular;
|
|
result.a = 1;
|
|
|
|
float fogAlpha = saturate(IN.LightPosition_Fog.w);
|
|
|
|
result.rgb = lerp(G(FogColor), result.rgb, fogAlpha);
|
|
|
|
return result;
|
|
}
|