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

View File

@ -3817,7 +3817,13 @@ class LeenkxExporter:
out_world['sun_direction'] = list(world.lnx_envtex_sun_direction)
out_world['turbidity'] = world.lnx_envtex_turbidity
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')

View File

@ -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')

View File

@ -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')

View 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')

View 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')

View 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')

View File

@ -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')

View File

@ -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')

View 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')

View 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')

View 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')

View File

@ -370,7 +370,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
func_cloud_radiance = 'float cloudRadiance(vec3 p, vec3 dir) {\n'
if '_EnvSky' in world.world_defs:
# Nishita sky
# Single scattering sky
if 'vec3 sunDir' in frag.uniforms:
func_cloud_radiance += '\tvec3 sun_dir = sunDir;\n'
# Hosek
@ -413,7 +413,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
if world.lnx_darken_clouds:
func_trace_clouds += '\t// Darken clouds when the sun is low\n'
if '_EnvSky' in world.world_defs:
# Nishita sky
# Single scattering sky
if 'vec3 sunDir' in frag.uniforms:
func_trace_clouds += '\tC *= smoothstep(-0.02, 0.25, sunDir.z);\n'
# Hosek

View File

@ -328,12 +328,13 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo
state.world.world_defs += '_EnvSky'
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)
elif node.sky_type == 'NISHITA':
return parse_sky_nishita(node, state)
elif node.sky_type == 'NISHITA' or node.sky_type == 'SINGLE_SCATTERING':
return parse_sky_single_scattering(node, state)
elif node.sky_type == 'MULTIPLE_SCATTERING':
return parse_sky_multiple_scattering(node, state)
else:
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;'
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.add_include('std/sky.glsl')
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')
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
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 = ''
if node.sun_disc:
@ -428,7 +431,29 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v
size = math.cos(theta)
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:

View File

@ -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_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_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_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)