This commit is contained in:
Gorochu
2026-06-24 00:05:37 -07:00
parent 6db83e559b
commit 38151eb233
30 changed files with 1129 additions and 373 deletions

View File

@ -348,7 +348,13 @@ typedef TWorldData = {
@:optional public var turbidity: Null<FastFloat>;
@:optional public var ground_albedo: Null<FastFloat>;
@:optional public var envmap: String;
@:optional public var nishita_density: Float32Array; // Rayleigh, Mie, ozone
@:optional public var sky_density: Float32Array; // Air, dust/aerosol, ozone density
@:optional public var sky_sun_elevation: Null<FastFloat>;
@:optional public var sky_sun_rotation: Null<FastFloat>;
@:optional public var sky_sun_size: Null<FastFloat>;
@:optional public var sky_sun_intensity: Null<FastFloat>;
@:optional public var sky_altitude: Null<FastFloat>;
@:optional public var sky_sun_disc: Null<Int>; // 0 or 1
}
#if js

View File

@ -1,27 +0,0 @@
package leenkx.logicnode;
import iron.math.Vec4;
class GetHosekWilkiePropertiesNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var world = iron.Scene.active.world.raw;
return switch (from) {
case 0:
world.turbidity;
case 1:
world.ground_albedo;
case 2:
new Vec4(world.sun_direction[0], world.sun_direction[1], world.sun_direction[2]);
default:
null;
}
return null;
}
}

View File

@ -1,29 +0,0 @@
package leenkx.logicnode;
import iron.math.Vec4;
class GetNishitaPropertiesNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var world = iron.Scene.active.world.raw;
return switch (from) {
case 0:
world.nishita_density[0];
case 1:
world.nishita_density[1];
case 2:
world.nishita_density[2];
case 3:
new Vec4(world.sun_direction[0], world.sun_direction[1], world.sun_direction[2]);
default:
null;
}
return null;
}
}

View File

@ -0,0 +1,15 @@
package leenkx.logicnode;
import iron.math.Vec4;
class GetWorldColorNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var col = iron.Scene.active.world.raw.background_color;
return new Vec4(((col >> 16) & 0xff) / 255, ((col >> 8) & 0xff) / 255, (col & 0xff) / 255, 1.0);
}
}

View File

@ -0,0 +1,50 @@
package leenkx.logicnode;
import iron.math.Vec4;
class GetWorldSkyNode extends LogicNode {
public var property0:String;
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var world = iron.Scene.active.world.raw;
if (property0 == 'hosek') {
return switch (from) {
case 0: world.turbidity != null ? world.turbidity : 1.0;
case 1: world.ground_albedo != null ? world.ground_albedo : 0.0;
case 2: world.sun_direction != null ? new Vec4(world.sun_direction[0], world.sun_direction[1], world.sun_direction[2]) : new Vec4(0, 0, 1);
default: null;
}
}
else if (property0 == 'single') {
return switch (from) {
case 0: world.sky_density != null ? world.sky_density[0] : 1.0;
case 1: world.sky_density != null ? world.sky_density[1] : 1.0;
case 2: world.sky_density != null ? world.sky_density[2] : 1.0;
case 3: world.sky_altitude != null ? world.sky_altitude : 0.0;
case 4: world.sun_direction != null ? new Vec4(world.sun_direction[0], world.sun_direction[1], world.sun_direction[2]) : new Vec4(0, 0, 1);
default: null;
}
}
else { // multi
return switch (from) {
case 0: world.sky_density != null ? world.sky_density[0] : 1.0;
case 1: world.sky_density != null ? world.sky_density[1] : 1.0;
case 2: world.sky_density != null ? world.sky_density[2] : 1.0;
case 3: world.sky_sun_elevation != null ? world.sky_sun_elevation : 0.0;
case 4: world.sky_sun_rotation != null ? world.sky_sun_rotation : 0.0;
case 5: world.sky_sun_size != null ? world.sky_sun_size : 0.545;
case 6: world.sky_sun_intensity != null ? world.sky_sun_intensity : 1.0;
case 7: world.sky_altitude != null ? world.sky_altitude : 0.0;
case 8: world.sky_sun_disc != null ? world.sky_sun_disc : 1;
case 9: world.sun_direction != null ? new Vec4(world.sun_direction[0], world.sun_direction[1], world.sun_direction[2]) : new Vec4(0, 0, 1);
default: null;
}
}
}
}

View File

@ -0,0 +1,17 @@
package leenkx.logicnode;
class GetWorldTextureNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var world = iron.Scene.active.world;
return switch (from) {
case 0: world.probe != null ? world.probe.raw.strength : 1.0;
case 1: world.raw.envmap != null ? world.raw.envmap : '';
default: null;
}
}
}

View File

