mirror of
https://github.com/MMqd/godot-nishita-sky-with-volumetric-clouds.git
synced 2024-12-23 00:17:30 +08:00
282 lines
11 KiB
GDScript
282 lines
11 KiB
GDScript
@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)
|