Object Occlusion Culling
This commit is contained in:
@ -1133,6 +1133,11 @@ class LeenkxExporter:
|
||||
}
|
||||
out_object['properties'].append(out_property)
|
||||
|
||||
if bobject.lnx_is_occluder:
|
||||
if 'properties' not in out_object:
|
||||
out_object['properties'] = []
|
||||
out_object['properties'].append({'name': 'occluder', 'value': True})
|
||||
|
||||
|
||||
# Export the object reference and material references
|
||||
objref = bobject.data
|
||||
@ -2125,7 +2130,8 @@ class LeenkxExporter:
|
||||
fp = self.get_meshes_file_path('mesh_' + oid, compressed=LeenkxExporter.compress_enabled)
|
||||
assets.add(fp)
|
||||
# No export necessary
|
||||
if bobject.data.lnx_cached and os.path.exists(fp) or fp in self.build_cache.exported_mesh_files:
|
||||
fp_json = fp.split('.lnx')[0] + '.json'
|
||||
if bobject.data.lnx_cached and (os.path.exists(fp) or os.path.exists(fp_json)) or fp in self.build_cache.exported_mesh_files:
|
||||
return
|
||||
|
||||
# Mesh users have different modifier stack
|
||||
@ -2202,6 +2208,22 @@ class LeenkxExporter:
|
||||
if armature:
|
||||
self.export_skin(bobject, armature, export_mesh, out_mesh)
|
||||
|
||||
rpdat = lnx.utils.get_rp()
|
||||
is_occluder = bobject.lnx_is_occluder
|
||||
if not is_occluder:
|
||||
for prop in bobject.lnx_propertylist:
|
||||
if prop.name_prop == 'occluder' and getattr(prop, prop.type_prop + '_prop') == True:
|
||||
is_occluder = True
|
||||
break
|
||||
use_global = False
|
||||
if not is_occluder and rpdat is not None and rpdat.lnx_occlusion_all_objects:
|
||||
is_occluder = rpdat.lnx_occlusion_min_size <= 0.0 or \
|
||||
max(bobject.dimensions) >= rpdat.lnx_occlusion_min_size
|
||||
use_global = is_occluder
|
||||
if is_occluder and rpdat is not None and rpdat.lnx_occlusion_culling:
|
||||
self.export_occluder_proxy(export_mesh, out_mesh, bobject, use_global)
|
||||
|
||||
|
||||
# Restore the morph state after mesh export
|
||||
if shape_keys:
|
||||
bobject.active_shape_key_index = active_shape_key_index
|
||||
@ -2311,6 +2333,139 @@ class LeenkxExporter:
|
||||
|
||||
|
||||
|
||||
def export_occluder_proxy(self, export_mesh: bpy.types.Mesh, out_mesh, bobject, use_global=False):
|
||||
rpdat = lnx.utils.get_rp()
|
||||
mode = bobject.lnx_occluder_mode
|
||||
shrink = bobject.lnx_occluder_shrink
|
||||
if use_global or mode == 'default':
|
||||
mode = rpdat.lnx_occlusion_proxy_mode
|
||||
if use_global or shrink < 0.0:
|
||||
shrink = rpdat.lnx_occlusion_proxy_shrink
|
||||
|
||||
if mode == 'box':
|
||||
proxy_positions, proxy_indices = self._box_proxy(export_mesh)
|
||||
self._apply_inward_offset(proxy_positions, shrink)
|
||||
elif mode == 'convex':
|
||||
proxy_positions, proxy_indices = self._convex_proxy(export_mesh)
|
||||
self._apply_inward_offset(proxy_positions, shrink)
|
||||
else:
|
||||
proxy_positions, proxy_indices = self._apply_decimate_to_mesh(
|
||||
export_mesh, rpdat, shrink)
|
||||
|
||||
if len(proxy_indices) == 0:
|
||||
return
|
||||
|
||||
out_mesh['occluder_proxy'] = {
|
||||
'positions': proxy_positions,
|
||||
'indices': proxy_indices
|
||||
}
|
||||
|
||||
def _box_proxy(self, export_mesh: bpy.types.Mesh):
|
||||
"""12-triangle axis-aligned bounding box proxy in local space."""
|
||||
if len(export_mesh.vertices) == 0:
|
||||
return [], []
|
||||
xs = [v.co.x for v in export_mesh.vertices]
|
||||
ys = [v.co.y for v in export_mesh.vertices]
|
||||
zs = [v.co.z for v in export_mesh.vertices]
|
||||
lo = (min(xs), min(ys), min(zs))
|
||||
hi = (max(xs), max(ys), max(zs))
|
||||
proxy_positions = [
|
||||
lo[0], lo[1], lo[2], hi[0], lo[1], lo[2],
|
||||
hi[0], hi[1], lo[2], lo[0], hi[1], lo[2],
|
||||
lo[0], lo[1], hi[2], hi[0], lo[1], hi[2],
|
||||
hi[0], hi[1], hi[2], lo[0], hi[1], hi[2]]
|
||||
proxy_indices = [
|
||||
0, 2, 1, 0, 3, 2,
|
||||
4, 5, 6, 4, 6, 7,
|
||||
0, 1, 5, 0, 5, 4,
|
||||
2, 3, 7, 2, 7, 6,
|
||||
1, 2, 6, 1, 6, 5,
|
||||
0, 4, 7, 0, 7, 3]
|
||||
return proxy_positions, proxy_indices
|
||||
|
||||
def _convex_proxy(self, export_mesh: bpy.types.Mesh):
|
||||
proxy_positions = []
|
||||
proxy_indices = []
|
||||
bm = bmesh.new()
|
||||
try:
|
||||
bm.from_mesh(export_mesh)
|
||||
bmesh.ops.convex_hull(bm, input=bm.verts)
|
||||
remap = {}
|
||||
for f in bm.faces:
|
||||
fv = f.verts
|
||||
for i in range(1, len(fv) - 1):
|
||||
for v in (fv[0], fv[i], fv[i + 1]):
|
||||
idx = remap.get(v.index)
|
||||
if idx is None:
|
||||
idx = len(remap)
|
||||
remap[v.index] = idx
|
||||
co = v.co
|
||||
proxy_positions.append(co.x)
|
||||
proxy_positions.append(co.y)
|
||||
proxy_positions.append(co.z)
|
||||
proxy_indices.append(idx)
|
||||
finally:
|
||||
bm.free()
|
||||
return proxy_positions, proxy_indices
|
||||
|
||||
def _apply_inward_offset(self, positions, shrink_pct):
|
||||
n = len(positions) // 3
|
||||
if n == 0 or shrink_pct <= 0.0:
|
||||
return
|
||||
factor = 1.0 - shrink_pct * 0.01
|
||||
cx = sum(positions[0::3]) / n
|
||||
cy = sum(positions[1::3]) / n
|
||||
cz = sum(positions[2::3]) / n
|
||||
for i in range(n):
|
||||
positions[i * 3] = cx + (positions[i * 3] - cx) * factor
|
||||
positions[i * 3 + 1] = cy + (positions[i * 3 + 1] - cy) * factor
|
||||
positions[i * 3 + 2] = cz + (positions[i * 3 + 2] - cz) * factor
|
||||
|
||||
def _apply_decimate_to_mesh(self, export_mesh: bpy.types.Mesh, rpdat, shrink):
|
||||
temp_mesh = export_mesh.copy()
|
||||
temp_obj = bpy.data.objects.new('lnx_decimate_temp', temp_mesh)
|
||||
try:
|
||||
self.scene.collection.objects.link(temp_obj)
|
||||
|
||||
mod = temp_obj.modifiers.new(name='lnx_decimate', type='DECIMATE')
|
||||
mod.decimate_type = rpdat.lnx_occlusion_decimate_mode
|
||||
if rpdat.lnx_occlusion_decimate_mode == 'COLLAPSE':
|
||||
mod.ratio = rpdat.lnx_occlusion_decimate_ratio
|
||||
elif rpdat.lnx_occlusion_decimate_mode == 'DISSOLVE':
|
||||
mod.angle_limit = math.radians(
|
||||
rpdat.lnx_occlusion_decimate_ratio * 180)
|
||||
elif rpdat.lnx_occlusion_decimate_mode == 'UNSUBDIV':
|
||||
mod.iterations = max(1, int(rpdat.lnx_occlusion_decimate_ratio * 10))
|
||||
|
||||
self.depsgraph.update()
|
||||
eval_obj = temp_obj.evaluated_get(self.depsgraph)
|
||||
eval_mesh = eval_obj.to_mesh(
|
||||
preserve_all_data_layers=False, depsgraph=self.depsgraph)
|
||||
try:
|
||||
eval_mesh.calc_loop_triangles()
|
||||
|
||||
proxy_positions = []
|
||||
for v in eval_mesh.vertices:
|
||||
co = v.co
|
||||
proxy_positions.append(co.x)
|
||||
proxy_positions.append(co.y)
|
||||
proxy_positions.append(co.z)
|
||||
|
||||
proxy_indices = []
|
||||
for tri in eval_mesh.loop_triangles:
|
||||
proxy_indices.append(tri.vertices[0])
|
||||
proxy_indices.append(tri.vertices[1])
|
||||
proxy_indices.append(tri.vertices[2])
|
||||
|
||||
self._apply_inward_offset(proxy_positions, shrink)
|
||||
finally:
|
||||
eval_obj.to_mesh_clear()
|
||||
finally:
|
||||
bpy.data.objects.remove(temp_obj, do_unlink=True)
|
||||
bpy.data.meshes.remove(temp_mesh, do_unlink=True)
|
||||
|
||||
return proxy_positions, proxy_indices
|
||||
|
||||
def export_light(self, object_ref):
|
||||
"""Exports a single light object."""
|
||||
rpdat = lnx.utils.get_rp()
|
||||
@ -2496,23 +2651,57 @@ class LeenkxExporter:
|
||||
b = proj[1][1]
|
||||
c = proj[2][2]
|
||||
d = proj[2][3]
|
||||
k = (c - 1.0) / (c + 1.0)
|
||||
o['fov'] = 2.0 * math.atan(1.0 / b)
|
||||
if abs(a) > 1e-6:
|
||||
o['aspect'] = b / a
|
||||
if with_planes:
|
||||
o['near_plane'] = (d * (1.0 - k)) / (2.0 * k)
|
||||
o['far_plane'] = k * o['near_plane']
|
||||
denom = c + 1.0
|
||||
k = (c - 1.0) / denom if abs(denom) >= 1e-6 else 0.0
|
||||
if abs(k) < 1e-6:
|
||||
o['near_plane'] = 0.1
|
||||
o['far_plane'] = 1000.0
|
||||
else:
|
||||
near = (d * (1.0 - k)) / (2.0 * k)
|
||||
far = k * near
|
||||
o['near_plane'] = max(0.001, min(near, 100000.0))
|
||||
o['far_plane'] = max(0.001, min(far, 100000.0))
|
||||
|
||||
@staticmethod
|
||||
def extract_ortho(o, proj):
|
||||
a = proj[0][0]
|
||||
b = proj[1][1]
|
||||
# left, right, bottom, top
|
||||
o['ortho'] = [-(1 + proj[3][0]) / proj[0][0], \
|
||||
(1 - proj[3][0]) / proj[0][0], \
|
||||
-(1 + proj[3][1]) / proj[1][1], \
|
||||
(1 - proj[3][1]) / proj[1][1]]
|
||||
o['ortho'] = [-(1 + proj[3][0]) / a, \
|
||||
(1 - proj[3][0]) / a, \
|
||||
-(1 + proj[3][1]) / b, \
|
||||
(1 - proj[3][1]) / b]
|
||||
o['near_plane'] = (1 + proj[3][2]) / proj[2][2]
|
||||
o['far_plane'] = -(1 - proj[3][2]) / proj[2][2]
|
||||
o['near_plane'] *= 2
|
||||
o['far_plane'] *= 2
|
||||
if abs(a) > 1e-6:
|
||||
o['aspect'] = b / a
|
||||
|
||||
@staticmethod
|
||||
def compute_effective_par(rpdat, render):
|
||||
if rpdat is None:
|
||||
return render.pixel_aspect_x, render.pixel_aspect_y
|
||||
mode = rpdat.lnx_aspect_mode
|
||||
if mode == 'blender':
|
||||
return render.pixel_aspect_x, render.pixel_aspect_y
|
||||
elif mode == 'square':
|
||||
return 1.0, 1.0
|
||||
elif mode == 'ntsc_43':
|
||||
return 0.889, 1.0
|
||||
elif mode == 'pal_43':
|
||||
return 1.067, 1.0
|
||||
elif mode == 'ntsc_169':
|
||||
return 1.212, 1.0
|
||||
elif mode == 'pal_169':
|
||||
return 1.459, 1.0
|
||||
elif mode == 'custom':
|
||||
return rpdat.lnx_aspect_custom, 1.0
|
||||
return render.pixel_aspect_x, render.pixel_aspect_y
|
||||
|
||||
def export_camera(self, objectRef):
|
||||
o = {}
|
||||
@ -2521,12 +2710,16 @@ class LeenkxExporter:
|
||||
|
||||
camera = objectRef[1]["objectTable"][0]
|
||||
render = self.scene.render
|
||||
rpdat = lnx.utils.get_rp()
|
||||
|
||||
par_x, par_y = self.compute_effective_par(rpdat, render)
|
||||
|
||||
proj = camera.calc_matrix_camera(
|
||||
self.depsgraph,
|
||||
x=render.resolution_x,
|
||||
y=render.resolution_y,
|
||||
scale_x=render.pixel_aspect_x,
|
||||
scale_y=render.pixel_aspect_y)
|
||||
scale_x=par_x,
|
||||
scale_y=par_y)
|
||||
if objref.type == 'PERSP':
|
||||
self.extract_projection(o, proj)
|
||||
else:
|
||||
@ -2594,7 +2787,7 @@ class LeenkxExporter:
|
||||
self.output['material_datas'].append(o)
|
||||
bpy.data.materials.remove(mat)
|
||||
rpdat = lnx.utils.get_rp()
|
||||
if not rpdat.lnx_culling:
|
||||
if not rpdat.lnx_backface_culling:
|
||||
o['override_context'] = {}
|
||||
o['override_context']['cull_mode'] = 'none'
|
||||
|
||||
@ -2673,7 +2866,7 @@ class LeenkxExporter:
|
||||
o['skip_context'] = material.lnx_skip_context
|
||||
|
||||
rpdat = lnx.utils.get_rp()
|
||||
if material.lnx_two_sided or not rpdat.lnx_culling:
|
||||
if material.lnx_two_sided or not rpdat.lnx_backface_culling:
|
||||
o['override_context'] = {}
|
||||
o['override_context']['cull_mode'] = 'none'
|
||||
elif material.lnx_cull_mode != 'clockwise':
|
||||
@ -3190,6 +3383,12 @@ class LeenkxExporter:
|
||||
'clear_color': self.get_camera_clear_color()
|
||||
}
|
||||
|
||||
render = self.scene.render
|
||||
rpdat = lnx.utils.get_rp()
|
||||
par_x, par_y = self.compute_effective_par(rpdat, render)
|
||||
res_aspect = render.resolution_x / render.resolution_y if render.resolution_y > 0 else 1.0
|
||||
effective_aspect = res_aspect * par_x / par_y
|
||||
|
||||
# Set viewport camera projection
|
||||
proj, is_persp = self.get_viewport_projection_matrix()
|
||||
if proj is not None:
|
||||
@ -3197,6 +3396,9 @@ class LeenkxExporter:
|
||||
self.extract_projection(out_camera, proj, with_planes=False)
|
||||
else:
|
||||
self.extract_ortho(out_camera, proj)
|
||||
|
||||
out_camera['aspect'] = effective_aspect
|
||||
|
||||
self.output['camera_datas'].append(out_camera)
|
||||
|
||||
out_object = {
|
||||
|
||||
@ -390,12 +390,14 @@ def send_event(event_id: str, opt_data: Any = None):
|
||||
elif event_id == 'cam_props':
|
||||
cam: bpy.types.Camera = obj_data.data
|
||||
render = bpy.context.scene.render
|
||||
rpdat = lnx.utils.get_rp()
|
||||
par_x, par_y = LeenkxExporter.compute_effective_par(rpdat, render)
|
||||
projection = obj_data.calc_matrix_camera(
|
||||
bpy.context.evaluated_depsgraph_get(),
|
||||
x=render.resolution_x,
|
||||
y=render.resolution_y,
|
||||
scale_x=render.pixel_aspect_x,
|
||||
scale_y=render.pixel_aspect_y)
|
||||
scale_x=par_x,
|
||||
scale_y=par_y)
|
||||
focal_y = projection[1][1]
|
||||
depth_z = projection[2][2]
|
||||
depth_w = projection[2][3]
|
||||
@ -403,8 +405,10 @@ def send_event(event_id: str, opt_data: Any = None):
|
||||
fov = 2.0 * math.atan(1.0 / focal_y)
|
||||
near = (depth_w * (1.0 - far_near_ratio)) / (2.0 * far_near_ratio)
|
||||
far = far_near_ratio * near
|
||||
res_aspect = render.resolution_x / render.resolution_y if render.resolution_y > 0 else 1.0
|
||||
effective_aspect = res_aspect * par_x / par_y
|
||||
cam_obj = f'iron.Scene.active.getCamera("{cam.name}")'
|
||||
js = f'var c = {cam_obj}; var cRaw = c.data.raw; cRaw.fov={fov}; cRaw.near_plane={near}; cRaw.far_plane={far}; cRaw.frustum_culling={"true" if cam.lnx_frustum_culling else "false"}; c.buildProjection();'
|
||||
js = f'var c = {cam_obj}; var cRaw = c.data.raw; cRaw.fov={fov}; cRaw.near_plane={near}; cRaw.far_plane={far}; cRaw.frustum_culling={"true" if cam.lnx_frustum_culling else "false"}; cRaw.aspect={effective_aspect}; c.buildProjection();'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'obj_visibility':
|
||||
|
||||
@ -190,6 +190,9 @@ def build():
|
||||
rpdat = lnx.utils.get_rp()
|
||||
project_path = lnx.utils.get_fp()
|
||||
|
||||
if rpdat.lnx_frustum_culling:
|
||||
assets.add_khafile_def('lnx_frustum_culling')
|
||||
|
||||
if rpdat.rp_driver != 'Leenkx' and lnx.api.drivers[rpdat.rp_driver]['make_rpath'] != None:
|
||||
lnx.api.drivers[rpdat.rp_driver]['make_rpath']()
|
||||
return
|
||||
@ -210,6 +213,9 @@ def build():
|
||||
if rpdat.rp_hdr:
|
||||
assets.add_khafile_def('rp_hdr')
|
||||
|
||||
if rpdat.lnx_occlusion_culling:
|
||||
assets.add_khafile_def('lnx_occlusion_culling')
|
||||
|
||||
assets.add_khafile_def('rp_renderer={0}'.format(rpdat.rp_renderer))
|
||||
if rpdat.rp_depthprepass:
|
||||
assets.add_khafile_def('rp_depthprepass')
|
||||
|
||||
@ -425,6 +425,16 @@ def init_properties():
|
||||
bpy.types.Object.lnx_sorting_index = compatible_prop(IntProperty, name="Sorting Index", description="Sorting index for the Render's Draw Order", default=0, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_spawn = compatible_prop(BoolProperty, name="Spawn", description="Auto-add this object when creating scene", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_mobile = compatible_prop(BoolProperty, name="Mobile", description="Object moves during gameplay", default=False, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_is_occluder = compatible_prop(BoolProperty, name="Is Occluder", description="Export simplified occluder geometry for software occlusion culling", default=False, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_occluder_mode = compatible_prop(EnumProperty,
|
||||
items=[('convex', 'Convex Hull', 'Convex hull of the mesh - tight on convex shapes, over-covers concave ones'),
|
||||
('box', 'Box', 'Axis-aligned bounding box - fastest, 12 triangles'),
|
||||
('decimate', 'Decimate', 'Decimated mesh - closest fit, slowest export')],
|
||||
name="Occluder Mode",
|
||||
description="Proxy geometry used for occlusion culling",
|
||||
default='convex',
|
||||
override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_occluder_shrink = compatible_prop(FloatProperty, name="Occluder Shrink %", description="Shrink proxy toward its center: 0 = full size, 50 = half size (default), 100 = point", default=50.0, min=0.0, max=100.0, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_visible = compatible_prop(BoolProperty, name="Visible", description="Render this object", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_visible_shadow = compatible_prop(BoolProperty, name="Lighting", description="Object contributes to the lighting even if invisible", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.lnx_soft_body_margin = FloatProperty(name="Soft Body Margin", description="Collision margin", default=0.04)
|
||||
|
||||
@ -232,6 +232,15 @@ def update_renderpath(self, context):
|
||||
assets.invalidate_shader_cache(self, context)
|
||||
bpy.data.worlds['Lnx'].lnx_recompile = True
|
||||
|
||||
def update_occlusion_culling(self, context):
|
||||
for m in bpy.data.meshes:
|
||||
m.lnx_cached = False
|
||||
for c in bpy.data.curves:
|
||||
c.lnx_cached = False
|
||||
for c in bpy.data.metaballs:
|
||||
c.lnx_cached = False
|
||||
update_renderpath(self, context)
|
||||
|
||||
def udpate_shadowmap_cascades(self, context):
|
||||
bpy.data.worlds['Lnx'].lnx_recompile = True
|
||||
update_renderpath(self, context)
|
||||
@ -461,6 +470,7 @@ class LnxRPListItem(bpy.types.PropertyGroup):
|
||||
('Distance', 'Distance', 'Distance'),
|
||||
('Index', 'Index', 'Index')],
|
||||
name='Draw Order', description='Sort objects', default='Auto', update=assets.invalidate_compiled_data)
|
||||
lnx_radix: BoolProperty(name="Radix Sort", description="Sort draw calls with the radix library instead of the comparison sort", default=True, update=update_renderpath)
|
||||
rp_depth_texture: BoolProperty(name="Depth Texture", description="Current render-path state", default=False)
|
||||
rp_depth_texture_state: EnumProperty(
|
||||
items=[('On', 'On', 'On'),
|
||||
@ -697,8 +707,48 @@ class LnxRPListItem(bpy.types.PropertyGroup):
|
||||
('CPU', 'CPU', 'CPU'),
|
||||
('Off', 'Off', 'Off')],
|
||||
name='Particles', description='Enable particle simulation', default='GPU', update=assets.invalidate_shader_cache)
|
||||
# Material override flags
|
||||
lnx_culling: BoolProperty(name="Culling", default=True)
|
||||
# Culling flags
|
||||
lnx_backface_culling: BoolProperty(name="Backface Culling", description="Cull back-facing triangles (winding-based)", default=True)
|
||||
lnx_frustum_culling: BoolProperty(name="Frustum Culling", description="Cull objects outside camera frustum", default=True)
|
||||
lnx_occlusion_culling: BoolProperty(name="Object Occlusion Culling (Experimental)", description="Software occlusion culling using Hi-Z buffer", default=False, update=update_occlusion_culling)
|
||||
lnx_occlusion_all_objects: BoolProperty(name="All Objects Occlude", description="Every mesh can occlude - ignores the per-object 'Is Occluder' flag", default=False, update=update_occlusion_culling)
|
||||
lnx_occlusion_min_size: FloatProperty(name="Min Occluder Size", description="Objects whose largest bounding-box side is smaller than this (meters) never export as occluders", default=0.5, min=0.0, update=update_occlusion_culling)
|
||||
lnx_occlusion_proxy_mode: EnumProperty(
|
||||
name="Proxy Mode",
|
||||
description="Occluder proxy geometry type",
|
||||
items=[
|
||||
('box', 'Box', 'Axis-aligned bounding box - fastest, 12 triangles'),
|
||||
('convex', 'Convex Hull', 'Convex hull of the mesh - tight on convex shapes, over-covers concave ones'),
|
||||
('decimate', 'Decimate', 'Decimated mesh - closest fit, slowest export'),
|
||||
],
|
||||
default='convex', update=update_occlusion_culling)
|
||||
lnx_occlusion_proxy_shrink: FloatProperty(name="Proxy Shrink %", description="Shrink proxy toward its center: 0 = full size, 50 = half size (default), 100 = point - keeps occluders conservative", default=50.0, min=0.0, max=100.0, precision=1, update=update_occlusion_culling)
|
||||
lnx_occlusion_box_shrink: IntProperty(name="Box Shrink %", description="0 = full AABB (aggressive), 50 = half-size (default, balanced), 100 = point (no occlusion)", default=50, min=0, max=100, update=update_renderpath)
|
||||
lnx_occlusion_depth_eps: IntProperty(name="Depth Epsilon", description="6 = 1e-6 (default, tight), higher = more tolerant (less aggressive culling), lower = stricter", default=6, min=1, max=10, update=update_renderpath)
|
||||
lnx_occlusion_decimate_ratio: FloatProperty(name="Ratio", description="Proportion of edges to collapse (COLLAPSE) or angle threshold in degrees (DISSOLVE) or iterations (UNSUBDIV)", default=0.5, min=0.01, max=1.0, precision=3, update=update_occlusion_culling)
|
||||
lnx_occlusion_decimate_mode: EnumProperty(
|
||||
name="Decimate Mode",
|
||||
description="Decimate method applied to occluder proxy geometry",
|
||||
items=[
|
||||
('COLLAPSE', 'Collapse', 'Collapse edges to reduce triangle count by ratio'),
|
||||
('DISSOLVE', 'Dissolve', 'Dissolve vertices with angle below threshold (ratio = degrees)'),
|
||||
('UNSUBDIV', 'Unsubdivide', 'Reverse subdivision (ratio = iterations, 0.1 = 1 iter, 1.0 = 10 iter)'),
|
||||
],
|
||||
default='COLLAPSE', update=update_occlusion_culling)
|
||||
lnx_aspect_mode: EnumProperty(
|
||||
name="Aspect Ratio",
|
||||
description="Pixel aspect ratio override for camera projection. 'Blender' uses the scene's resolution and pixel aspect settings",
|
||||
items=[
|
||||
('blender', 'Blender', 'Use Blender\'s Output Properties resolution and pixel aspect ratio'),
|
||||
('square', 'Square (1.0)', 'Square pixels (1:1)'),
|
||||
('ntsc_43', 'NTSC 4:3', 'NTSC pixel aspect ratio (0.889)'),
|
||||
('pal_43', 'PAL 4:3', 'PAL pixel aspect ratio (1.067)'),
|
||||
('ntsc_169', 'NTSC 16:9', 'NTSC widescreen pixel aspect ratio (1.212)'),
|
||||
('pal_169', 'PAL 16:9', 'PAL widescreen pixel aspect ratio (1.459)'),
|
||||
('custom', 'Custom', 'Custom pixel aspect ratio'),
|
||||
],
|
||||
default='blender')
|
||||
lnx_aspect_custom: FloatProperty(name="Custom PAR", description="Custom pixel aspect ratio (width/height of a single pixel)", default=1.0, min=0.01, max=10.0, precision=3)
|
||||
lnx_two_sided_area_light: BoolProperty(name="Two-Sided Area Light", description="Emit light from both faces of area plane", default=False, update=assets.invalidate_shader_cache)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -112,6 +112,10 @@ class LNX_PT_ObjectPropsPanel(bpy.types.Panel):
|
||||
sub.prop(obj, 'lnx_visible_shadow')
|
||||
|
||||
if obj.type == 'MESH':
|
||||
layout.prop(obj, 'lnx_is_occluder')
|
||||
if obj.lnx_is_occluder:
|
||||
layout.prop(obj, 'lnx_occluder_mode')
|
||||
layout.prop(obj, 'lnx_occluder_shrink')
|
||||
layout.prop(obj, 'lnx_instanced')
|
||||
if 'Rot' in obj.lnx_instanced:
|
||||
layout.prop(obj, 'lnx_instanced_rot_type')
|
||||
@ -1843,6 +1847,7 @@ class LNX_PT_RenderPathRendererPanel(bpy.types.Panel):
|
||||
layout.prop(rpdat, 'rp_blending_state')
|
||||
layout.prop(rpdat, 'rp_depth_texture_state')
|
||||
layout.prop(rpdat, 'rp_draw_order')
|
||||
layout.prop(rpdat, 'lnx_radix')
|
||||
layout.prop(rpdat, 'lnx_samples_per_pixel')
|
||||
layout.prop(rpdat, 'lnx_texture_filter')
|
||||
layout.prop(rpdat, 'rp_sss_state')
|
||||
@ -1878,7 +1883,23 @@ class LNX_PT_RenderPathRendererPanel(bpy.types.Panel):
|
||||
col = layout.column()
|
||||
col.prop(rpdat, "rp_hdr")
|
||||
col.prop(rpdat, "rp_stereo")
|
||||
col.prop(rpdat, 'lnx_culling')
|
||||
col.prop(rpdat, 'lnx_backface_culling')
|
||||
col.prop(rpdat, 'lnx_frustum_culling')
|
||||
col.prop(rpdat, 'lnx_occlusion_culling')
|
||||
if rpdat.lnx_occlusion_culling:
|
||||
col.prop(rpdat, 'lnx_occlusion_all_objects')
|
||||
if rpdat.lnx_occlusion_all_objects:
|
||||
col.prop(rpdat, 'lnx_occlusion_min_size')
|
||||
col.prop(rpdat, 'lnx_occlusion_proxy_mode')
|
||||
if rpdat.lnx_occlusion_proxy_mode == 'decimate':
|
||||
col.prop(rpdat, 'lnx_occlusion_decimate_mode')
|
||||
col.prop(rpdat, 'lnx_occlusion_decimate_ratio')
|
||||
col.prop(rpdat, 'lnx_occlusion_proxy_shrink')
|
||||
col.prop(rpdat, 'lnx_occlusion_box_shrink')
|
||||
col.prop(rpdat, 'lnx_occlusion_depth_eps')
|
||||
col.prop(rpdat, 'lnx_aspect_mode')
|
||||
if rpdat.lnx_aspect_mode == 'custom':
|
||||
col.prop(rpdat, 'lnx_aspect_custom')
|
||||
col.prop(rpdat, 'rp_pp')
|
||||
|
||||
|
||||
|
||||
@ -249,6 +249,9 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' +
|
||||
if not os.path.exists(os.path.join('Libraries', 'lib/hscript')):
|
||||
khafile.write(add_leenkx_library(sdk_path, 'lib/hscript', rel_path=do_relpath_sdk))
|
||||
khafile.write("project.addDefine('" + 'hscript' + "');\n")
|
||||
if lnx.utils.get_rp().lnx_radix:
|
||||
if not os.path.exists(os.path.join('Libraries', 'lib/radix')):
|
||||
khafile.write(add_leenkx_library(sdk_path, 'lib/radix', rel_path=do_relpath_sdk))
|
||||
|
||||
# Project libraries
|
||||
if os.path.exists('Libraries'):
|
||||
@ -522,6 +525,9 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' +
|
||||
if rpdat.rp_draw_order == 'Index':
|
||||
assets.add_khafile_def('lnx_draworder_index')
|
||||
|
||||
if rpdat.lnx_radix:
|
||||
assets.add_khafile_def('lnx_radix')
|
||||
|
||||
if lnx.utils.get_viewport_controls() == 'azerty':
|
||||
assets.add_khafile_def('lnx_azerty')
|
||||
|
||||
@ -721,6 +727,11 @@ class Main {
|
||||
f.write("\n" + content_indent + "iron.object.LightObject.cascadeSplitFactor = " + str(rpdat.lnx_shadowmap_split) + ";")
|
||||
if rpdat.lnx_shadowmap_bounds != 1.0:
|
||||
f.write("\n" + content_indent + "iron.object.LightObject.cascadeBounds = " + str(rpdat.lnx_shadowmap_bounds) + ";")
|
||||
|
||||
if rpdat.lnx_occlusion_culling:
|
||||
depth_eps = max(10.0 ** -int(rpdat.lnx_occlusion_depth_eps), 1e-6)
|
||||
f.write("\n" + content_indent + "iron.culling.CullSystem.boxShrink = " + str(rpdat.lnx_occlusion_box_shrink / 100) + ";")
|
||||
f.write("\n" + content_indent + "iron.culling.CullSystem.depthEps = " + str(depth_eps) + ";")
|
||||
|
||||
asset_references = list(set(assets.assets))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user