forked from LeenkxTeam/LNXSDK
Update
This commit is contained in:
@ -1,11 +1,11 @@
|
|||||||
/* Various sky functions
|
/* Various sky functions
|
||||||
* =====================
|
* =====================
|
||||||
*
|
*
|
||||||
* Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License)
|
* Single scattering model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License)
|
||||||
*
|
*
|
||||||
* Changes to the original implementation:
|
* Changes to the original implementation:
|
||||||
* - r and pSun parameters of nishita_atmosphere() are already normalized
|
* - r and pSun parameters of single_scatter_atmosphere() are already normalized
|
||||||
* - Some original parameters of nishita_atmosphere() are replaced with pre-defined values
|
* - Some original parameters of single_scatter_atmosphere() are replaced with pre-defined values
|
||||||
* - Implemented air, dust and ozone density node parameters (see Blender source)
|
* - Implemented air, dust and ozone density node parameters (see Blender source)
|
||||||
* - Replaced the inner integral calculation with a LUT lookup
|
* - Replaced the inner integral calculation with a LUT lookup
|
||||||
*
|
*
|
||||||
@ -22,8 +22,8 @@
|
|||||||
|
|
||||||
#include "std/math.glsl"
|
#include "std/math.glsl"
|
||||||
|
|
||||||
uniform sampler2D nishitaLUT;
|
uniform sampler2D singleScatterLUT;
|
||||||
uniform vec2 nishitaDensity;
|
uniform vec2 skyDensity;
|
||||||
|
|
||||||
#ifndef PI
|
#ifndef PI
|
||||||
#define PI 3.141592
|
#define PI 3.141592
|
||||||
@ -32,33 +32,33 @@ uniform vec2 nishitaDensity;
|
|||||||
#define HALF_PI 1.570796
|
#define HALF_PI 1.570796
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define nishita_iSteps 16
|
#define single_scatter_iSteps 16
|
||||||
|
|
||||||
// These values are taken from Cycles code if they
|
// These values are taken from Cycles code if they
|
||||||
// exist there, otherwise they are taken from the example
|
// exist there, otherwise they are taken from the example
|
||||||
// in the glsl-atmosphere repo
|
// in the glsl-atmosphere repo
|
||||||
#define nishita_sun_intensity 22.0
|
#define single_scatter_sun_intensity 22.0
|
||||||
#define nishita_atmo_radius 6420e3
|
#define single_scatter_atmo_radius 6420e3
|
||||||
#define nishita_rayleigh_scale 8e3
|
#define single_scatter_rayleigh_scale 8e3
|
||||||
#define nishita_rayleigh_coeff vec3(5.5e-6, 13.0e-6, 22.4e-6)
|
#define single_scatter_rayleigh_coeff vec3(5.5e-6, 13.0e-6, 22.4e-6)
|
||||||
#define nishita_mie_scale 1.2e3
|
#define single_scatter_mie_scale 1.2e3
|
||||||
#define nishita_mie_coeff 2e-5
|
#define single_scatter_mie_coeff 2e-5
|
||||||
#define nishita_mie_dir 0.76 // Aerosols anisotropy ("direction")
|
#define single_scatter_mie_dir 0.76 // Aerosols anisotropy ("direction")
|
||||||
#define nishita_mie_dir_sq 0.5776 // Squared aerosols anisotropy
|
#define single_scatter_mie_dir_sq 0.5776 // Squared aerosols anisotropy
|
||||||
|
|
||||||
// Values from [Hill: 60]
|
// Values from [Hill: 60]
|
||||||
#define sun_limb_darkening_col vec3(0.397, 0.503, 0.652)
|
#define sun_limb_darkening_col vec3(0.397, 0.503, 0.652)
|
||||||
|
|
||||||
vec3 nishita_lookupLUT(const float height, const float sunTheta) {
|
vec3 single_scatter_lookupLUT(const float height, const float sunTheta) {
|
||||||
vec2 coords = vec2(
|
vec2 coords = vec2(
|
||||||
sqrt(height * (1 / nishita_atmo_radius)),
|
sqrt(height * (1 / single_scatter_atmo_radius)),
|
||||||
0.5 + 0.5 * sign(sunTheta - HALF_PI) * sqrt(abs(sunTheta * (1 / HALF_PI) - 1))
|
0.5 + 0.5 * sign(sunTheta - HALF_PI) * sqrt(abs(sunTheta * (1 / HALF_PI) - 1))
|
||||||
);
|
);
|
||||||
return textureLod(nishitaLUT, coords, 0.0).rgb;
|
return textureLod(singleScatterLUT, coords, 0.0).rgb;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* See raySphereIntersection() in leenkx/Sources/renderpath/Nishita.hx */
|
/* See raySphereIntersection() in leenkx/Sources/renderpath/Sky.hx */
|
||||||
vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) {
|
vec2 single_scatter_rsi(const vec3 r0, const vec3 rd, const float sr) {
|
||||||
float a = dot(rd, rd);
|
float a = dot(rd, rd);
|
||||||
float b = 2.0 * dot(rd, r0);
|
float b = 2.0 * dot(rd, r0);
|
||||||
float c = dot(r0, r0) - (sr * sr);
|
float c = dot(r0, r0) - (sr * sr);
|
||||||
@ -74,12 +74,12 @@ vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) {
|
|||||||
* pSun: normalized sun direction
|
* pSun: normalized sun direction
|
||||||
* rPlanet: planet radius
|
* rPlanet: planet radius
|
||||||
*/
|
*/
|
||||||
vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet) {
|
vec3 single_scatter_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet) {
|
||||||
// Calculate the step size of the primary ray
|
// Calculate the step size of the primary ray
|
||||||
vec2 p = nishita_rsi(r0, r, nishita_atmo_radius);
|
vec2 p = single_scatter_rsi(r0, r, single_scatter_atmo_radius);
|
||||||
if (p.x > p.y) return vec3(0.0);
|
if (p.x > p.y) return vec3(0.0);
|
||||||
p.y = min(p.y, nishita_rsi(r0, r, rPlanet).x);
|
p.y = min(p.y, single_scatter_rsi(r0, r, rPlanet).x);
|
||||||
float iStepSize = (p.y - p.x) / float(nishita_iSteps);
|
float iStepSize = (p.y - p.x) / float(single_scatter_iSteps);
|
||||||
|
|
||||||
// Primary ray time
|
// Primary ray time
|
||||||
float iTime = 0.0;
|
float iTime = 0.0;
|
||||||
@ -96,18 +96,18 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa
|
|||||||
float mu = dot(r, pSun);
|
float mu = dot(r, pSun);
|
||||||
float mumu = mu * mu;
|
float mumu = mu * mu;
|
||||||
float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu);
|
float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu);
|
||||||
float pMie = 3.0 / (8.0 * PI) * ((1.0 - nishita_mie_dir_sq) * (mumu + 1.0)) / (pow(1.0 + nishita_mie_dir_sq - 2.0 * mu * nishita_mie_dir, 1.5) * (2.0 + nishita_mie_dir_sq));
|
float pMie = 3.0 / (8.0 * PI) * ((1.0 - single_scatter_mie_dir_sq) * (mumu + 1.0)) / (pow(1.0 + single_scatter_mie_dir_sq - 2.0 * mu * single_scatter_mie_dir, 1.5) * (2.0 + single_scatter_mie_dir_sq));
|
||||||
|
|
||||||
// Sample the primary ray
|
// Sample the primary ray
|
||||||
for (int i = 0; i < nishita_iSteps; i++) {
|
for (int i = 0; i < single_scatter_iSteps; i++) {
|
||||||
|
|
||||||
// Calculate the primary ray sample position and height
|
// Calculate the primary ray sample position and height
|
||||||
vec3 iPos = r0 + r * (iTime + iStepSize * 0.5);
|
vec3 iPos = r0 + r * (iTime + iStepSize * 0.5);
|
||||||
float iHeight = length(iPos) - rPlanet;
|
float iHeight = length(iPos) - rPlanet;
|
||||||
|
|
||||||
// Calculate the optical depth of the Rayleigh and Mie scattering for this step
|
// Calculate the optical depth of the Rayleigh and Mie scattering for this step
|
||||||
float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * nishitaDensity.x * iStepSize;
|
float odStepRlh = exp(-iHeight / single_scatter_rayleigh_scale) * skyDensity.x * iStepSize;
|
||||||
float odStepMie = exp(-iHeight / nishita_mie_scale) * nishitaDensity.y * iStepSize;
|
float odStepMie = exp(-iHeight / single_scatter_mie_scale) * skyDensity.y * iStepSize;
|
||||||
|
|
||||||
// Accumulate optical depth
|
// Accumulate optical depth
|
||||||
iOdRlh += odStepRlh;
|
iOdRlh += odStepRlh;
|
||||||
@ -116,12 +116,12 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa
|
|||||||
// Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the
|
// Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the
|
||||||
// inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith).
|
// inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith).
|
||||||
float sunTheta = safe_acos(dot(normalize(iPos), normalize(pSun)));
|
float sunTheta = safe_acos(dot(normalize(iPos), normalize(pSun)));
|
||||||
vec3 jAttn = nishita_lookupLUT(iHeight, sunTheta);
|
vec3 jAttn = single_scatter_lookupLUT(iHeight, sunTheta);
|
||||||
|
|
||||||
// Calculate attenuation
|
// Calculate attenuation
|
||||||
vec3 iAttn = exp(-(
|
vec3 iAttn = exp(-(
|
||||||
nishita_mie_coeff * iOdMie
|
single_scatter_mie_coeff * iOdMie
|
||||||
+ nishita_rayleigh_coeff * iOdRlh
|
+ single_scatter_rayleigh_coeff * iOdRlh
|
||||||
// + 0 for ozone
|
// + 0 for ozone
|
||||||
));
|
));
|
||||||
vec3 attn = iAttn * jAttn;
|
vec3 attn = iAttn * jAttn;
|
||||||
@ -136,7 +136,7 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa
|
|||||||
iTime += iStepSize;
|
iTime += iStepSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nishita_sun_intensity * (pRlh * nishita_rayleigh_coeff * totalRlh + pMie * nishita_mie_coeff * totalMie);
|
return single_scatter_sun_intensity * (pRlh * single_scatter_rayleigh_coeff * totalRlh + pMie * single_scatter_mie_coeff * totalMie);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 sun_disk(const vec3 n, const vec3 light_dir, const float disk_size, const float intensity) {
|
vec3 sun_disk(const vec3 n, const vec3 light_dir, const float disk_size, const float intensity) {
|
||||||
@ -149,7 +149,76 @@ vec3 sun_disk(const vec3 n, const vec3 light_dir, const float disk_size, const f
|
|||||||
float mu = sqrt(invDist * invDist);
|
float mu = sqrt(invDist * invDist);
|
||||||
vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col));
|
vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col));
|
||||||
|
|
||||||
return 1 + (1.0 - step(1.0, dist)) * nishita_sun_intensity * intensity * limb_darkening;
|
return 1 + (1.0 - step(1.0, dist)) * single_scatter_sun_intensity * intensity * limb_darkening;
|
||||||
|
}
|
||||||
|
|
||||||
|
uniform sampler2D multiScatterLUT;
|
||||||
|
uniform vec4 multiScatterParams; // x=elevation, y=rotation, z=angular_diameter, w=intensity
|
||||||
|
uniform vec4 multiScatterSunBottom; // xyz=sun_bottom, w=earth_intersection_angle
|
||||||
|
uniform vec3 multiScatterSunTop;
|
||||||
|
|
||||||
|
// XYZ to sRGB/Rec.709 conversion (D65 white point)
|
||||||
|
vec3 xyz_to_rgb(vec3 xyz) {
|
||||||
|
return vec3(
|
||||||
|
3.2406 * xyz.x - 1.5372 * xyz.y - 0.4986 * xyz.z,
|
||||||
|
-0.9689 * xyz.x + 1.8758 * xyz.y + 0.0415 * xyz.z,
|
||||||
|
0.0557 * xyz.x - 0.2040 * xyz.y + 1.0570 * xyz.z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
float sky_elevation_to_v(float elevation) {
|
||||||
|
float abs_el = abs(elevation);
|
||||||
|
float l = sign(elevation) * sqrt(abs_el / 1.5707963);
|
||||||
|
float v = (l + 1.0) * 0.5;
|
||||||
|
return clamp(v, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 multi_scatter_sample_lut(vec3 dir, float sun_rotation) {
|
||||||
|
float azimuth = atan(dir.x, dir.y);
|
||||||
|
float elevation = asin(clamp(dir.z, -1.0, 1.0));
|
||||||
|
|
||||||
|
azimuth -= sun_rotation;
|
||||||
|
float u = fract(azimuth / (2.0 * PI));
|
||||||
|
float v = sky_elevation_to_v(elevation);
|
||||||
|
|
||||||
|
return textureLod(multiScatterLUT, vec2(u, v), 0.0).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 multi_scatter_sun_disc(vec3 dir, vec3 sun_dir, float angular_diameter, float intensity) {
|
||||||
|
float dist = distance(dir, sun_dir) / (angular_diameter * 0.5);
|
||||||
|
if (dist > 1.0) return vec3(0.0);
|
||||||
|
|
||||||
|
float invDist = 1.0 - dist;
|
||||||
|
float mu = sqrt(invDist * invDist);
|
||||||
|
vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col));
|
||||||
|
|
||||||
|
float sun_elev = multiScatterParams.x;
|
||||||
|
float dir_elev = asin(clamp(dir.z, -1.0, 1.0));
|
||||||
|
float t = clamp((dir_elev - (sun_elev - angular_diameter * 0.5)) / angular_diameter, 0.0, 1.0);
|
||||||
|
vec3 sun_color = mix(multiScatterSunBottom.rgb, multiScatterSunTop, t) * intensity * limb_darkening;
|
||||||
|
|
||||||
|
return xyz_to_rgb(sun_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 multi_scatter_atmosphere(vec3 dir) {
|
||||||
|
float sun_elevation = multiScatterParams.x;
|
||||||
|
float sun_rotation = multiScatterParams.y;
|
||||||
|
float angular_diameter = multiScatterParams.z;
|
||||||
|
float sun_intensity = multiScatterParams.w;
|
||||||
|
|
||||||
|
vec3 xyz = multi_scatter_sample_lut(dir, sun_rotation);
|
||||||
|
vec3 radiance = xyz_to_rgb(xyz);
|
||||||
|
|
||||||
|
if (sun_intensity > 0.0) {
|
||||||
|
vec3 computed_sun_dir = vec3(
|
||||||
|
sin(sun_rotation) * cos(sun_elevation),
|
||||||
|
cos(sun_rotation) * cos(sun_elevation),
|
||||||
|
sin(sun_elevation)
|
||||||
|
);
|
||||||
|
radiance += multi_scatter_sun_disc(dir, computed_sun_dir, angular_diameter, sun_intensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return radiance;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -88,53 +88,98 @@ vec4 rayCast(vec3 dir) {
|
|||||||
}
|
}
|
||||||
#endif //SSR
|
#endif //SSR
|
||||||
|
|
||||||
|
vec3 sampleWaterNormals(vec2 hitXY, float speed, out vec2 tcnor0, out vec2 tcnor1) {
|
||||||
|
tcnor0 = hitXY / 3.0;
|
||||||
|
vec3 n0 = textureLod(sdetail, tcnor0 + vec2(speed / 60.0, speed / 120.0), 0.0).rgb;
|
||||||
|
tcnor1 = hitXY / 6.0 + n0.xy / 20.0;
|
||||||
|
vec3 n1 = textureLod(sbase, tcnor1 + vec2(speed / 40.0, speed / 80.0), 0.0).rgb;
|
||||||
|
return normalize(n0 + n1 - 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
float gdepth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
float gdepth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||||
if (gdepth == 1.0) {
|
|
||||||
fragColor = vec4(0.0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Eye below water
|
|
||||||
if (eye.z < waterLevel) {
|
|
||||||
fragColor = vec4(0.0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Displace surface
|
|
||||||
vec3 vray = normalize(viewRay);
|
vec3 vray = normalize(viewRay);
|
||||||
vec3 p = getPos(eye, eyeLook, vray, gdepth, cameraProj);
|
|
||||||
float speed = time * 2.0 * waterSpeed;
|
float speed = time * 2.0 * waterSpeed;
|
||||||
p.z += sin(p.x * 10.0 / waterDisplace + speed) * cos(p.y * 10.0 / waterDisplace + speed) / 50.0 * waterDisplace;
|
bool isSky = (gdepth == 1.0);
|
||||||
|
|
||||||
|
// Ray-plane intersection with water surface (z = waterLevel)
|
||||||
|
float denom = dot(vray, vec3(0.0, 0.0, 1.0));
|
||||||
|
float tWater = (waterLevel - eye.z) / denom;
|
||||||
|
bool hasWaterHit = (abs(denom) > 0.0001) && (tWater > 0.0);
|
||||||
|
|
||||||
|
if (eye.z < waterLevel) {
|
||||||
|
vec2 tc = texCoord;
|
||||||
|
float fogFactor;
|
||||||
|
|
||||||
|
if (hasWaterHit && denom > 0.0) {
|
||||||
|
// Looking up at water surface - apply normal distortion
|
||||||
|
vec3 hit = eye + tWater * vray;
|
||||||
|
vec2 tc0, tc1;
|
||||||
|
vec3 n2 = sampleWaterNormals(hit.xy * waterFreq, speed, tc0, tc1);
|
||||||
|
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||||
|
fogFactor = clamp(tWater * waterDensity, 0.0, 0.95);
|
||||||
|
} else {
|
||||||
|
// Looking forward/down - distort via water surface above fragment
|
||||||
|
vec3 p = getPos(eye, eyeLook, vray, gdepth, cameraProj);
|
||||||
|
vec2 wxy = isSky ? (eye.xy + vray.xy * 50.0) : p.xy;
|
||||||
|
vec2 tc0, tc1;
|
||||||
|
vec3 n2 = sampleWaterNormals(wxy * waterFreq, speed, tc0, tc1);
|
||||||
|
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||||
|
float waterDist = isSky ? 50.0 : length(p - eye);
|
||||||
|
fogFactor = clamp(waterDist * waterDensity, 0.0, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 refracted = textureLod(tex, tc, 0.0).rgb;
|
||||||
|
fragColor.rgb = mix(refracted, waterColor, fogFactor);
|
||||||
|
fragColor.a = 1.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Above water
|
// Above water
|
||||||
if (p.z > waterLevel) {
|
if (!hasWaterHit || denom >= 0.0) {
|
||||||
fragColor = vec4(0.0);
|
fragColor = vec4(0.0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSky) tWater = min(tWater, 100.0); // Clamp to prevent aliasing at horizon
|
||||||
|
vec3 p = isSky ? (eye + tWater * vray) : getPos(eye, eyeLook, vray, gdepth, cameraProj);
|
||||||
|
|
||||||
|
float horizonFactor = clamp(1.0 - tWater / 60.0, 0.0, 1.0);
|
||||||
|
if (!isSky && p.z > waterLevel) {
|
||||||
|
fragColor = vec4(0.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Displace surface
|
||||||
|
p.z += (sin(p.x * 10.0 / waterDisplace + speed) * cos(p.y * 10.0 / waterDisplace + speed)
|
||||||
|
+ sin(p.x * 20.0 / waterDisplace + speed * 1.3) * cos(p.y * 20.0 / waterDisplace + speed * 1.3) * 0.5)
|
||||||
|
/ 50.0 * waterDisplace;
|
||||||
|
|
||||||
// Hit plane to determine uvs
|
// Hit plane to determine uvs
|
||||||
vec3 v = normalize(eye - p.xyz);
|
vec3 v = normalize(eye - p);
|
||||||
float t = -(dot(eye, vec3(0.0, 0.0, 1.0)) - waterLevel) / dot(v, vec3(0.0, 0.0, 1.0));
|
float t = (waterLevel - eye.z) / dot(v, vec3(0.0, 0.0, 1.0));
|
||||||
vec3 hit = eye + t * v;
|
vec3 hit = eye + t * v;
|
||||||
hit.xy *= waterFreq;
|
|
||||||
hit.z += waterLevel;
|
|
||||||
|
|
||||||
// Sample normal maps
|
// Sample normal maps
|
||||||
vec2 tcnor0 = hit.xy / 3.0;
|
vec2 tcnor0, tcnor1;
|
||||||
vec3 n0 = textureLod(sdetail, tcnor0 + vec2(speed / 60.0, speed / 120.0), 0.0).rgb;
|
vec3 n2 = sampleWaterNormals(hit.xy * waterFreq, speed, tcnor0, tcnor1);
|
||||||
|
|
||||||
vec2 tcnor1 = hit.xy / 6.0 + n0.xy / 20.0;
|
// Refraction
|
||||||
vec3 n1 = textureLod(sbase, tcnor1 + vec2(speed / 40.0, speed / 80.0), 0.0).rgb;
|
vec2 tc;
|
||||||
vec3 n2 = normalize(((n1 + n0) / 2.0) * 2.0 - 1.0);
|
if (isSky) {
|
||||||
|
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||||
float ddepth = textureLod(gbufferD, texCoord + (n2.xy * n2.z) / 40.0, 0.0).r * 2.0 - 1.0;
|
} else {
|
||||||
vec3 p2 = getPos(eye, eyeLook, vray, ddepth, cameraProj);
|
float ddepth = textureLod(gbufferD, texCoord + (n2.xy * n2.z) / 40.0, 0.0).r * 2.0 - 1.0;
|
||||||
vec2 tc = p2.z > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
vec3 p2 = getPos(eye, eyeLook, vray, ddepth, cameraProj);
|
||||||
|
tc = p2.z > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||||
|
}
|
||||||
|
|
||||||
// Light
|
// Light
|
||||||
float fresnel = 1.0 - max(dot(n2, v), 0.0);
|
float fresnel = 1.0 - max(dot(n2, v), 0.0);
|
||||||
fresnel = pow(fresnel, 30.0) * 0.45;
|
fresnel = 0.02 + 0.98 * pow(fresnel, 5.0);
|
||||||
vec3 r = reflect(-v, n2);
|
vec3 r = reflect(-v, n2);
|
||||||
#ifdef _Rad
|
#ifdef _Rad
|
||||||
vec3 reflectedEnv = textureLod(senvmapRadiance, envMapEquirect(r), 0).rgb;
|
vec3 reflectedEnv = textureLod(senvmapRadiance, envMapEquirect(r), 0).rgb;
|
||||||
#else
|
#else
|
||||||
const vec3 reflectedEnv = vec3(0.5);
|
const vec3 reflectedEnv = vec3(0.5);
|
||||||
#endif
|
#endif
|
||||||
@ -147,8 +192,8 @@ void main() {
|
|||||||
float spec = 0.9;//fract(textureLod(gbuffer1, texCoord, 0.0).a);
|
float spec = 0.9;//fract(textureLod(gbuffer1, texCoord, 0.0).a);
|
||||||
//if (spec == 0.0) { fragColor.rgb = vec3(0.0); return; }
|
//if (spec == 0.0) { fragColor.rgb = vec3(0.0); return; }
|
||||||
|
|
||||||
vec3 viewNormal = n2;
|
vec3 viewNormal = V3 * n2;
|
||||||
vec3 viewPos = getPosView(viewRay, gdepth, cameraProj);
|
vec3 viewPos = isSky ? vec3(0.0) : getPosView(viewRay, gdepth, cameraProj);
|
||||||
vec3 reflected = reflect(normalize(viewPos), viewNormal);
|
vec3 reflected = reflect(normalize(viewPos), viewNormal);
|
||||||
hitCoord = viewPos;
|
hitCoord = viewPos;
|
||||||
|
|
||||||
@ -158,7 +203,6 @@ void main() {
|
|||||||
vec3 dir = reflected * (1.0 - rand(texCoord) * ssrJitter * roughness) * 2.0;
|
vec3 dir = reflected * (1.0 - rand(texCoord) * ssrJitter * roughness) * 2.0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// * max(ssrMinRayStep, -viewPos.z)
|
|
||||||
vec4 coords = rayCast(dir);
|
vec4 coords = rayCast(dir);
|
||||||
|
|
||||||
vec2 deltaCoords = abs(vec2(0.5, 0.5) - coords.xy);
|
vec2 deltaCoords = abs(vec2(0.5, 0.5) - coords.xy);
|
||||||
@ -177,20 +221,32 @@ void main() {
|
|||||||
#else
|
#else
|
||||||
fragColor.rgb = mix(refracted, reflectedEnv, waterReflect * fresnel);
|
fragColor.rgb = mix(refracted, reflectedEnv, waterReflect * fresnel);
|
||||||
#endif
|
#endif
|
||||||
fragColor.rgb *= waterColor;
|
// Water color tint - blend rather than multiply to preserve brightness
|
||||||
fragColor.rgb += clamp(pow(max(dot(r, ld), 0.0), 200.0) * (200.0 + 8.0) / (PI * 8.0), 0.0, 2.0);
|
float colorMix = isSky ? 0.7 : 0.5;
|
||||||
fragColor.rgb *= 1.0 - (clamp(-(p.z - waterLevel) * waterDensity, 0.0, 0.9));
|
fragColor.rgb = mix(fragColor.rgb, fragColor.rgb * waterColor, colorMix * horizonFactor);
|
||||||
fragColor.a = clamp(abs(p.z - waterLevel) * 5.0, 0.0, 1.0);
|
// Blinn-Phong specular using half-vector, faded at horizon
|
||||||
|
vec3 h = normalize(v + ld);
|
||||||
|
float specAmount = pow(max(dot(n2, h), 0.0), 200.0) * (200.0 + 8.0) / (PI * 8.0);
|
||||||
|
fragColor.rgb += specAmount * (isSky ? 0.3 : 1.0) * horizonFactor;
|
||||||
|
// Depth fog - blend toward waterColor with depth, faded at horizon
|
||||||
|
float depthFog = clamp(-(p.z - waterLevel) * waterDensity, 0.0, 0.9);
|
||||||
|
fragColor.rgb = mix(fragColor.rgb, waterColor, depthFog * horizonFactor);
|
||||||
|
// Alpha fades smoothly at horizon instead of hard cut
|
||||||
|
fragColor.a = isSky ? horizonFactor : clamp(abs(p.z - waterLevel) * 5.0, 0.0, 1.0);
|
||||||
|
|
||||||
// Foam
|
// Foam - based on actual geometry depth below water surface
|
||||||
float fd = abs(p.z - waterLevel);
|
float fd = isSky ? 1.0 : abs(p.z - waterLevel);
|
||||||
if (fd < 0.1) {
|
if (fd < 0.1) {
|
||||||
// Based on foam by Owen Deery
|
// Based on foam by Owen Deery
|
||||||
// http://fire-face.com/personal/water
|
// http://fire-face.com/personal/water
|
||||||
vec3 foamMask0 = textureLod(sfoam, tcnor0 * 10, 0.0).rgb;
|
// Distance-based LOD blurs foam at range to reduce noise
|
||||||
vec3 foamMask1 = textureLod(sfoam, tcnor1 * 11, 0.0).rgb;
|
float foamLod = clamp(tWater / 15.0, 0.0, 5.0);
|
||||||
vec3 foam = vec3(1.0) - foamMask0.rrr - foamMask1.bbb;
|
vec2 foamUV0 = tcnor0 * 3.0 + vec2(speed / 30.0, speed / 50.0);
|
||||||
float fac = 1.0 - (fd * (1.0 / 0.1));
|
vec2 foamUV1 = tcnor1 * 4.0 + vec2(-speed / 35.0, speed / 45.0);
|
||||||
fragColor.rgb = mix(fragColor.rgb, clamp(foam, 0.0, 1.0), clamp(fac, 0.0, 1.0));
|
vec3 foamMask0 = textureLod(sfoam, foamUV0, foamLod).rgb;
|
||||||
|
vec3 foamMask1 = textureLod(sfoam, foamUV1, foamLod).rgb;
|
||||||
|
float foamStrength = clamp(1.0 - foamMask0.r * 0.5 - foamMask1.b * 0.5, 0.0, 1.0);
|
||||||
|
float fac = (1.0 - (fd * (1.0 / 0.1))) * horizonFactor;
|
||||||
|
fragColor.rgb = mix(fragColor.rgb, mix(fragColor.rgb, waterColor + 0.2, foamStrength), clamp(fac, 0.0, 1.0) * 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -348,7 +348,13 @@ typedef TWorldData = {
|
|||||||
@:optional public var turbidity: Null<FastFloat>;
|
@:optional public var turbidity: Null<FastFloat>;
|
||||||
@:optional public var ground_albedo: Null<FastFloat>;
|
@:optional public var ground_albedo: Null<FastFloat>;
|
||||||
@:optional public var envmap: String;
|
@: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
|
#if js
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
15
leenkx/Sources/leenkx/logicnode/GetWorldColorNode.hx
Normal file
15
leenkx/Sources/leenkx/logicnode/GetWorldColorNode.hx
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
leenkx/Sources/leenkx/logicnode/GetWorldSkyNode.hx
Normal file
50
leenkx/Sources/leenkx/logicnode/GetWorldSkyNode.hx
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
leenkx/Sources/leenkx/logicnode/GetWorldTextureNode.hx
Normal file
17
leenkx/Sources/leenkx/logicnode/GetWorldTextureNode.hx
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
leenkx/Sources/leenkx/logicnode/SetWorldColorNode.hx
Normal file
23
leenkx/Sources/leenkx/logicnode/SetWorldColorNode.hx
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
leenkx/Sources/leenkx/logicnode/SetWorldSkyNode.hx
Normal file
63
leenkx/Sources/leenkx/logicnode/SetWorldSkyNode.hx
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
leenkx/Sources/leenkx/logicnode/SetWorldTextureNode.hx
Normal file
25
leenkx/Sources/leenkx/logicnode/SetWorldTextureNode.hx
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,7 +18,7 @@ class Uniforms {
|
|||||||
iron.object.Uniforms.externalTextureLinks = [textureLink];
|
iron.object.Uniforms.externalTextureLinks = [textureLink];
|
||||||
iron.object.Uniforms.externalVec2Links = [vec2Link];
|
iron.object.Uniforms.externalVec2Links = [vec2Link];
|
||||||
iron.object.Uniforms.externalVec3Links = [vec3Link];
|
iron.object.Uniforms.externalVec3Links = [vec3Link];
|
||||||
iron.object.Uniforms.externalVec4Links = [];
|
iron.object.Uniforms.externalVec4Links = [vec4Link];
|
||||||
iron.object.Uniforms.externalFloatLinks = [floatLink];
|
iron.object.Uniforms.externalFloatLinks = [floatLink];
|
||||||
iron.object.Uniforms.externalFloatsLinks = [floatsLink];
|
iron.object.Uniforms.externalFloatsLinks = [floatsLink];
|
||||||
iron.object.Uniforms.externalIntLinks = [intLink];
|
iron.object.Uniforms.externalIntLinks = [intLink];
|
||||||
@ -26,9 +26,13 @@ class Uniforms {
|
|||||||
|
|
||||||
public static function textureLink(object: Object, mat: MaterialData, link: String): Null<kha.Image> {
|
public static function textureLink(object: Object, mat: MaterialData, link: String): Null<kha.Image> {
|
||||||
switch (link) {
|
switch (link) {
|
||||||
case "_nishitaLUT": {
|
case "_singleScatterLUT": {
|
||||||
if (leenkx.renderpath.Nishita.data == null) leenkx.renderpath.Nishita.recompute(Scene.active.world);
|
if (leenkx.renderpath.Sky.singleScatterData == null) leenkx.renderpath.Sky.recomputeSingleScatter(Scene.active.world);
|
||||||
return leenkx.renderpath.Nishita.data.lut;
|
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
|
#if lnx_ltc
|
||||||
case "_ltcMat": {
|
case "_ltcMat": {
|
||||||
@ -169,6 +173,17 @@ class Uniforms {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#end
|
#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;
|
return v;
|
||||||
}
|
}
|
||||||
@ -176,13 +191,13 @@ class Uniforms {
|
|||||||
public static function vec2Link(object: Object, mat: MaterialData, link: String): Null<iron.math.Vec4> {
|
public static function vec2Link(object: Object, mat: MaterialData, link: String): Null<iron.math.Vec4> {
|
||||||
var v: Vec4 = null;
|
var v: Vec4 = null;
|
||||||
switch (link) {
|
switch (link) {
|
||||||
case "_nishitaDensity": {
|
case "_skyDensity": {
|
||||||
var w = Scene.active.world;
|
var w = Scene.active.world;
|
||||||
if (w != null) {
|
if (w != null) {
|
||||||
v = iron.object.Uniforms.helpVec;
|
v = iron.object.Uniforms.helpVec;
|
||||||
// We only need Rayleigh and Mie density in the sky shader -> Vec2
|
// We only need Rayleigh and Mie density in the sky shader -> Vec2
|
||||||
v.x = w.raw.nishita_density[0];
|
v.x = w.raw.sky_density[0];
|
||||||
v.y = w.raw.nishita_density[1];
|
v.y = w.raw.sky_density[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,6 +205,35 @@ class Uniforms {
|
|||||||
return v;
|
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> {
|
public static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
|
||||||
switch (link) {
|
switch (link) {
|
||||||
#if rp_dynres
|
#if rp_dynres
|
||||||
|
|||||||
@ -8,50 +8,99 @@ import kha.graphics4.Usage;
|
|||||||
import iron.data.WorldData;
|
import iron.data.WorldData;
|
||||||
import iron.math.Vec2;
|
import iron.math.Vec2;
|
||||||
import iron.math.Vec3;
|
import iron.math.Vec3;
|
||||||
|
import iron.math.Vec4;
|
||||||
|
|
||||||
import leenkx.math.Helper;
|
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)!
|
Do not call this method on every frame (it's slow)!
|
||||||
**/
|
**/
|
||||||
public static function recompute(world: WorldData) {
|
public static function recomputeSingleScatter(world: WorldData) {
|
||||||
if (world == null || world.raw.nishita_density == null) return;
|
if (world == null || world.raw.sky_density == null) return;
|
||||||
if (data == null) data = new NishitaData();
|
if (singleScatterData == null) singleScatterData = new SingleScatteringData();
|
||||||
|
|
||||||
var density = world.raw.nishita_density;
|
var density = world.raw.sky_density;
|
||||||
data.computeLUT(new Vec3(density[0], density[1], density[2]));
|
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) {
|
public static function setDensity(world: WorldData, densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
|
||||||
if (world == null) return;
|
if (world == null) return;
|
||||||
|
|
||||||
if (world.raw.nishita_density == null) world.raw.nishita_density = new Float32Array(3);
|
if (world.raw.sky_density == null) world.raw.sky_density = new Float32Array(3);
|
||||||
var density = world.raw.nishita_density;
|
var density = world.raw.sky_density;
|
||||||
density[0] = Helper.clamp(densityAir, 0, 10);
|
density[0] = Helper.clamp(densityAir, 0, 10);
|
||||||
density[1] = Helper.clamp(densityDust, 0, 10);
|
density[1] = Helper.clamp(densityDust, 0, 10);
|
||||||
density[2] = Helper.clamp(densityOzone, 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
|
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).
|
[`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;
|
public var lut: kha.Image;
|
||||||
|
|
||||||
@ -229,3 +278,397 @@ class NishitaData {
|
|||||||
return jAttenuation;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -841,18 +841,6 @@ class RenderPathDeferred {
|
|||||||
}
|
}
|
||||||
#end
|
#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)
|
#if (!kha_opengl)
|
||||||
path.setDepthFrom("tex", "gbuffer0"); // Re-bind depth
|
path.setDepthFrom("tex", "gbuffer0"); // Re-bind depth
|
||||||
#end
|
#end
|
||||||
@ -879,6 +867,18 @@ class RenderPathDeferred {
|
|||||||
}
|
}
|
||||||
#end
|
#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 rp_ssr
|
||||||
{
|
{
|
||||||
if (leenkx.data.Config.raw.rp_ssr != false) {
|
if (leenkx.data.Config.raw.rp_ssr != false) {
|
||||||
|
|||||||
@ -3817,7 +3817,13 @@ class LeenkxExporter:
|
|||||||
out_world['sun_direction'] = list(world.lnx_envtex_sun_direction)
|
out_world['sun_direction'] = list(world.lnx_envtex_sun_direction)
|
||||||
out_world['turbidity'] = world.lnx_envtex_turbidity
|
out_world['turbidity'] = world.lnx_envtex_turbidity
|
||||||
out_world['ground_albedo'] = world.lnx_envtex_ground_albedo
|
out_world['ground_albedo'] = world.lnx_envtex_ground_albedo
|
||||||
out_world['nishita_density'] = list(world.lnx_nishita_density)
|
out_world['sky_density'] = list(world.lnx_sky_density)
|
||||||
|
out_world['sky_sun_elevation'] = world.lnx_sky_sun_elevation
|
||||||
|
out_world['sky_sun_rotation'] = world.lnx_sky_sun_rotation
|
||||||
|
out_world['sky_sun_size'] = world.lnx_sky_sun_size
|
||||||
|
out_world['sky_sun_intensity'] = world.lnx_sky_sun_intensity
|
||||||
|
out_world['sky_altitude'] = world.lnx_sky_altitude
|
||||||
|
out_world['sky_sun_disc'] = world.lnx_sky_sun_disc
|
||||||
|
|
||||||
disable_hdr = world.lnx_envtex_name.endswith('.jpg')
|
disable_hdr = world.lnx_envtex_name.endswith('.jpg')
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
from lnx.logicnode.lnx_nodes import *
|
|
||||||
|
|
||||||
class GetHosekWilkiePropertiesNode(LnxLogicTreeNode):
|
|
||||||
"""Gets the HosekWilkie properties."""
|
|
||||||
bl_idname = 'LNGetHosekWilkiePropertiesNode'
|
|
||||||
bl_label = 'Get HosekWilkie Properties'
|
|
||||||
lnx_version = 1
|
|
||||||
|
|
||||||
def lnx_init(self, context):
|
|
||||||
self.add_output('LnxFloatSocket', 'Turbidity')
|
|
||||||
self.add_output('LnxFloatSocket', 'Ground Albedo')
|
|
||||||
self.add_output('LnxVectorSocket', 'Sun Direction')
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
from lnx.logicnode.lnx_nodes import *
|
|
||||||
|
|
||||||
class GetNishitaPropertiesNode(LnxLogicTreeNode):
|
|
||||||
"""Gets the Nishita properties."""
|
|
||||||
bl_idname = 'LNGetNishitaPropertiesNode'
|
|
||||||
bl_label = 'Get Nishita Properties'
|
|
||||||
lnx_version = 1
|
|
||||||
|
|
||||||
def lnx_init(self, context):
|
|
||||||
self.add_output('LnxFloatSocket', 'Air')
|
|
||||||
self.add_output('LnxFloatSocket', 'Dust')
|
|
||||||
self.add_output('LnxFloatSocket', 'Ozone')
|
|
||||||
self.add_output('LnxVectorSocket', 'Sun Direction')
|
|
||||||
|
|
||||||
11
leenkx/blender/lnx/logicnode/world/LN_get_world_color.py
Normal file
11
leenkx/blender/lnx/logicnode/world/LN_get_world_color.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from lnx.logicnode.lnx_nodes import *
|
||||||
|
|
||||||
|
|
||||||
|
class GetWorldColorNode(LnxLogicTreeNode):
|
||||||
|
"""Gets the background color of the active world."""
|
||||||
|
bl_idname = 'LNGetWorldColorNode'
|
||||||
|
bl_label = 'Get World Color'
|
||||||
|
lnx_version = 1
|
||||||
|
|
||||||
|
def lnx_init(self, context):
|
||||||
|
self.add_output('LnxColorSocket', 'Color')
|
||||||
51
leenkx/blender/lnx/logicnode/world/LN_get_world_sky.py
Normal file
51
leenkx/blender/lnx/logicnode/world/LN_get_world_sky.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from lnx.logicnode.lnx_nodes import *
|
||||||
|
|
||||||
|
|
||||||
|
class GetWorldSkyNode(LnxLogicTreeNode):
|
||||||
|
"""Gets the sky properties for the selected sky model."""
|
||||||
|
bl_idname = 'LNGetWorldSkyNode'
|
||||||
|
bl_label = 'Get World Sky'
|
||||||
|
lnx_version = 1
|
||||||
|
legacy = 'Nishita ' if bpy.app.version < (5, 0, 0) else ''
|
||||||
|
|
||||||
|
def update_inputs(self, context):
|
||||||
|
while len(self.outputs) > 0:
|
||||||
|
self.outputs.remove(self.outputs[0])
|
||||||
|
if self.property0 == 'hosek':
|
||||||
|
self.add_output('LnxFloatSocket', 'Turbidity')
|
||||||
|
self.add_output('LnxFloatSocket', 'Ground Albedo')
|
||||||
|
self.add_output('LnxVectorSocket', 'Sun Direction')
|
||||||
|
elif self.property0 == 'single':
|
||||||
|
self.add_output('LnxFloatSocket', 'Air')
|
||||||
|
self.add_output('LnxFloatSocket', 'Dust')
|
||||||
|
self.add_output('LnxFloatSocket', 'Ozone')
|
||||||
|
self.add_output('LnxFloatSocket', 'Altitude')
|
||||||
|
self.add_output('LnxVectorSocket', 'Sun Direction')
|
||||||
|
elif self.property0 == 'multi':
|
||||||
|
self.add_output('LnxFloatSocket', 'Air')
|
||||||
|
self.add_output('LnxFloatSocket', 'Dust')
|
||||||
|
self.add_output('LnxFloatSocket', 'Ozone')
|
||||||
|
self.add_output('LnxFloatSocket', 'Sun Elevation')
|
||||||
|
self.add_output('LnxFloatSocket', 'Sun Rotation')
|
||||||
|
self.add_output('LnxFloatSocket', 'Sun Size')
|
||||||
|
self.add_output('LnxFloatSocket', 'Sun Intensity')
|
||||||
|
self.add_output('LnxFloatSocket', 'Altitude')
|
||||||
|
self.add_output('LnxIntSocket', 'Sun Disc')
|
||||||
|
self.add_output('LnxVectorSocket', 'Sun Direction')
|
||||||
|
|
||||||
|
property0: HaxeEnumProperty(
|
||||||
|
'property0',
|
||||||
|
items=[('hosek', 'Hosek Wilkie', 'Hosek-Wilkie / Preetham sky model'),
|
||||||
|
('single', f'{legacy}Single Scattering', 'Single scattering sky model'),
|
||||||
|
('multi', f'{legacy}Multiple Scattering', 'Multiple scattering sky model')],
|
||||||
|
name='', default='single', update=update_inputs)
|
||||||
|
|
||||||
|
def lnx_init(self, context):
|
||||||
|
self.add_output('LnxFloatSocket', 'Air')
|
||||||
|
self.add_output('LnxFloatSocket', 'Dust')
|
||||||
|
self.add_output('LnxFloatSocket', 'Ozone')
|
||||||
|
self.add_output('LnxFloatSocket', 'Altitude')
|
||||||
|
self.add_output('LnxVectorSocket', 'Sun Direction')
|
||||||
|
|
||||||
|
def draw_buttons(self, context, layout):
|
||||||
|
layout.prop(self, 'property0')
|
||||||
12
leenkx/blender/lnx/logicnode/world/LN_get_world_texture.py
Normal file
12
leenkx/blender/lnx/logicnode/world/LN_get_world_texture.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from lnx.logicnode.lnx_nodes import *
|
||||||
|
|
||||||
|
|
||||||
|
class GetWorldTextureNode(LnxLogicTreeNode):
|
||||||
|
"""Gets the texture properties of the active world (strength, envmap)."""
|
||||||
|
bl_idname = 'LNGetWorldTextureNode'
|
||||||
|
bl_label = 'Get World Texture'
|
||||||
|
lnx_version = 1
|
||||||
|
|
||||||
|
def lnx_init(self, context):
|
||||||
|
self.add_output('LnxFloatSocket', 'Strength')
|
||||||
|
self.add_output('LnxStringSocket', 'Envmap')
|
||||||
@ -1,38 +0,0 @@
|
|||||||
from lnx.logicnode.lnx_nodes import *
|
|
||||||
|
|
||||||
class SetHosekWilkiePropertiesNode(LnxLogicTreeNode):
|
|
||||||
"""Sets the HosekWilkie properties."""
|
|
||||||
bl_idname = 'LNSetHosekWilkiePropertiesNode'
|
|
||||||
bl_label = 'Set HosekWilkie Properties'
|
|
||||||
lnx_version = 1
|
|
||||||
|
|
||||||
def remove_extra_inputs(self, context):
|
|
||||||
while len(self.inputs) > 1:
|
|
||||||
self.inputs.remove(self.inputs[-1])
|
|
||||||
if self.property0 == 'Turbidity/Ground Albedo':
|
|
||||||
self.add_input('LnxFloatSocket', 'Turbidity')
|
|
||||||
self.add_input('LnxFloatSocket', 'Ground Albedo')
|
|
||||||
if self.property0 == 'Turbidity':
|
|
||||||
self.add_input('LnxFloatSocket', 'Turbidity')
|
|
||||||
if self.property0 == 'Ground Albedo':
|
|
||||||
self.add_input('LnxFloatSocket', 'Ground Albedo')
|
|
||||||
if self.property0 == 'Sun Direction':
|
|
||||||
self.add_input('LnxVectorSocket', 'Sun Direction')
|
|
||||||
|
|
||||||
property0: HaxeEnumProperty(
|
|
||||||
'property0',
|
|
||||||
items = [('Turbidity/Ground Albedo', 'Turbidity/Ground Albedo', 'Turbidity, Ground Albedo'),
|
|
||||||
('Turbidity', 'Turbidity', 'Turbidity'),
|
|
||||||
('Ground Albedo', 'Ground Albedo', 'Ground Albedo'),
|
|
||||||
('Sun Direction', 'Sun Direction', 'Sun Direction')],
|
|
||||||
name='', default='Turbidity/Ground Albedo', update=remove_extra_inputs)
|
|
||||||
|
|
||||||
def lnx_init(self, context):
|
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
|
||||||
self.add_input('LnxFloatSocket', 'Turbidity')
|
|
||||||
self.add_input('LnxFloatSocket', 'Ground_Albedo')
|
|
||||||
|
|
||||||
self.add_output('LnxNodeSocketAction', 'Out')
|
|
||||||
|
|
||||||
def draw_buttons(self, context, layout):
|
|
||||||
layout.prop(self, 'property0')
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
from lnx.logicnode.lnx_nodes import *
|
|
||||||
|
|
||||||
class SetNishitaPropertiesNode(LnxLogicTreeNode):
|
|
||||||
"""Sets the Nishita properties"""
|
|
||||||
bl_idname = 'LNSetNishitaPropertiesNode'
|
|
||||||
bl_label = 'Set Nishita Properties'
|
|
||||||
lnx_version = 1
|
|
||||||
|
|
||||||
def remove_extra_inputs(self, context):
|
|
||||||
while len(self.inputs) > 1:
|
|
||||||
self.inputs.remove(self.inputs[-1])
|
|
||||||
if self.property0 == 'Density':
|
|
||||||
self.add_input('LnxFloatSocket', 'Air')
|
|
||||||
self.add_input('LnxFloatSocket', 'Dust')
|
|
||||||
self.add_input('LnxFloatSocket', 'Ozone')
|
|
||||||
if self.property0 == 'Air':
|
|
||||||
self.add_input('LnxFloatSocket', 'Air')
|
|
||||||
if self.property0 == 'Dust':
|
|
||||||
self.add_input('LnxFloatSocket', 'Dust')
|
|
||||||
if self.property0 == 'Ozone':
|
|
||||||
self.add_input('LnxFloatSocket', 'Ozone')
|
|
||||||
if self.property0 == 'Sun Direction':
|
|
||||||
self.add_input('LnxVectorSocket', 'Sun Direction')
|
|
||||||
|
|
||||||
property0: HaxeEnumProperty(
|
|
||||||
'property0',
|
|
||||||
items = [('Density', 'Density', 'Air, Dust, Ozone'),
|
|
||||||
('Air', 'Air', 'Air'),
|
|
||||||
('Dust', 'Dust', 'Dust'),
|
|
||||||
('Ozone', 'Ozone', 'Ozone'),
|
|
||||||
('Sun Direction', 'Sun Direction', 'Sun Direction')],
|
|
||||||
name='', default='Density', update=remove_extra_inputs)
|
|
||||||
|
|
||||||
def lnx_init(self, context):
|
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
|
||||||
self.add_input('LnxFloatSocket', 'Air')
|
|
||||||
self.add_input('LnxFloatSocket', 'Dust')
|
|
||||||
self.add_input('LnxFloatSocket', 'Ozone')
|
|
||||||
|
|
||||||
self.add_output('LnxNodeSocketAction', 'Out')
|
|
||||||
|
|
||||||
def draw_buttons(self, context, layout):
|
|
||||||
layout.prop(self, 'property0')
|
|
||||||
14
leenkx/blender/lnx/logicnode/world/LN_set_world_color.py
Normal file
14
leenkx/blender/lnx/logicnode/world/LN_set_world_color.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from lnx.logicnode.lnx_nodes import *
|
||||||
|
|
||||||
|
|
||||||
|
class SetWorldColorNode(LnxLogicTreeNode):
|
||||||
|
"""Sets the background color of the active world."""
|
||||||
|
bl_idname = 'LNSetWorldColorNode'
|
||||||
|
bl_label = 'Set World Color'
|
||||||
|
lnx_version = 1
|
||||||
|
|
||||||
|
def lnx_init(self, context):
|
||||||
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
|
self.add_input('LnxColorSocket', 'Color', default_value=[0.0, 0.0, 0.0, 1.0])
|
||||||
|
|
||||||
|
self.add_output('LnxNodeSocketAction', 'Out')
|
||||||
54
leenkx/blender/lnx/logicnode/world/LN_set_world_sky.py
Normal file
54
leenkx/blender/lnx/logicnode/world/LN_set_world_sky.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from lnx.logicnode.lnx_nodes import *
|
||||||
|
|
||||||
|
|
||||||
|
class SetWorldSkyNode(LnxLogicTreeNode):
|
||||||
|
"""Sets the sky properties for the selected sky model."""
|
||||||
|
bl_idname = 'LNSetWorldSkyNode'
|
||||||
|
bl_label = 'Set World Sky'
|
||||||
|
lnx_version = 1
|
||||||
|
legacy = 'Nishita ' if bpy.app.version < (5, 0, 0) else ''
|
||||||
|
|
||||||
|
def update_inputs(self, context):
|
||||||
|
while len(self.inputs) > 1:
|
||||||
|
self.inputs.remove(self.inputs[-1])
|
||||||
|
if self.property0 == 'hosek':
|
||||||
|
self.add_input('LnxFloatSocket', 'Turbidity', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Ground Albedo', default_value=0.0)
|
||||||
|
self.add_input('LnxVectorSocket', 'Sun Direction', default_value=[0.0, 0.0, 1.0])
|
||||||
|
elif self.property0 == 'single':
|
||||||
|
self.add_input('LnxFloatSocket', 'Air', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Dust', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Ozone', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Altitude', default_value=1.0)
|
||||||
|
self.add_input('LnxVectorSocket', 'Sun Direction', default_value=[0.0, 0.0, 1.0])
|
||||||
|
elif self.property0 == 'multi':
|
||||||
|
self.add_input('LnxFloatSocket', 'Air', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Dust', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Ozone', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Sun Elevation', default_value=0.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Sun Rotation', default_value=0.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Sun Size', default_value=0.545)
|
||||||
|
self.add_input('LnxFloatSocket', 'Sun Intensity', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Altitude', default_value=1.0)
|
||||||
|
self.add_input('LnxBoolSocket', 'Sun Disc', default_value=True)
|
||||||
|
self.add_input('LnxVectorSocket', 'Sun Direction', default_value=[0.0, 0.0, 1.0])
|
||||||
|
|
||||||
|
property0: HaxeEnumProperty(
|
||||||
|
'property0',
|
||||||
|
items=[('hosek', 'Hosek Wilkie', 'Hosek-Wilkie / Preetham sky model'),
|
||||||
|
('single', f'{legacy}Single Scattering', 'Single scattering sky model'),
|
||||||
|
('multi', f'{legacy}Multiple Scattering', 'Multiple scattering sky model')],
|
||||||
|
name='', default='single', update=update_inputs)
|
||||||
|
|
||||||
|
def lnx_init(self, context):
|
||||||
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
|
self.add_input('LnxFloatSocket', 'Air', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Dust', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Ozone', default_value=1.0)
|
||||||
|
self.add_input('LnxFloatSocket', 'Altitude', default_value=1.0)
|
||||||
|
self.add_input('LnxVectorSocket', 'Sun Direction', default_value=[0.0, 0.0, 1.0])
|
||||||
|
|
||||||
|
self.add_output('LnxNodeSocketAction', 'Out')
|
||||||
|
|
||||||
|
def draw_buttons(self, context, layout):
|
||||||
|
layout.prop(self, 'property0')
|
||||||
15
leenkx/blender/lnx/logicnode/world/LN_set_world_texture.py
Normal file
15
leenkx/blender/lnx/logicnode/world/LN_set_world_texture.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from lnx.logicnode.lnx_nodes import *
|
||||||
|
|
||||||
|
|
||||||
|
class SetWorldTextureNode(LnxLogicTreeNode):
|
||||||
|
"""Sets the texture properties of the active world (strength, envmap)."""
|
||||||
|
bl_idname = 'LNSetWorldTextureNode'
|
||||||
|
bl_label = 'Set World Texture'
|
||||||
|
lnx_version = 1
|
||||||
|
|
||||||
|
def lnx_init(self, context):
|
||||||
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
|
self.add_input('LnxFloatSocket', 'Strength', default_value=1.0)
|
||||||
|
self.add_input('LnxStringSocket', 'Envmap')
|
||||||
|
|
||||||
|
self.add_output('LnxNodeSocketAction', 'Out')
|
||||||
@ -370,7 +370,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
|
|||||||
|
|
||||||
func_cloud_radiance = 'float cloudRadiance(vec3 p, vec3 dir) {\n'
|
func_cloud_radiance = 'float cloudRadiance(vec3 p, vec3 dir) {\n'
|
||||||
if '_EnvSky' in world.world_defs:
|
if '_EnvSky' in world.world_defs:
|
||||||
# Nishita sky
|
# Single scattering sky
|
||||||
if 'vec3 sunDir' in frag.uniforms:
|
if 'vec3 sunDir' in frag.uniforms:
|
||||||
func_cloud_radiance += '\tvec3 sun_dir = sunDir;\n'
|
func_cloud_radiance += '\tvec3 sun_dir = sunDir;\n'
|
||||||
# Hosek
|
# Hosek
|
||||||
@ -413,7 +413,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
|
|||||||
if world.lnx_darken_clouds:
|
if world.lnx_darken_clouds:
|
||||||
func_trace_clouds += '\t// Darken clouds when the sun is low\n'
|
func_trace_clouds += '\t// Darken clouds when the sun is low\n'
|
||||||
if '_EnvSky' in world.world_defs:
|
if '_EnvSky' in world.world_defs:
|
||||||
# Nishita sky
|
# Single scattering sky
|
||||||
if 'vec3 sunDir' in frag.uniforms:
|
if 'vec3 sunDir' in frag.uniforms:
|
||||||
func_trace_clouds += '\tC *= smoothstep(-0.02, 0.25, sunDir.z);\n'
|
func_trace_clouds += '\tC *= smoothstep(-0.02, 0.25, sunDir.z);\n'
|
||||||
# Hosek
|
# Hosek
|
||||||
|
|||||||
@ -328,12 +328,13 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo
|
|||||||
state.world.world_defs += '_EnvSky'
|
state.world.world_defs += '_EnvSky'
|
||||||
|
|
||||||
if node.sky_type == 'PREETHAM' or node.sky_type == 'HOSEK_WILKIE':
|
if node.sky_type == 'PREETHAM' or node.sky_type == 'HOSEK_WILKIE':
|
||||||
if node.sky_type == 'PREETHAM':
|
|
||||||
log.info('Info: Preetham sky model is not supported, using Hosek Wilkie sky model instead')
|
|
||||||
return parse_sky_hosekwilkie(node, state)
|
return parse_sky_hosekwilkie(node, state)
|
||||||
|
|
||||||
elif node.sky_type == 'NISHITA':
|
elif node.sky_type == 'NISHITA' or node.sky_type == 'SINGLE_SCATTERING':
|
||||||
return parse_sky_nishita(node, state)
|
return parse_sky_single_scattering(node, state)
|
||||||
|
|
||||||
|
elif node.sky_type == 'MULTIPLE_SCATTERING':
|
||||||
|
return parse_sky_multiple_scattering(node, state)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
log.error(f'Unsupported sky model: {node.sky_type}!')
|
log.error(f'Unsupported sky model: {node.sky_type}!')
|
||||||
@ -397,18 +398,20 @@ def parse_sky_hosekwilkie(node: bpy.types.ShaderNodeTexSky, state: ParserState)
|
|||||||
return 'Z * hosekWilkie(cos_theta, gamma_val, cos_gamma) * envmapStrength;'
|
return 'Z * hosekWilkie(cos_theta, gamma_val, cos_gamma) * envmapStrength;'
|
||||||
|
|
||||||
|
|
||||||
def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str:
|
def parse_sky_single_scattering(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str:
|
||||||
curshader = state.curshader
|
curshader = state.curshader
|
||||||
curshader.add_include('std/sky.glsl')
|
curshader.add_include('std/sky.glsl')
|
||||||
curshader.add_uniform('vec3 sunDir', link='_sunDirection')
|
curshader.add_uniform('vec3 sunDir', link='_sunDirection')
|
||||||
curshader.add_uniform('sampler2D nishitaLUT', link='_nishitaLUT', included=True,
|
curshader.add_uniform('sampler2D singleScatterLUT', link='_singleScatterLUT', included=True,
|
||||||
tex_addr_u='clamp', tex_addr_v='clamp')
|
tex_addr_u='clamp', tex_addr_v='clamp')
|
||||||
curshader.add_uniform('vec2 nishitaDensity', link='_nishitaDensity', included=True)
|
curshader.add_uniform('vec2 skyDensity', link='_skyDensity', included=True)
|
||||||
|
|
||||||
planet_radius = 6360e3 # Earth radius used in Blender
|
planet_radius = 6360e3 # Earth radius used in Blender
|
||||||
ray_origin_z = planet_radius + node.altitude
|
ray_origin_z = planet_radius + node.altitude
|
||||||
|
|
||||||
state.world.lnx_nishita_density = [node.air_density, node.dust_density, node.ozone_density]
|
dust_density = node.aerosol_density if bpy.app.version >= (5, 0, 0) else node.dust_density
|
||||||
|
state.world.lnx_sky_density = [node.air_density, dust_density, node.ozone_density]
|
||||||
|
state.world.lnx_envtex_sun_direction = [node.sun_direction[0], node.sun_direction[1], node.sun_direction[2]]
|
||||||
|
|
||||||
sun = ''
|
sun = ''
|
||||||
if node.sun_disc:
|
if node.sun_disc:
|
||||||
@ -428,7 +431,29 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v
|
|||||||
size = math.cos(theta)
|
size = math.cos(theta)
|
||||||
sun = f'* sun_disk(pos, sunDir, {size}, {node.sun_intensity})'
|
sun = f'* sun_disk(pos, sunDir, {size}, {node.sun_intensity})'
|
||||||
|
|
||||||
return f'nishita_atmosphere(pos, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}){sun}'
|
return f'single_scatter_atmosphere(pos, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}){sun}'
|
||||||
|
|
||||||
|
|
||||||
|
def parse_sky_multiple_scattering(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str:
|
||||||
|
curshader = state.curshader
|
||||||
|
curshader.add_include('std/sky.glsl')
|
||||||
|
curshader.add_uniform('vec3 sunDir', link='_sunDirection')
|
||||||
|
curshader.add_uniform('sampler2D multiScatterLUT', link='_multiScatterLUT', included=True, tex_addr_u='repeat', tex_addr_v='clamp')
|
||||||
|
curshader.add_uniform('vec4 multiScatterParams', link='_multiScatterParams', included=True)
|
||||||
|
curshader.add_uniform('vec4 multiScatterSunBottom', link='_multiScatterSunBottom', included=True)
|
||||||
|
curshader.add_uniform('vec3 multiScatterSunTop', link='_multiScatterSunTop', included=True)
|
||||||
|
|
||||||
|
dust_density = node.aerosol_density if bpy.app.version >= (5, 0, 0) else node.dust_density
|
||||||
|
state.world.lnx_sky_density = [node.air_density, dust_density, node.ozone_density]
|
||||||
|
state.world.lnx_sky_sun_elevation = node.sun_elevation
|
||||||
|
state.world.lnx_sky_sun_rotation = node.sun_rotation
|
||||||
|
state.world.lnx_sky_sun_size = node.sun_size
|
||||||
|
state.world.lnx_sky_sun_intensity = node.sun_intensity if node.sun_disc else 0.0
|
||||||
|
state.world.lnx_sky_altitude = node.altitude
|
||||||
|
state.world.lnx_sky_sun_disc = 1 if node.sun_disc else 0
|
||||||
|
state.world.lnx_envtex_sun_direction = [node.sun_direction[0], node.sun_direction[1], node.sun_direction[2]]
|
||||||
|
|
||||||
|
return f'multi_scatter_atmosphere(pos)'
|
||||||
|
|
||||||
|
|
||||||
def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
|
||||||
|
|||||||
@ -464,7 +464,13 @@ def init_properties():
|
|||||||
bpy.types.World.lnx_envtex_sun_direction = FloatVectorProperty(name="Sun Direction", size=3, default=[0,0,0])
|
bpy.types.World.lnx_envtex_sun_direction = FloatVectorProperty(name="Sun Direction", size=3, default=[0,0,0])
|
||||||
bpy.types.World.lnx_envtex_turbidity = FloatProperty(name="Turbidity", default=1.0)
|
bpy.types.World.lnx_envtex_turbidity = FloatProperty(name="Turbidity", default=1.0)
|
||||||
bpy.types.World.lnx_envtex_ground_albedo = FloatProperty(name="Ground Albedo", default=0.0)
|
bpy.types.World.lnx_envtex_ground_albedo = FloatProperty(name="Ground Albedo", default=0.0)
|
||||||
bpy.types.World.lnx_nishita_density = FloatVectorProperty(name="Nishita Density", size=3, default=[1, 1, 1])
|
bpy.types.World.lnx_sky_density = FloatVectorProperty(name="Sky Density", size=3, default=[1, 1, 1])
|
||||||
|
bpy.types.World.lnx_sky_sun_elevation = FloatProperty(name="Sky Sun Elevation", default=0.0)
|
||||||
|
bpy.types.World.lnx_sky_sun_rotation = FloatProperty(name="Sky Sun Rotation", default=0.0)
|
||||||
|
bpy.types.World.lnx_sky_sun_size = FloatProperty(name="Sky Sun Size", default=0.545)
|
||||||
|
bpy.types.World.lnx_sky_sun_intensity = FloatProperty(name="Sky Sun Intensity", default=1.0)
|
||||||
|
bpy.types.World.lnx_sky_altitude = FloatProperty(name="Sky Altitude", default=0.0)
|
||||||
|
bpy.types.World.lnx_sky_sun_disc = IntProperty(name="Sky Sun Disc", default=1)
|
||||||
bpy.types.Material.lnx_cast_shadow = BoolProperty(name="Cast Shadow", default=True)
|
bpy.types.Material.lnx_cast_shadow = BoolProperty(name="Cast Shadow", default=True)
|
||||||
bpy.types.Material.lnx_receive_shadow = BoolProperty(name="Receive Shadow", description="Requires forward render path", default=True)
|
bpy.types.Material.lnx_receive_shadow = BoolProperty(name="Receive Shadow", description="Requires forward render path", default=True)
|
||||||
bpy.types.Material.lnx_depth_write = BoolProperty(name="Write Depth", description="Allow this material to write to the depth buffer", default=True)
|
bpy.types.Material.lnx_depth_write = BoolProperty(name="Write Depth", description="Allow this material to write to the depth buffer", default=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user