From 22d3530863e659e81f4df74494d7a9c1b9a2b816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 21 Jan 2021 16:12:32 +0100 Subject: [PATCH] Blender 2.9: Add Nishita sky model implementation --- Shaders/std/sky.glsl | 118 ++++++++++++++++++ .../material/cycles_nodes/nodes_texture.py | 27 +++- 2 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 Shaders/std/sky.glsl diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl new file mode 100644 index 00000000..21c64ea0 --- /dev/null +++ b/Shaders/std/sky.glsl @@ -0,0 +1,118 @@ +/* Various sky functions + * + * Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License) + * Changes to the original implementation: + * - r and pSun parameters of nishita_atmosphere() are already normalized + */ + +#ifndef _SKY_GLSL_ +#define _SKY_GLSL_ + +#define PI 3.141592 + +#define nishita_iSteps 16 +#define nishita_jSteps 8 + +/* ray-sphere intersection that assumes + * the sphere is centered at the origin. + * No intersection when result.x > result.y */ +vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) { + float a = dot(rd, rd); + float b = 2.0 * dot(rd, r0); + float c = dot(r0, r0) - (sr * sr); + float d = (b*b) - 4.0*a*c; + + if (d < 0.0) return vec2(1e5,-1e5); + return vec2( + (-b - sqrt(d))/(2.0*a), + (-b + sqrt(d))/(2.0*a) + ); +} + +vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float iSun, const float rPlanet, const float rAtmos, const vec3 kRlh, const float kMie, const float shRlh, const float shMie, const float g) { + // r and pSun must be already normalized! + + // Calculate the step size of the primary ray. + vec2 p = nishita_rsi(r0, r, rAtmos); + if (p.x > p.y) return vec3(0,0,0); + p.y = min(p.y, nishita_rsi(r0, r, rPlanet).x); + float iStepSize = (p.y - p.x) / float(nishita_iSteps); + + // Initialize the primary ray time. + float iTime = 0.0; + + // Initialize accumulators for Rayleigh and Mie scattering. + vec3 totalRlh = vec3(0,0,0); + vec3 totalMie = vec3(0,0,0); + + // Initialize optical depth accumulators for the primary ray. + float iOdRlh = 0.0; + float iOdMie = 0.0; + + // Calculate the Rayleigh and Mie phases. + float mu = dot(r, pSun); + float mumu = mu * mu; + float gg = g * g; + float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu); + float pMie = 3.0 / (8.0 * PI) * ((1.0 - gg) * (mumu + 1.0)) / (pow(1.0 + gg - 2.0 * mu * g, 1.5) * (2.0 + gg)); + + // Sample the primary ray. + for (int i = 0; i < nishita_iSteps; i++) { + + // Calculate the primary ray sample position. + vec3 iPos = r0 + r * (iTime + iStepSize * 0.5); + + // Calculate the height of the sample. + float iHeight = length(iPos) - rPlanet; + + // Calculate the optical depth of the Rayleigh and Mie scattering for this step. + float odStepRlh = exp(-iHeight / shRlh) * iStepSize; + float odStepMie = exp(-iHeight / shMie) * iStepSize; + + // Accumulate optical depth. + iOdRlh += odStepRlh; + iOdMie += odStepMie; + + // Calculate the step size of the secondary ray. + float jStepSize = nishita_rsi(iPos, pSun, rAtmos).y / float(nishita_jSteps); + + // Initialize the secondary ray time. + float jTime = 0.0; + + // Initialize optical depth accumulators for the secondary ray. + float jOdRlh = 0.0; + float jOdMie = 0.0; + + // Sample the secondary ray. + for (int j = 0; j < nishita_jSteps; j++) { + + // Calculate the secondary ray sample position. + vec3 jPos = iPos + pSun * (jTime + jStepSize * 0.5); + + // Calculate the height of the sample. + float jHeight = length(jPos) - rPlanet; + + // Accumulate the optical depth. + jOdRlh += exp(-jHeight / shRlh) * jStepSize; + jOdMie += exp(-jHeight / shMie) * jStepSize; + + // Increment the secondary ray time. + jTime += jStepSize; + } + + // Calculate attenuation. + vec3 attn = exp(-(kMie * (iOdMie + jOdMie) + kRlh * (iOdRlh + jOdRlh))); + + // Accumulate scattering. + totalRlh += odStepRlh * attn; + totalMie += odStepMie * attn; + + // Increment the primary ray time. + iTime += iStepSize; + } + + // Calculate and return the final color. + return iSun * (pRlh * kRlh * totalRlh + pMie * kMie * totalMie); +} + +#endif diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index b9f57daf..f3f9c070 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -293,6 +293,21 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo # Pass through return c.to_vec3([0.0, 0.0, 0.0]) + if node.sky_type == 'PREETHAM' or node.sky_type == 'HOSEK_WILKIE': + if node.sky_type == 'PREETHAM': + log.warn('Preetham sky model is not supported, using Hosek Wilkie sky model instead') + + return parse_sky_hosekwilkie(node, state) + + elif node.sky_type == 'NISHITA': + return parse_sky_nishita(node, state) + + else: + log.error(f'Unsupported sky model: {node.sky_type}!') + return c.to_vec3([0.0, 0.0, 0.0]) + + +def parse_sky_hosekwilkie(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str: world = state.world curshader = state.curshader @@ -312,10 +327,10 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo curshader.add_uniform('vec3 I', link="_hosekI") curshader.add_uniform('vec3 Z', link="_hosekZ") curshader.add_uniform('vec3 hosekSunDirection', link="_hosekSunDirection") - curshader.add_function('''vec3 hosekWilkie(float cos_theta, float gamma, float cos_gamma) { + curshader.add_function("""vec3 hosekWilkie(float cos_theta, float gamma, float cos_gamma) { \tvec3 chi = (1 + cos_gamma * cos_gamma) / pow(1 + H * H - 2 * cos_gamma * H, vec3(1.5)); \treturn (1 + A * exp(B / (cos_theta + 0.01))) * (C + D * exp(E * gamma) + F * (cos_gamma * cos_gamma) + G * chi + I * sqrt(cos_theta)); -}''') +}""") world.arm_envtex_sun_direction = [node.sun_direction[0], node.sun_direction[1], node.sun_direction[2]] world.arm_envtex_turbidity = node.turbidity @@ -353,6 +368,14 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo return 'Z * hosekWilkie(cos_theta, gamma_val, cos_gamma) * envmapStrength;' +def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str: + curshader = state.curshader + curshader.add_include('std/sky.glsl') + curshader.add_uniform('vec3 sunDir', link='_sunDirection') + + return 'nishita_atmosphere(n, vec3(0,0,6372e3), sunDir, 22.0, 6371e3, 6471e3, vec3(5.5e-6,13.0e-6,22.4e-6), 21e-6, 8e3, 1.2e3, 0.758)' + + def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: if state.context == ParserContext.OBJECT: log.warn('Environment Texture node is not supported for object node trees, using default value')