Begin with Nishita LUT implementation for better performance
This commit is contained in:
parent
742b9ce1e1
commit
5f55b00710
|
@ -19,6 +19,9 @@
|
||||||
#ifndef _SKY_GLSL_
|
#ifndef _SKY_GLSL_
|
||||||
#define _SKY_GLSL_
|
#define _SKY_GLSL_
|
||||||
|
|
||||||
|
// OpenGl ES doesn't support 1D textures so we use a 1 px height sampler2D here...
|
||||||
|
uniform sampler2D nishitaLUT;
|
||||||
|
|
||||||
#define PI 3.141592
|
#define PI 3.141592
|
||||||
|
|
||||||
#define nishita_iSteps 16
|
#define nishita_iSteps 16
|
||||||
|
@ -46,6 +49,8 @@
|
||||||
// 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)
|
||||||
|
|
||||||
|
#define heightToLUT(h) (textureLod(nishitaLUT, vec2(clamp(h * (1 / 60000.0), 0.0, 1.0), 0.0), 0.0).xyz * 10.0)
|
||||||
|
|
||||||
/* Approximates the density of ozone for a given sample height. Values taken from Cycles code. */
|
/* Approximates the density of ozone for a given sample height. Values taken from Cycles code. */
|
||||||
float nishita_density_ozone(const float height) {
|
float nishita_density_ozone(const float height) {
|
||||||
return (height < 10000.0 || height >= 40000.0) ? 0.0 : (height < 25000.0 ? (height - 10000.0) / 15000.0 : -((height - 40000.0) / 15000.0));
|
return (height < 10000.0 || height >= 40000.0) ? 0.0 : (height < 25000.0 ? (height - 10000.0) / 15000.0 : -((height - 40000.0) / 15000.0));
|
||||||
|
@ -108,8 +113,9 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa
|
||||||
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) * density.x * iStepSize;
|
vec3 iLookup = heightToLUT(iHeight);
|
||||||
float odStepMie = exp(-iHeight / nishita_mie_scale) * density.y * iStepSize;
|
float odStepRlh = iLookup.x * iStepSize;
|
||||||
|
float odStepMie = iLookup.y * iStepSize;
|
||||||
|
|
||||||
// Accumulate optical depth.
|
// Accumulate optical depth.
|
||||||
iOdRlh += odStepRlh;
|
iOdRlh += odStepRlh;
|
||||||
|
@ -134,11 +140,8 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa
|
||||||
float jHeight = length(jPos) - rPlanet;
|
float jHeight = length(jPos) - rPlanet;
|
||||||
|
|
||||||
// Accumulate the optical depth.
|
// Accumulate the optical depth.
|
||||||
jODepth += vec3(
|
vec3 jLookup = heightToLUT(jHeight);
|
||||||
exp(-jHeight / nishita_rayleigh_scale) * density.x * jStepSize,
|
jODepth += jLookup * jStepSize;
|
||||||
exp(-jHeight / nishita_mie_scale) * density.y * jStepSize,
|
|
||||||
nishita_density_ozone(jHeight) * density.z * jStepSize
|
|
||||||
);
|
|
||||||
|
|
||||||
// Increment the secondary ray time.
|
// Increment the secondary ray time.
|
||||||
jTime += jStepSize;
|
jTime += jStepSize;
|
||||||
|
|
|
@ -72,4 +72,13 @@ class Helper {
|
||||||
if (value <= leftMin) return rightMin;
|
if (value <= leftMin) return rightMin;
|
||||||
return map(value, leftMin, leftMax, rightMin, rightMax);
|
return map(value, leftMin, leftMax, rightMin, rightMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return the sign of the given value represented as `1.0` (positive value)
|
||||||
|
or `-1.0` (negative value). The sign of `0` is `0`.
|
||||||
|
**/
|
||||||
|
public static inline function sign(value: Float): Float {
|
||||||
|
if (value == 0) return 0;
|
||||||
|
return (value < 0) ? -1.0 : 1.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,12 @@ class Uniforms {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image {
|
public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image {
|
||||||
|
if (link == "_nishitaLUT") {
|
||||||
|
if (armory.renderpath.Nishita.data == null) armory.renderpath.Nishita.recompute(Scene.active.world);
|
||||||
|
return armory.renderpath.Nishita.data.optDepthLUT;
|
||||||
|
}
|
||||||
#if arm_ltc
|
#if arm_ltc
|
||||||
if (link == "_ltcMat") {
|
else if (link == "_ltcMat") {
|
||||||
if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC();
|
if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC();
|
||||||
return armory.data.ConstData.ltcMatTex;
|
return armory.data.ConstData.ltcMatTex;
|
||||||
}
|
}
|
||||||
|
|
80
Sources/armory/renderpath/Nishita.hx
Normal file
80
Sources/armory/renderpath/Nishita.hx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package armory.renderpath;
|
||||||
|
|
||||||
|
import kha.FastFloat;
|
||||||
|
import kha.graphics4.TextureFormat;
|
||||||
|
import kha.graphics4.Usage;
|
||||||
|
|
||||||
|
import iron.data.WorldData;
|
||||||
|
|
||||||
|
import armory.math.Helper;
|
||||||
|
|
||||||
|
class Nishita {
|
||||||
|
|
||||||
|
public static var data: NishitaData = null;
|
||||||
|
|
||||||
|
public static function recompute(world: WorldData) {
|
||||||
|
if (world == null || world.raw.sun_direction == null) return;
|
||||||
|
if (data == null) data = new NishitaData();
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
data.recompute(1.0, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NishitaData {
|
||||||
|
static inline var LUT_WIDTH = 16;
|
||||||
|
|
||||||
|
/** Maximum ray height as defined by Cycles **/
|
||||||
|
static inline var MAX_HEIGHT = 60000;
|
||||||
|
|
||||||
|
static inline var RAYLEIGH_SCALE = 8e3;
|
||||||
|
static inline var MIE_SCALE = 1.2e3;
|
||||||
|
|
||||||
|
public var optDepthLUT: kha.Image;
|
||||||
|
|
||||||
|
public function new() {}
|
||||||
|
|
||||||
|
function getOzoneDensity(height: FastFloat): FastFloat {
|
||||||
|
if (height < 10000.0 || height >= 40000.0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (height < 25000.0) {
|
||||||
|
return (height - 10000.0) / 15000.0;
|
||||||
|
}
|
||||||
|
return -((height - 40000.0) / 15000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The RGBA texture layout looks as follows:
|
||||||
|
R = Rayleigh optical depth at height \in [0, 60000]
|
||||||
|
G = Mie optical depth at height \in [0, 60000]
|
||||||
|
B = Ozone optical depth at height \in [0, 60000]
|
||||||
|
A = Unused
|
||||||
|
**/
|
||||||
|
public function recompute(densityFacAir: FastFloat, densityFacDust: FastFloat, densityFacOzone: FastFloat) {
|
||||||
|
optDepthLUT = kha.Image.create(LUT_WIDTH, 1, TextureFormat.RGBA32, Usage.StaticUsage);
|
||||||
|
|
||||||
|
var textureData = optDepthLUT.lock();
|
||||||
|
for (i in 0...LUT_WIDTH) {
|
||||||
|
// Get the height for each LUT pixel i (in [-1, 1] range)
|
||||||
|
var height = (i / LUT_WIDTH) * 2 - 1;
|
||||||
|
|
||||||
|
// Use quadratic height for better horizon precision
|
||||||
|
// See https://sebh.github.io/publications/egsr2020.pdf (5.3)
|
||||||
|
height = 0.5 + 0.5 * Helper.sign(height) * Math.sqrt(Math.abs(height));
|
||||||
|
height *= MAX_HEIGHT; // Denormalize
|
||||||
|
|
||||||
|
// Make sure we use 32 bit floats
|
||||||
|
var optDepthRayleigh: FastFloat = Math.exp(-height / RAYLEIGH_SCALE) * densityFacAir;
|
||||||
|
var optDepthMie: FastFloat = Math.exp(-height / MIE_SCALE) * densityFacDust;
|
||||||
|
var optDepthOzone: FastFloat = getOzoneDensity(height) * densityFacOzone;
|
||||||
|
|
||||||
|
// 10 is the maximum density, so we divide by it to be able to use normalized values
|
||||||
|
textureData.set(i * 4 + 0, Std.int(optDepthRayleigh * 255 / 10));
|
||||||
|
textureData.set(i * 4 + 1, Std.int(optDepthMie * 255 / 10));
|
||||||
|
textureData.set(i * 4 + 2, Std.int(optDepthOzone * 255 / 10));
|
||||||
|
textureData.set(i * 4 + 3, 255); // Unused
|
||||||
|
}
|
||||||
|
optDepthLUT.unlock();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue