mirror of
https://github.com/MMqd/godot-nishita-sky-with-volumetric-clouds.git
synced 2024-12-22 16:07:29 +08:00
Add files via upload
This commit is contained in:
commit
695ab4fb4d
7
LICENSE.md
Normal file
7
LICENSE.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Copyright (c) 2023 MMqd.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
281
NishitaSky.gd
Normal file
281
NishitaSky.gd
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
@tool
|
||||||
|
extends Node3D
|
||||||
|
|
||||||
|
var sun_color := Color.BLACK
|
||||||
|
@export var sun_enabled := true
|
||||||
|
@export var light_color := Color.WHITE
|
||||||
|
@export var sky_material : Material = null
|
||||||
|
@export var sun_object_path: NodePath
|
||||||
|
@export var sun_ground_Height := 1000.0
|
||||||
|
@export var sun_saturation_scale := 100.0
|
||||||
|
@export var sun_saturation_mult := 0.3
|
||||||
|
@export_range(0.0000001, 1.0) var sun_desaturation_height := 0.25
|
||||||
|
@export var sun_gradient : GradientTexture1D = null
|
||||||
|
@export var sun_cloud_gradient : GradientTexture1D = null
|
||||||
|
@export var sun_cloud_ambient_gradient : GradientTexture1D = null
|
||||||
|
@export var sun_cloud_ground_gradient : GradientTexture1D = null
|
||||||
|
@export var compute_gradient_toggle := false:
|
||||||
|
get:
|
||||||
|
return compute_gradient_toggle
|
||||||
|
set(value):
|
||||||
|
if value:
|
||||||
|
compute_gradient_toggle = false
|
||||||
|
var cloud_height = (sky_material.get_shader_parameter("cloud_bottom")+sky_material.get_shader_parameter("cloud_top"))*0.5 + sky_material.get_shader_parameter("Height")
|
||||||
|
var sun_min_angle_mult := 1.0
|
||||||
|
var min_sun_y := sun_min_angle_mult*sin(acos(sky_material.get_shader_parameter("earthRadius") / (sky_material.get_shader_parameter("earthRadius") + sun_ground_Height)))
|
||||||
|
var min_cloud_sun_y := sun_min_angle_mult*sin(acos(sky_material.get_shader_parameter("earthRadius") / (sky_material.get_shader_parameter("earthRadius") + cloud_height)))
|
||||||
|
|
||||||
|
sun_gradient = compute_sun_gradient(sun_ground_Height, min_sun_y)
|
||||||
|
sun_cloud_gradient = compute_sun_gradient(min_cloud_sun_y, cloud_height)
|
||||||
|
sun_cloud_ambient_gradient = compute_sun_gradient(min_cloud_sun_y, sky_material.get_shader_parameter("cloud_top"), true)
|
||||||
|
sun_cloud_ground_gradient = compute_sun_gradient(min_cloud_sun_y, sky_material.get_shader_parameter("cloud_bottom"))
|
||||||
|
|
||||||
|
func compute_sun_gradient(h: float, min_sun_y : float, ambient: bool = false):
|
||||||
|
var gradient := GradientTexture1D.new()
|
||||||
|
gradient.gradient = Gradient.new()
|
||||||
|
var sample_count := 256
|
||||||
|
var max_col := 0.0
|
||||||
|
var cols : Array[Color] = []
|
||||||
|
var poss : Array[float] = []
|
||||||
|
|
||||||
|
var max_sky : Vector4
|
||||||
|
if ambient:
|
||||||
|
max_sky = sample_sky(Basis.from_euler(Vector3(PI*0.5, PI*0.5, 0.0)).z, Vector3.UP*h, Vector3.UP)
|
||||||
|
else:
|
||||||
|
max_sky = sample_sky(Vector3.UP, Vector3.UP*h, Vector3.UP)
|
||||||
|
for i in range(sample_count):
|
||||||
|
var new_i : float = i/(sample_count+1.0)
|
||||||
|
var dir : float = lerp(-0.5*PI, 0.5*PI, new_i)
|
||||||
|
var b_sun : Basis
|
||||||
|
|
||||||
|
var sun_rot := Vector3(dir, 0.0, 0.0)
|
||||||
|
sun_rot.x = min(Vector3(dir, 0.0, 0.0).x, asin(min_sun_y))
|
||||||
|
b_sun = Basis.from_euler(sun_rot)
|
||||||
|
|
||||||
|
var b_sample : Basis = Basis.from_euler(Vector3(dir, 0.0, 0.0))
|
||||||
|
if ambient:
|
||||||
|
b_sample = Basis.from_euler(Vector3(PI*0.5, PI*0.5, 0.0))
|
||||||
|
|
||||||
|
var sky : Vector4 = sample_sky(b_sample.z, Vector3.UP*h, b_sun.z)
|
||||||
|
var col : Color = Color(sky.x, sky.y, sky.z).srgb_to_linear()
|
||||||
|
if not ambient:
|
||||||
|
col = saturate(col, clamp((sun_desaturation_height-b_sun.z.y)/sun_desaturation_height, 0.0, 1.0))
|
||||||
|
max_col = max(max_col, col.r, col.g, col.b)
|
||||||
|
cols.append(col)
|
||||||
|
poss.append(new_i)
|
||||||
|
|
||||||
|
for i in range(sample_count):
|
||||||
|
var new_i : float = i/(sample_count+1.0)
|
||||||
|
cols[i] /= max_col
|
||||||
|
cols[i].r *= light_color.r
|
||||||
|
cols[i].g *= light_color.g
|
||||||
|
cols[i].b *= light_color.b
|
||||||
|
cols[i].a = 1.0
|
||||||
|
if i > 0 and cols[i]==cols[i-1]:
|
||||||
|
continue
|
||||||
|
gradient.gradient.add_point(poss[i], cols[i])
|
||||||
|
|
||||||
|
gradient.gradient.remove_point(len(gradient.gradient.offsets)-1)
|
||||||
|
gradient.gradient.remove_point(0)
|
||||||
|
return gradient
|
||||||
|
|
||||||
|
#func rot_to_gradient(rot: float) -> float:
|
||||||
|
# if rot > 0.5*PI:
|
||||||
|
# return fmod(rot, 0.5*PI)/PI - 0.5
|
||||||
|
# elif rot < -0.5*PI:
|
||||||
|
# return 0.5-fmod(rot, 0.5*PI)/PI
|
||||||
|
# return rot/PI
|
||||||
|
|
||||||
|
func rot_to_gradient(rot: float) -> float:
|
||||||
|
return (1.0-rot)*0.5
|
||||||
|
|
||||||
|
func normalized_color(col: Vector4) -> Vector4:
|
||||||
|
if max(col.x, col.y, col.z) == 0.0:
|
||||||
|
col = Vector4.ZERO
|
||||||
|
else:
|
||||||
|
col = col / max(col.x, col.y, col.z)
|
||||||
|
|
||||||
|
return col
|
||||||
|
|
||||||
|
func saturate(col: Color, saturation: float) -> Color:
|
||||||
|
return Color.from_hsv(col.h,
|
||||||
|
clamp(log(col.s*saturation*sun_saturation_scale+1.0)*sun_saturation_mult, 0.0, 1.0),
|
||||||
|
col.v)
|
||||||
|
|
||||||
|
func loop(val: float, val_range: float) -> float:
|
||||||
|
if val > val_range:
|
||||||
|
return fmod(val, val_range) - val_range
|
||||||
|
elif val < -val_range:
|
||||||
|
return fmod(val, -val_range) + val_range
|
||||||
|
return val
|
||||||
|
|
||||||
|
func loop_angle(val: float) -> float:
|
||||||
|
if val > 2*PI:
|
||||||
|
return fmod(val, 2*PI) - 2*PI
|
||||||
|
elif val < -2*PI:
|
||||||
|
return -fmod(val, -2*PI) + 2*PI
|
||||||
|
return val
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
var cloud_height = (sky_material.get_shader_parameter("cloud_bottom")+sky_material.get_shader_parameter("cloud_top"))*0.5 + sky_material.get_shader_parameter("Height")
|
||||||
|
var sun_dir : Vector3 = global_transform.basis.z
|
||||||
|
var sun_min_angle_mult := 1.0
|
||||||
|
var min_sun_y := sun_min_angle_mult*sin(acos(sky_material.get_shader_parameter("earthRadius") / (sky_material.get_shader_parameter("earthRadius") + sun_ground_Height)))
|
||||||
|
var min_cloud_sun_y := sun_min_angle_mult*sin(acos(sky_material.get_shader_parameter("earthRadius") / (sky_material.get_shader_parameter("earthRadius") + cloud_height)))
|
||||||
|
|
||||||
|
var sun_object = get_node(sun_object_path)
|
||||||
|
|
||||||
|
# print(loop_angle(rotation.x))
|
||||||
|
rotation.x = loop(rotation.x, PI)
|
||||||
|
rotation.y = loop(rotation.y, PI)
|
||||||
|
rotation.z = loop(rotation.z,PI)
|
||||||
|
|
||||||
|
sun_object.rotation = rotation
|
||||||
|
sun_object.rotation.x = max(rotation.x, PI-asin(min_sun_y)) if (rotation.x > PI*0.5) else min(rotation.x, asin(min_sun_y))
|
||||||
|
|
||||||
|
if sun_enabled:
|
||||||
|
sun_object.visible = sun_dir.y > -sin(deg_to_rad(sun_object.light_angular_distance)+acos(sky_material.get_shader_parameter("earthRadius") / (sky_material.get_shader_parameter("earthRadius") + sky_material.get_shader_parameter("cloud_top") * float(sky_material.get_shader_parameter("clouds")) )))
|
||||||
|
sky_material.set_shader_parameter("precomputed_sun_visible", sun_object.visible)
|
||||||
|
sky_material.set_shader_parameter("precomputed_sun_enabled", sun_enabled)
|
||||||
|
else:
|
||||||
|
sun_object.visible = false
|
||||||
|
sky_material.set_shader_parameter("precomputed_sun_visible", false)
|
||||||
|
sky_material.set_shader_parameter("precomputed_sun_enabled", false)
|
||||||
|
|
||||||
|
var gradient_pos := rot_to_gradient(sun_dir.y)
|
||||||
|
sun_object.light_color = sun_gradient.gradient.sample(gradient_pos)
|
||||||
|
sky_material.set_shader_parameter("precomputed_sun_dir", sun_dir)
|
||||||
|
sky_material.set_shader_parameter("precomputed_sun_color", light_color)
|
||||||
|
|
||||||
|
#Precomputed cloud lighting
|
||||||
|
if sky_material.get_shader_parameter("clouds"):
|
||||||
|
var cloud_sun_rot := rotation
|
||||||
|
cloud_sun_rot.x = min(rotation.x, asin(min_cloud_sun_y))
|
||||||
|
sky_material.set_shader_parameter("precomputed_Atmosphere_sun", sun_cloud_gradient.gradient.sample(gradient_pos))
|
||||||
|
sky_material.set_shader_parameter("precomputed_Atmosphere_ambient", sun_cloud_ambient_gradient.gradient.sample(gradient_pos))
|
||||||
|
sky_material.set_shader_parameter("precomputed_Atmosphere_ground", sun_cloud_ground_gradient.gradient.sample(gradient_pos))
|
||||||
|
|
||||||
|
var ground_color : Vector3 = Vector3(0.1, 0.07, 0.034)
|
||||||
|
var ground_brightness : float = 1.0
|
||||||
|
|
||||||
|
func solve_quadratic(origin : Vector3, dir : Vector3, Radius : float) -> Vector3:
|
||||||
|
var b := 2.0 * dir.dot(origin)
|
||||||
|
var c := origin.dot(origin) - Radius * Radius
|
||||||
|
var d := b*b - 4.0 * c
|
||||||
|
var det := sqrt(d)
|
||||||
|
return Vector3((-b + det) * 0.5, (-b - det) * 0.5, d)
|
||||||
|
|
||||||
|
func atmosphere(Direction: Vector3, pos: Vector3, SunDirection: Vector3, intensity: float = 1.0) -> Array[Vector3]:
|
||||||
|
var shader_Height := 1.0
|
||||||
|
# var intensity : float = sky_material.get_shader_parameter("intensity")
|
||||||
|
var Re : float = sky_material.get_shader_parameter("earthRadius")
|
||||||
|
var Ra : float = sky_material.get_shader_parameter("atmosphereRadius")
|
||||||
|
var Hr : float = sky_material.get_shader_parameter("rayleighScaleHeight")
|
||||||
|
var Hm : float = sky_material.get_shader_parameter("mieScaleHeight")
|
||||||
|
var mie_eccentricity : float = sky_material.get_shader_parameter("mie_eccentricity")
|
||||||
|
var turbidity : float = sky_material.get_shader_parameter("turbidity")
|
||||||
|
|
||||||
|
var ground := 0.0
|
||||||
|
|
||||||
|
var mu := Direction.dot(SunDirection)
|
||||||
|
var phaseR := (3.0 / (16.0 * PI)) * (1.0 + mu * mu)
|
||||||
|
var phaseM := (3.0 / (8.0 * PI)) * ((1.0 - mie_eccentricity * mie_eccentricity) * (1.0 + mu * mu) / ((2.0 + mie_eccentricity * mie_eccentricity) * pow(1.0 + mie_eccentricity * mie_eccentricity - 2.0 * mie_eccentricity * mu, 1.5)))
|
||||||
|
|
||||||
|
var SumR := Vector3.ZERO
|
||||||
|
var SumM := Vector3.ZERO
|
||||||
|
|
||||||
|
var begin := Vector3.ZERO
|
||||||
|
var end := Vector3.ZERO
|
||||||
|
|
||||||
|
var cameraPos := Vector3(0,Re + sun_ground_Height + max(0.0, pos.y),0)
|
||||||
|
begin = cameraPos
|
||||||
|
|
||||||
|
var d1 := solve_quadratic(cameraPos, Direction, Ra)
|
||||||
|
if (d1.x > d1.y && d1.x > 0.0):
|
||||||
|
end = cameraPos + Direction * d1.x
|
||||||
|
|
||||||
|
if (d1.y > 0.0):
|
||||||
|
begin = cameraPos + Direction * d1.y
|
||||||
|
else:
|
||||||
|
return [Vector3.ZERO, Vector3.ONE, Vector3.ONE]
|
||||||
|
|
||||||
|
|
||||||
|
var d2 = solve_quadratic(cameraPos, Direction, Re)
|
||||||
|
if (d2.x > 0.0 && d2.y > 0.0):
|
||||||
|
end = begin + Direction * d2.y
|
||||||
|
ground=1.0
|
||||||
|
|
||||||
|
var numSamples := 16*16
|
||||||
|
var numSamplesL := 8*2
|
||||||
|
|
||||||
|
var segmentLength := begin.distance_to(end) / float(numSamples)
|
||||||
|
var opticalDepthR := 0.0
|
||||||
|
var opticalDepthM := 0.0
|
||||||
|
var atmosphere_atten := Vector3.ZERO
|
||||||
|
|
||||||
|
var BetaR : Vector3 = sky_material.get_shader_parameter("rayleigh_color") * 22.4e-6 * sky_material.get_shader_parameter("rayleigh")
|
||||||
|
var BetaM : Vector3 = sky_material.get_shader_parameter("mie_color") * 20e-6 * sky_material.get_shader_parameter("mie")
|
||||||
|
|
||||||
|
for i in range(numSamples):
|
||||||
|
var Px := begin + Direction * segmentLength * (float(i) + 0.5)
|
||||||
|
var sampleHeight := Px.length() - Re
|
||||||
|
|
||||||
|
var Hr_sample := exp(-sampleHeight/(Hr*turbidity)) * segmentLength
|
||||||
|
var Hm_sample := exp(-sampleHeight/(Hm*turbidity)) * segmentLength
|
||||||
|
|
||||||
|
opticalDepthR += Hr_sample
|
||||||
|
opticalDepthM += Hm_sample
|
||||||
|
|
||||||
|
var opticalDepthLR := 0.0
|
||||||
|
var opticalDepthLM := 0.0
|
||||||
|
|
||||||
|
var d3 = solve_quadratic(Px, SunDirection, Ra)
|
||||||
|
var d4 = solve_quadratic(Px, SunDirection, Re)
|
||||||
|
|
||||||
|
if (d4.x > 0.0 and d4.y > 0.0):
|
||||||
|
continue
|
||||||
|
|
||||||
|
var j2 := 0
|
||||||
|
|
||||||
|
var segmentLengthL : float = max(d3.x, d3.y) / float(numSamplesL)
|
||||||
|
|
||||||
|
for j in range(numSamplesL):
|
||||||
|
var Pl : Vector3 = Px + SunDirection * segmentLengthL * (j + 0.5)
|
||||||
|
var sampleHeightL : float = Pl.length() - Re
|
||||||
|
if (sampleHeightL < 0.0):
|
||||||
|
break
|
||||||
|
opticalDepthLR += exp(-sampleHeightL/(Hr*turbidity))
|
||||||
|
opticalDepthLM += exp(-sampleHeightL/(Hm*turbidity))
|
||||||
|
j2 += 1
|
||||||
|
|
||||||
|
if (j2 == numSamplesL):
|
||||||
|
opticalDepthLR *= segmentLengthL
|
||||||
|
opticalDepthLM *= segmentLengthL
|
||||||
|
var tau := BetaR * (opticalDepthR + opticalDepthLR) + BetaM * 1.1 * (opticalDepthM + opticalDepthLM)
|
||||||
|
var attenuation := v3exp(-tau)
|
||||||
|
atmosphere_atten += tau
|
||||||
|
|
||||||
|
SumR += Hr_sample * attenuation
|
||||||
|
SumM += Hm_sample * attenuation
|
||||||
|
|
||||||
|
var sky := SumR * phaseR * BetaR + SumM * phaseM * BetaM
|
||||||
|
return [sky, atmosphere_atten*(1.0-ground), v3exp(-(opticalDepthR * BetaR + opticalDepthM * BetaM))]
|
||||||
|
|
||||||
|
func v3exp(input: Vector3) -> Vector3:
|
||||||
|
return Vector3(exp(input.x), exp(input.y), exp(input.z))
|
||||||
|
|
||||||
|
func sample_sky(dir: Vector3, pos: Vector3, sun_dir: Vector3, LIGHT0_ENERGY: Vector3 = Vector3.ONE, LIGHT0_COLOR: Vector3 = Vector3.ONE) -> Vector4:
|
||||||
|
var sun_object = get_node(sun_object_path)
|
||||||
|
var sky : Array[Vector3] = atmosphere(dir, pos, sun_dir)
|
||||||
|
var skyxyz : Vector3 = sky[0]
|
||||||
|
var sun : Vector3 = Vector3.ZERO
|
||||||
|
sun = (
|
||||||
|
(Vector3.ONE-v3exp(-sky[1])) * ( Vector3.ONE*max(max(dir.dot(sun_dir),0.0)-(cos( deg_to_rad(sun_object.light_angular_distance) )), 0.0) * sky_material.get_shader_parameter("sun_brightness")
|
||||||
|
+ (Vector3.ONE-v3exp(-sky[2])) * ground_color * max(sun_dir.y, 0.0) * sky[2].x * ground_brightness )
|
||||||
|
* LIGHT0_ENERGY)
|
||||||
|
|
||||||
|
var col := skyxyz + sun
|
||||||
|
return Vector4(col.x, col.y, col.z, 1.0)
|
||||||
|
|
||||||
|
func mix(start: Vector3, end: Vector3, factor: float):
|
||||||
|
return lerp(start, end, factor)
|
78
README.md
Normal file
78
README.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Nishita Sky With Volumetric Clouds
|
||||||
|
|
||||||
|
This is a Nishita sky shader for godot 4.0, with [Clay John's volumetric clouds](https://github.com/clayjohn/godot-volumetric-cloud-demo) based on [a tutorial by scratch pixel](https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/simulating-sky/simulating-colors-of-the-sky.html), which is recommended to read to understand what the sky parameters represent, when configuring the sky.
|
||||||
|
|
||||||
|
## Screenshots (stars amplified for demonstration purposes)
|
||||||
|
|
||||||
|
**Day**
|
||||||
|
![day](Screenshots/day.png)
|
||||||
|
|
||||||
|
**Day Without Clouds**
|
||||||
|
![day without clouds](Screenshots/day%20without%20clouds.png)
|
||||||
|
|
||||||
|
**High Quality Day**
|
||||||
|
![high quality day](Screenshots/high%20quality%20day.png)
|
||||||
|
|
||||||
|
**Sunset**
|
||||||
|
![sunset](Screenshots/sunset.png)
|
||||||
|
|
||||||
|
**After Sunset**
|
||||||
|
![after sunset](Screenshots/after%20sunset.png)
|
||||||
|
|
||||||
|
**Cloudy Night Sky After Sunset**
|
||||||
|
![cloudy night sky after sunset](Screenshots/cloudy%20night%20sky%20after%20sunset.png)
|
||||||
|
|
||||||
|
**Cloudy Night Sky**
|
||||||
|
![cloudy night sky](Screenshots/cloudy%20night%20sky.png)
|
||||||
|
|
||||||
|
**Atmosphere from 100km**
|
||||||
|
![atmosphere from 100km](Screenshots/atmosphere%20from%20100km.png)
|
||||||
|
|
||||||
|
**Low Sun Atmosphere from 100km**
|
||||||
|
![low sun atmosphere from 100km](Screenshots/low%20sun%20atmosphere%20from%20100km.png)
|
||||||
|
|
||||||
|
**Very Low Sun Atmosphere From 100km**
|
||||||
|
![very low sun atmosphere from 100km](Screenshots/very%20low%20sun%20atmosphere%20from%20100km.png)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* Game-ready asset (although in alpha)
|
||||||
|
* Raymarched sky
|
||||||
|
* Raymarched clouds that move with the camera
|
||||||
|
* Different times of day by rotating the "NishitaSky" node
|
||||||
|
* Realistic lighting at different altitudes
|
||||||
|
* A night sky
|
||||||
|
* A directional light that takes on the color of the sun in the shader
|
||||||
|
* All elements interact with each other: the night sky is blocked by the clouds and attenuated by the atmosphere
|
||||||
|
* Ability to configure quality of the shader and turn the clouds on/off
|
||||||
|
* Performance optimizations
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
* Performance heavy, especially with clouds on
|
||||||
|
* The camera must remain below the clouds (but is clamped to cloud height if it goes higher), since the clouds do not actully exist
|
||||||
|
|
||||||
|
## Improvements
|
||||||
|
* For the sky precompute the optical depth between the sun and an arbitrary point along the ray (from Nishita's paper)
|
||||||
|
* Add multiple scattering to clouds and sky
|
||||||
|
* Physical raytraced clouds, with better lighting (curently the clouds are evenly lit)
|
||||||
|
* Better cloud density textures
|
||||||
|
* Use cloud sample distance for cloud fog (currently uses distance to clouds)
|
||||||
|
* Physically accurate ground material (currently the brightness is just a dot product to the sun)
|
||||||
|
* Better sun color saturation (currently some hacks are nessary to get the expected sun brightness and saturation)
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
To implement this sky into a project
|
||||||
|
1. Copy the "NishitaSky" node from the main scene into a the project
|
||||||
|
2. In the "NishitaSky" node set "sun_object_path" variable to the desired directional light, do not make this directional light a child of the "NishitaSky" node
|
||||||
|
3. Create an "WorldEnvironment" node, set the sky material to the "nishita_sky" material
|
||||||
|
4. Click copy on the sky section of the "WorldEnvironment" node, and paste it into the "sky_material" section of the "NishitaSky" node. **THE MATERIALS MUST BE LINKED FOR THE SKY PARAMETERS TO BE THE SAME ON THE SCRIPT AND THE SHADER**
|
||||||
|
5. Set the correct "sun_ground_height" on the "NishitaSky" node, this is the height of objects on the ground
|
||||||
|
6. After adjusting all settings as needed click the "Compute Gradient Toggle" to precompute the sun color gradient
|
||||||
|
7. It may be necessary to reload the scene to make the sky work in the editor
|
||||||
|
8. If the sky is very slow try changing the process mode to "High-Quality Incremental" in the Sky settings in the WorldEnvironment
|
||||||
|
|
||||||
|
## Todo
|
||||||
|
* Fix clouds "jumping" after some time
|
||||||
|
* Remove bug: broken pixels sometimes appear on the edge of the atmosphere, when above the atmosphere
|
||||||
|
* Clean up code
|
||||||
|
* Rework sun saturation
|
||||||
|
* Set WorldEnvironment fog color based on sky color
|
10
fps_counter.gd
Normal file
10
fps_counter.gd
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
extends Label
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
$"/root/Main/Camera3D".position.y = pow($"%HeightSlider".value, 4.0)-$"/root/Main/WorldEnvironment".environment.sky.sky_material.get_shader_parameter("Height")+1.0
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
set_text("FPS: " + str(Engine.get_frames_per_second()))
|
||||||
|
|
||||||
|
func _on_height_slider_value_changed(value):
|
||||||
|
$"/root/Main/Camera3D".position.y = pow(value, 4.0)-$"/root/Main/WorldEnvironment".environment.sky.sky_material.get_shader_parameter("Height")+1.0
|
1
icon.svg
Normal file
1
icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><g transform="translate(32 32)"><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99z" fill="#363d52"/><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99zm0 4h96c6.64 0 12 5.35 12 11.99v95.98c0 6.64-5.35 11.99-12 11.99h-96c-6.64 0-12-5.35-12-11.99v-95.98c0-6.64 5.36-11.99 12-11.99z" fill-opacity=".4"/></g><g stroke-width="9.92746" transform="matrix(.10073078 0 0 .10073078 12.425923 2.256365)"><path d="m0 0s-.325 1.994-.515 1.976l-36.182-3.491c-2.879-.278-5.115-2.574-5.317-5.459l-.994-14.247-27.992-1.997-1.904 12.912c-.424 2.872-2.932 5.037-5.835 5.037h-38.188c-2.902 0-5.41-2.165-5.834-5.037l-1.905-12.912-27.992 1.997-.994 14.247c-.202 2.886-2.438 5.182-5.317 5.46l-36.2 3.49c-.187.018-.324-1.978-.511-1.978l-.049-7.83 30.658-4.944 1.004-14.374c.203-2.91 2.551-5.263 5.463-5.472l38.551-2.75c.146-.01.29-.016.434-.016 2.897 0 5.401 2.166 5.825 5.038l1.959 13.286h28.005l1.959-13.286c.423-2.871 2.93-5.037 5.831-5.037.142 0 .284.005.423.015l38.556 2.75c2.911.209 5.26 2.562 5.463 5.472l1.003 14.374 30.645 4.966z" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 919.24059 771.67186)"/><path d="m0 0v-47.514-6.035-5.492c.108-.001.216-.005.323-.015l36.196-3.49c1.896-.183 3.382-1.709 3.514-3.609l1.116-15.978 31.574-2.253 2.175 14.747c.282 1.912 1.922 3.329 3.856 3.329h38.188c1.933 0 3.573-1.417 3.855-3.329l2.175-14.747 31.575 2.253 1.115 15.978c.133 1.9 1.618 3.425 3.514 3.609l36.182 3.49c.107.01.214.014.322.015v4.711l.015.005v54.325c5.09692 6.4164715 9.92323 13.494208 13.621 19.449-5.651 9.62-12.575 18.217-19.976 26.182-6.864-3.455-13.531-7.369-19.828-11.534-3.151 3.132-6.7 5.694-10.186 8.372-3.425 2.751-7.285 4.768-10.946 7.118 1.09 8.117 1.629 16.108 1.846 24.448-9.446 4.754-19.519 7.906-29.708 10.17-4.068-6.837-7.788-14.241-11.028-21.479-3.842.642-7.702.88-11.567.926v.006c-.027 0-.052-.006-.075-.006-.024 0-.049.006-.073.006v-.006c-3.872-.046-7.729-.284-11.572-.926-3.238 7.238-6.956 14.642-11.03 21.479-10.184-2.264-20.258-5.416-29.703-10.17.216-8.34.755-16.331 1.848-24.448-3.668-2.35-7.523-4.367-10.949-7.118-3.481-2.678-7.036-5.24-10.188-8.372-6.297 4.165-12.962 8.079-19.828 11.534-7.401-7.965-14.321-16.562-19.974-26.182 4.4426579-6.973692 9.2079702-13.9828876 13.621-19.449z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 104.69892 525.90697)"/><path d="m0 0-1.121-16.063c-.135-1.936-1.675-3.477-3.611-3.616l-38.555-2.751c-.094-.007-.188-.01-.281-.01-1.916 0-3.569 1.406-3.852 3.33l-2.211 14.994h-31.459l-2.211-14.994c-.297-2.018-2.101-3.469-4.133-3.32l-38.555 2.751c-1.936.139-3.476 1.68-3.611 3.616l-1.121 16.063-32.547 3.138c.015-3.498.06-7.33.06-8.093 0-34.374 43.605-50.896 97.781-51.086h.066.067c54.176.19 97.766 16.712 97.766 51.086 0 .777.047 4.593.063 8.093z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 784.07144 817.24284)"/><path d="m0 0c0-12.052-9.765-21.815-21.813-21.815-12.042 0-21.81 9.763-21.81 21.815 0 12.044 9.768 21.802 21.81 21.802 12.048 0 21.813-9.758 21.813-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 389.21484 625.67104)"/><path d="m0 0c0-7.994-6.479-14.473-14.479-14.473-7.996 0-14.479 6.479-14.479 14.473s6.483 14.479 14.479 14.479c8 0 14.479-6.485 14.479-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 367.36686 631.05679)"/><path d="m0 0c-3.878 0-7.021 2.858-7.021 6.381v20.081c0 3.52 3.143 6.381 7.021 6.381s7.028-2.861 7.028-6.381v-20.081c0-3.523-3.15-6.381-7.028-6.381" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 511.99336 724.73954)"/><path d="m0 0c0-12.052 9.765-21.815 21.815-21.815 12.041 0 21.808 9.763 21.808 21.815 0 12.044-9.767 21.802-21.808 21.802-12.05 0-21.815-9.758-21.815-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 634.78706 625.67104)"/><path d="m0 0c0-7.994 6.477-14.473 14.471-14.473 8.002 0 14.479 6.479 14.479 14.473s-6.477 14.479-14.479 14.479c-7.994 0-14.471-6.485-14.471-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 656.64056 631.05679)"/></g></svg>
|
After Width: | Height: | Size: 4.1 KiB |
37
icon.svg.import
Normal file
37
icon.svg.import
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c4hd3ttt8f2x8"
|
||||||
|
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icon.svg"
|
||||||
|
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/bptc_ldr=0
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
392
nishita_sky.gdshader
Normal file
392
nishita_sky.gdshader
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
shader_type sky;
|
||||||
|
render_mode use_half_res_pass, use_quarter_res_pass;
|
||||||
|
|
||||||
|
//These properties are precomputed by the NishitaSky script, and cannot be modified
|
||||||
|
uniform float precomputed_sun_visible = 1.0;
|
||||||
|
uniform float precomputed_sun_enabled = 1.0;
|
||||||
|
uniform vec3 precomputed_sun_dir = vec3(1.0, 0.0, 0.0);
|
||||||
|
uniform vec3 precomputed_sun_color : source_color = vec3(1.0);
|
||||||
|
uniform vec3 precomputed_Atmosphere_sun : source_color = vec3(0.0);
|
||||||
|
uniform vec3 precomputed_Atmosphere_ambient : source_color = vec3(0.0);
|
||||||
|
uniform vec3 precomputed_Atmosphere_ground : source_color = vec3(0.0);
|
||||||
|
|
||||||
|
//Controls the scattering responsible for the blue color of a real life sky
|
||||||
|
uniform vec3 rayleigh_color = vec3(0.258928571428571, 0.580357142857143, 1.0);
|
||||||
|
uniform float rayleigh : hint_range(0, 64) = 1.0; // higher values absorb more rayleigh_color, more blue by default
|
||||||
|
|
||||||
|
//Controls the scattering responsible for the white horizon of a real life sky
|
||||||
|
uniform vec3 mie_color = vec3(1.0, 1.0, 1.0);
|
||||||
|
uniform float mie : hint_range(0, 64) = 1.0; // higher values make a "foggy" atmosphere
|
||||||
|
uniform float mie_eccentricity : hint_range(-1, 0.99999) = 0.76;
|
||||||
|
|
||||||
|
//Sample counts for different parts of the sky
|
||||||
|
uniform int atmosphere_samples_max = 32; //maximum allowed atmosphere samples per pixel
|
||||||
|
uniform int atmosphere_samples_min = 12; //minimum allowed atmosphere samples per pixel
|
||||||
|
uniform float atmosphere_samples_horizon_bias = 0.5; //lower values bias more samples towards the horizon
|
||||||
|
uniform int atmosphere_sun_samples = 32; //extra samples around sun, does not exceed maximum
|
||||||
|
uniform int atmosphere_light_samples = 8; //scattering samples from each direction towards the sun
|
||||||
|
|
||||||
|
uniform float turbidity : hint_range(0, 1000) = 1.0;
|
||||||
|
uniform vec3 ground_color : source_color = vec3(0.1, 0.07, 0.034);
|
||||||
|
|
||||||
|
//Brightness controls
|
||||||
|
uniform float intensity = 10.0; //Intensity of sky. Does not affect clouds
|
||||||
|
uniform float sun_brightness = 100000.0; //brightness of only the solar disk
|
||||||
|
uniform float ground_brightness = 0.5;
|
||||||
|
uniform float night_sky_brightness = 0.001;
|
||||||
|
|
||||||
|
//Night sky texture
|
||||||
|
uniform sampler2D night_sky : source_color, hint_default_black;
|
||||||
|
|
||||||
|
//Height of viewer above the atmosphere, in addition to the camera's y position. Does not affect cloud height
|
||||||
|
uniform float Height = 1000.0;
|
||||||
|
|
||||||
|
uniform float earthRadius = 6360e3;
|
||||||
|
uniform float atmosphereRadius = 6420e3;
|
||||||
|
uniform float rayleighScaleHeight = 7994.0; // Scale height for Rayleigh scattering
|
||||||
|
uniform float mieScaleHeight = 1200.0; // Scale height for Mie scattering
|
||||||
|
|
||||||
|
const float BetaRScale = 22.4e-6;
|
||||||
|
const float BetaMScale = 20e-6;
|
||||||
|
//const vec3 BetaR = vec3(5.8e-6,13.0e-6,22.4e-6);
|
||||||
|
//const vec3 BetaM = vec3(20e-6);
|
||||||
|
|
||||||
|
//dir is normalized
|
||||||
|
vec3 solve_quadratic(vec3 origin, vec3 dir, float Radius){
|
||||||
|
float b = 2.0 * dot(dir, origin);
|
||||||
|
float c = dot(origin, origin) - Radius * Radius;
|
||||||
|
float d = b*b - 4.0 * c;
|
||||||
|
float dsqrt = sqrt(d);
|
||||||
|
float x1 = (-b + dsqrt) * 0.5;
|
||||||
|
float x2 = (-b - dsqrt) * 0.5;
|
||||||
|
return vec3(x1, x2, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3[4] atmosphere(vec3 dir, vec3 pos){
|
||||||
|
vec3 SunDirection = precomputed_sun_dir;
|
||||||
|
vec3 begin = vec3(0.0);
|
||||||
|
vec3 end = vec3(0.0);
|
||||||
|
|
||||||
|
vec3 cameraPos = vec3(0,earthRadius + Height + max(0.0, pos.y),0);
|
||||||
|
begin = cameraPos;
|
||||||
|
|
||||||
|
vec3 d1 = solve_quadratic(cameraPos, dir, atmosphereRadius);
|
||||||
|
// Find atmosphere end point, exit if no intersection
|
||||||
|
if (d1.x > d1.y && d1.x > 0.0){
|
||||||
|
end = cameraPos + dir * d1.x;
|
||||||
|
|
||||||
|
// If the ray starts outside the atmosphere, set the origin to the edge of the atmosphere
|
||||||
|
if (d1.y > 0.0){
|
||||||
|
begin = cameraPos + dir * d1.y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {vec3(0.0), vec3(1.0), vec3(0.0), vec3(1.0) };
|
||||||
|
}
|
||||||
|
|
||||||
|
float ground = 0.0;
|
||||||
|
// Check if ray intersects with ground, and set the end point to the ground if it intersects
|
||||||
|
vec3 d2 = solve_quadratic(cameraPos, dir, earthRadius);
|
||||||
|
if (d2.x > 0.0 && d2.y > 0.0){
|
||||||
|
end = cameraPos + dir * d2.y; ground=1.0; // enable ground
|
||||||
|
// end = cameraPos + dir * d1.y; // optionally disable ground, buggy at high altitudes and low sample counts, set a higher height instead.
|
||||||
|
// return {vec3(0.0), vec3(1.0), vec3(1.0), vec3(1.0) }; // optionally disable atmosphere lighting, must set ground brightness to 0 as well.
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 SumR = vec3(0.0);
|
||||||
|
vec3 SumM = vec3(0.0);
|
||||||
|
float mu = dot(dir, SunDirection);
|
||||||
|
float phaseR = (3.0 / (16.0 * PI)) * (1.0 + mu * mu);
|
||||||
|
float phaseM = (3.0 / (8.0 * PI)) * ((1.0 - mie_eccentricity*mie_eccentricity) * (1.0 + mu * mu) / ((2.0 + mie_eccentricity * mie_eccentricity) * pow(1.0 + mie_eccentricity * mie_eccentricity - 2.0 * mie_eccentricity * mu, 1.5)));
|
||||||
|
|
||||||
|
float segmentLength = distance (begin, end);
|
||||||
|
float horizon = sin(acos(earthRadius / (earthRadius + Height)));
|
||||||
|
|
||||||
|
// Bias atmosphere samples towards sun and horizon
|
||||||
|
float weighted_atmosphere_samples = ceil(clamp(
|
||||||
|
(max(clamp(1.0 - pow(abs(dir.y + horizon), atmosphere_samples_horizon_bias), 0.0, 1.0)
|
||||||
|
* float(atmosphere_samples_max)
|
||||||
|
, pow(max(mu, 0.0), 3.0) * float(atmosphere_sun_samples))
|
||||||
|
), float(atmosphere_samples_min), float(atmosphere_samples_max)));
|
||||||
|
|
||||||
|
segmentLength /= weighted_atmosphere_samples;
|
||||||
|
|
||||||
|
float opticalDepthR = 0.0;
|
||||||
|
float opticalDepthM = 0.0;
|
||||||
|
|
||||||
|
vec3 atmosphere_atten = vec3(0.0);
|
||||||
|
vec3 BetaR = rayleigh_color * BetaRScale * rayleigh;
|
||||||
|
vec3 BetaM = mie_color * BetaMScale * mie;
|
||||||
|
|
||||||
|
|
||||||
|
for (float i = 0.5; i < weighted_atmosphere_samples + 0.5; i++) {
|
||||||
|
vec3 Px = begin + dir * segmentLength * i;
|
||||||
|
float sampleHeight = length(Px) - earthRadius;
|
||||||
|
|
||||||
|
float Hr_sample = exp(-sampleHeight / (rayleighScaleHeight * turbidity)) * segmentLength;
|
||||||
|
float Hm_sample = exp(-sampleHeight / (mieScaleHeight * turbidity)) * segmentLength;
|
||||||
|
|
||||||
|
opticalDepthR += Hr_sample;
|
||||||
|
opticalDepthM += Hm_sample;
|
||||||
|
|
||||||
|
if (precomputed_sun_enabled<0.5)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float opticalDepthLR = 0.0;
|
||||||
|
float opticalDepthLM = 0.0;
|
||||||
|
|
||||||
|
vec3 d3 = solve_quadratic(Px, SunDirection, atmosphereRadius);
|
||||||
|
vec3 d4 = solve_quadratic(Px, SunDirection, earthRadius);
|
||||||
|
|
||||||
|
// Ignore sample if sun is below horizon, used for performance boost at night time. Also fixes spots at edges at high altitudes
|
||||||
|
if ((d4.x > 0.0 && d4.y > 0.0) || d3.z < 0.0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float segmentLengthL = max(d3.x, d3.y) / float(atmosphere_light_samples);
|
||||||
|
|
||||||
|
int j2 = 0;
|
||||||
|
for (float j = 0.5; j < float(atmosphere_light_samples) + 0.5; j++) {
|
||||||
|
float sampleHeightL = length(Px + SunDirection * segmentLengthL * j) - earthRadius;
|
||||||
|
|
||||||
|
// Ignore light samples inside planet, used for performance boost at night time
|
||||||
|
if (sampleHeightL < 0.0)
|
||||||
|
break;
|
||||||
|
opticalDepthLR += exp(-sampleHeightL/(rayleighScaleHeight*turbidity));
|
||||||
|
opticalDepthLM += exp(-sampleHeightL/(mieScaleHeight*turbidity));
|
||||||
|
j2++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attenuation
|
||||||
|
if (j2 == atmosphere_light_samples){
|
||||||
|
opticalDepthLR *= segmentLengthL;
|
||||||
|
opticalDepthLM *= segmentLengthL;
|
||||||
|
vec3 tau = BetaR * (opticalDepthR + opticalDepthLR) + BetaM * 1.1 * (opticalDepthM + opticalDepthLM);
|
||||||
|
vec3 attenuation = exp(-tau);
|
||||||
|
|
||||||
|
atmosphere_atten += attenuation;
|
||||||
|
SumR += Hr_sample * attenuation;
|
||||||
|
SumM += Hm_sample * attenuation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 Sky = SumR * phaseR * BetaR + SumM * phaseM * BetaM;
|
||||||
|
return {Sky, atmosphere_atten, vec3(ground), exp(-(opticalDepthR * BetaR + opticalDepthM * BetaM)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4[2] sample_sky(vec3 dir, vec3 pos){
|
||||||
|
vec3[4] sky = atmosphere(dir, pos);
|
||||||
|
vec3 sun = vec3(0.0);
|
||||||
|
sun =
|
||||||
|
// Sun, with sun-specific attenuation
|
||||||
|
( (vec3(1.0)-exp(-sky[1])) * max(max(dot(dir, precomputed_sun_dir), 0.0) - (cos(LIGHT0_SIZE * 0.5)), 0.0) * sun_brightness * (1.0 - sky[2].r) +
|
||||||
|
|
||||||
|
// ground, with generic attenuation
|
||||||
|
(vec3(1.0)-exp(-sky[3])) * ground_color * max(precomputed_sun_dir.y, 0.0) * sky[2].r * ground_brightness ) * LIGHT0_ENERGY ;
|
||||||
|
vec3 col = (sun + sky[0].xyz);
|
||||||
|
return {vec4(col * precomputed_sun_color, sky[2].r), vec4(sky[3] * (1.0 - sky[2].r), 1.0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Begin Cloud Parameters */
|
||||||
|
|
||||||
|
// Cloud Raymarching based on: A. Schneider. “The earthRadiusal-Time Volumetric Cloudscapes Of Horizon: Zero Dawn”. ACM SIGGRAPH. Los Angeles, CA: ACM SIGGRAPH, 2015. Web. 26 Aug. 2015.
|
||||||
|
|
||||||
|
uniform sampler3D worlnoise : filter_linear_mipmap, repeat_enable;
|
||||||
|
uniform sampler3D perlworlnoise : filter_linear_mipmap, repeat_enable;
|
||||||
|
uniform sampler2D weathermap : filter_linear_mipmap, repeat_enable;
|
||||||
|
|
||||||
|
uniform bool clouds = true;
|
||||||
|
uniform int cloud_samples_horizon = 54;
|
||||||
|
uniform int cloud_samples_sky = 96;
|
||||||
|
uniform float _density : hint_range(0.01, 0.5) = 0.097;
|
||||||
|
uniform float cloud_coverage : hint_range(0.0, 1.0) = 0.25;
|
||||||
|
uniform float _time_scale : hint_range(0.0, 20.0) = 1.0;
|
||||||
|
uniform float _time_offset = 0.0;
|
||||||
|
|
||||||
|
uniform float cloud_bottom = 1500.0;
|
||||||
|
uniform float cloud_top = 4000.0;
|
||||||
|
uniform float cloud_brightness = 1.5;
|
||||||
|
uniform float cloud_ambient_brightness = 0.5;
|
||||||
|
|
||||||
|
// From: https://www.shadertoy.com/view/4sfGzS credit to iq
|
||||||
|
float hash(vec3 p) {
|
||||||
|
p = fract( p * 0.3183099 + 0.1 );
|
||||||
|
p *= 17.0;
|
||||||
|
return fract(p.x * p.y * p.z * (p.x + p.y + p.z));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function that maps a value from one range to another.
|
||||||
|
float remap(float originalValue, float originalMin, float originalMax, float newMin, float newMax) {
|
||||||
|
return newMin + (((originalValue - originalMin) / (originalMax - originalMin)) * (newMax - newMin));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase function
|
||||||
|
float henyey_greenstein(float cos_theta, float G) {
|
||||||
|
const float k = 0.0795774715459;
|
||||||
|
return k * (1.0 - G * G) / (pow(1.0 + G * G - 2.0 * G * cos_theta, 1.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float GetHeightFractionForPoint(float inPosition) {
|
||||||
|
float height_fraction = (inPosition - cloud_bottom - earthRadius) / (cloud_top - cloud_bottom);
|
||||||
|
return clamp(height_fraction, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 mixGradients(float cloudType){
|
||||||
|
const vec4 STRATUS_GRADIENT = vec4(0.02f, 0.05f, 0.09f, 0.11f);
|
||||||
|
const vec4 STRATOCUMULUS_GRADIENT = vec4(0.02f, 0.2f, 0.48f, 0.625f);
|
||||||
|
const vec4 CUMULUS_GRADIENT = vec4(0.01f, 0.0625f, 0.78f, 1.0f);
|
||||||
|
float stratus = 1.0f - clamp(cloudType * 2.0f, 0.0, 1.0);
|
||||||
|
float stratocumulus = 1.0f - abs(cloudType - 0.5f) * 2.0f;
|
||||||
|
float cumulus = clamp(cloudType - 0.5f, 0.0, 1.0) * 2.0f;
|
||||||
|
return STRATUS_GRADIENT * stratus + STRATOCUMULUS_GRADIENT * stratocumulus + CUMULUS_GRADIENT * cumulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
float densityHeightGradient(float heightFrac, float cloudType) {
|
||||||
|
vec4 cloudGradient = mixGradients(cloudType);
|
||||||
|
return smoothstep(cloudGradient.x, cloudGradient.y, heightFrac) - smoothstep(cloudGradient.z, cloudGradient.w, heightFrac);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns density at a given point
|
||||||
|
// Heavily based on method from Schneider
|
||||||
|
float density(vec3 pip, vec3 weather, float mip) {
|
||||||
|
float time = mod(TIME, 100.0);
|
||||||
|
vec3 p = pip;
|
||||||
|
p.x += time * 1.0 * _time_scale + _time_offset;
|
||||||
|
float height_fraction = GetHeightFractionForPoint(length(p));
|
||||||
|
vec4 n = textureLod(perlworlnoise, p.xyz*0.00008, mip-2.0);
|
||||||
|
float fbm = n.g*0.625+n.b*0.25+n.a*0.125;
|
||||||
|
float G = densityHeightGradient(height_fraction, weather.r);
|
||||||
|
float base_cloud = remap(n.r, -(1.0-fbm), 1.0, 0.0, 1.0);
|
||||||
|
float weather_coverage = cloud_coverage*weather.b;
|
||||||
|
base_cloud = remap(base_cloud*G, 1.0-(weather_coverage), 1.0, 0.0, 1.0);
|
||||||
|
base_cloud *= weather_coverage;
|
||||||
|
p.xy -= time * 4.0 * _time_scale + _time_offset;
|
||||||
|
vec3 hn = textureLod(worlnoise, p*0.001, mip).rgb;
|
||||||
|
float hfbm = hn.r*0.625+hn.g*0.25+hn.b*0.125;
|
||||||
|
hfbm = mix(hfbm, 1.0-hfbm, clamp(height_fraction*4.0, 0.0, 1.0));
|
||||||
|
base_cloud = remap(base_cloud, hfbm*0.4 * height_fraction, 1.0, 0.0, 1.0);
|
||||||
|
return pow(clamp(base_cloud, 0.0, 1.0), (1.0 - height_fraction) * 0.8 + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 march(vec3 pos, vec3 end, vec3 dir, int depth, float sun_visible, float true_density) {
|
||||||
|
const vec3 RANDOM_VECTORS[6] = {vec3( 0.38051305f, 0.92453449f, -0.02111345f),vec3(-0.50625799f, -0.03590792f, -0.86163418f),vec3(-0.32509218f, -0.94557439f, 0.01428793f),vec3( 0.09026238f, -0.27376545f, 0.95755165f),vec3( 0.28128598f, 0.42443639f, -0.86065785f),vec3(-0.16852403f, 0.14748697f, 0.97460106f)};
|
||||||
|
float T = 1.0;
|
||||||
|
float alpha = 0.0;
|
||||||
|
vec3 ldir = precomputed_sun_dir;
|
||||||
|
float ss = length(dir);
|
||||||
|
dir = dir/ss;
|
||||||
|
vec3 p = pos + hash(pos * 10.0) * ss;
|
||||||
|
float t_dist = cloud_top - cloud_bottom;
|
||||||
|
float lss = (t_dist / 36.0);
|
||||||
|
vec3 L = vec3(0.0);
|
||||||
|
float t = 1.0;
|
||||||
|
float costheta = dot(ldir, dir);
|
||||||
|
// Stack multiple phase functions to emulate some backscattering
|
||||||
|
float phase = max(max(henyey_greenstein(costheta, 0.6), henyey_greenstein(costheta, (0.4 - 1.4 * ldir.y))), henyey_greenstein(costheta, -0.2));
|
||||||
|
vec3 atmosphere_sun = precomputed_Atmosphere_sun * ss * cloud_brightness * LIGHT0_ENERGY * phase;
|
||||||
|
vec3 atmosphere_ambient = precomputed_Atmosphere_ambient * cloud_ambient_brightness * intensity;
|
||||||
|
vec3 atmosphere_ground = precomputed_Atmosphere_ground*ground_color.xyz*ground_brightness * LIGHT0_ENERGY * intensity;
|
||||||
|
|
||||||
|
const float weather_scale = 0.00006;
|
||||||
|
float time = mod(TIME, 100.0) * 0.0003 * _time_scale + 0.005*_time_offset;
|
||||||
|
vec2 weather_pos = vec2(time * 0.9, time);
|
||||||
|
|
||||||
|
for (int i = 0; i < depth; i++) {
|
||||||
|
p += dir * ss;
|
||||||
|
vec3 weather_sample = texture(weathermap, p.xz * weather_scale + 0.5 + weather_pos).xyz;
|
||||||
|
float height_fraction = GetHeightFractionForPoint(length(p));
|
||||||
|
|
||||||
|
t = density(p, weather_sample, 0.0);
|
||||||
|
float dt = exp(-true_density*t*ss);
|
||||||
|
T *= dt;
|
||||||
|
vec3 lp = p;
|
||||||
|
float lt = 1.0;
|
||||||
|
float cd = 0.0;
|
||||||
|
|
||||||
|
if (t > 0.0) { //calculate lighting, but only when we are in the cloud
|
||||||
|
for (float j = 0.0; j < 6.0 * sun_visible; j++) {
|
||||||
|
lp += (ldir + RANDOM_VECTORS[int(j)]*j)*lss;
|
||||||
|
vec3 lweather = texture(weathermap, lp.xz * weather_scale + 0.5 + weather_pos).xyz;
|
||||||
|
lt = density(lp, lweather, j);
|
||||||
|
cd += lt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take a single distant sample
|
||||||
|
lp = p + ldir * 18.0 * lss;
|
||||||
|
float lheight_fraction = GetHeightFractionForPoint(length(lp));
|
||||||
|
vec3 lweather = texture(weathermap, lp.xz * weather_scale + 0.5).xyz;
|
||||||
|
lt = pow(density(lp, lweather, 5.0), (1.0 - lheight_fraction) * 0.8 + 0.5);
|
||||||
|
cd += lt;
|
||||||
|
|
||||||
|
// captures the direct lighting from the sun
|
||||||
|
float beers = exp(-true_density * cd * lss);
|
||||||
|
float beers2 = exp(-true_density * cd * lss * 0.25) * 0.7;
|
||||||
|
float beers_total = max(beers, beers2);
|
||||||
|
|
||||||
|
vec3 ambient = mix(atmosphere_ground, atmosphere_ambient, smoothstep(0.0, 1.0, height_fraction)) * beers;
|
||||||
|
// vec3 ambient = mix(atmosphere_ground, vec3(1.0), smoothstep(0.0, 1.0, height_fraction)) * true_density * mix(atmosphere_ambient, vec3(1.0), 0.4); // * (ldir .y);
|
||||||
|
alpha += (1.0 - dt) * (1.0 - alpha);
|
||||||
|
L += ((ambient + beers_total * atmosphere_sun) * alpha) * T * t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vec4(L*cloud_brightness, clamp(alpha, 0.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End Cloud Parameters */
|
||||||
|
|
||||||
|
|
||||||
|
void sky() {
|
||||||
|
vec3 dir = EYEDIR;
|
||||||
|
|
||||||
|
// float sun_visible = precomputed_sun_visible * max(sign(precomputed_sun_dir.y + sin(acos(earthRadius / (earthRadius + cloud_top)) + LIGHT0_SIZE)), 0.0);
|
||||||
|
vec4 cloud_col = vec4(0.0);
|
||||||
|
float cloud_fade_stars = 1.0;
|
||||||
|
float cloud_fade = 1.0;
|
||||||
|
bool AT_FULL_RES_PASS = !(AT_HALF_RES_PASS || AT_QUARTER_RES_PASS);
|
||||||
|
|
||||||
|
if (clouds){
|
||||||
|
|
||||||
|
/* Begin Clouds */
|
||||||
|
float horizon = sin(acos(earthRadius / (earthRadius + max(POSITION.y, 0.0) + Height)));
|
||||||
|
|
||||||
|
if ((AT_CUBEMAP_PASS && AT_QUARTER_RES_PASS) || (!AT_CUBEMAP_PASS && AT_HALF_RES_PASS)){
|
||||||
|
float true_density = pow(1.0-clamp((POSITION.y-cloud_bottom)/(cloud_top-cloud_bottom), 0.0, 1.0),2.0)*_density;
|
||||||
|
vec4 volume = vec4(0.0);
|
||||||
|
|
||||||
|
if (dir.y>-horizon && true_density>0.0){
|
||||||
|
vec3 camPos = vec3(POSITION.x, min(POSITION.y, cloud_bottom) + earthRadius, POSITION.z);
|
||||||
|
float cloud_start_distance = solve_quadratic(camPos, dir, cloud_bottom + earthRadius).x;
|
||||||
|
float cloud_end_distance = solve_quadratic(camPos, dir, cloud_top + earthRadius).x;
|
||||||
|
vec3 start = camPos + dir * cloud_start_distance;
|
||||||
|
vec3 end = camPos + dir * cloud_end_distance;
|
||||||
|
float shelldist = (cloud_end_distance-cloud_start_distance);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Take more steps towards horizon, less steps in foggy clouds, and less steps at night
|
||||||
|
float steps = ceil(mix(float(cloud_samples_horizon) * (1.0 - 0.25 * (1.0 - precomputed_sun_visible * (1.0-cloud_coverage))),
|
||||||
|
float(cloud_samples_sky) * (1.0 - 0.25 * (1.0 - precomputed_sun_visible)),
|
||||||
|
sqrt(clamp(dir.y+horizon, 0.0, 1.0))) );
|
||||||
|
vec3 raystep = dir * shelldist / steps;
|
||||||
|
volume = march(start, end, raystep, int(steps), precomputed_sun_visible, true_density )*vec4(precomputed_sun_color, 1.0);
|
||||||
|
volume.xyz *= precomputed_sun_visible;
|
||||||
|
}
|
||||||
|
COLOR = volume.xyz;
|
||||||
|
ALPHA = volume.w;
|
||||||
|
} else if (AT_FULL_RES_PASS){
|
||||||
|
cloud_fade = clamp(dir.y, 0.0, 1.0);
|
||||||
|
cloud_fade_stars = clamp(dir.y+horizon, 0.0, 1.0);
|
||||||
|
cloud_col = AT_CUBEMAP_PASS ? QUARTER_RES_COLOR : HALF_RES_COLOR;
|
||||||
|
}
|
||||||
|
/* End Clouds */
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AT_FULL_RES_PASS){
|
||||||
|
vec4[2] background = sample_sky(dir, POSITION);
|
||||||
|
vec3 col_stars = texture(night_sky, SKY_COORDS ).xyz * night_sky_brightness * background[1].xyz;
|
||||||
|
vec3 col = background[0].xyz * intensity * precomputed_sun_enabled;
|
||||||
|
|
||||||
|
COLOR = col*(1.0-cloud_col.a*cloud_fade_stars) + cloud_col.xyz*cloud_fade + col_stars * (1.0-cloud_col.a)*cloud_fade_stars;
|
||||||
|
}
|
||||||
|
}
|
66
nishita_sky.tres
Normal file
66
nishita_sky.tres
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
[gd_resource type="ShaderMaterial" load_steps=8 format=3 uid="uid://b45abiagp8tuf"]
|
||||||
|
|
||||||
|
[ext_resource type="Shader" path="res://nishita_sky.gdshader" id="1_3qhyv"]
|
||||||
|
[ext_resource type="CompressedTexture3D" uid="uid://dbfbysid168mx" path="res://godot-volumetric-cloud-demo textures/perlworlnoise.tga" id="1_qd3aw"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://dfkye0uf4i6w1" path="res://godot-volumetric-cloud-demo textures/weather.bmp" id="2_bohio"]
|
||||||
|
[ext_resource type="CompressedTexture3D" uid="uid://c4dp6g6gouj2b" path="res://godot-volumetric-cloud-demo textures/worlnoise.bmp" id="3_5fbd6"]
|
||||||
|
|
||||||
|
[sub_resource type="Gradient" id="Gradient_gllyc"]
|
||||||
|
interpolation_mode = 2
|
||||||
|
offsets = PackedFloat32Array(0.693133, 1)
|
||||||
|
|
||||||
|
[sub_resource type="FastNoiseLite" id="FastNoiseLite_d5sdi"]
|
||||||
|
seed = 309
|
||||||
|
frequency = 1.0
|
||||||
|
|
||||||
|
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_q4x3w"]
|
||||||
|
width = 2048
|
||||||
|
height = 2048
|
||||||
|
color_ramp = SubResource("Gradient_gllyc")
|
||||||
|
noise = SubResource("FastNoiseLite_d5sdi")
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
shader = ExtResource("1_3qhyv")
|
||||||
|
shader_parameter/precomputed_sun_visible = true
|
||||||
|
shader_parameter/precomputed_sun_enabled = true
|
||||||
|
shader_parameter/precomputed_sun_dir = Vector3(0, 0.0132469, -0.999903)
|
||||||
|
shader_parameter/precomputed_sun_color = Color(1, 1, 1, 1)
|
||||||
|
shader_parameter/precomputed_Atmosphere_sun = Color(0.994039, 0.390172, 0.20851, 1)
|
||||||
|
shader_parameter/precomputed_Atmosphere_ambient = Color(0.0282629, 0.0132965, 0.00359788, 1)
|
||||||
|
shader_parameter/precomputed_Atmosphere_ground = Color(0.994039, 0.390172, 0.20851, 1)
|
||||||
|
shader_parameter/rayleigh_color = Vector3(0.258929, 0.580357, 1)
|
||||||
|
shader_parameter/rayleigh = 1.0
|
||||||
|
shader_parameter/mie_color = Vector3(1, 1, 1)
|
||||||
|
shader_parameter/mie = 1.0
|
||||||
|
shader_parameter/mie_eccentricity = 0.76
|
||||||
|
shader_parameter/atmosphere_samples_max = 32
|
||||||
|
shader_parameter/atmosphere_samples_min = 12
|
||||||
|
shader_parameter/atmosphere_samples_horizon_bias = 0.5
|
||||||
|
shader_parameter/atmosphere_sun_samples = 32
|
||||||
|
shader_parameter/atmosphere_light_samples = 8
|
||||||
|
shader_parameter/turbidity = 1.0
|
||||||
|
shader_parameter/ground_color = Color(0.1, 0.07, 0.034, 1)
|
||||||
|
shader_parameter/intensity = 10.0
|
||||||
|
shader_parameter/sun_brightness = 100000.0
|
||||||
|
shader_parameter/ground_brightness = 0.5
|
||||||
|
shader_parameter/night_sky_brightness = 1.0
|
||||||
|
shader_parameter/Height = 1000.0
|
||||||
|
shader_parameter/earthRadius = 6.36e+06
|
||||||
|
shader_parameter/atmosphereRadius = 6.42e+06
|
||||||
|
shader_parameter/rayleighScaleHeight = 7994.0
|
||||||
|
shader_parameter/mieScaleHeight = 1200.0
|
||||||
|
shader_parameter/clouds = true
|
||||||
|
shader_parameter/cloud_samples_horizon = 54
|
||||||
|
shader_parameter/cloud_samples_sky = 96
|
||||||
|
shader_parameter/_density = 0.097
|
||||||
|
shader_parameter/cloud_coverage = 0.25
|
||||||
|
shader_parameter/_time_scale = 1.0
|
||||||
|
shader_parameter/_time_offset = 0.0
|
||||||
|
shader_parameter/cloud_bottom = 1500.0
|
||||||
|
shader_parameter/cloud_top = 4000.0
|
||||||
|
shader_parameter/cloud_brightness = 1.5
|
||||||
|
shader_parameter/cloud_ambient_brightness = 0.5
|
||||||
|
shader_parameter/night_sky = SubResource("NoiseTexture2D_q4x3w")
|
||||||
|
shader_parameter/worlnoise = ExtResource("3_5fbd6")
|
||||||
|
shader_parameter/perlworlnoise = ExtResource("1_qd3aw")
|
||||||
|
shader_parameter/weathermap = ExtResource("2_bohio")
|
22
project.godot
Normal file
22
project.godot
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=5
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
config/name="Nishita Sky With Clouds"
|
||||||
|
run/main_scene="res://main.tscn"
|
||||||
|
config/features=PackedStringArray("4.0", "Forward Plus")
|
||||||
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
|
[rendering]
|
||||||
|
|
||||||
|
lights_and_shadows/use_physical_light_units=true
|
||||||
|
anti_aliasing/quality/msaa_2d=3
|
||||||
|
anti_aliasing/quality/msaa_3d=3
|
Loading…
Reference in New Issue
Block a user