Fix atmosphere_sky shader (#3514)

* Fix atmosphere_sky shader

https://files.facepunch.com/sampavlovic/1b3011b1/sbox-dev_JyR30GkkzO.png

I am not sure if we should have this on core shaders, this was one of our earliest shaders while we were still experimenting with the engine, but I fixed some issues people were having with it anyway since it's here

When we moved to new Lightbinner we didnt had a dedicated variable for sunlight anymore so I had quickly made a hack to get this working on atmosphere light, but it sucked and had issues with multiple lights in the scene

I don't think this needs to be passed from C# to shader since this is so niche, so I am calculating sun light in the compute shader and done properly now, this makes it more perfomant and reduces the need of redundant operations, from lightbinner directional light has infinite range

Removed atmosphere_sky.fxc, just put everything in the same shader in COMMON

Also fixes envmap probes and DDGI not rendering correctly on sky
https://files.facepunch.com/sampavlovic/1b3011b1/sbox-dev_DpFWvNuLgv.mp4

This does not support multiple directional lights but I dont think anyone would care

* Fix signed comparison in uint
This commit is contained in:
Sam Pavlovic
2025-12-01 03:36:47 -03:00
committed by GitHub
parent 3cd7bf3873
commit eb1c7fda31
2 changed files with 166 additions and 198 deletions

View File

@@ -1,137 +0,0 @@
#define PI 3.141592
#define iSteps 16
#define jSteps 2
float3x3 rotmat(float3 axis, float angle)
{
axis = normalize(axis);
float s = sin(angle);
float c = cos(angle);
float oc = 1.0 - c;
return float3x3(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s,
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s,
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c);
}
float2 rsi(float3 r0, float3 rd, float sr) {
// ray-sphere intersection that assumes
// the sphere is centered at the origin.
// No intersection when result.x > result.y
float a = dot(rd, rd);
float b = 2.0 * dot(rd, r0);
float c = dot(r0, r0) - (sr * sr);
float d = (b*b) - 4.0*a*c;
if (d < 0.0) return float2(1e5,-1e5);
return float2(
(-b - sqrt(d))/(2.0*a),
(-b + sqrt(d))/(2.0*a)
);
}
float3 atmosphere(float3 r, float3 r0, float3 pSun, float iSun, float rPlanet, float rAtmos, float3 kRlh, float kMie, float shRlh, float shMie, float g) {
// Normalize the sun and view directions.
pSun = normalize(pSun);
r = normalize(r);
// Calculate the step size of the primary ray.
float2 p = rsi(r0, r, rAtmos);
if (p.x > p.y) return float3(0,0,0);
p.y = min(p.y, rsi(r0, r, rPlanet).x);
float iStepSize = (p.y - p.x) / float(iSteps);
// Initialize the primary ray time.
float iTime = 0.0;
// Initialize accumulators for Rayleigh and Mie scattering.
float3 totalRlh = float3(0,0,0);
float3 totalMie = float3(0,0,0);
// Initialize optical depth accumulators for the primary ray.
float iOdRlh = 0.0;
float iOdMie = 0.0;
// Calculate the Rayleigh and Mie phases.
float mu = dot(r, pSun);
float mumu = mu * mu;
float gg = g * g;
float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu);
float pMie = 3.0 / (8.0 * PI) * ((1.0 - gg) * (mumu + 1.0)) / (pow(1.0 + gg - 2.0 * mu * g, 1.5) * (2.0 + gg));
// Sample the primary ray.
for (int i = 0; i < iSteps; i++) {
// Calculate the primary ray sample position.
float3 iPos = r0 + r * (iTime + iStepSize * 0.5);
// Calculate the height of the sample.
float iHeight = length(iPos) - rPlanet;
// Calculate the optical depth of the Rayleigh and Mie scattering for this step.
float odStepRlh = exp(-iHeight / shRlh) * iStepSize;
float odStepMie = exp(-iHeight / shMie) * iStepSize;
// Accumulate optical depth.
iOdRlh += odStepRlh;
iOdMie += odStepMie;
// Calculate the step size of the secondary ray.
float jStepSize = rsi(iPos, pSun, rAtmos).y / float(jSteps);
// Initialize the secondary ray time.
float jTime = 0.0;
// Initialize optical depth accumulators for the secondary ray.
float jOdRlh = 0.0;
float jOdMie = 0.0;
// Sample the secondary ray.
for (int j = 0; j < jSteps; j++) {
// Calculate the secondary ray sample position.
float3 jPos = iPos + pSun * (jTime + jStepSize * 0.5);
// Calculate the height of the sample.
float jHeight = length(jPos) - rPlanet;
// Accumulate the optical depth.
jOdRlh += exp(-jHeight / shRlh) * jStepSize;
jOdMie += exp(-jHeight / shMie) * jStepSize;
// Increment the secondary ray time.
jTime += jStepSize;
}
// Calculate attenuation.
float3 attn = exp(-(kMie * (iOdMie + jOdMie) + kRlh * (iOdRlh + jOdRlh)));
// Accumulate scattering.
totalRlh += odStepRlh * attn;
totalMie += odStepMie * attn;
// Increment the primary ray time.
iTime += iStepSize;
}
// Calculate and return the final color.
return iSun * (pRlh * kRlh * totalRlh + pMie * kMie * totalMie);
}
float smart_inverse_dot(float dt, float coeff){
return 1.0 - (1.0 / (1.0 + dt * coeff));
}
float3 getSunColorDirectly(float roughness, float3 sunDir){
float3 sunBase = 15.0;
float dt = max(0.0, (dot(sunDir, float3(0,0,1) )));
float dtx = smoothstep(-0.0, 0.1, dt);
float dt2 = 0.9 + 0.1 * (1.0 - dt);
float st = max(0.0, 1.0 - smart_inverse_dot(dt, 11.0));
float3 supersundir = max(0.0, 1.0) - st * 4.0 * pow(float3(50.0/255.0, 111.0/255.0, 153.0/255.0), 2.4);
// supersundir /= length(supersundir) * 1.0 + 1.0;
return supersundir * 4.0 ;
//return mix(supersundir * 1.0, sunBase, st);
//return max(float3(0.3, 0.3, 0.0), ( sunBase - float3(5.5, 18.0, 20.4) * pow(1.0 - dt, 8.0)));
}

