Skip to content

Commit 76393d3

Browse files
authored
Shader rework (FAForever#6485)
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.
1 parent 8d5096b commit 76393d3

File tree

3 files changed

+81
-63
lines changed

3 files changed

+81
-63
lines changed

changelog/snippets/other.6485.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- (#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.
2+
-

effects/mesh.fx

+2-2
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ struct SHIELDIMPACT_VERTEX
378378
///
379379
///////////////////////////////////////
380380

381-
bool IsExperimentalShader() {
381+
bool MapUsesAdvancedWater() {
382382
// lightMultiplier is one of the few variables that is driven by the map,
383383
// but accessible by the mesh shader.
384384
return lightMultiplier > 2.1;
@@ -602,7 +602,7 @@ float3 ApplyWaterColor(float depth, float3 viewDirection, float3 color, float3 e
602602
// disable the whole thing on land-only maps
603603
if (surfaceElevation > 0) {
604604
// we need this switch to make it consistent with the terrain shader coloration
605-
if (IsExperimentalShader()) {
605+
if (MapUsesAdvancedWater()) {
606606
// We need to multiply by 2 to match the terrain shader.
607607
float scaledDepth = (-depth / (surfaceElevation - abyssElevation)) * 2;
608608
float3 up = float3(0,1,0);

effects/terrain.fx

+77-61
Original file line numberDiff line numberDiff line change
@@ -344,16 +344,36 @@ VS_OUT FixedFuncVS( VS_IN In )
344344
return Out;
345345
}
346346

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

352-
// in order to trigger this you can set the albedo scale to be bigger than the map
353-
// size. Use the value 10000 to be safe for any map
354+
// In order to trigger this you need to set the albedo scale to be bigger than the
355+
// map size in the editor. Use the value 10000 to be safe for any map
354356
return UpperAlbedoTile.x < 1.0;
355357
}
356358

359+
bool ShaderUsesPbrRendering() {
360+
// The tile value basically says how often the texture gets repeated on the map.
361+
// A value less than one doesn't make sense under normal conditions, so it is
362+
// relatively save to use it as our switch.
363+
// We use the stratum 7 normal slot to store the roughness texture, so we don't need
364+
// the tile value for anything else.
365+
366+
// In order to trigger this you need to set the normal scale to be bigger than the
367+
// map size in the editor. Use the value 10000 to be safe for any map
368+
return Stratum7NormalTile.x < 1.0;
369+
}
370+
371+
bool MapUsesAdvancedWater() {
372+
// LightingMultiplier is one of the few variables that is driven by the map,
373+
// but accessible by the mesh shader.
374+
return LightingMultiplier > 2.1;
375+
}
376+
357377
// sample a texture that is another buffer the same size as the one
358378
// we are rendering into and with the viewport setup the same way.
359379
float4 SampleScreen(sampler inSampler, float4 inTex)
@@ -388,35 +408,29 @@ float ComputeShadow( float4 vShadowCoord )
388408
return tex2D( ShadowSampler, vShadowCoord ).g;
389409
}
390410

391-
// apply the water color
392-
float3 ApplyWaterColor(float terrainHeight, float waterDepth, float3 color)
411+
float3 ApplyWaterColor(float3 viewDirection, float terrainHeight, float waterDepth, float3 color)
393412
{
394413
if (waterDepth > 0) {
395414
// With this extra check we get rid of unwanted coloration on steep cliffs when zoomed in,
396415
// but we prevent that terrain tesselation swallows too much of the water when zoomed out
397416
float opacity = saturate(smoothstep(10, 200, CameraPosition.y - WaterElevation) + step(terrainHeight, WaterElevation));
398-
float4 waterColor = tex1D(WaterRampSampler, waterDepth);
399-
color = lerp(color.xyz, waterColor.rgb, waterColor.a * opacity);
400-
}
401-
return color;
402-
}
403-
404-
float3 ApplyWaterColorExponentially(float3 viewDirection, float terrainHeight, float waterDepth, float3 color)
405-
{
406-
if (waterDepth > 0) {
407-
float opacity = saturate(smoothstep(10, 200, CameraPosition.y - WaterElevation) + step(terrainHeight, WaterElevation));
408-
float3 up = float3(0,1,0);
409-
// this is the length that the light travels underwater back to the camera
410-
float oneOverCosV = 1 / max(dot(up, normalize(viewDirection)), 0.0001);
411-
// Light gets absorbed exponentially,
412-
// to simplify, we assume that the light enters vertically into the water.
413-
// We need to multiply by 2 to reach 98% absorption as the waterDepth can't go over 1.
414-
float waterAbsorption = 1 - saturate(exp(-waterDepth * 2 * (1 + oneOverCosV)));
415-
// darken the color first to simulate the light absorption on the way in and out
416-
color *= 1 - waterAbsorption * opacity;
417-
// lerp in the watercolor to simulate the scattered light from the dirty water
418-
float4 waterColor = tex1D(WaterRampSampler, waterAbsorption);
419-
color = lerp(color, waterColor.rgb, waterAbsorption * opacity);
417+
if (MapUsesAdvancedWater()) {
418+
float3 up = float3(0,1,0);
419+
// this is the length that the light travels underwater back to the camera
420+
float oneOverCosV = 1 / max(dot(up, normalize(viewDirection)), 0.0001);
421+
// Light gets absorbed exponentially,
422+
// to simplify, we assume that the light enters vertically into the water.
423+
// We need to multiply by 2 to reach 98% absorption as the waterDepth can't go over 1.
424+
float waterAbsorption = 1 - saturate(exp(-waterDepth * 2 * (1 + oneOverCosV)));
425+
// darken the color first to simulate the light absorption on the way in and out
426+
color *= 1 - waterAbsorption * opacity;
427+
// lerp in the watercolor to simulate the scattered light from the dirty water
428+
float4 waterColor = tex1D(WaterRampSampler, waterAbsorption);
429+
color = lerp(color, waterColor.rgb, waterAbsorption * opacity);
430+
} else {
431+
float4 waterColor = tex1D(WaterRampSampler, waterDepth);
432+
color = lerp(color, waterColor.rgb, waterColor.a * opacity);
433+
}
420434
}
421435
return color;
422436
}
@@ -427,10 +441,10 @@ float4 CalculateLighting( float3 inNormal, float3 worldTerrain, float3 inAlbedo,
427441
float4 color = float4( 0, 0, 0, 0 );
428442

429443
float shadow = ( inShadows && ( 1 == ShadowsEnabled ) ) ? ComputeShadow(shadowCoords) : 1;
430-
if (IsExperimentalShader()) {
444+
if (ShaderUsesTerrainInfoTexture()) {
431445
float3 position = TerrainScale * worldTerrain;
432-
float mapShadow = tex2D(UpperAlbedoSampler, position.xy).w;
433-
shadow = shadow * mapShadow;
446+
float terrainShadow = tex2D(UpperAlbedoSampler, position.xy).w;
447+
shadow = shadow * terrainShadow;
434448
}
435449

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

447-
if (IsExperimentalShader()) {
448-
color.rgb = ApplyWaterColorExponentially(-viewDirection, worldTerrain.z, waterDepth, color);
449-
} else {
450-
color.rgb = ApplyWaterColor(worldTerrain.z, waterDepth, color);
451-
}
461+
color.rgb = ApplyWaterColor(-viewDirection, worldTerrain.z, waterDepth, color);
452462

453463
color.a = 0.01f + (specular*SpecularColor.w);
454464
return color;
@@ -494,14 +504,14 @@ float GeometrySmith(float3 n, float nDotV, float3 l, float roughness)
494504
return gs1 * gs2;
495505
}
496506

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

500510
float shadow = 1;
501511
if (ShadowsEnabled == 1) {
502-
float mapShadow = tex2D(UpperAlbedoSampler, position.xy).w; // 1 where sun is, 0 where shadow is
512+
float terrainShadow = tex2D(UpperAlbedoSampler, TerrainScale * inV.mTexWT).w; // 1 where sun is, 0 where shadow is
503513
shadow = tex2D(ShadowSampler, inV.mShadow.xy).g; // 1 where sun is, 0 where shadow is
504-
shadow *= mapShadow;
514+
shadow *= terrainShadow;
505515
}
506516

507517
float facingSpecular = 0.04;
@@ -839,7 +849,7 @@ float4 TerrainBasisPS( VS_OUTPUT inV ) : COLOR
839849
float4 TerrainBasisPSBiCubic( VS_OUTPUT inV ) : COLOR
840850
{
841851
float4 result;
842-
if (IsExperimentalShader()) {
852+
if (ShaderUsesTerrainInfoTexture()) {
843853
float4 position = TerrainScale * inV.mTexWT;
844854
result = (float4(1, 1, tex2D(UpperAlbedoSampler, position.xy).xy));
845855
} else {
@@ -959,7 +969,7 @@ float4 TerrainAlbedoXP( VS_OUTPUT pixel) : COLOR
959969
albedo.rgb = light * ( albedo.rgb + specular.rgb );
960970

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

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

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

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

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

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

20272043
return float4(color, 0.01f);
2028-
// SpecularColor.ba, LowerNormalTile, Stratum7AlbedoTile and Stratum7NormalTile are unused now
2044+
// SpecularColor.ba, LowerNormalTile and Stratum7AlbedoTile are unused now
20292045
// Candidates for configurable values are the rotation matrix values
20302046
}
20312047

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

21702186
// x = normals-x
21712187
// y = normals-z
2172-
// z = unused
2188+
// z = water depth
21732189
// w = shadows
2174-
float4 utility = tex2D(UpperAlbedoSampler, coordinates.xy);
2175-
float mapShadow = utility.w;
2190+
float4 terrainInfo = tex2D(UpperAlbedoSampler, coordinates.xy);
2191+
float terrainShadow = terrainInfo.w;
21762192

21772193
// disable shadows when game settings tell us to
21782194
if (0 == ShadowsEnabled) {
2179-
mapShadow = 1.0f;
2195+
terrainShadow = 1.0f;
21802196
}
21812197

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

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

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

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

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

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

23962412
// x = normals-x
23972413
// y = normals-z
2398-
// z = unused
2414+
// z = water depth
23992415
// w = shadows
2400-
float4 utility01 = tex2D(UpperAlbedoSampler, coordinates.xy);
2401-
float mapShadow = utility01.w;
2416+
float4 terrainInfo = tex2D(UpperAlbedoSampler, coordinates.xy);
2417+
float terrainShadow = terrainInfo.w;
24022418

24032419
// disable shadows when game settings tell us to
24042420
if (0 == ShadowsEnabled) {
2405-
mapShadow = 1.0f;
2421+
terrainShadow = 1.0f;
24062422
}
24072423

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

24382454
// compute the shadows, combining the baked and dynamic shadows
24392455
float shadow = tex2D(ShadowSampler, inV.mShadow.xy).g;
2440-
shadow = shadow * mapShadow;
2456+
shadow = shadow * terrainShadow;
24412457

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

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

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

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

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

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

28062822
return float4(color, 0.01f);
28072823
}

0 commit comments

Comments
 (0)