Skip to content

Commit

Permalink
Shader rework (#6485)
Browse files Browse the repository at this point in the history
Now every shader is capable of using the new, better way of calculating
the water absorption. The only requirement is that the light multiplier
is bigger than 2.1. This is good, because the mesh shader doesn't know
what terrain shader is in use and has worked like this before. This
could lead to unfitting results, if the light multiplier was big enough,
but a terrain shader was used that only supported the legacy water
calculation. This is now fixed.

I also made the decals able to use pbr light calculations. Now they
behave consistently like the ground they are on. (Also the map editor
does this automatically and we can't prevent it, so it helps the editor
produce results that are consistent with the game.)
Splats are unaffected for now.

One thing about the decals is that they theoretically have a texture to
define the specularity (or roughness), but this texture was black for
all decals that I tested. I don't know if there are any decals that have
this defined.
  • Loading branch information
BlackYps authored Nov 16, 2024
1 parent 8d5096b commit 76393d3
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 63 deletions.
2 changes: 2 additions & 0 deletions changelog/snippets/other.6485.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- (#6485) The new, better way of calculating the water absorption is now available for all terrain shaders. The only requirement is that the light multiplier is set to more than 2.1. Decals now use PBR light calculations if the terrain shader uses it, making them more consistent with the ground they are on.
-
4 changes: 2 additions & 2 deletions effects/mesh.fx
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ struct SHIELDIMPACT_VERTEX
///
///////////////////////////////////////

bool IsExperimentalShader() {
bool MapUsesAdvancedWater() {
// lightMultiplier is one of the few variables that is driven by the map,
// but accessible by the mesh shader.
return lightMultiplier > 2.1;
Expand Down Expand Up @@ -602,7 +602,7 @@ float3 ApplyWaterColor(float depth, float3 viewDirection, float3 color, float3 e
// disable the whole thing on land-only maps
if (surfaceElevation > 0) {
// we need this switch to make it consistent with the terrain shader coloration
if (IsExperimentalShader()) {
if (MapUsesAdvancedWater()) {
// We need to multiply by 2 to match the terrain shader.
float scaledDepth = (-depth / (surfaceElevation - abyssElevation)) * 2;
float3 up = float3(0,1,0);
Expand Down
138 changes: 77 additions & 61 deletions effects/terrain.fx
Original file line number Diff line number Diff line change
Expand Up @@ -344,16 +344,36 @@ VS_OUT FixedFuncVS( VS_IN In )
return Out;
}

bool IsExperimentalShader() {
bool ShaderUsesTerrainInfoTexture() {
// The tile value basically says how often the texture gets repeated on the map.
// A value less than one doesn't make sense under normal conditions, so it is
// relatively save to use it as our switch.
// We use the upper layer slot to store the terrain info texture, so we don't need
// the tile value for anything else.

// in order to trigger this you can set the albedo scale to be bigger than the map
// size. Use the value 10000 to be safe for any map
// In order to trigger this you need to set the albedo scale to be bigger than the
// map size in the editor. Use the value 10000 to be safe for any map
return UpperAlbedoTile.x < 1.0;
}

bool ShaderUsesPbrRendering() {
// The tile value basically says how often the texture gets repeated on the map.
// A value less than one doesn't make sense under normal conditions, so it is
// relatively save to use it as our switch.
// We use the stratum 7 normal slot to store the roughness texture, so we don't need
// the tile value for anything else.

// In order to trigger this you need to set the normal scale to be bigger than the
// map size in the editor. Use the value 10000 to be safe for any map
return Stratum7NormalTile.x < 1.0;
}

bool MapUsesAdvancedWater() {
// LightingMultiplier is one of the few variables that is driven by the map,
// but accessible by the mesh shader.
return LightingMultiplier > 2.1;
}

// sample a texture that is another buffer the same size as the one
// we are rendering into and with the viewport setup the same way.
float4 SampleScreen(sampler inSampler, float4 inTex)
Expand Down Expand Up @@ -388,35 +408,29 @@ float ComputeShadow( float4 vShadowCoord )
return tex2D( ShadowSampler, vShadowCoord ).g;
}

// apply the water color
float3 ApplyWaterColor(float terrainHeight, float waterDepth, float3 color)
float3 ApplyWaterColor(float3 viewDirection, float terrainHeight, float waterDepth, float3 color)
{
if (waterDepth > 0) {
// With this extra check we get rid of unwanted coloration on steep cliffs when zoomed in,
// but we prevent that terrain tesselation swallows too much of the water when zoomed out
float opacity = saturate(smoothstep(10, 200, CameraPosition.y - WaterElevation) + step(terrainHeight, WaterElevation));
float4 waterColor = tex1D(WaterRampSampler, waterDepth);
color = lerp(color.xyz, waterColor.rgb, waterColor.a * opacity);
}
return color;
}

float3 ApplyWaterColorExponentially(float3 viewDirection, float terrainHeight, float waterDepth, float3 color)
{
if (waterDepth > 0) {
float opacity = saturate(smoothstep(10, 200, CameraPosition.y - WaterElevation) + step(terrainHeight, WaterElevation));
float3 up = float3(0,1,0);
// this is the length that the light travels underwater back to the camera
float oneOverCosV = 1 / max(dot(up, normalize(viewDirection)), 0.0001);
// Light gets absorbed exponentially,
// to simplify, we assume that the light enters vertically into the water.
// We need to multiply by 2 to reach 98% absorption as the waterDepth can't go over 1.
float waterAbsorption = 1 - saturate(exp(-waterDepth * 2 * (1 + oneOverCosV)));
// darken the color first to simulate the light absorption on the way in and out
color *= 1 - waterAbsorption * opacity;
// lerp in the watercolor to simulate the scattered light from the dirty water
float4 waterColor = tex1D(WaterRampSampler, waterAbsorption);
color = lerp(color, waterColor.rgb, waterAbsorption * opacity);
if (MapUsesAdvancedWater()) {
float3 up = float3(0,1,0);
// this is the length that the light travels underwater back to the camera
float oneOverCosV = 1 / max(dot(up, normalize(viewDirection)), 0.0001);
// Light gets absorbed exponentially,
// to simplify, we assume that the light enters vertically into the water.
// We need to multiply by 2 to reach 98% absorption as the waterDepth can't go over 1.
float waterAbsorption = 1 - saturate(exp(-waterDepth * 2 * (1 + oneOverCosV)));
// darken the color first to simulate the light absorption on the way in and out
color *= 1 - waterAbsorption * opacity;
// lerp in the watercolor to simulate the scattered light from the dirty water
float4 waterColor = tex1D(WaterRampSampler, waterAbsorption);
color = lerp(color, waterColor.rgb, waterAbsorption * opacity);
} else {
float4 waterColor = tex1D(WaterRampSampler, waterDepth);
color = lerp(color, waterColor.rgb, waterColor.a * opacity);
}
}
return color;
}
Expand All @@ -427,10 +441,10 @@ float4 CalculateLighting( float3 inNormal, float3 worldTerrain, float3 inAlbedo,
float4 color = float4( 0, 0, 0, 0 );

float shadow = ( inShadows && ( 1 == ShadowsEnabled ) ) ? ComputeShadow(shadowCoords) : 1;
if (IsExperimentalShader()) {
if (ShaderUsesTerrainInfoTexture()) {
float3 position = TerrainScale * worldTerrain;
float mapShadow = tex2D(UpperAlbedoSampler, position.xy).w;
shadow = shadow * mapShadow;
float terrainShadow = tex2D(UpperAlbedoSampler, position.xy).w;
shadow = shadow * terrainShadow;
}

// calculate some specular
Expand All @@ -444,11 +458,7 @@ float4 CalculateLighting( float3 inNormal, float3 worldTerrain, float3 inAlbedo,
light = LightingMultiplier * light + ShadowFillColor * ( 1 - light );
color.rgb = light * inAlbedo;

if (IsExperimentalShader()) {
color.rgb = ApplyWaterColorExponentially(-viewDirection, worldTerrain.z, waterDepth, color);
} else {
color.rgb = ApplyWaterColor(worldTerrain.z, waterDepth, color);
}
color.rgb = ApplyWaterColor(-viewDirection, worldTerrain.z, waterDepth, color);

color.a = 0.01f + (specular*SpecularColor.w);
return color;
Expand Down Expand Up @@ -494,14 +504,14 @@ float GeometrySmith(float3 n, float nDotV, float3 l, float roughness)
return gs1 * gs2;
}

float3 PBR(VS_OUTPUT inV, float4 position, float3 albedo, float3 n, float roughness, float waterDepth) {
float3 PBR(VS_OUTPUT inV, float3 albedo, float3 n, float roughness, float waterDepth) {
// See https://blog.selfshadow.com/publications/s2013-shading-course/

float shadow = 1;
if (ShadowsEnabled == 1) {
float mapShadow = tex2D(UpperAlbedoSampler, position.xy).w; // 1 where sun is, 0 where shadow is
float terrainShadow = tex2D(UpperAlbedoSampler, TerrainScale * inV.mTexWT).w; // 1 where sun is, 0 where shadow is
shadow = tex2D(ShadowSampler, inV.mShadow.xy).g; // 1 where sun is, 0 where shadow is
shadow *= mapShadow;
shadow *= terrainShadow;
}

float facingSpecular = 0.04;
Expand Down Expand Up @@ -839,7 +849,7 @@ float4 TerrainBasisPS( VS_OUTPUT inV ) : COLOR
float4 TerrainBasisPSBiCubic( VS_OUTPUT inV ) : COLOR
{
float4 result;
if (IsExperimentalShader()) {
if (ShaderUsesTerrainInfoTexture()) {
float4 position = TerrainScale * inV.mTexWT;
result = (float4(1, 1, tex2D(UpperAlbedoSampler, position.xy).xy));
} else {
Expand Down Expand Up @@ -959,7 +969,7 @@ float4 TerrainAlbedoXP( VS_OUTPUT pixel) : COLOR
albedo.rgb = light * ( albedo.rgb + specular.rgb );

float waterDepth = tex2D(UtilitySamplerC,pixel.mTexWT*TerrainScale).g;
albedo.rgb = ApplyWaterColor(pixel.mTexWT.z, waterDepth, albedo.rgb);
albedo.rgb = ApplyWaterColor(-pixel.mViewDirection, pixel.mTexWT.z, waterDepth, albedo.rgb);

return float4(albedo.rgb, 0.01f);
}
Expand Down Expand Up @@ -1339,7 +1349,13 @@ float4 DecalsPS( VS_OUTPUT inV, uniform bool inShadows) : COLOR

float waterDepth = tex2Dproj(UtilitySamplerC, inV.mTexWT * TerrainScale).g;

float3 color = CalculateLighting(normal, inV.mTexWT.xyz, decalAlbedo.xyz, decalSpec.r, waterDepth, inV.mShadow, inShadows).xyz;
float3 color;
// We want the decals to behave consistently with the rest of the ground
if (ShaderUsesPbrRendering()) {
color = PBR(inV, decalAlbedo.rgb, normal, 0.9 * (1-decalSpec.r), waterDepth);
} else {
color = CalculateLighting(normal, inV.mTexWT.xyz, decalAlbedo.xyz, decalSpec.r, waterDepth, inV.mShadow, inShadows).xyz;
}

return float4(color.rgb, decalAlbedo.w * decalMask.w * DecalAlpha);
}
Expand Down Expand Up @@ -2021,11 +2037,11 @@ float4 TerrainPBRAlbedoPS ( VS_OUTPUT inV) : COLOR
float roughness = saturate(albedo.a * mask1.w * 2 + 0.01);

float waterDepth = tex2D(UpperAlbedoSampler, position.xy).b;
float3 color = PBR(inV, position, albedo, normal, roughness, waterDepth);
color = ApplyWaterColorExponentially(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color);
float3 color = PBR(inV, albedo, normal, roughness, waterDepth);
color = ApplyWaterColor(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color);

return float4(color, 0.01f);
// SpecularColor.ba, LowerNormalTile, Stratum7AlbedoTile and Stratum7NormalTile are unused now
// SpecularColor.ba, LowerNormalTile and Stratum7AlbedoTile are unused now
// Candidates for configurable values are the rotation matrix values
}

Expand Down Expand Up @@ -2169,14 +2185,14 @@ float4 Terrain001AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR

// x = normals-x
// y = normals-z
// z = unused
// z = water depth
// w = shadows
float4 utility = tex2D(UpperAlbedoSampler, coordinates.xy);
float mapShadow = utility.w;
float4 terrainInfo = tex2D(UpperAlbedoSampler, coordinates.xy);
float terrainShadow = terrainInfo.w;

// disable shadows when game settings tell us to
if (0 == ShadowsEnabled) {
mapShadow = 1.0f;
terrainShadow = 1.0f;
}

// sample the albedo's
Expand All @@ -2203,7 +2219,7 @@ float4 Terrain001AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR

// compute the shadows, combining the baked and dynamic shadows
float shadow = tex2D(ShadowSampler, inV.mShadow.xy).g; // 1 where sun is, 0 where shadow is
shadow = shadow * mapShadow;
shadow = shadow * terrainShadow;

// normalize the pre-computed normal
float3 normal = normalize(2 * SampleScreen(NormalSampler,inV.mTexSS).xyz - 1);
Expand All @@ -2222,13 +2238,13 @@ float4 Terrain001AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR

// compute water ramp intensity
float waterDepth = tex2Dproj(UtilitySamplerC, coordinates).g;
albedo.rgb = ApplyWaterColorExponentially(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, albedo.rgb);
albedo.rgb = ApplyWaterColor(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, albedo.rgb);

return float4(albedo.rgb, 0.01f);
}

/* # Similar to TTerrainXP, but upperAlbedo is used for map-wide #
# textures and we use better water color calculations. #
# textures. #
# It is designed to be a drop-in replacement for TTerrainXP. # */
technique Terrain001 <
string usage = "composite";
Expand Down Expand Up @@ -2395,14 +2411,14 @@ float4 Terrain003AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR

// x = normals-x
// y = normals-z
// z = unused
// z = water depth
// w = shadows
float4 utility01 = tex2D(UpperAlbedoSampler, coordinates.xy);
float mapShadow = utility01.w;
float4 terrainInfo = tex2D(UpperAlbedoSampler, coordinates.xy);
float terrainShadow = terrainInfo.w;

// disable shadows when game settings tell us to
if (0 == ShadowsEnabled) {
mapShadow = 1.0f;
terrainShadow = 1.0f;
}

// x = specular
Expand Down Expand Up @@ -2437,7 +2453,7 @@ float4 Terrain003AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR

// compute the shadows, combining the baked and dynamic shadows
float shadow = tex2D(ShadowSampler, inV.mShadow.xy).g;
shadow = shadow * mapShadow;
shadow = shadow * terrainShadow;

// normalize the pre-computed normal
float3 normal = normalize(2 * SampleScreen(NormalSampler,inV.mTexSS).xyz - 1);
Expand All @@ -2456,7 +2472,7 @@ float4 Terrain003AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR

// compute water ramp intensity
float waterDepth = tex2D(UtilitySamplerC, coordinates).g;
albedo.rgb = ApplyWaterColorExponentially(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, albedo.rgb);
albedo.rgb = ApplyWaterColor(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, albedo.rgb);

return float4(albedo.rgb, 0.01f);
}
Expand Down Expand Up @@ -2615,8 +2631,8 @@ float4 Terrain101AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR
float roughness = saturate(albedo.a * mask1.w * 2 + 0.01);

float waterDepth = tex2D(UpperAlbedoSampler, position.xy).b;
float3 color = PBR(inV, position, albedo, normal, roughness, waterDepth);
color = ApplyWaterColorExponentially(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color);
float3 color = PBR(inV, albedo, normal, roughness, waterDepth);
color = ApplyWaterColor(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color);

return float4(color, 0.01f);
}
Expand Down Expand Up @@ -2800,8 +2816,8 @@ float4 Terrain301AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR
float roughness = saturate(albedo.a * mask1.w * 2 + 0.01);

float waterDepth = tex2D(UpperAlbedoSampler, position.xy).b;
float3 color = PBR(inV, position, albedo, normal, roughness, waterDepth);
color = ApplyWaterColorExponentially(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color);
float3 color = PBR(inV, albedo, normal, roughness, waterDepth);
color = ApplyWaterColor(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color);

return float4(color, 0.01f);
}
Expand Down

0 comments on commit 76393d3

Please sign in to comment.