View File

@@ -22,6 +22,103 @@ COMMON
{
#include "system.fxc"
#include "vr_common.fxc"
static const float kAtmosPi = 3.141592f;
static const int kAtmosPrimarySteps = 16;
static const int kAtmosSecondarySteps = 2;
float2 RaySphereIntersection( float3 origin, float3 direction, float radius )
{
float a = dot( direction, direction );
float b = 2.0f * dot( direction, origin );
float c = dot( origin, origin ) - ( radius * radius );
float discriminant = ( b * b ) - 4.0f * a * c;
if ( discriminant < 0.0f )
{
return float2( 1e5f, -1e5f );
}
float sqrtDiscriminant = sqrt( discriminant );
float invDenominator = 0.5f / a;
return float2(
( -b - sqrtDiscriminant ) * invDenominator,
( -b + sqrtDiscriminant ) * invDenominator
);
}
float3 atmosphere( float3 viewDir, float3 rayOrigin, float3 sunDirection, float sunIntensity, float planetRadius, float atmosphereRadius, float3 rayleighCoefficients, float mieCoefficient, float rayleighScaleHeight, float mieScaleHeight, float miePreferredScatteringDirection )
{
sunDirection = normalize( sunDirection );
viewDir = normalize( viewDir );
float2 intersections = RaySphereIntersection( rayOrigin, viewDir, atmosphereRadius );
if ( intersections.x > intersections.y )
{
return 0.0f;
}
intersections.y = min( intersections.y, RaySphereIntersection( rayOrigin, viewDir, planetRadius ).x );
float primaryStepSize = ( intersections.y - intersections.x ) / float( kAtmosPrimarySteps );
float rayleighOpticalDepth = 0.0f;
float mieOpticalDepth = 0.0f;
float3 accumulatedRayleigh = 0.0f;
float3 accumulatedMie = 0.0f;
float cosTheta = dot( viewDir, sunDirection );
float cosThetaSquared = cosTheta * cosTheta;
float g = miePreferredScatteringDirection;
float gSquared = g * g;
float rayleighPhase = ( 3.0f / ( 16.0f * kAtmosPi ) ) * ( 1.0f + cosThetaSquared );
float miePhaseDenominator = pow( 1.0f + gSquared - 2.0f * cosTheta * g, 1.5f ) * ( 2.0f + gSquared );
float miePhaseNumerator = ( 1.0f - gSquared ) * ( cosThetaSquared + 1.0f );
float miePhase = ( 3.0f / ( 8.0f * kAtmosPi ) ) * ( miePhaseNumerator / miePhaseDenominator );
float primaryTime = 0.0f;
[loop]
for ( int primaryStep = 0; primaryStep < kAtmosPrimarySteps; ++primaryStep )
{
float3 samplePosition = rayOrigin + viewDir * ( primaryTime + primaryStepSize * 0.5f );
float sampleHeight = length( samplePosition ) - planetRadius;
float rayleighStep = exp( -sampleHeight / rayleighScaleHeight ) * primaryStepSize;
float mieStep = exp( -sampleHeight / mieScaleHeight ) * primaryStepSize;
rayleighOpticalDepth += rayleighStep;
mieOpticalDepth += mieStep;
float secondaryStepSize = RaySphereIntersection( samplePosition, sunDirection, atmosphereRadius ).y / float( kAtmosSecondarySteps );
float secondaryTime = 0.0f;
float secondaryRayleighOpticalDepth = 0.0f;
float secondaryMieOpticalDepth = 0.0f;
[loop]
for ( int secondaryStep = 0; secondaryStep < kAtmosSecondarySteps; ++secondaryStep )
{
float3 secondaryPosition = samplePosition + sunDirection * ( secondaryTime + secondaryStepSize * 0.5f );
float secondaryHeight = length( secondaryPosition ) - planetRadius;
secondaryRayleighOpticalDepth += exp( -secondaryHeight / rayleighScaleHeight ) * secondaryStepSize;
secondaryMieOpticalDepth += exp( -secondaryHeight / mieScaleHeight ) * secondaryStepSize;
secondaryTime += secondaryStepSize;
}
float3 transmittance = exp( -( mieCoefficient * ( mieOpticalDepth + secondaryMieOpticalDepth ) + rayleighCoefficients * ( rayleighOpticalDepth + secondaryRayleighOpticalDepth ) ) );
accumulatedRayleigh += rayleighStep * transmittance;
accumulatedMie += mieStep * transmittance;
primaryTime += primaryStepSize;
}
float3 rayleighContribution = rayleighPhase * rayleighCoefficients * accumulatedRayleigh;
float3 mieContribution = ( miePhase * mieCoefficient ) * accumulatedMie;
return sunIntensity * ( rayleighContribution + mieContribution );
}
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -34,6 +131,8 @@ struct VS_INPUT
struct PS_INPUT
{
float3 vPositionWs : TEXCOORD1;
nointerpolation float3 SunDirectionWs : TEXCOORD2;
nointerpolation float3 SunColor : TEXCOORD3;
#if ( PROGRAM == VFX_PROGRAM_VS )
float4 vPositionPs : SV_Position;
@@ -56,17 +155,51 @@ VS
// Constants ----------------------------------------------------------------------------------------------------------------------------------------------
// Main ---------------------------------------------------------------------------------------------------------------------------------------------------
int FindSunLightIndex()
{
[loop]
for ( uint lightIndex = 0u; lightIndex < uint( NumDynamicLights ); ++lightIndex )
{
BinnedLight light = BinnedLightBuffer[ lightIndex ];
float3 lightPosition = light.GetPosition();
float FLOAT32_MAX = 3.402823466e+38;
if ( light.GetRadius() == FLOAT32_MAX )
{
return lightIndex;
}
}
return -1;
}
void FetchSunLight( out float3 sunDirectionWs, out float3 sunColor )
{
sunDirectionWs = float3( 0.0f, 0.0f, 0.0f );
sunColor = float3( 0.0f, 0.0f, 0.0f );
int sunLightIndex = FindSunLightIndex();
if ( sunLightIndex == -1 )
return;
BinnedLight sunLight = BinnedLightBuffer[ sunLightIndex ];
sunDirectionWs = normalize( sunLight.GetPosition() );
sunColor = sunLight.GetColor();
}
PS_INPUT MainVs( const VS_INPUT i )
{
PS_INPUT o;
float flSkyboxScale = g_flNearPlane + g_flFarPlane;
float3 vPositionWs = g_vCameraPositionWs.xyz + i.vPositionOs.xyz * flSkyboxScale;
o.vPositionWs = i.vPositionOs.xyz;
o.vPositionPs = Position3WsToPs( vPositionWs );
o.vPositionWs = vPositionWs;
float3 vPositionWs = g_vCameraPositionWs.xyz + i.vPositionOs.xyz * 3.1415f;
o.vPositionPs.xyzw = Position3WsToPs( vPositionWs.xyz );
o.vPositionWs.xyz = vPositionWs;
// Precalculate sun light direction and color instead of doing it in the pixel shader
FetchSunLight( o.SunDirectionWs, o.SunColor );
return o;
}
@@ -76,7 +209,6 @@ VS
PS
{
#include "vr_lighting.fxc"
#include "atmosphere_sky.fxc"
#include "volumetric_fog.fxc"
// Combos -------------------------------------------------------------------------------------------------------------------------------------------------
@@ -100,7 +232,6 @@ PS
// Main ---------------------------------------------------------------------------------------------------------------------------------------------------
// Could be better
float noise( in float3 x )
{
float3 p = floor(x);
@@ -111,48 +242,32 @@ PS
return lerp( rg.x, rg.y, f.z );
}
float3 GetSunDir(BinnedLight light)
float3 GetAtmosphere( float3 ray, float3 sunDirectionWs, float3 sunColor )
{
return normalize( light.GetPosition() );
//return normalize( float3( 1,0, ( fmod(g_flTime * 0.1, 0.5) - 0.25 ) ) );
}
float3 GetSunColor(BinnedLight light)
{
return light.GetColor();
}
float3 GetAtmosphere(float3 ray, BinnedLight light ){
float3 fSunIntensity = GetSunColor( light );
float fPlanetSize = 6371e3;
float fAtmosphereSize = 100e3;
float fSeaLevel = 512.0f;
float3 uSunPos = GetSunDir( light );
float3 color = atmosphere
(
ray.xzy, // normalized ray direction
float3(0,fPlanetSize + g_vCameraPositionWs.z + fSeaLevel,0), // ray origin
uSunPos.xzy, // position of the sun
50, // intensity of the sun
fPlanetSize, // radius of the planet in meters
fPlanetSize + fAtmosphereSize, // radius of the atmosphere in meters
float3(5.5e-6, 13.0e-6, 22.4e-6), // Rayleigh scattering coefficient
21e-6, // Mie scattering coefficient
8e3, // Rayleigh scale height
1.2e3, // Mie scale height
0.758 // Mie preferred scattering direction
ray.xzy, // normalized ray direction
float3(0,fPlanetSize + g_vCameraPositionWs.z + fSeaLevel,0), // ray origin
sunDirectionWs.xzy, // position of the sun
50, // intensity of the sun
fPlanetSize, // radius of the planet in meters
fPlanetSize + fAtmosphereSize, // radius of the atmosphere in meters
float3(5.5e-6, 13.0e-6, 22.4e-6), // Rayleigh scattering coefficient
21e-6, // Mie scattering coefficient
8e3, // Rayleigh scale height
1.2e3, // Mie scale height
0.758 // Mie preferred scattering direction
);
return color * fSunIntensity;
return color * sunColor;
}
float Stars( in float3 vRay )
{
//vRay.z = abs(vRay.z);
const float fStarScale = 0.3;
const float fStarAmount = 1.0;
@@ -170,40 +285,30 @@ PS
return vStars;
}
float3 Sun( in float3 vRay, BinnedLight light )
float3 Sun( in float3 vRay, float3 sunDirectionWs, float3 sunColor )
{
float fSun = pow( saturate(dot( vRay, GetSunDir( light ) ) + 0.00025 ), 10000.0f ) * 10;
float fSun = pow( saturate(dot( vRay, sunDirectionWs ) + 0.00025 ), 10000.0f ) * 10;
fSun *= saturate( vRay.z * 100 );
return GetSunColor( light ) * fSun * 5;
fSun *= saturate( vRay.z ); // Fade sun when below horizon
return sunColor * fSun;
}
PS_OUTPUT MainPs( PS_INPUT i )
{
PS_OUTPUT o;
// Generate Object->World matrix and animation scale
float3 vPositionWs = i.vPositionWs.xyz;
float3 vPositionWs = i.vPositionWs;
float3 vRay = normalize( vPositionWs - g_vCameraPositionWs );
float3 vCamDir = g_vCameraDirWs;
float3 vColor;
float3 vColor = Stars( vRay );
vColor = Stars( vRay );
for (uint j = 0; j < NumDynamicLights; j++)
{
BinnedLight light = BinnedLightBuffer[j];
const float sunDirLengthSq = dot( i.SunDirectionWs, i.SunDirectionWs );
const float sunColorLengthSq = dot( i.SunColor, i.SunColor );
if ( sunDirLengthSq > 0.0f && sunColorLengthSq > 0.0f )
{
vColor += GetAtmosphere( vRay, i.SunDirectionWs, i.SunColor );
vColor += Sun( vRay, i.SunDirectionWs, i.SunColor );
}
if (length( light.GetPosition() ) < 10000.0f)
continue;
vColor += GetAtmosphere(vRay, light);
vColor += Sun(vRay, light);
}
//vColor += GetAtmosphere( vRay );
//vColor += Sun( vRay );
o.vColor0.rgba = float4( vColor , 1.0 );
o.vColor0 = float4( vColor, 1.0 );
o.vColor0.rgb = ApplyVolumetricFog( o.vColor0.rgb, i.vPositionWs, i.vPositionSs.xy );
return o;
}