@ -1,41 +0,0 @@
package leenkx.logicnode;
import leenkx.renderpath.HosekWilkie;
import iron.math.Vec4;
class SetHosekWilkiePropertiesNode extends LogicNode {
public var property0:String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var world = iron.Scene.active.world;
if(property0 == 'Turbidity/Ground Albedo'){
world.raw.turbidity = inputs[1].get();
world.raw.ground_albedo = inputs[2].get();
}
if(property0 == 'Turbidity')
world.raw.turbidity = inputs[1].get();
if(property0 == 'Ground Albedo')
world.raw.ground_albedo = inputs[1].get();
if(property0 == 'Sun Direction'){
var vec:Vec4 = inputs[1].get();
world.raw.sun_direction[0] = vec.x;
world.raw.sun_direction[1] = vec.y;
world.raw.sun_direction[2] = vec.z;
}
HosekWilkie.recompute(world);
runOutput(0);
}
}

View File

@ -1,45 +0,0 @@
package leenkx.logicnode;
import leenkx.renderpath.Nishita;
import iron.math.Vec4;
class SetNishitaPropertiesNode extends LogicNode {
public var property0:String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var world = iron.Scene.active.world;
if(property0 == 'Density'){
world.raw.nishita_density[0] = inputs[1].get();
world.raw.nishita_density[1] = inputs[2].get();
world.raw.nishita_density[2] = inputs[3].get();
}
if(property0 == 'Air')
world.raw.nishita_density[0] = inputs[1].get();
if(property0 == 'Dust')
world.raw.nishita_density[1] = inputs[1].get();
if(property0 == 'Ozone')
world.raw.nishita_density[2] = inputs[1].get();
if(property0 == 'Sun Direction'){
var vec:Vec4 = inputs[1].get();
world.raw.sun_direction[0] = vec.x;
world.raw.sun_direction[1] = vec.y;
world.raw.sun_direction[2] = vec.z;
}
Nishita.recompute(world);
runOutput(0);
}
}

View File

@ -0,0 +1,23 @@
package leenkx.logicnode;
import iron.math.Vec4;
class SetWorldColorNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var world = iron.Scene.active.world;
var raw = world.raw;
var vec:Vec4 = inputs[1].get();
var r = Std.int(Math.max(0, Math.min(1, vec.x)) * 255);
var g = Std.int(Math.max(0, Math.min(1, vec.y)) * 255);
var b = Std.int(Math.max(0, Math.min(1, vec.z)) * 255);
raw.background_color = (r << 16) | (g << 8) | b;
runOutput(0);
}
}

View File

@ -0,0 +1,63 @@
package leenkx.logicnode;
import leenkx.renderpath.Sky;
import leenkx.renderpath.HosekWilkie;
import iron.math.Vec4;
class SetWorldSkyNode extends LogicNode {
public var property0:String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var world = iron.Scene.active.world;
var raw = world.raw;
if (property0 == 'hosek') {
raw.turbidity = inputs[1].get();
raw.ground_albedo = inputs[2].get();
var vec:Vec4 = inputs[3].get();
if (raw.sun_direction == null) raw.sun_direction = new kha.arrays.Float32Array(3);
raw.sun_direction[0] = vec.x;
raw.sun_direction[1] = vec.y;
raw.sun_direction[2] = vec.z;
HosekWilkie.recompute(world);
}
else if (property0 == 'single') {
if (raw.sky_density == null) raw.sky_density = new kha.arrays.Float32Array(3);
raw.sky_density[0] = inputs[1].get();
raw.sky_density[1] = inputs[2].get();
raw.sky_density[2] = inputs[3].get();
raw.sky_altitude = inputs[4].get();
var vec:Vec4 = inputs[5].get();
if (raw.sun_direction == null) raw.sun_direction = new kha.arrays.Float32Array(3);
raw.sun_direction[0] = vec.x;
raw.sun_direction[1] = vec.y;
raw.sun_direction[2] = vec.z;
Sky.recomputeSingleScatter(world);
}
else if (property0 == 'multi') {
if (raw.sky_density == null) raw.sky_density = new kha.arrays.Float32Array(3);
raw.sky_density[0] = inputs[1].get();
raw.sky_density[1] = inputs[2].get();
raw.sky_density[2] = inputs[3].get();
raw.sky_sun_elevation = inputs[4].get();
raw.sky_sun_rotation = inputs[5].get();
raw.sky_sun_size = inputs[6].get();
raw.sky_sun_intensity = inputs[7].get();
raw.sky_altitude = inputs[8].get();
raw.sky_sun_disc = inputs[9].get() ? 1 : 0;
var vec:Vec4 = inputs[10].get();
if (raw.sun_direction == null) raw.sun_direction = new kha.arrays.Float32Array(3);
raw.sun_direction[0] = vec.x;
raw.sun_direction[1] = vec.y;
raw.sun_direction[2] = vec.z;
Sky.recomputeMultiScatter(world);
}
runOutput(0);
}
}

View File

