Shader "Hidden/Atmosphere" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "../Includes/Math.cginc" // struct appdata { float4 vertex : POSITION; float4 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 viewVector : TEXCOORD1; }; v2f vert (appdata v) { v2f output; output.pos = UnityObjectToClipPos(v.vertex); output.uv = v.uv; // Camera space matches OpenGL convention where cam forward is -z. In unity forward is positive z. // (https://docs.unity3d.com/ScriptReference/Camera-cameraToWorldMatrix.html) float3 viewVector = mul(unity_CameraInvProjection, float4(v.uv.xy * 2 - 1, 0, -1)); output.viewVector = mul(unity_CameraToWorld, float4(viewVector,0)); return output; } float2 squareUV(float2 uv) { float width = _ScreenParams.x; float height =_ScreenParams.y; //float minDim = min(width, height); float scale = 1000; float x = uv.x * width; float y = uv.y * height; return float2 (x/scale, y/scale); } sampler2D _BlueNoise; sampler2D _MainTex; sampler2D _BakedOpticalDepth; sampler2D _CameraDepthTexture; float4 params; float3 dirToSun; float3 planetCentre; float atmosphereRadius; float oceanRadius; float planetRadius; // Paramaters int numInScatteringPoints; int numOpticalDepthPoints; float intensity; float4 scatteringCoefficients; float ditherStrength; float ditherScale; float densityFalloff; float densityAtPoint(float3 densitySamplePoint) { float heightAboveSurface = length(densitySamplePoint - planetCentre) - planetRadius; float height01 = heightAboveSurface / (atmosphereRadius - planetRadius); float localDensity = exp(-height01 * densityFalloff) * (1 - height01); return localDensity; } float opticalDepth(float3 rayOrigin, float3 rayDir, float rayLength) { float3 densitySamplePoint = rayOrigin; float stepSize = rayLength / (numOpticalDepthPoints - 1); float opticalDepth = 0; for (int i = 0; i < numOpticalDepthPoints; i ++) { float localDensity = densityAtPoint(densitySamplePoint); opticalDepth += localDensity * stepSize; densitySamplePoint += rayDir * stepSize; } return opticalDepth; } float opticalDepthBaked(float3 rayOrigin, float3 rayDir) { float height = length(rayOrigin - planetCentre) - planetRadius; float height01 = saturate(height / (atmosphereRadius - planetRadius)); float uvX = 1 - (dot(normalize(rayOrigin - planetCentre), rayDir) * .5 + .5); return tex2Dlod(_BakedOpticalDepth, float4(uvX, height01,0,0)); } float opticalDepthBaked2(float3 rayOrigin, float3 rayDir, float rayLength) { float3 endPoint = rayOrigin + rayDir * rayLength; float d = dot(rayDir, normalize(rayOrigin-planetCentre)); float opticalDepth = 0; const float blendStrength = 1.5; float w = saturate(d * blendStrength + .5); float d1 = opticalDepthBaked(rayOrigin, rayDir) - opticalDepthBaked(endPoint, rayDir); float d2 = opticalDepthBaked(endPoint, -rayDir) - opticalDepthBaked(rayOrigin, -rayDir); opticalDepth = lerp(d2, d1, w); return opticalDepth; } float3 calculateLight(float3 rayOrigin, float3 rayDir, float rayLength, float3 originalCol, float2 uv) { float blueNoise = tex2Dlod(_BlueNoise, float4(squareUV(uv) * ditherScale,0,0)); blueNoise = (blueNoise - 0.5) * ditherStrength; float3 inScatterPoint = rayOrigin; float stepSize = rayLength / (numInScatteringPoints - 1); float3 inScatteredLight = 0; float viewRayOpticalDepth = 0; for (int i = 0; i < numInScatteringPoints; i ++) { float sunRayLength = raySphere(planetCentre, atmosphereRadius, inScatterPoint, dirToSun).y; float sunRayOpticalDepth = opticalDepthBaked(inScatterPoint + dirToSun * ditherStrength, dirToSun); float localDensity = densityAtPoint(inScatterPoint); viewRayOpticalDepth = opticalDepthBaked2(rayOrigin, rayDir, stepSize * i); float3 transmittance = exp(-(sunRayOpticalDepth + viewRayOpticalDepth) * scatteringCoefficients); inScatteredLight += localDensity * transmittance; inScatterPoint += rayDir * stepSize; } inScatteredLight *= scatteringCoefficients * intensity * stepSize / planetRadius; inScatteredLight += blueNoise * 0.01; // Attenuate brightness of original col (i.e light reflected from planet surfaces) // This is a hacky mess, TODO: figure out a proper way to do this const float brightnessAdaptionStrength = 0.15; const float reflectedLightOutScatterStrength = 3; float brightnessAdaption = dot (inScatteredLight,1) * brightnessAdaptionStrength; float brightnessSum = viewRayOpticalDepth * intensity * reflectedLightOutScatterStrength + brightnessAdaption; float reflectedLightStrength = exp(-brightnessSum); float hdrStrength = saturate(dot(originalCol,1)/3-1); reflectedLightStrength = lerp(reflectedLightStrength, 1, hdrStrength); float3 reflectedLight = originalCol * reflectedLightStrength; float3 finalCol = reflectedLight + inScatteredLight; return finalCol; } float4 frag (v2f i) : SV_Target { float4 originalCol = tex2D(_MainTex, i.uv); float sceneDepthNonLinear = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); float sceneDepth = LinearEyeDepth(sceneDepthNonLinear) * length(i.viewVector); float3 rayOrigin = _WorldSpaceCameraPos; float3 rayDir = normalize(i.viewVector); float dstToOcean = raySphere(planetCentre, oceanRadius, rayOrigin, rayDir); float dstToSurface = min(sceneDepth, dstToOcean); float2 hitInfo = raySphere(planetCentre, atmosphereRadius, rayOrigin, rayDir); float dstToAtmosphere = hitInfo.x; float dstThroughAtmosphere = min(hitInfo.y, dstToSurface - dstToAtmosphere); if (dstThroughAtmosphere > 0) { const float epsilon = 0.0001; float3 pointInAtmosphere = rayOrigin + rayDir * (dstToAtmosphere + epsilon); float3 light = calculateLight(pointInAtmosphere, rayDir, dstThroughAtmosphere - epsilon * 2, originalCol, i.uv); return float4(light, 1); } return originalCol; } ENDCG } } }