Bliss-Shader/shaders/lib/volumetricClouds.glsl
2024-11-16 18:15:48 +01:00

483 lines
17 KiB
GLSL

#define ALTOSTRATUS_LAYER 2
#define LARGECUMULUS_LAYER 1
#define SMALLCUMULUS_LAYER 0
uniform int worldDay;
uniform int worldTime;
float cloud_movement = (worldTime + mod(worldDay,100)*24000.0) / 24.0 * Cloud_Speed;
float densityAtPos(in vec3 pos){
pos /= 18.;
pos.xz *= 0.5;
vec3 p = floor(pos);
vec3 f = fract(pos);
vec2 uv = p.xz + f.xz + p.y * vec2(0.0,193.0);
vec2 coord = uv / 512.0;
//The y channel has an offset to avoid using two textures fetches
vec2 xy = texture2D(noisetex, coord).yx;
return mix(xy.r,xy.g, f.y);
}
float getCloudShape(int LayerIndex, int LOD, in vec3 position, float minHeight, float maxHeight){
vec3 samplePos = position*vec3(1.0, 1.0/48.0, 1.0)/4.0;
float coverage = 0.0;
float shape = 0.0;
float largeCloud = 0.0;
float smallCloud = 0.0;
if(LayerIndex == ALTOSTRATUS_LAYER){
coverage = dailyWeatherParams0.z;
largeCloud = texture2D(noisetex, (position.xz + cloud_movement)/100000.).b;
smallCloud = 1.0 - texture2D(noisetex, (position.xz - cloud_movement)/7500. - vec2(1.0-largeCloud, -largeCloud)/5.0).b;
smallCloud = largeCloud + smallCloud * 0.4 * clamp(1.5-largeCloud,0.0,1.0);
float val = coverage;
shape = min(max(val - smallCloud,0.0)/sqrt(val),1.0);
shape *= shape;
return shape;
}
if(LayerIndex == LARGECUMULUS_LAYER){
coverage = dailyWeatherParams0.y;
largeCloud = texture2D(noisetex, (samplePos.zx + cloud_movement*2.0)/10000.0).b;
smallCloud = texture2D(noisetex, (samplePos.zx - cloud_movement*2.0)/2500.0).b;
smallCloud = abs(largeCloud* -0.7) + smallCloud;
float val = coverage;
shape = min(max(val - smallCloud,0.0)/sqrt(val),1.0) ;
}
if(LayerIndex == SMALLCUMULUS_LAYER){
coverage = dailyWeatherParams0.x;
largeCloud = texture2D(noisetex, (samplePos.xz + cloud_movement)/5000.0).b;
smallCloud = 1.0-texture2D(noisetex, (samplePos.xz - cloud_movement)/500.0).r;
smallCloud = abs(largeCloud-0.6) + smallCloud*smallCloud;
float val = coverage;
shape = min(max(val - smallCloud,0.0)/sqrt(val),1.0) ;
// shape = abs(largeCloud*2.0 - 1.2)*0.5 - (1.0-smallCloud);
}
// clamp density of the cloud within its upper/lower bounds
shape = min(min(shape, clamp(maxHeight - position.y,0,1)), 1.0 - clamp(minHeight - position.y,0,1));
// carve out the upper part of clouds. make sure it rounds out at its upper bound
float topShape = min(max(maxHeight-position.y,0.0) / max(maxHeight-minHeight,1.0),1.0);
topShape = min(exp(-0.5 * (1.0-topShape)), 1.0-pow(1.0-topShape,5.0));
// round out the bottom part slightly
float bottomShape = 1.0-pow(1.0-min(max(position.y-minHeight,0.0) / 25.0, 1.0), 5.0);
shape = max((shape - 1.0) + topShape * bottomShape,0.0);
/// erosion noise
if(shape > 0.001){
float erodeAmount = 0.5;
// shrink the coverage slightly so it is a similar shape to clouds with erosion. this helps cloud lighting and cloud shadows.
if (LOD < 1) return max(shape - 0.27*erodeAmount,0.0);
samplePos.xz -= cloud_movement/4.0;
// da wind
// if(LayerIndex == SMALLCUMULUS_LAYER)
samplePos.xz += pow( max(position.y - (minHeight+20.0), 0.0) / (max(maxHeight-minHeight,1.0)*0.20), 1.5);
float erosion = 0.0;
if(LayerIndex == SMALLCUMULUS_LAYER){
erosion += (1.0-densityAtPos(samplePos * 200.0)) * sqrt(1.0-shape);
float falloff = 1.0 - clamp((maxHeight - position.y)/100.0,0.0,1.0);
erosion += abs(densityAtPos(samplePos * 600.0) - falloff) * 0.75 * (1.0-shape) * (1.0-falloff*0.25);
erosion = erosion*erosion*erosion*erosion;
}
if(LayerIndex == LARGECUMULUS_LAYER){
erosion += (1.0 - densityAtPos(samplePos * 100.0)) * sqrt(1.0-shape);
float falloff = 1.0 - clamp((maxHeight - position.y)/200.0,0.0,1.0);
erosion += abs(densityAtPos(samplePos * 450.0) - falloff) * 0.75 * (1.0-shape) * (1.0-falloff*0.5);
erosion = erosion*erosion*erosion*erosion;
}
return max(shape - erosion*erodeAmount,0.0);
} else return 0.0;
}
float getPlanetShadow(vec3 playerPos, vec3 WsunVec){
float planetShadow = min(max(playerPos.y - (-100.0 + 1.0 / abs(WsunVec.y*0.1)),0.0) / 100.0, 1.0);
planetShadow = mix(pow(1.0-pow(1.0-planetShadow,2.0),2.0), 1.0, pow(abs(WsunVec.y),2.0));
return planetShadow;
}
float GetCloudShadow(vec3 playerPos, vec3 sunVector){
float totalShadow = getPlanetShadow(playerPos, sunVector);
vec3 startPosition = playerPos;
float cloudShadows = 0.0;
#ifdef CloudLayer0
startPosition = playerPos + sunVector / abs(sunVector.y) * max((CloudLayer0_height + 20.0) - playerPos.y, 0.0);
cloudShadows = getCloudShape(SMALLCUMULUS_LAYER, 0, startPosition, CloudLayer0_height, CloudLayer0_height+100.0)*dailyWeatherParams1.x;
#endif
#ifdef CloudLayer1
startPosition = playerPos + sunVector / abs(sunVector.y) * max((CloudLayer1_height + 20.0) - playerPos.y, 0.0);
cloudShadows += getCloudShape(LARGECUMULUS_LAYER, 0, startPosition, CloudLayer1_height, CloudLayer1_height+100.0)*dailyWeatherParams1.y;
#endif
#ifdef CloudLayer2
startPosition = playerPos + sunVector / abs(sunVector.y) * max(CloudLayer2_height - playerPos.y, 0.0);
cloudShadows += getCloudShape(ALTOSTRATUS_LAYER, 0, startPosition, CloudLayer2_height, CloudLayer2_height)*dailyWeatherParams1.z * (1.0-abs(WsunVec.y));
#endif
#if defined CloudLayer0 || defined CloudLayer1 || defined CloudLayer2
totalShadow *= exp((cloudShadows*cloudShadows) * -200.0);
#endif
return totalShadow;
}
#ifndef CLOUDSHADOWSONLY
float phaseCloud(float x, float g){
float gg = g * g;
return (gg * -0.25 + 0.25) * pow(-2.0 * (g * x) + (gg + 1.0), -1.5) / 3.14;
}
float getCloudScattering(
int LayerIndex,
vec3 rayPosition,
vec3 sunVector,
float dither,
float minHeight,
float maxHeight,
float density
){
int samples = 3;
int LOD = 0;
if(LayerIndex == ALTOSTRATUS_LAYER) samples = 2;
float shadow = 0.0;
vec3 shadowRayPosition = vec3(0.0);
for (int i = 0; i < samples; i++){
if(LayerIndex == ALTOSTRATUS_LAYER){
shadowRayPosition = rayPosition + sunVector * (1.0 + i * dither) / (pow(abs(sunVector.y*0.5),3.0) * 0.995 + 0.005);
}else{
shadowRayPosition = rayPosition + sunVector * (1.0 + i + dither)*20.0;
}
// float fadeddensity = density * pow(clamp((shadowRayPosition.y - minHeight)/(max(maxHeight-minHeight,1.0)*0.25),0.0,1.0),2.0);
shadow += getCloudShape(LayerIndex, LOD, shadowRayPosition, minHeight, maxHeight) * density;
}
return shadow;
}
vec3 getCloudLighting(
float shape,
float shapeFaded,
float sunShadowMask,
vec3 directLightCol,
vec3 directLightCol_multi,
float indirectShadowMask,
vec3 indirectLightCol,
float distanceFade
){
float powderEffect = 1.0 - exp(-3.0*shapeFaded);
vec3 directScattering = directLightCol * exp(-10.0*sunShadowMask) + directLightCol_multi * exp(-3.0*(sunShadowMask - (1.0-indirectShadowMask*indirectShadowMask)*0.5)) * powderEffect;
vec3 indirectScattering = indirectLightCol * mix(1.0, exp2(-5.0*shape), (indirectShadowMask*indirectShadowMask) * distanceFade);
// return indirectScattering;
// return directScattering;
return indirectScattering + directScattering;
}
vec4 raymarchCloud(
int LayerIndex,
float samples,
vec3 rayPosition,
vec3 rayDirection,
float dither,
float minHeight,
float maxHeight,
vec3 sunVector,
vec3 sunScattering,
vec3 sunMultiScattering,
vec3 skyScattering,
float distanceFade,
float referenceDistance
){
vec3 color = vec3(0.0);
float totalAbsorbance = 1.0;
float planetShadow = getPlanetShadow(rayPosition, sunVector);
sunScattering *= planetShadow;
sunMultiScattering *= planetShadow;
float distanceFactor = length(rayDirection);
if(LayerIndex == ALTOSTRATUS_LAYER){
float density = dailyWeatherParams1.z;
bool ifAboveOrBelowPlane = max(mix(-1.0, 1.0, clamp(cameraPosition.y - minHeight,0.0,1.0)) * normalize(rayDirection).y,0.0) > 0.0;
// check if the ray staring position is going farther than the reference distance, if yes, dont begin marching. this is to check for intersections with the world.
// check if the camera is above or below the cloud plane, so it doesnt waste work on the opposite hemisphere
#ifndef VL_CLOUDS_DEFERRED
if(length(rayPosition - cameraPosition) > referenceDistance || ifAboveOrBelowPlane) return vec4(color, totalAbsorbance);
#else
if(ifAboveOrBelowPlane) return vec4(color, totalAbsorbance);
#endif
float shape = getCloudShape(LayerIndex, 1, rayPosition, minHeight, maxHeight);
float shapeWithDensity = shape*density;
// check if the pixel has visible clouds before doing work.
if(shapeWithDensity > 1e-5){
// can add the initial cloud shape sample for a free shadow starting step :D
float sunShadowMask = (shapeWithDensity + getCloudScattering(LayerIndex, rayPosition, sunVector, dither, minHeight, maxHeight, density)) * (1.0-abs(WsunVec.y));
float indirectShadowMask = 0.5;
vec3 lighting = getCloudLighting(shapeWithDensity, shapeWithDensity, sunShadowMask, sunScattering, sunMultiScattering, indirectShadowMask, skyScattering, distanceFade);
float densityCoeff = exp(-distanceFactor*shapeWithDensity);
color += (lighting - lighting * densityCoeff) * totalAbsorbance;
totalAbsorbance *= densityCoeff;
}
return vec4(color, totalAbsorbance);
}
if(LayerIndex < ALTOSTRATUS_LAYER){
float density = dailyWeatherParams1.x;
if(LayerIndex == LARGECUMULUS_LAYER) density = dailyWeatherParams1.y;
float skylightOcclusion = 1.0;
#if defined CloudLayer1 && defined CloudLayer0
if(LayerIndex == SMALLCUMULUS_LAYER) {
float upperLayerOcclusion = getCloudShape(LARGECUMULUS_LAYER, 0, rayPosition + vec3(0.0,1.0,0.0) * max((CloudLayer1_height+20) - rayPosition.y,0.0), CloudLayer1_height, CloudLayer1_height+100.0);
skylightOcclusion = mix(mix(0.0,0.2,dailyWeatherParams1.y), 1.0, pow(1.0 - upperLayerOcclusion*dailyWeatherParams1.y,2));
}
skylightOcclusion = mix(1.0, skylightOcclusion, distanceFade);
#endif
for(int i = 0; i < int(samples); i++) {
// check if the ray staring position is going farther than the reference distance, if yes, dont begin marching. this is to check for intersections with the world.
#ifndef VL_CLOUDS_DEFERRED
if(length(rayPosition - cameraPosition) > referenceDistance) break;
#endif
// check if the pixel is in the bounding box before doing work.
if(clamp(rayPosition.y - maxHeight,0.0,1.0) < 1.0 && clamp(rayPosition.y - minHeight,0.0,1.0) > 0.0){
float shape = getCloudShape(LayerIndex, 1, rayPosition, minHeight, maxHeight);
float shapeWithDensity = shape*density;
float shapeWithDensityFaded = shape*density * pow(clamp((rayPosition.y - minHeight)/(max(maxHeight-minHeight,1.0)*0.25),0.0,1.0),2.0);
// check if the pixel has visible clouds before doing work.
if(shapeWithDensityFaded > 1e-5){
// can add the initial cloud shape sample for a free shadow starting step :D
float indirectShadowMask = 1.0 - min(max(rayPosition.y - minHeight,0.0) / max(maxHeight-minHeight,1.0), 1.0);
float sunShadowMask = shapeWithDensity + getCloudScattering(LayerIndex, rayPosition, sunVector, dither, minHeight, maxHeight, density);
// do cloud shadows from one layer to another
// large cumulus layer -> small cumulus layer
#if defined CloudLayer0 && defined CloudLayer1
if(LayerIndex == SMALLCUMULUS_LAYER){
vec3 shadowStartPos = rayPosition + sunVector / abs(sunVector.y) * max((CloudLayer1_height + 20.0) - rayPosition.y, 0.0);
sunShadowMask += 3.0 * getCloudShape(LARGECUMULUS_LAYER, 0, shadowStartPos, CloudLayer1_height, CloudLayer1_height+100.0)*dailyWeatherParams1.y;
}
#endif
// altostratus layer -> all cumulus layers
#if defined CloudLayer2
vec3 shadowStartPos = rayPosition + sunVector / abs(sunVector.y) * max(CloudLayer2_height - rayPosition.y, 0.0);
sunShadowMask += getCloudShape(ALTOSTRATUS_LAYER, 0, shadowStartPos, CloudLayer2_height, CloudLayer2_height) * dailyWeatherParams1.z * (1.0-abs(sunVector.y));
#endif
vec3 lighting = getCloudLighting(shapeWithDensity, shapeWithDensityFaded, sunShadowMask, sunScattering, sunMultiScattering, indirectShadowMask, skyScattering * skylightOcclusion, distanceFade);
float densityCoeff = exp(-distanceFactor*shapeWithDensityFaded);
color += (lighting - lighting * densityCoeff) * totalAbsorbance;
totalAbsorbance *= densityCoeff;
// check if you can see through the cloud on the pixel before doing the next iteration
if (totalAbsorbance < 1e-5) break;
}
}
rayPosition += rayDirection;
}
return vec4(color, totalAbsorbance);
}
}
vec3 getRayOrigin(
vec3 rayStartPos,
vec3 cameraPos,
float dither,
float minHeight,
float maxHeight
){
// allow passing through/above/below the plane without limits
float flip = mix(max(cameraPos.y - maxHeight,0.0), max(minHeight - cameraPos.y,0.0), clamp(rayStartPos.y,0.0,1.0));
// orient the ray to be a flat plane facing up/down
vec3 position = rayStartPos*dither + cameraPos + (rayStartPos/abs(rayStartPos.y)) * flip;
return position;
}
vec4 GetVolumetricClouds(
vec3 viewPos,
vec2 dither,
vec3 sunVector,
vec3 directLightCol,
vec3 indirectLightCol
){
#ifndef VOLUMETRIC_CLOUDS
return vec4(0.0,0.0,0.0,1.0);
#endif
vec3 color = vec3(0.0);
float totalAbsorbance = 1.0;
vec4 cloudColor = vec4(color, totalAbsorbance);
float cloudheight = 100.0;
float minHeight = CloudLayer0_height;
float maxHeight = cloudheight + minHeight;
float heightRelativeToClouds = clamp(1.0 - max(cameraPosition.y - minHeight,0.0) / 100.0 ,0.0,1.0);
#if defined DISTANT_HORIZONS
float maxdist = dhRenderDistance + 16 * 32;
#else
float maxdist = far + 16*5.0;
#endif
float lViewPosM = length(viewPos) < maxdist ? length(viewPos) - 1.0 : 100000000.0;
vec4 NormPlayerPos = normalize(gbufferModelViewInverse * vec4(viewPos, 1.0) + vec4(gbufferModelViewInverse[3].xyz,0.0));
vec3 signedSunVec = sunVector;
vec3 unignedSunVec = sunVector * (float(sunElevation > 1e-5)*2.0-1.0);
float SdotV = dot(unignedSunVec, NormPlayerPos.xyz);
NormPlayerPos.y += 0.025*heightRelativeToClouds;
int maxSamples = 15;
int minSamples = 10;
int samples = int(clamp(maxSamples / sqrt(exp2(NormPlayerPos.y)),0.0, minSamples));
// int samples = 30;
///------- setup the ray
vec3 rayDirection = NormPlayerPos.xyz * (cloudheight/abs(NormPlayerPos.y)/samples);
vec3 rayPosition = getRayOrigin(rayDirection, cameraPosition, dither.y, minHeight, maxHeight);
///------- do color stuff outside of the raymarcher loop
vec3 sunScattering = directLightCol * (phaseCloud(SdotV, 0.85) + phaseCloud(SdotV, 0.75)) * 3.14;
vec3 sunMultiScattering = directLightCol * 0.8;// * (phaseCloud(SdotV, 0.35) + phaseCloud(-SdotV, 0.35) * 0.5) * 6.28;
vec3 skyScattering = indirectLightCol;
vec3 distanceEstimation = normalize(NormPlayerPos.xyz * (cloudheight/abs(NormPlayerPos.y)/samples));
// terrible fake rayleigh scattering
// vec3 rC = vec3(sky_coefficientRayleighR*1e-6, sky_coefficientRayleighG*1e-5, sky_coefficientRayleighB*1e-5)*3.0;
// vec3 rayleighScatter = exp(-10000.0 * rC * exp(abs(distanceEstimation.y) * -5.0));
// sunMultiScattering *= rayleighScatter;
// sunScattering *= rayleighScatter;
float distanceFade = 1.0 - clamp(exp2(pow(abs(distanceEstimation.y),1.5) * -100.0),0.0,1.0)*heightRelativeToClouds;
// - pow(1.0-clamp(signedSunVec.y,0.0,1.0),5.0)
skyScattering *= mix(1.0, 2.0, distanceFade);
sunScattering *= distanceFade;
sunMultiScattering *= distanceFade;
////------- RENDER SMALL CUMULUS CLOUDS
vec4 smallCumulusClouds = cloudColor;
#ifdef CloudLayer0
smallCumulusClouds = raymarchCloud(SMALLCUMULUS_LAYER, samples, rayPosition, rayDirection, dither.x, minHeight, maxHeight, unignedSunVec, sunScattering, sunMultiScattering, skyScattering, distanceFade, lViewPosM);
#endif
////------- RENDER LARGE CUMULUS CLOUDS
vec4 largeCumulusClouds = cloudColor;
#ifdef CloudLayer1
cloudheight = 200.0;
minHeight = CloudLayer1_height;
maxHeight = cloudheight + minHeight;
rayDirection = NormPlayerPos.xyz * (cloudheight/abs(NormPlayerPos.y)/samples);
rayPosition = getRayOrigin(rayDirection, cameraPosition, dither.y, minHeight, maxHeight);
if(smallCumulusClouds.a > 1e-5) largeCumulusClouds = raymarchCloud(LARGECUMULUS_LAYER, samples, rayPosition, rayDirection, dither.x, minHeight, maxHeight, unignedSunVec, sunScattering, sunMultiScattering, skyScattering, distanceFade, lViewPosM);
#endif
////------- RENDER ALTOSTRATUS CLOUDS
vec4 altoStratusClouds = cloudColor;
#ifdef CloudLayer2
cloudheight = 5.0;
minHeight = CloudLayer2_height;
maxHeight = cloudheight + minHeight;
rayDirection = NormPlayerPos.xyz * (cloudheight/abs(NormPlayerPos.y));
rayPosition = getRayOrigin(rayDirection, cameraPosition, dither.y, minHeight, maxHeight);
if(smallCumulusClouds.a > 1e-5 || largeCumulusClouds.a > 1e-5) altoStratusClouds = raymarchCloud(ALTOSTRATUS_LAYER, samples, rayPosition, rayDirection, dither.x, minHeight, maxHeight, unignedSunVec, sunScattering, sunMultiScattering, skyScattering, distanceFade, lViewPosM);
#endif
////------- BLEND LAYERS
#ifdef CloudLayer2
cloudColor = altoStratusClouds;
#endif
#ifdef CloudLayer1
cloudColor.rgb *= largeCumulusClouds.a;
cloudColor.rgb += largeCumulusClouds.rgb;
cloudColor.a *= largeCumulusClouds.a;
#endif
#ifdef CloudLayer0
cloudColor.rgb *= smallCumulusClouds.a;
cloudColor.rgb += smallCumulusClouds.rgb;
cloudColor.a *= smallCumulusClouds.a;
#endif
color = cloudColor.rgb;
totalAbsorbance = cloudColor.a;
return vec4(color, totalAbsorbance);
}
#endif