Hello!
Just want to share to you all my procedural sky generator. It has a number of options and it also works in EEVEE (and Cycles ofc)
Check it out here for free:
Hello!
Just want to share to you all my procedural sky generator. It has a number of options and it also works in EEVEE (and Cycles ofc)
Check it out here for free:
Thank you… this looks really cool.
it makes the cloud too?
Not at the moment, but I’m planning to add clouds sometime.
I already have a setup that works in EEVEE, for Cycles though its a bit different, as volumes in the world shader behave differently there. I might just make it only for Objects instead.
looks like the unity3D procedural skybox.
Nice work ! i love it
Now i guess it’s time for nightstars and clouds
Thanks ! and happy blending !
I did not take time for having a eye at your work but i got a U3D modified shader ( writen in shadelab language ) with stars and a cloudbow overlay.
If you want i can give you the source ( that honestly have to be cleaned and reworked lol )
Happy blending !
Sure thing! I’d love to see your implementation!
I have been trying out ways to do night stars, using voronoi like in this one,
Would like to see how others had done these things, and get more insight about it
Thanks!
aww… errr… i guess i’ll be ashamed to give you my code as you results appear really awesone
anyway lemme be back in minutes with my code…
Here it is:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
Shader "Skybox/mon_skybox" {
Properties {
[KeywordEnum(None, Simple, High Quality)] _SunDisk ("Sun", Int) = 2
_SunSize ("Sun Size", Range(0,1)) = 0.04
_SunSizeConvergence("Sun Size Convergence", Range(1,10)) = 5
_AtmosphereThickness ("Atmosphere Thickness", Range(0,5)) = 1.0
_SkyTint ("Sky Tint", Color) = (.5, .5, .5, 1)
_GroundColor ("Ground", Color) = (.369, .349, .341, 1)
_Exposure("Exposure", Range(0, 8)) = 1.3
_stars("stars", Range(0,512)) = 256
_stars_int("stars intensity", Range(0,1)) = 0.5
_clouds_intensity("clouds intensity", Range(-1,1)) = 0.5
_clouds_presence("clouds presence", Range(0,1)) = 0.5
_Grisaille("Ciel gris", Range(0,1)) = 0
_Cubemap("Cubemap", Cube) = "white" {}
}
SubShader {
Tags { "Queue"="Background" "RenderType"="Background" "PreviewType"="Skybox" }
Cull Off ZWrite Off
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma multi_compile _SUNDISK_NONE _SUNDISK_SIMPLE _SUNDISK_HIGH_QUALITY
uniform half _Exposure; // HDR exposure
uniform half3 _GroundColor;
uniform half _SunSize;
uniform half _SunSizeConvergence;
uniform half3 _SkyTint;
uniform half _AtmosphereThickness;
// +cjl
samplerCUBE _Cubemap;
#if defined(UNITY_COLORSPACE_GAMMA)
#define GAMMA 2
#define COLOR_2_GAMMA(color) color
#define COLOR_2_LINEAR(color) color*color
#define LINEAR_2_OUTPUT(color) sqrt(color)
#else
#define GAMMA 2.2
// HACK: to get gfx-tests in Gamma mode to agree until UNITY_ACTIVE_COLORSPACE_IS_GAMMA is working properly
#define COLOR_2_GAMMA(color) ((unity_ColorSpaceDouble.r>2.0) ? pow(color,1.0/GAMMA) : color)
#define COLOR_2_LINEAR(color) color
#define LINEAR_2_LINEAR(color) color
#endif
// RGB wavelengths
// .35 (.62=158), .43 (.68=174), .525 (.75=190)
static const float3 kDefaultScatteringWavelength = float3(.65, .57, .475);
static const float3 kVariableRangeForScatteringWavelength = float3(.15, .15, .15);
#define OUTER_RADIUS 1.025
static const float kOuterRadius = OUTER_RADIUS;
static const float kOuterRadius2 = OUTER_RADIUS*OUTER_RADIUS;
static const float kInnerRadius = 1.0;
static const float kInnerRadius2 = 1.0;
static const float kCameraHeight = 0.0001;
#define kRAYLEIGH (lerp(0.0, 0.0025, pow(_AtmosphereThickness,2.5))) // Rayleigh constant
#define kMIE 0.0010 // Mie constant
#define kSUN_BRIGHTNESS 20.0 // Sun brightness
#define kMAX_SCATTER 50.0 // Maximum scattering value, to prevent math overflows on Adrenos
static const half kHDSundiskIntensityFactor = 15.0;
static const half kSimpleSundiskIntensityFactor = 27.0;
static const half kSunScale = 400.0 * kSUN_BRIGHTNESS;
static const float kKmESun = kMIE * kSUN_BRIGHTNESS;
static const float kKm4PI = kMIE * 4.0 * 3.14159265;
static const float kScale = 1.0 / (OUTER_RADIUS - 1.0);
static const float kScaleDepth = 0.25;
static const float kScaleOverScaleDepth = (1.0 / (OUTER_RADIUS - 1.0)) / 0.25;
static const float kSamples = 2.0; // THIS IS UNROLLED MANUALLY, DON'T TOUCH
#define MIE_G (-0.990)
#define MIE_G2 0.9801
#define SKY_GROUND_THRESHOLD 0.02
// fine tuning of performance. You can override defines here if you want some specific setup
// or keep as is and allow later code to set it according to target api
// if set vprog will output color in final color space (instead of linear always)
// in case of rendering in gamma mode that means that we will do lerps in gamma mode too, so there will be tiny difference around horizon
// #define SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 0
// sun disk rendering:
// no sun disk - the fastest option
#define SKYBOX_SUNDISK_NONE 0
// simplistic sun disk - without mie phase function
#define SKYBOX_SUNDISK_SIMPLE 1
// full calculation - uses mie phase function
#define SKYBOX_SUNDISK_HQ 2
// uncomment this line and change SKYBOX_SUNDISK_SIMPLE to override material settings
// #define SKYBOX_SUNDISK SKYBOX_SUNDISK_SIMPLE
#ifndef SKYBOX_SUNDISK
#if defined(_SUNDISK_NONE)
#define SKYBOX_SUNDISK SKYBOX_SUNDISK_NONE
#elif defined(_SUNDISK_SIMPLE)
#define SKYBOX_SUNDISK SKYBOX_SUNDISK_SIMPLE
#else
#define SKYBOX_SUNDISK SKYBOX_SUNDISK_HQ
#endif
#endif
#ifndef SKYBOX_COLOR_IN_TARGET_COLOR_SPACE
#if defined(SHADER_API_MOBILE)
#define SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 1
#else
#define SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 0
#endif
#endif
half _stars;
half _stars_int;
half _clouds_intensity;
half _clouds_presence;
half _Grisaille;
// Calculates the Rayleigh phase function
half getRayleighPhase(half eyeCos2)
{
return 0.75 + 0.75*eyeCos2;
}
half getRayleighPhase(half3 light, half3 ray)
{
half eyeCos = dot(light, ray);
return getRayleighPhase(eyeCos * eyeCos);
}
struct appdata_t
{
float4 vertex : POSITION;
float3 texcoord :TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 pos : SV_POSITION;
#if SKYBOX_SUNDISK == SKYBOX_SUNDISK_HQ
// for HQ sun disk, we need vertex itself to calculate ray-dir per-pixel
float3 vertex : TEXCOORD0;
#elif SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE
half3 rayDir : TEXCOORD0;
#else
// as we dont need sun disk we need just rayDir.y (sky/ground threshold)
half skyGroundFactor : TEXCOORD0;
#endif
// calculate sky colors in vprog
half3 groundColor : TEXCOORD1;
half3 skyColor : TEXCOORD2;
#if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE
half3 sunColor : TEXCOORD3;
#endif
half3 uv : TEXCOORD4;
UNITY_VERTEX_OUTPUT_STEREO
};
float scale(float inCos)
{
float x = 1.0 - inCos;
#if defined(SHADER_API_N3DS)
// The polynomial expansion here generates too many swizzle instructions for the 3DS vertex assembler
// Approximate by removing x^1 and x^2
return 0.25 * exp(-0.00287 + x*x*x*(-6.80 + x*5.25));
#else
return 0.25 * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
#endif
}
v2f vert (appdata_t v)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.pos = UnityObjectToClipPos(v.vertex);
float3 kSkyTintInGammaSpace = COLOR_2_GAMMA(_SkyTint); // convert tint from Linear back to Gamma
float3 kScatteringWavelength = lerp (
kDefaultScatteringWavelength-kVariableRangeForScatteringWavelength,
kDefaultScatteringWavelength+kVariableRangeForScatteringWavelength,
half3(1,1,1) - kSkyTintInGammaSpace); // using Tint in sRGB gamma allows for more visually linear interpolation and to keep (.5) at (128, gray in sRGB) point
float3 kInvWavelength = 1.0 / pow(kScatteringWavelength, 4);
float kKrESun = kRAYLEIGH * kSUN_BRIGHTNESS;
float kKr4PI = kRAYLEIGH * 4.0 * 3.14159265;
float3 cameraPos = float3(0,kInnerRadius + kCameraHeight,0); // The camera's current position
// Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere)
float3 eyeRay = normalize(mul((float3x3)unity_ObjectToWorld, v.vertex.xyz));
float far = 0.0;
half3 cIn, cOut;
OUT.uv = v.texcoord.xyz*_stars;
if(eyeRay.y >= 0.0)
{
// Sky
// Calculate the length of the "atmosphere"
far = sqrt(kOuterRadius2 + kInnerRadius2 * eyeRay.y * eyeRay.y - kInnerRadius2) - kInnerRadius * eyeRay.y;
float3 pos = cameraPos + far * eyeRay;
// Calculate the ray's starting position, then calculate its scattering offset
float height = kInnerRadius + kCameraHeight;
float depth = exp(kScaleOverScaleDepth * (-kCameraHeight));
float startAngle = dot(eyeRay, cameraPos) / height;
float startOffset = depth*scale(startAngle);
// Initialize the scattering loop variables
float sampleLength = far / kSamples;
float scaledLength = sampleLength * kScale;
float3 sampleRay = eyeRay * sampleLength;
float3 samplePoint = cameraPos + sampleRay * 0.5;
// Now loop through the sample rays
float3 frontColor = float3(0.0, 0.0, 0.0);
// Weird workaround: WP8 and desktop FL_9_3 do not like the for loop here
// (but an almost identical loop is perfectly fine in the ground calculations below)
// Just unrolling this manually seems to make everything fine again.
// for(int i=0; i<int(kSamples); i++)
{
float height = length(samplePoint);
float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
float lightAngle = dot(_WorldSpaceLightPos0.xyz, samplePoint) / height;
float cameraAngle = dot(eyeRay, samplePoint) / height;
float scatter = (startOffset + depth*(scale(lightAngle) - scale(cameraAngle)));
float3 attenuate = exp(-clamp(scatter, 0.0, kMAX_SCATTER) * (kInvWavelength * kKr4PI + kKm4PI));
frontColor += attenuate * (depth * scaledLength);
samplePoint += sampleRay;
}
{
float height = length(samplePoint);
float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
float lightAngle = dot(_WorldSpaceLightPos0.xyz, samplePoint) / height;
float cameraAngle = dot(eyeRay, samplePoint) / height;
float scatter = (startOffset + depth*(scale(lightAngle) - scale(cameraAngle)));
float3 attenuate = exp(-clamp(scatter, 0.0, kMAX_SCATTER) * (kInvWavelength * kKr4PI + kKm4PI));
frontColor += attenuate * (depth * scaledLength);
samplePoint += sampleRay;
}
// Finally, scale the Mie and Rayleigh colors and set up the varying variables for the pixel shader
cIn = frontColor * (kInvWavelength * kKrESun);
cOut = frontColor * kKmESun;
}
else
{
// Ground
far = (-kCameraHeight) / (min(-0.001, eyeRay.y));
float3 pos = cameraPos + far * eyeRay;
// Calculate the ray's starting position, then calculate its scattering offset
float depth = exp((-kCameraHeight) * (1.0/kScaleDepth));
float cameraAngle = dot(-eyeRay, pos);
float lightAngle = dot(_WorldSpaceLightPos0.xyz, pos);
float cameraScale = scale(cameraAngle);
float lightScale = scale(lightAngle);
float cameraOffset = depth*cameraScale;
float temp = (lightScale + cameraScale);
// Initialize the scattering loop variables
float sampleLength = far / kSamples;
float scaledLength = sampleLength * kScale;
float3 sampleRay = eyeRay * sampleLength;
float3 samplePoint = cameraPos + sampleRay * 0.5;
// Now loop through the sample rays
float3 frontColor = float3(0.0, 0.0, 0.0);
float3 attenuate;
// for(int i=0; i<int(kSamples); i++) // Loop removed because we kept hitting SM2.0 temp variable limits. Doesn't affect the image too much.
{
float height = length(samplePoint);
float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
float scatter = depth*temp - cameraOffset;
attenuate = exp(-clamp(scatter, 0.0, kMAX_SCATTER) * (kInvWavelength * kKr4PI + kKm4PI));
frontColor += attenuate * (depth * scaledLength);
samplePoint += sampleRay;
}
cIn = frontColor * (kInvWavelength * kKrESun + kKmESun);
cOut = clamp(attenuate, 0.0, 1.0);
}
#if SKYBOX_SUNDISK == SKYBOX_SUNDISK_HQ
OUT.vertex = -v.vertex;
#elif SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE
OUT.rayDir = half3(-eyeRay);
#else
OUT.skyGroundFactor = -eyeRay.y / SKY_GROUND_THRESHOLD;
#endif
// if we want to calculate color in vprog:
// 1. in case of linear: multiply by _Exposure in here (even in case of lerp it will be common multiplier, so we can skip mul in fshader)
// 2. in case of gamma and SKYBOX_COLOR_IN_TARGET_COLOR_SPACE: do sqrt right away instead of doing that in fshader
OUT.groundColor = _Exposure * (cIn + COLOR_2_LINEAR(_GroundColor) * cOut);
OUT.skyColor = _Exposure * (cIn * getRayleighPhase(_WorldSpaceLightPos0.xyz, -eyeRay));
#if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE
// The sun should have a stable intensity in its course in the sky. Moreover it should match the highlight of a purely specular material.
// This matching was done using the standard shader BRDF1 on the 5/31/2017
// Finally we want the sun to be always bright even in LDR thus the normalization of the lightColor for low intensity.
half lightColorIntensity = clamp(length(_LightColor0.xyz), 0.25, 1);
#if SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE
OUT.sunColor = kSimpleSundiskIntensityFactor * saturate(cOut * kSunScale) * _LightColor0.xyz / lightColorIntensity;
#else // SKYBOX_SUNDISK_HQ
OUT.sunColor = kHDSundiskIntensityFactor * saturate(cOut) * _LightColor0.xyz / lightColorIntensity;
#endif
#endif
#if defined(UNITY_COLORSPACE_GAMMA) && SKYBOX_COLOR_IN_TARGET_COLOR_SPACE
OUT.groundColor = sqrt(OUT.groundColor);
OUT.skyColor = sqrt(OUT.skyColor);
#if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE
OUT.sunColor= sqrt(OUT.sunColor);
#endif
#endif
return OUT;
}
// Calculates the Mie phase function
half getMiePhase(half eyeCos, half eyeCos2)
{
half temp = 1.0 + MIE_G2 - 2.0 * MIE_G * eyeCos;
temp = pow(temp, pow(_SunSize,0.65) * 10);
temp = max(temp,1.0e-4); // prevent division by zero, esp. in half precision
temp = 1.5 * ((1.0 - MIE_G2) / (2.0 + MIE_G2)) * (1.0 + eyeCos2) / temp;
#if defined(UNITY_COLORSPACE_GAMMA) && SKYBOX_COLOR_IN_TARGET_COLOR_SPACE
temp = pow(temp, .454545);
#endif
return temp;
}
half random (half3 st)
{
return frac(sin(dot(st.xyz,float3(12.9898,78.233,1)))*43758.5453123);
}
// Calculates the sun shape
half calcSunAttenuation(half3 lightPos, half3 ray)
{
#if SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE
half3 delta = lightPos - ray;
half dist = length(delta);
half spot = 1.0 - smoothstep(0.0, _SunSize, dist);
return spot * spot;
#else // SKYBOX_SUNDISK_HQ
half focusedEyeCos = pow(saturate(dot(lightPos, ray)), _SunSizeConvergence);
return (getMiePhase(-focusedEyeCos, focusedEyeCos * focusedEyeCos));
#endif
}
half4 frag (v2f IN) : SV_Target
{
half3 col = half3(0.0, 0.0, 0.0);
// if y > 1 [eyeRay.y < -SKY_GROUND_THRESHOLD] - ground
// if y >= 0 and < 1 [eyeRay.y <= 0 and > -SKY_GROUND_THRESHOLD] - horizon
// if y < 0 [eyeRay.y > 0] - sky
#if SKYBOX_SUNDISK == SKYBOX_SUNDISK_HQ
half3 ray = normalize(mul((float3x3)unity_ObjectToWorld, IN.vertex));
half y = ray.y / SKY_GROUND_THRESHOLD;
#elif SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE
half3 ray = IN.rayDir.xyz;
half y = ray.y / SKY_GROUND_THRESHOLD;
#else
half y = IN.skyGroundFactor;
#endif
// if we did precalculate color in vprog: just do lerp between them
col = lerp(IN.skyColor, IN.groundColor, saturate(y));
#if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE
if(y < 0.0)
{
col += IN.sunColor * calcSunAttenuation(_WorldSpaceLightPos0.xyz, -ray);
}
#endif
col = lerp(col, dot(col, float3(0.3, 0.59, 0.11)), _Grisaille);
float4 c = texCUBE(_Cubemap, IN.uv); // nuages
float3 tmpcol = ((c.rgb-_clouds_intensity)*(c.a-(col.b*-2)));
// stars generation
half rnd = random(round(IN.uv));
half2 tcol = saturate( half2( rnd - 0.997, rnd - 0.99)) ;
// la grisaille efface les etoiles
col+=((tcol.x * 100 + tcol.y * 5)*(saturate(1-col.b*5))*_stars_int)*(1-_Grisaille);
#if defined(UNITY_COLORSPACE_GAMMA) && !SKYBOX_COLOR_IN_TARGET_COLOR_SPACE
col = LINEAR_2_OUTPUT(col);
#endif
return half4(col+tmpcol*(_clouds_presence),1.0);
}
ENDCG
}
}
Fallback Off
CustomEditor "SkyboxProceduralShaderGUI"
}
I have to confess i didn’t catch all of the U3D procedural sky subtleties.
My small part of code is located at the end.
Happy blending !
EDIT: sorry for the french words in comments and shader params.
Just ask me if you need clearing those terms
EDIT2: i put this topic in my very own personnal list