@ -0,0 +1,25 @@
package leenkx.logicnode;
class SetWorldTextureNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var world = iron.Scene.active.world;
var raw = world.raw;
if (world.probe != null) {
world.probe.raw.strength = inputs[1].get();
}
var envmap:String = inputs[2].get();
if (envmap != null && envmap != '' && envmap != raw.envmap) {
raw.envmap = envmap;
world.loadEnvmap(function(w) {});
}
runOutput(0);
}
}

View File

@ -18,7 +18,7 @@ class Uniforms {
iron.object.Uniforms.externalTextureLinks = [textureLink];
iron.object.Uniforms.externalVec2Links = [vec2Link];
iron.object.Uniforms.externalVec3Links = [vec3Link];
iron.object.Uniforms.externalVec4Links = [];
iron.object.Uniforms.externalVec4Links = [vec4Link];
iron.object.Uniforms.externalFloatLinks = [floatLink];
iron.object.Uniforms.externalFloatsLinks = [floatsLink];
iron.object.Uniforms.externalIntLinks = [intLink];
@ -26,9 +26,13 @@ class Uniforms {
public static function textureLink(object: Object, mat: MaterialData, link: String): Null<kha.Image> {
switch (link) {
case "_nishitaLUT": {
if (leenkx.renderpath.Nishita.data == null) leenkx.renderpath.Nishita.recompute(Scene.active.world);
return leenkx.renderpath.Nishita.data.lut;
case "_singleScatterLUT": {
if (leenkx.renderpath.Sky.singleScatterData == null) leenkx.renderpath.Sky.recomputeSingleScatter(Scene.active.world);
return leenkx.renderpath.Sky.singleScatterData.lut;
}
case "_multiScatterLUT": {
if (leenkx.renderpath.Sky.multiScatterData == null) leenkx.renderpath.Sky.recomputeMultiScatter(Scene.active.world);
return leenkx.renderpath.Sky.multiScatterData.lut;
}
#if lnx_ltc
case "_ltcMat": {
@ -169,6 +173,17 @@ class Uniforms {
}
}
#end
case "_multiScatterSunTop": {
if (leenkx.renderpath.Sky.multiScatterData == null) {
leenkx.renderpath.Sky.recomputeMultiScatter(Scene.active.world);
}
if (leenkx.renderpath.Sky.multiScatterData != null) {
v = iron.object.Uniforms.helpVec;
v.x = leenkx.renderpath.Sky.multiScatterData.sunTop.x;
v.y = leenkx.renderpath.Sky.multiScatterData.sunTop.y;
v.z = leenkx.renderpath.Sky.multiScatterData.sunTop.z;
}
}
}
return v;
}
@ -176,13 +191,13 @@ class Uniforms {
public static function vec2Link(object: Object, mat: MaterialData, link: String): Null<iron.math.Vec4> {
var v: Vec4 = null;
switch (link) {
case "_nishitaDensity": {
case "_skyDensity": {
var w = Scene.active.world;
if (w != null) {
v = iron.object.Uniforms.helpVec;
// We only need Rayleigh and Mie density in the sky shader -> Vec2
v.x = w.raw.nishita_density[0];
v.y = w.raw.nishita_density[1];
v.x = w.raw.sky_density[0];
v.y = w.raw.sky_density[1];
}
}
}
@ -190,6 +205,35 @@ class Uniforms {
return v;
}
public static function vec4Link(object: Object, mat: MaterialData, link: String): Null<iron.math.Vec4> {
var v: Vec4 = null;
switch (link) {
case "_multiScatterParams": {
var w = Scene.active.world;
if (w != null && w.raw.sky_sun_elevation != null) {
v = iron.object.Uniforms.helpVec;
v.x = w.raw.sky_sun_elevation;
v.y = w.raw.sky_sun_rotation != null ? w.raw.sky_sun_rotation : 0.0;
v.z = w.raw.sky_sun_size != null ? w.raw.sky_sun_size : 0.545;
v.w = w.raw.sky_sun_intensity != null ? w.raw.sky_sun_intensity : 1.0;
}
}
case "_multiScatterSunBottom": {
if (leenkx.renderpath.Sky.multiScatterData == null) {
leenkx.renderpath.Sky.recomputeMultiScatter(Scene.active.world);
}
if (leenkx.renderpath.Sky.multiScatterData != null) {
v = iron.object.Uniforms.helpVec;
v.x = leenkx.renderpath.Sky.multiScatterData.sunBottom.x;
v.y = leenkx.renderpath.Sky.multiScatterData.sunBottom.y;
v.z = leenkx.renderpath.Sky.multiScatterData.sunBottom.z;
v.w = leenkx.renderpath.Sky.multiScatterData.earthIntersectionAngle;
}
}
}
return v;
}
public static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
switch (link) {
#if rp_dynres

View File

@ -8,50 +8,99 @@ import kha.graphics4.Usage;
import iron.data.WorldData;
import iron.math.Vec2;
import iron.math.Vec3;
import iron.math.Vec4;
import leenkx.math.Helper;
/**
Utility class to control the Nishita sky model.
Utility class to control the sky models (single and multiple scattering).
**/
class Nishita {
class Sky {
public static var data: NishitaData = null;
public static var singleScatterData: SingleScatteringData = null;
public static var multiScatterData: MultipleScatteringData = null;
/**
Recomputes the nishita lookup table after the density settings changed.
Recomputes the single scattering lookup table after the density settings changed.
Do not call this method on every frame (it's slow)!
**/
public static function recompute(world: WorldData) {
if (world == null || world.raw.nishita_density == null) return;
if (data == null) data = new NishitaData();
public static function recomputeSingleScatter(world: WorldData) {
if (world == null || world.raw.sky_density == null) return;
if (singleScatterData == null) singleScatterData = new SingleScatteringData();
var density = world.raw.nishita_density;
data.computeLUT(new Vec3(density[0], density[1], density[2]));
var density = world.raw.sky_density;
singleScatterData.computeLUT(new Vec3(density[0], density[1], density[2]));
}
/** Sets the sky's density parameters and calls `recompute()` afterwards. **/
/** Sets the sky's density parameters and calls `recomputeSingleScatter()` afterwards. **/
public static function setDensity(world: WorldData, densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
if (world == null) return;
if (world.raw.nishita_density == null) world.raw.nishita_density = new Float32Array(3);
var density = world.raw.nishita_density;
if (world.raw.sky_density == null) world.raw.sky_density = new Float32Array(3);
var density = world.raw.sky_density;
density[0] = Helper.clamp(densityAir, 0, 10);
density[1] = Helper.clamp(densityDust, 0, 10);
density[2] = Helper.clamp(densityOzone, 0, 10);
recompute(world);
recomputeSingleScatter(world);
}
public static function recomputeMultiScatter(world: WorldData) {
if (world == null) return;
var raw = world.raw;
if (raw.sky_density == null) return;
if (multiScatterData == null) multiScatterData = new MultipleScatteringData();
var density = raw.sky_density;
var sunElevation = raw.sky_sun_elevation != null ? raw.sky_sun_elevation : 0.0;
var sunRotation = raw.sky_sun_rotation != null ? raw.sky_sun_rotation : 0.0;
var sunSize = raw.sky_sun_size != null ? raw.sky_sun_size : 0.545;
var sunIntensity = raw.sky_sun_intensity != null ? raw.sky_sun_intensity : 1.0;
var altitude = raw.sky_altitude != null ? raw.sky_altitude : 0.0;
var sunDisc = raw.sky_sun_disc != null ? raw.sky_sun_disc : 1;
multiScatterData.compute(
sunElevation,
sunRotation,
sunSize,
sunIntensity,
altitude,
sunDisc != 0,
new Vec3(density[0], density[1], density[2])
);
}
public static function setMultiScatterParams(world: WorldData, sunElevation: FastFloat, sunRotation: FastFloat,
sunSize: FastFloat, sunIntensity: FastFloat, altitude: FastFloat, sunDisc: Bool,
densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
if (world == null) return;
if (world.raw.sky_density == null) world.raw.sky_density = new Float32Array(3);
var density = world.raw.sky_density;
density[0] = Helper.clamp(densityAir, 0, 10);
density[1] = Helper.clamp(densityDust, 0, 10);
density[2] = Helper.clamp(densityOzone, 0, 10);
world.raw.sky_sun_elevation = sunElevation;
world.raw.sky_sun_rotation = sunRotation;
world.raw.sky_sun_size = sunSize;
world.raw.sky_sun_intensity = sunIntensity;
world.raw.sky_altitude = altitude;
world.raw.sky_sun_disc = sunDisc ? 1 : 0;
recomputeMultiScatter(world);
}
}
/**
This class holds the precalculated result of the inner scattering integral
of the Nishita sky model. The outer integral is calculated in
of the single scattering sky model. The outer integral is calculated in
[`leenkx/Shaders/std/sky.glsl`](https://github.com/leenkx3d/leenkx/blob/master/Shaders/std/sky.glsl).
@see `leenkx.renderpath.Nishita`
@see `leenkx.renderpath.Sky`
**/
class NishitaData {
class SingleScatteringData {
public var lut: kha.Image;
@ -229,3 +278,397 @@ class NishitaData {
return jAttenuation;
}
}
/**
This class precomputes the full sky radiance LUT
Unlike the single scattering model matching Blenders implementation.
**/
class MultipleScatteringData {
public var lut: kha.Image;
public var sunBottom: Vec3;
public var sunTop: Vec3;
public var earthIntersectionAngle: FastFloat;
public static var lutWidth = 256;
public static var lutHeight = 128;
static var transmittanceResX = 256;
static var transmittanceResY = 64;
var transmittanceLUT: haxe.io.Float32Array;
static var transmittanceSteps = 64;
static var inScatteringSteps = 64;
// Atmosphere constants sky_multiple_scattering.cpp
static var EARTH_RADIUS = 6371.0;
static var ATMOSPHERE_THICKNESS = 100.0;
static var ATMOSPHERE_RADIUS = 6471.0; // EARTH_RADIUS + ATMOSPHERE_THICKNESS
static var PHASE_ISOTROPIC = 1.0 / (4.0 * Math.PI);
static var RAYLEIGH_PHASE_SCALE = (3.0 / 16.0) * (1.0 / Math.PI);
static var G = 0.8;
static var SQR_G = 0.64;
static var GROUND_ALBEDO: Array<Float> = [0.3, 0.3, 0.3, 0.3];
// Spectral data sampled at 630, 560, 490, 430 nm
static var SUN_SPECTRAL_IRRADIANCE: Array<Float> = [1.679, 1.828, 1.986, 1.307];
static var MOLECULAR_SCATTERING_COEFFICIENT_BASE: Array<Float> = [6.605e-3, 1.067e-2, 1.842e-2, 3.156e-2];
static var OZONE_ABSORPTION_CROSS_SECTION: Array<Float> = [3.472e-25, 3.914e-25, 1.349e-25, 11.03e-27];
static var OZONE_MEAN_DOBSON = 334.5;
static var AEROSOL_ABSORPTION_CROSS_SECTION: Array<Float> = [2.8722e-24, 4.6168e-24, 7.9706e-24, 1.3578e-23];
static var AEROSOL_SCATTERING_CROSS_SECTION: Array<Float> = [1.5908e-22, 1.7711e-22, 2.0942e-22, 2.4033e-22];
static var AEROSOL_BASE_DENSITY = 1.3681e20;
static var AEROSOL_BACKGROUND_DENSITY = 2e6;
static var AEROSOL_HEIGHT_SCALE = 0.73;
static var SPECTRAL_XYZ: Array<Array<Float>> = [
[53.386917738564668023, 22.981337506691024754, 0.0],
[43.904844466369358263, 71.347795700053393866, 0.102506867965741307],
[1.6137278251608962005, 18.422960591455485011, 31.742921188390805758],
[20.762668673810577145, 2.3614213523314368527, 110.48009643252140334]
];
var airDensity: Float = 1.0;
var aerosolDensity: Float = 1.0;
var ozoneDensity: Float = 1.0;
public function new() {
sunBottom = new Vec3();
sunTop = new Vec3();
earthIntersectionAngle = 0.0;
transmittanceLUT = new haxe.io.Float32Array(transmittanceResY * transmittanceResX * 4);
}
static inline function exp4(a: Array<Float>): Array<Float> {
return [Math.exp(a[0]), Math.exp(a[1]), Math.exp(a[2]), Math.exp(a[3])];
}
static inline function scale4(a: Array<Float>, s: Float): Array<Float> {
return [a[0] * s, a[1] * s, a[2] * s, a[3] * s];
}
static inline function add4(a: Array<Float>, b: Array<Float>): Array<Float> {
return [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]];
}
static inline function mult4(a: Array<Float>, b: Array<Float>): Array<Float> {
return [a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]];
}
static inline function div4(a: Array<Float>, b: Array<Float>): Array<Float> {
return [a[0] / b[0], a[1] / b[1], a[2] / b[2], a[3] / b[3]];
}
static inline function max4(a: Array<Float>, b: Float): Array<Float> {
return [Math.max(a[0], b), Math.max(a[1], b), Math.max(a[2], b), Math.max(a[3], b)];
}
static inline function mix4(a: Array<Float>, b: Array<Float>, t: Float): Array<Float> {
return [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1]), a[2] + t * (b[2] - a[2]), a[3] + t * (b[3] - a[3])];
}
// Atmospheric functions
function getMolecularScatteringCoefficient(h: Float): Array<Float> {
// MOLECULAR_SCATTERING_COEFFICIENT_BASE * exp(-0.07771971 * h^1.16364243)
var f = Math.exp(-0.07771971 * Math.pow(h, 1.16364243));
return scale4(MOLECULAR_SCATTERING_COEFFICIENT_BASE, f);
}
function getMolecularAbsorptionCoefficient(h: Float): Array<Float> {
var logH = Math.log(Math.max(h, 1e-4));
var density = 3.78547397e20 * Math.exp(-(logH - 3.22261) * (logH - 3.22261) * 5.55555555 - logH);
var result: Array<Float> = [];
for (i in 0...4) {
result[i] = OZONE_ABSORPTION_CROSS_SECTION[i] * OZONE_MEAN_DOBSON * density;
}
return result;
}
function getAerosolDensity(h: Float): Float {
var division = AEROSOL_BACKGROUND_DENSITY / AEROSOL_BASE_DENSITY;
return AEROSOL_BASE_DENSITY * (Math.exp(-h / AEROSOL_HEIGHT_SCALE) + division);
}
function getAtmosphereCollisionCoefficients(h: Float): {
aerosolAbs: Array<Float>, aerosolSca: Array<Float>, molAbs: Array<Float>, molSca: Array<Float>
} {
var localAerosolDensity = getAerosolDensity(h) * aerosolDensity;
var aerosolAbs = scale4(AEROSOL_ABSORPTION_CROSS_SECTION, localAerosolDensity);
var aerosolSca = scale4(AEROSOL_SCATTERING_CROSS_SECTION, localAerosolDensity);
var molAbs = scale4(getMolecularAbsorptionCoefficient(h), ozoneDensity);
var molSca = scale4(getMolecularScatteringCoefficient(h), airDensity);
return { aerosolAbs: aerosolAbs, aerosolSca: aerosolSca, molAbs: molAbs, molSca: molSca };
}
function molecularPhaseFunction(cosTheta: Float): Float {
return RAYLEIGH_PHASE_SCALE * (1.0 + cosTheta * cosTheta);
}
function aerosolPhaseFunction(cosTheta: Float): Float {
var den = 1.0 + SQR_G + 2.0 * G * cosTheta;
return PHASE_ISOTROPIC * (1.0 - SQR_G) / (den * Math.sqrt(den));
}
// nearest positive intersection distance, or -1 if no intersection
function raySphereIntersection(pos: Vec3, dir: Vec3, radius: Float): Float {
var b = pos.dot(dir);
var c = pos.dot(pos) - radius * radius;
if (c > 0.0 && b > 0.0) return -1.0;
var d = b * b - c;
if (d < 0.0) return -1.0;
if (d >= b * b) return -b + Math.sqrt(d);
return -b - Math.sqrt(d);
}
function sunDirection(cosTheta: Float): Vec3 {
// Place sun at azimuth=0 in the atan(dir.x, dir.y) convention
// Blender uses (-sqrt(1-cos²θ), 0, cosθ) but our azimuth convention
// requires (0, sqrt(1-cos²θ), cosθ) for the sun to be at azimuth=0
return new Vec3(0.0, Math.sqrt(1.0 - cosTheta * cosTheta), cosTheta);
}
function spectralToXYZ(L: Array<Float>): Vec3 {
var x = 0.0, y = 0.0, z = 0.0;
for (i in 0...4) {
x += SPECTRAL_XYZ[i][0] * L[i];
y += SPECTRAL_XYZ[i][1] * L[i];
z += SPECTRAL_XYZ[i][2] * L[i];
}
return new Vec3(x, y, z);
}
function getTransmittance(cosTheta: Float, normalizedAltitude: Float): Array<Float> {
var sunDir = sunDirection(cosTheta);
var distToCenter = EARTH_RADIUS + (ATMOSPHERE_RADIUS - EARTH_RADIUS) * normalizedAltitude;
var rayOrigin = new Vec3(0.0, 0.0, distToCenter);
var tD = raySphereIntersection(rayOrigin, sunDir, ATMOSPHERE_RADIUS);
var tStep = tD / transmittanceSteps;
var result: Array<Float> = [0.0, 0.0, 0.0, 0.0];
for (step in 0...transmittanceSteps) {
var t = (step + 0.5) * tStep;
var xT = rayOrigin.clone().add(sunDir.clone().mult(t));
var altitude = Math.max(xT.length() - EARTH_RADIUS, 0.0);
var coeffs = getAtmosphereCollisionCoefficients(altitude);
var extinction = add4(add4(coeffs.aerosolAbs, coeffs.aerosolSca), add4(coeffs.molAbs, coeffs.molSca));
result = add4(result, scale4(extinction, tStep));
}
return exp4(scale4(result, -1.0));
}
function precomputeTransmittanceLUT() {
for (y in 0...transmittanceResY) {
for (x in 0...transmittanceResX) {
var u = x / (transmittanceResX - 1);
var v = y / (transmittanceResY - 1);
var t = getTransmittance(u * 2.0 - 1.0, v);
var idx = (y * transmittanceResX + x) * 4;
transmittanceLUT[idx] = t[0];
transmittanceLUT[idx + 1] = t[1];
transmittanceLUT[idx + 2] = t[2];
transmittanceLUT[idx + 3] = t[3];
}
}
}
function lookupTransmittance(cosTheta: Float, normalizedAltitude: Float): Array<Float> {
var u = Math.max(0.0, Math.min(1.0, cosTheta * 0.5 + 0.5));
var v = Math.max(0.0, Math.min(1.0, normalizedAltitude));
var fx = u * (transmittanceResX - 1);
var fy = v * (transmittanceResY - 1);
var x1 = Std.int(fx);
var y1 = Std.int(fy);
var x2 = Std.int(Math.min(x1 + 1, transmittanceResX - 1));
var y2 = Std.int(Math.min(y1 + 1, transmittanceResY - 1));
var dxF = fx - x1;
var dyF = fy - y1;
function getPixel(px: Int, py: Int): Array<Float> {
var idx = (py * transmittanceResX + px) * 4;
return [transmittanceLUT[idx], transmittanceLUT[idx + 1], transmittanceLUT[idx + 2], transmittanceLUT[idx + 3]];
}
var bottom = mix4(getPixel(x1, y1), getPixel(x2, y1), dxF);
var top = mix4(getPixel(x1, y2), getPixel(x2, y2), dxF);
return mix4(bottom, top, dyF);
}
function lookupTransmittanceAtGround(cosTheta: Float): Array<Float> {
var u = Math.max(0.0, Math.min(1.0, cosTheta * 0.5 + 0.5));
var fx = u * (transmittanceResX - 1);
var x1 = Std.int(fx);
var x2 = Std.int(Math.min(x1 + 1, transmittanceResX - 1));
var dxF = fx - x1;
var y = 0;
function getPixel(px: Int): Array<Float> {
var idx = (y * transmittanceResX + px) * 4;
return [transmittanceLUT[idx], transmittanceLUT[idx + 1], transmittanceLUT[idx + 2], transmittanceLUT[idx + 3]];
}
return mix4(getPixel(x1), getPixel(x2), dxF);
}
function lookupTransmittanceToSun(normalizedAltitude: Float): Array<Float> {
var v = Math.max(0.0, Math.min(1.0, normalizedAltitude));
var fy = v * (transmittanceResY - 1);
var y1 = Std.int(fy);
var y2 = Std.int(Math.min(y1 + 1, transmittanceResY - 1));
var dyF = fy - y1;
var x = transmittanceResX - 1;
function getPixel(py: Int): Array<Float> {
var idx = (py * transmittanceResX + x) * 4;
return [transmittanceLUT[idx], transmittanceLUT[idx + 1], transmittanceLUT[idx + 2], transmittanceLUT[idx + 3]];
}
return mix4(getPixel(y1), getPixel(y2), dyF);
}
// approximation
function lookupMultiscattering(cosTheta: Float, normalizedHeight: Float, d: Float): Array<Float> {
var omega = 2.0 * Math.PI * (1.0 - Math.sqrt(Math.max(0.0, 1.0 - (EARTH_RADIUS / d) * (EARTH_RADIUS / d))));
var tToGround = lookupTransmittanceAtGround(cosTheta);
var tGroundToSample = div4(lookupTransmittanceToSun(0.0), lookupTransmittanceToSun(normalizedHeight));
// 2nd order scattering from the ground
var lGround = scale4(mult4(mult4(mult4(scale4(GROUND_ALBEDO, 1.0 / Math.PI), tToGround), tGroundToSample), [cosTheta, cosTheta, cosTheta, cosTheta]), PHASE_ISOTROPIC * omega);
// Fit of Earth's multiple scattering from other points in the atmosphere
var msFactor = 1.0 / (1.0 + 5.0 * Math.exp(-17.92 * cosTheta));
var lMs = scale4([0.217, 0.347, 0.594, 1.0], 0.02 * msFactor);
return add4(lMs, lGround);
}
function getInscattering(sunDir: Vec3, rayOrigin: Vec3, rayDir: Vec3, tD: Float): Array<Float> {
var cosTheta = rayDir.clone().mult(-1.0).dot(sunDir);
var molPhase = molecularPhaseFunction(cosTheta);
var aerPhase = aerosolPhaseFunction(cosTheta);
var dt = tD / inScatteringSteps;
var lInscattering: Array<Float> = [0.0, 0.0, 0.0, 0.0];
var transmittance: Array<Float> = [1.0, 1.0, 1.0, 1.0];
for (i in 0...inScatteringSteps) {
var t = (i + 0.5) * dt;
var xT = rayOrigin.clone().add(rayDir.clone().mult(t));
var distToCenter = xT.length();
var zenithDir = xT.clone().mult(1.0 / distToCenter);
var altitude = Math.max(distToCenter - EARTH_RADIUS, 0.0);
var normalizedAltitude = altitude / ATMOSPHERE_THICKNESS;
var sampleCosTheta = zenithDir.dot(sunDir);
var coeffs = getAtmosphereCollisionCoefficients(altitude);
var extinction = add4(add4(coeffs.aerosolAbs, coeffs.aerosolSca), add4(coeffs.molAbs, coeffs.molSca));
var tToSun = lookupTransmittance(sampleCosTheta, normalizedAltitude);
var ms = lookupMultiscattering(sampleCosTheta, normalizedAltitude, distToCenter);
// S = SUN_SPECTRAL_IRRADIANCE * (molSca * (molPhase * tToSun + ms) + aerSca * (aerPhase * tToSun + ms))
var s: Array<Float> = [0.0, 0.0, 0.0, 0.0];
for (w in 0...4) {
var molTerm = coeffs.molSca[w] * (molPhase * tToSun[w] + ms[w]);
var aerTerm = coeffs.aerosolSca[w] * (aerPhase * tToSun[w] + ms[w]);
s[w] = SUN_SPECTRAL_IRRADIANCE[w] * (molTerm + aerTerm);
}
var stepTransmittance = exp4(scale4(extinction, -dt));
var cutExt = max4(extinction, 1e-7);
var sInt: Array<Float> = [];
for (w in 0...4) {
sInt[w] = (s[w] - s[w] * stepTransmittance[w]) / cutExt[w];
}
lInscattering = add4(lInscattering, mult4(transmittance, sInt));
transmittance = mult4(transmittance, stepTransmittance);
}
return lInscattering;
}
function computeEarthAngle(altitude: Float): Float {
return Math.PI / 2.0 - Math.asin(EARTH_RADIUS / (EARTH_RADIUS + altitude / 1000.0));
}
function precomputeSun(sunElevation: Float, angularDiameter: Float, altitude: Float): { bottom: Vec3, top: Vec3 } {
var halfAngular = angularDiameter / 2.0;
var solidAngle = 2.0 * Math.PI * (1.0 - Math.cos(halfAngular));
var normalizedAltitude = altitude / ATMOSPHERE_THICKNESS;
function getSunXYZ(elevation: Float): Vec3 {
var sunZenithCosAngle = Math.cos(Math.PI / 2.0 - elevation);
var tToSun = getTransmittance(sunZenithCosAngle, normalizedAltitude);
var spectrum: Array<Float> = [];
for (w in 0...4) {
spectrum[w] = SUN_SPECTRAL_IRRADIANCE[w] * tToSun[w] / solidAngle;
}
return spectralToXYZ(spectrum);
}
var bottom = getSunXYZ(sunElevation - halfAngular);
var top = getSunXYZ(sunElevation + halfAngular);
return { bottom: bottom, top: top };
}
public function compute(sunElevation: Float, sunRotation: Float, sunSize: Float,
sunIntensity: Float, altitude: Float, sunDisc: Bool, density: Vec3) {
airDensity = density.x;
aerosolDensity = density.y;
ozoneDensity = density.z;
precomputeTransmittanceLUT();
var altKm = Math.max(1.0, Math.min(99999.0, altitude)) / 1000.0;
var sunZenithCosAngle = Math.cos(Math.PI / 2.0 - sunElevation);
var sunDir = sunDirection(sunZenithCosAngle);
var rayOrigin = new Vec3(0.0, 0.0, EARTH_RADIUS + altKm);
earthIntersectionAngle = computeEarthAngle(altitude);
var imageData = new haxe.io.Float32Array(lutWidth * lutHeight * 4);
for (y in 0...lutHeight) {
var uvY = (y + 0.5) / lutHeight;
var l = uvY * 2.0 - 1.0;
var elevation = (l < 0 ? -1.0 : 1.0) * l * l * Math.PI / 2.0;
var cosEl = Math.cos(elevation);
var sinEl = Math.sin(elevation);
for (x in 0...lutWidth) {
var uvX = (x + 0.5) / lutWidth;
var azimuth = 2.0 * Math.PI * uvX;
// atan(dir.x, dir.y) convention
var rayDir = new Vec3(
Math.sin(azimuth) * cosEl,
Math.cos(azimuth) * cosEl,
sinEl
).normalize();
var atmosDist = raySphereIntersection(rayOrigin, rayDir, ATMOSPHERE_RADIUS);
var groundDist = raySphereIntersection(rayOrigin, rayDir, EARTH_RADIUS);
var tD = (groundDist < 0.0) ? atmosDist : groundDist;
var radiance = getInscattering(sunDir, rayOrigin, rayDir, tD);
var xyz = spectralToXYZ(radiance);
var pixelIndex = (x + y * lutWidth) * 4;
imageData[pixelIndex + 0] = xyz.x;
imageData[pixelIndex + 1] = xyz.y;
imageData[pixelIndex + 2] = xyz.z;
imageData[pixelIndex + 3] = 1.0;
}
}
lut = kha.Image.fromBytes(imageData.view.buffer, lutWidth, lutHeight, TextureFormat.RGBA128, Usage.StaticUsage);
if (sunDisc) {
var sunData = precomputeSun(sunElevation, sunSize, altKm);
sunBottom = sunData.bottom;
sunTop = sunData.top;
} else {
sunBottom.set(0, 0, 0);
sunTop.set(0, 0, 0);
}
}
}

View File

@ -841,18 +841,6 @@ class RenderPathDeferred {
}
#end
#if rp_water
{
path.setTarget("buf");
path.bindTarget("tex", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("tex");
path.bindTarget("_main", "gbufferD");
path.bindTarget("buf", "tex");
path.drawShader("shader_datas/water_pass/water_pass");
}
#end
#if (!kha_opengl)
path.setDepthFrom("tex", "gbuffer0"); // Re-bind depth
#end
@ -879,6 +867,18 @@ class RenderPathDeferred {
}
#end
#if rp_water
{
path.setTarget("buf");
path.bindTarget("tex", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("tex");
path.bindTarget("_main", "gbufferD");
path.bindTarget("buf", "tex");
path.drawShader("shader_datas/water_pass/water_pass");
}
#end
#if rp_ssr
{
if (leenkx.data.Config.raw.rp_ssr != false) {