
// Nishita Sky Adapted for Redshift
// Base Nishita implementation built by Ben Simonds - https://github.com/BenSimonds/NishitaSky/blob/master/NishitaAtmos.osl
/**
Modified: 2021-03-23 by Nischal Pokhrel for Redshift 3D.
This file is licensed under Apache 2.0 license
Added: Z-Up | Light rotation inputs | Sun, Sky & Mie tint controls | Sun disc + intensity, size & tint control | Exposure, saturation & gamma control
Aces mode | Faux ozone | Stars based on elevation and height.
// Starfield based on shader by Thomas Dinges from https://github.com/sambler/osl-shaders
**/
#include <stdosl.h>
int has_solutions (vector StartPoint, vector UnitVector, float Radius) {
float b = 2 * dot (UnitVector, StartPoint);
float bsquared = pow(b,2);
float c = dot (StartPoint,StartPoint) - pow(Radius,2);
float fourac = 4 * c;
if (bsquared > fourac) {
return 1;
} else {
return 0;
}
}
int linetype (vector StartPoint, vector UnitVector, float Radius) {
float b = 2 * dot (UnitVector, StartPoint);
float bsquared = pow(b,2);
float c = dot (StartPoint,StartPoint) - pow(Radius,2);
float fourac = 4 * c;
if (bsquared > fourac) {
float d1 = (-b + pow(bsquared - fourac, 0.5)) / 2;
float d2 = (-b - pow(bsquared - fourac, 0.5)) / 2;
if (d1 < 0 && d2 < 0) {
return 2;
} else if (d1 < 0 || d2 < 0) {
return 1;
} else {
return 0;
}
} else {
return 3;
}
}
vector lmin (vector StartPoint, vector UnitVector, float Radius) {
float b = 2 * dot (UnitVector, StartPoint);
float bsquared = pow(b,2);
float c = dot (StartPoint,StartPoint) - pow(Radius,2);
float fourac = 4 * c;
if (bsquared > fourac) {
float d1 = (-b + pow(bsquared - fourac, 0.5)) / 2;
float d2 = (-b - pow(bsquared - fourac, 0.5)) / 2;
return StartPoint + UnitVector * min(d1, d2);
} else {
return vector(0);
}
}
vector lmax (vector StartPoint, vector UnitVector, float Radius) {
float b = 2 * dot (UnitVector, StartPoint);
float bsquared = pow(b,2);
float c = dot (StartPoint,StartPoint) - pow(Radius,2);
float fourac = 4 * c;
if (bsquared > fourac) {
float d1 = (-b + pow(bsquared - fourac, 0.5)) / 2;
float d2 = (-b - pow(bsquared - fourac, 0.5)) / 2;
return StartPoint + UnitVector * max(d1, d2);
} else {
return vector(0);
}
}
color softLight (color a, color b){
color ab = 0.0;
if (length(b) < 0.5){
ab = 2*(a*b) + (a*a) * (1-(2*b));
}
else {
ab = sqrt(a) * ((2*b) - 1) + ((2*a) * (1-b));
}
return (ab);
}
struct linesample {
int has_solutions;
int type;
vector min;
vector max;
};
shader nishita_sky
[[ string help = "Nishita Sky Model for Environments",
string label = "Nishita Sky" ]]
(
float Intensity = 20 [[string page = "1: Color Settings"]],
float Exposure = 0 [[float slidermin = -16, float slidermax = 16, int connectable = 0, string page = "1: Color Settings"]],
float Gamma = 1 [[float slidermin = 0, float slidermax = 2, int connectable = 0, string page = "1: Color Settings"]],
float Saturation = 1 [[float slidermin = 0, float slidermax = 1.5, int connectable = 0, string page = "1: Color Settings"]],
float discIntensity = 10 [[float slidermin = 0, float slidermax = 100, string label = "Disc Intensity", string page = "1: Color Settings"]],
color discColor = color(1,1,1) [[string label = "Disc Tint", string page = "1: Color Settings"]],
color sunTint = color(1, 1, 1) [[string label = "Sun Halo Tint", string page = "1: Color Settings"]],
color skyTint = color(1, 1, 1) [[string label = "Sky Tint", string page = "1: Color Settings"]],
int convToAces = 0 [[string widget = "checkBox", string label = "ACES-AP1", int connectable = 0, string page = "1: Color Settings"]],
float Scattering = 1 [[float slidermin = 0.1, float slidermax = 1.315, float slidercenter = 1, int connectable = 0, string page = "2: Control Settings"]],
float ozoneDensity = 0 [[float slidermin = -3, float slidermax = 3, float slidercenter = 0, string label = "Ozone Density Multiplier", string page = "2: Control Settings"]],
float Height = 200 [[string page = "2: Control Settings"]],
float discSize = 1 [[float slidermin = 0, float slidermax = 100, string label = "Disc Size", string page = "2: Control Settings"]],
float Azimuth = 0 [[float slidermin = 0, float slidermax = 360, int connectable = 0, string page = "2: Control Settings"]],
float Elevation = 0 [[float slidermin = -40, float slidermax = 220, int connectable = 0, string page = "2: Control Settings"]],
int useLight = 0 [[string widget = "checkBox", string label = "Use External Rotations", string page = "2: Control Settings"]],
vector Light = vector(0, 0, 0) [[string label = "Rotation Coordinates", string page = "2: Control Settings"]],
int flipAxis = 0 [[string widget = "checkBox", string label = "Z-Up", int connectable = 0, string page = "2: Control Settings"]],
int useStars = 1 [[string widget = "checkBox", string label = "Enable Stars",string page = "3: Stars Settings"]],
float starsIntensity = 1 [[string label = "Stars Intensity", float slidermin = 0, float slidermax = 4 ,string page = "3: Stars Settings"]],
float starsFrequency = 0.5 [[string label = "Stars Frequency", float slidermin = 0, float slidermax = 1, string page = "3: Stars Settings"]],
float starsWidth = 0.5 [[string label = "Stars Width", float slidermin = 0, float slidermax = 1, string page = "3: Stars Settings"]],
output color Sky = 0.0)
{
color Rayleigh = 0.0;
color Mie = 0.0;
color Ozone = 0.0;
color outSun = 0.0;
color Stars = 0.0;
//Conversion matrices
matrix yUp =
{1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1};
matrix zUp =
{1,0,0,0,
0,1*flipAxis,1-flipAxis,0,
0,1-flipAxis,-1*flipAxis,0,
0,0,0,1};
matrix xyz_to_rgb =
{3.24096994,1.53738318,-0.49861076,0,
-0.96924364,1.8759675,0.04155506,0,
0.05563008,-0.20397696,1.05697151,0,
0,0,0,1
};
//Variables:
float Re = 6360e3; // Radius of the earth
float Ra = 6460e3; // Radius of the atmosphere
float Hr = 8000; // Rayleigh scale height
float Hm = 1200; // Mie scale height
float Ho = 10000; // Ozone scale height
float g = 0.76 * clamp(Scattering, 0.1, 1.315); // Anisotropy term for Mie scattering.
float oD = 0.00000022512612292678; // Ozone Coef at wavelength 18
vector BetaR = vector(5.8e-6,13.0e-6,22.4e-6);
vector BetaM_max = vector(9.73e-6);
vector BetaM_min = vector(13.28e-6);
vector BetaM = vector(20e-6);
vector BetaO = vector(3.808e-6);
//Clean up inputs:
vector SunDirection = vector(0, 0, 1);
SunDirection = normalize(SunDirection);
vector Direction = normalize(I);
//Sun azimuth, elevation and misc light logic
if (useLight == 1){
if (flipAxis == 0){
SunDirection = transform(yUp, SunDirection);
SunDirection = rotate(SunDirection, radians(360) - radians(Light[0]), vector(-1,0,0));
SunDirection = rotate(SunDirection, radians(Light[1]) + radians(180), vector(0,-1,0));
}
if (flipAxis == 1){
SunDirection = rotate(SunDirection, radians(270) - radians(Light[0]), vector(1,0,0));
SunDirection = rotate(SunDirection, radians(Light[2]), vector(0,0,-1));
}
}
else {
if (flipAxis == 0){
SunDirection = transform(yUp, SunDirection);
if (Elevation >= -40){
SunDirection = rotate(SunDirection, radians(360) - radians(Elevation), vector(1,0,0));
}
if (Azimuth >= 0){
SunDirection = rotate(SunDirection, radians(Azimuth), vector(0,1,0));
}
}
if (flipAxis == 1){
if (Elevation >= -40){
SunDirection = rotate(SunDirection, radians(270) - radians(Elevation), vector(1,0,0));
}
if (Azimuth >= 0){
SunDirection = rotate(SunDirection, radians(Azimuth), vector(0,0,-1));
}
}
}
//Calcualte Rayleigh and mie phases.
float mu = dot(Direction, SunDirection);
float phaseR = (3/(16 * M_PI)) * (1 + mu*mu);
float phaseM = (3/(8 * M_PI)) * ((1 - g*g) * (1 + mu*mu) / ((2 + g*g) * pow(1 + g*g - 2 * g * mu, 1.5)));
// Sun Disc
float sunDiameter = discSize * 0.53 * M_PI / 180.0;
float sunAmount = step(cos(sunDiameter / 2.0), mu);
//Stuff that will eventually help form my output.
vector SumR = vector(0);
vector SumM = vector(0);
vector SumO = vector(0);
//Set up start and end points for my integrals.
vector Pu = vector(0,0,0);
vector Pv = vector(0,0,0);
//Get start and end point for View Line.
if (Height > 0) {
vector Pc = vector (0, 0, Re + Height);
if (flipAxis == 0){
Pc = transform(yUp, Pc);
Pc = rotate(Pc, radians(-90), vector(1,0,0));
}
if (flipAxis == 1){
Pc = transform(zUp, Pc);
}
Pu = Pc;
//Ozone
oD = 0;
if (ozoneDensity != 0){
if (Height >= 0.0) {
oD = (ozoneDensity * 1000 / -15000.0);
oD = clamp(oD, -2000, 3000);
}
}
//Stars stuff
point PP = I;
float val;
float sf = starsFrequency;
sf /= 300;
val = smoothstep(1 - starsWidth, 2, noise("perlin", PP / sf));
vector cosSunGround = dot(normalize(Pu), normalize(SunDirection));
vector sunAngle = acos(cosSunGround);
float sunAngleDeg = degrees(sunAngle[0]);
float sunAngleDeg_N = (sunAngleDeg - 87) / (135 - 87);
if (useStars == 1){
if (Height <= 30000){
Stars = val * pow(4, clamp(2.5 * starsIntensity, 0, 4)) * sunAngleDeg_N;
Stars = -sunAmount + Stars;
}
else {
Stars = val * pow(1.8, clamp(2.5 * starsIntensity, 0, 4));
}
Stars = clamp(Stars, 0, 4);
}
linesample groundline;
groundline.has_solutions = has_solutions(Pc, Direction, Re);
groundline.type = linetype(Pc, Direction, Re);
groundline.min = lmin(Pc, Direction, Re);
groundline.max = lmax(Pc, Direction, Re);
linesample atmosphereline;
atmosphereline.has_solutions = has_solutions(Pc, Direction, Ra);
atmosphereline.type = linetype(Pc, Direction, Ra);
atmosphereline.min = lmin(Pc, Direction, Ra);
atmosphereline.max = lmax(Pc, Direction, Ra);
Pv = atmosphereline.max;
if (atmosphereline.has_solutions && atmosphereline.type == 0) {
//Line starts outside atmoshere, so Pu becomes Line.min
Pu = atmosphereline.min;
}
if (groundline.has_solutions && groundline.type == 0) {
//Line hits ground, Pv becomes point at which line hits ground, which will be Line.min
Pv = groundline.min;
sunAmount = 0;
Stars = 0;
}
}
// Create samples along view ray.
int numSamples = 16;
int numSamplesL = 8;
float segmentLength = distance (Pu, Pv) / numSamples;
float opticalDepthR = 0;
float opticalDepthM = 0;
float opticalDepthO = 0;
for (int i = 0; i < numSamples; i++) {
vector Px = Pu + segmentLength * Direction * (i + 0.5);
float sampleHeight = length(Px) - Re;
/* Get optical depth for light at this sample: */
float Hr_sample = exp(-sampleHeight/Hr) * segmentLength;
float Hm_sample = exp(-sampleHeight/Hm) * segmentLength;
float Ho_sample = exp(-sampleHeight/Ho) * segmentLength;
opticalDepthR += Hr_sample;
opticalDepthM += Hm_sample;
opticalDepthO += Ho_sample;
//Get light depth to sun at this sample.
float opticalDepthLR = 0;
float opticalDepthLM = 0;
float opticalDepthLO = 0;
linesample atmosphereline_sample;
atmosphereline_sample.has_solutions = has_solutions(Px, SunDirection, Ra);
atmosphereline_sample.type = linetype(Px, SunDirection, Ra);
atmosphereline_sample.min = lmin(Px, SunDirection, Ra);
atmosphereline_sample.max = lmax(Px, SunDirection, Ra);
linesample groundline_sample;
groundline_sample.has_solutions = has_solutions(Px, SunDirection, Re);
groundline_sample.type = linetype(Px, SunDirection, Re);
groundline_sample.min = lmin(Px, SunDirection, Re);
groundline_sample.max = lmax(Px, SunDirection, Re);
vector Ps = atmosphereline_sample.max;
int j = 0;
if (groundline_sample.has_solutions && groundline_sample.type == 0) {
//Light is below horizon from this point. No sample needed.
opticalDepthLR += 0;
opticalDepthLM += 0;
opticalDepthLO += 0;
} else {
float segmentLengthL = distance(Px, Ps) / numSamplesL;
for (j = 0; j < numSamplesL; j++) {
vector Pl = Px + segmentLengthL * SunDirection * (j + 0.5);
float sampleHeightL = length(Pl) - Re;
if (sampleHeightL < 0) break;
// ignore sampleheights < 0 they're inside the planet...
float Hlr_sample = exp(-sampleHeightL/Hr)*segmentLengthL;
float Hlm_sample = exp(-sampleHeightL/Hm)*segmentLengthL;
float Hlo_sample = exp(-sampleHeightL/Ho)*segmentLengthL;
opticalDepthLR += Hlr_sample;
opticalDepthLM += Hlm_sample;
opticalDepthLO += Hlo_sample;
}
}
// With our light samples done we can calculate the attenuation.
// Only include a sample if we reached j (so not if we hit the break clause for rays that hit the ground in finding the sun)
if (j == numSamplesL) {
vector tauR = BetaR * (opticalDepthR + opticalDepthLR);
vector tauM = BetaM * 1.1 * (opticalDepthM + opticalDepthLM);
vector tauO = BetaO * (opticalDepthO + opticalDepthLO);
vector tau = tauR + tauM + tauO;
vector attnR = vector (exp(-tauR[0]), exp(-tauR[1]), exp(-tauR[2]));
vector attnM = vector (exp(-tauM));
vector attenuation = vector (exp(-tau[0]), exp(-tau[1]), exp(-tau[2]));
SumR += Hr_sample * attenuation;
SumM += Hm_sample * attenuation;
SumO += Ho_sample * attenuation;
}
}
matrix srgbToAcesAP1 = {
0.6131, 0.0701, 0.0206, 0,
0.3395, 0.9164, 0.1096, 0,
0.0474, 0.0135, 0.8698, 0,
0, 0, 0, 1 };
Rayleigh = color (SumR * phaseR * BetaR) * Intensity * clamp(skyTint, 0, 1);
Mie = color (SumM * phaseM * BetaM) * Intensity * clamp(sunTint, 0, 1);
Ozone = color (SumO * BetaO * oD) * Intensity * clamp(sunTint, 0, 1);
outSun = color (SumM * phaseM * BetaM * discColor) * sunAmount * pow(2, discIntensity);
// Subtly merge the rayleigh colors with the stars, cull very dim stars
// Merge the rest of the atmosphere
color atmosMix = (softLight((Stars * Stars), Rayleigh) + Rayleigh + Mie + Ozone);
Sky = (atmosMix + outSun);
color toHSV = transformc("hsv", Sky);
toHSV[1] = toHSV[1] * Saturation;
color toRGB = transformc("hsv", "rgb", toHSV);
Sky = toRGB;
Sky = pow(Sky, clamp(Gamma, 0, 2)) * pow(2.0, Exposure);
float sR = Sky[0];
float sG = Sky[1];
float sB = Sky[2];
if (convToAces == 1){
Sky = transform(srgbToAcesAP1, vector(sR, sG, sB));
Sky = pow(Sky, Gamma) * pow(2.0, Exposure);
}
}