Browse Source

Merge pull request #122 from Hoikas/lm-passes

Add named lightmap passes
pull/123/head
Adam Johnson 6 years ago committed by GitHub
parent
commit
4523f5a2fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      korman/exporter/convert.py
  2. 37
      korman/exporter/etlight.py
  3. 6
      korman/exporter/mesh.py
  4. 30
      korman/operators/op_export.py
  5. 2
      korman/properties/__init__.py
  6. 82
      korman/properties/modifiers/render.py
  7. 46
      korman/properties/prop_scene.py
  8. 1
      korman/ui/__init__.py
  9. 30
      korman/ui/modifiers/render.py
  10. 53
      korman/ui/ui_render_layer.py
  11. 12
      korman/ui/ui_world.py

5
korman/exporter/convert.py

@ -61,7 +61,7 @@ class Exporter:
self.mesh.add_progress_presteps(self.report)
self.report.progress_add_step("Collecting Objects")
self.report.progress_add_step("Harvesting Actors")
if self._op.bake_lighting:
if self._op.lighting_method != "skip":
etlight.LightBaker.add_progress_steps(self.report)
self.report.progress_add_step("Exporting Scene Objects")
self.report.progress_add_step("Exporting Logic Nodes")
@ -86,7 +86,6 @@ class Exporter:
# Step 2.9: It is assumed that static lighting is available for the mesh exporter.
# Indeed, in PyPRP it was a manual step. So... BAKE NAO!
if self._op.bake_lighting:
self._bake_static_lighting()
# Step 3: Export all the things!
@ -113,6 +112,8 @@ class Exporter:
self.report.save()
def _bake_static_lighting(self):
lighting_method = self._op.lighting_method
if lighting_method != "skip":
oven = etlight.LightBaker(self.report)
oven.bake_static_lighting(self._objects)

37
korman/exporter/etlight.py

@ -78,9 +78,9 @@ class LightBaker(_MeshManager):
self._select_only(objs, toggle)
bpy.ops.object.bake_image()
def _bake_vcols(self, objs):
def _bake_vcols(self, objs, layers):
with GoodNeighbor() as toggle:
bpy.context.scene.layers = (True,) * _NUM_RENDER_LAYERS
bpy.context.scene.layers = layers
self._apply_render_settings(toggle, True)
self._select_only(objs, toggle)
bpy.ops.object.bake_image()
@ -147,14 +147,15 @@ class LightBaker(_MeshManager):
if value:
if key[0] == "lightmap":
num_objs = len(value)
self._report.msg("{} Lightmap(s) [H:{:X}]", num_objs, hash(key), indent=1)
self._report.msg("{} Lightmap(s) [H:{:X}]", num_objs, hash(key[1:]), indent=1)
if largest_pass > 1 and num_objs < round(largest_pass * 0.02):
obj_names = ", ".join((i.name for i in value))
self._report.warn("Small lightmap pass! Objects: {}".format(obj_names), indent=2)
pass_names = set((i.plasma_modifiers.lightmap.bake_pass_name for i in value))
pass_msg = ", ".join(pass_names)
self._report.warn("Small lightmap bake pass! Bake Pass(es): {}".format(pass_msg), indent=2)
self._bake_lightmaps(value, key[1:])
elif key[0] == "vcol":
self._report.msg("{} Crap Light(s)", len(value), indent=1)
self._bake_vcols(value)
self._report.msg("{} Vertex Color(s) [H:{:X}]", len(value), hash(key[1:]), indent=1)
self._bake_vcols(value, key[1:])
else:
raise RuntimeError(key[0])
inc_progress()
@ -219,8 +220,9 @@ class LightBaker(_MeshManager):
def _harvest_bakable_objects(self, objs):
# The goal here is to minimize the calls to bake_image, so we are going to collect everything
# that needs to be baked and sort it out by configuration.
bake = { ("vcol",): [] }
bake_vcol = bake[("vcol",)]
default_layers = tuple((True,) * _NUM_RENDER_LAYERS)
bake, bake_passes = {}, bpy.context.scene.plasma_scene.bake_passes
bake_vcol = bake.setdefault(("vcol",) + default_layers, [])
for i in objs:
if i.type != "MESH":
@ -231,15 +233,24 @@ class LightBaker(_MeshManager):
mods = i.plasma_modifiers
lightmap_mod = mods.lightmap
if lightmap_mod.enabled:
if lightmap_mod.bake_pass_name:
bake_pass = bake_passes.get(lightmap_mod.bake_pass_name, None)
if bake_pass is None:
raise ExportError("Bake Lighting '{}': Could not find pass '{}'".format(i.name, lightmap_mod.bake_pass_name))
lm_layers = tuple(bake_pass.render_layers)
else:
lm_layers = default_layers
# In order for Blender to be able to bake this properly, at least one of the
# layers this object is on must be selected. We will sanity check this now.
lm_layers, obj_layers = tuple(lightmap_mod.render_layers), tuple(i.layers)
obj_layers = tuple(i.layers)
lm_active_layers = set((i for i, value in enumerate(lm_layers) if value))
obj_active_layers = set((i for i, value in enumerate(obj_layers) if value))
if not lm_active_layers & obj_active_layers:
raise ExportError("Lightmap '{}': At least one layer the object is on must be selected".format(i.name))
raise ExportError("Bake Lighting '{}': At least one layer the object is on must be selected".format(i.name))
key = ("lightmap",) + lm_layers
method = "lightmap" if lightmap_mod.bake_lightmap else "vcol"
key = (method,) + lm_layers
bake_pass = bake.setdefault(key, [])
bake_pass.append(i)
elif mods.lighting.preshade:
@ -356,9 +367,11 @@ class LightBaker(_MeshManager):
def _prep_for_vcols(self, bo, toggle):
mesh = bo.data
modifier = bo.plasma_modifiers.lightmap
vcols = mesh.vertex_colors
# Create a special light group for baking
user_lg = modifier.lights if modifier.enabled else None
if not self._generate_lightgroup(bo):
return False

6
korman/exporter/mesh.py

@ -195,7 +195,8 @@ class MeshConverter(_MeshManager):
# Lightmapping requires its own LIGHTMAPGEN channel
# NOTE: the LIGHTMAPGEN texture has already been created, so it is in num_user_texs
if bo.plasma_modifiers.lightmap.enabled:
lm = bo.plasma_modifiers.lightmap
if lm.enabled and lm.bake_type == "lightmap":
num_user_texs -= 1
max_user_texs -= 1
@ -261,12 +262,13 @@ class MeshConverter(_MeshManager):
bumpmap = self.material.get_bump_layer(bo)
# Locate relevant vertex color layers now...
lm = bo.plasma_modifiers.lightmap
color, alpha = None, None
for vcol_layer in mesh.tessface_vertex_colors:
name = vcol_layer.name.lower()
if name in _VERTEX_COLOR_LAYERS:
color = vcol_layer.data
elif name == "autocolor" and color is None and not bo.plasma_modifiers.lightmap.enabled:
elif name == "autocolor" and color is None and not lm.bake_lightmap:
color = vcol_layer.data
elif name == "alpha":
alpha = vcol_layer.data

30
korman/operators/op_export.py

@ -37,10 +37,6 @@ class ExportOperator(bpy.types.Operator):
"description": "Profiles the exporter using cProfile",
"default": False}),
"bake_lighting": (BoolProperty, {"name": "Bake Static Lights",
"description": "Bake all lightmaps and vertex shading on export",
"default": True}),
"verbose": (BoolProperty, {"name": "Display Verbose Log",
"description": "Shows the verbose export log in the console",
"default": False}),
@ -58,6 +54,18 @@ class ExportOperator(bpy.types.Operator):
("use", "Use Texture Cache", "Use (and update, if needed) cached textures."),
("rebuild", "Rebuild Texture Cache", "Rebuilds the texture cache from scratch.")],
"default": "use"}),
"lighting_method": (EnumProperty, {"name": "Static Lighting",
"description": "Static Lighting Settings",
"items": [("skip", "Don't Bake Lighting", "Static lighting is not baked during this export (fastest export)"),
("bake", "Bake Lighting", "Static lighting is baked according to your specifications"),
("force_vcol", "Force Vertex Color Bake", "All static lighting is baked as vertex colors (faster export)"),
("force_lightmap", "Force Lightmap Bake", "All static lighting is baked as lightmaps (slower export)")],
"default": "bake"}),
"export_active": (BoolProperty, {"name": "INTERNAL: Export currently running",
"default": False,
"options": {"SKIP_SAVE"}}),
}
# This wigs out and very bad things happen if it's not directly on the operator...
@ -77,7 +85,7 @@ class ExportOperator(bpy.types.Operator):
# The crazy mess we're doing with props on the fly means we have to explicitly draw them :(
layout.prop(self, "version")
layout.prop(age, "texcache_method", text="")
layout.prop(age, "bake_lighting")
layout.prop(age, "lighting_method")
row = layout.row()
row.enabled = ConsoleToggler.is_platform_supported()
row.prop(age, "show_console")
@ -89,6 +97,12 @@ class ExportOperator(bpy.types.Operator):
return getattr(bpy.context.scene.world.plasma_age, attr)
raise AttributeError(attr)
def __setattr__(self, attr, value):
if attr in self._properties:
setattr(bpy.context.scene.world.plasma_age, attr, value)
else:
super().__setattr__(attr, value)
@property
def has_reports(self):
return hasattr(self.report)
@ -120,6 +134,7 @@ class ExportOperator(bpy.types.Operator):
with _UiHelper(context) as _ui:
e = exporter.Exporter(self)
try:
self.export_active = True
if self.profile_export:
profile = path.with_name("{}_cProfile".format(ageName))
profile = cProfile.runctx("e.run()", globals(), locals(), str(profile))
@ -136,6 +151,8 @@ class ExportOperator(bpy.types.Operator):
stats = stats.sort_stats("time", "calls")
stats.print_stats()
return {"FINISHED"}
finally:
self.export_active = False
def invoke(self, context, event):
# Called when a user hits "export" from the menu
@ -157,7 +174,8 @@ class ExportOperator(bpy.types.Operator):
for name, (prop, options) in cls._properties.items():
# Hide these settings from being seen on the age properties
age_options = dict(options)
age_options["options"] = {"HIDDEN"}
bl_options = age_options.setdefault("options", set())
bl_options.add("HIDDEN")
# Now do the majick
setattr(PlasmaAge, name, prop(**age_options))

2
korman/properties/__init__.py

@ -20,6 +20,7 @@ from .prop_image import *
from .prop_lamp import *
from . import modifiers
from .prop_object import *
from .prop_scene import *
from .prop_texture import *
from .prop_world import *
@ -30,6 +31,7 @@ def register():
bpy.types.Lamp.plasma_lamp = bpy.props.PointerProperty(type=PlasmaLamp)
bpy.types.Object.plasma_net = bpy.props.PointerProperty(type=PlasmaNet)
bpy.types.Object.plasma_object = bpy.props.PointerProperty(type=PlasmaObject)
bpy.types.Scene.plasma_scene = bpy.props.PointerProperty(type=PlasmaScene)
bpy.types.Texture.plasma_layer = bpy.props.PointerProperty(type=PlasmaLayer)
bpy.types.World.plasma_age = bpy.props.PointerProperty(type=PlasmaAge)
bpy.types.World.plasma_fni = bpy.props.PointerProperty(type=PlasmaFni)

82
korman/properties/modifiers/render.py

@ -142,12 +142,14 @@ class PlasmaFollowMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
return True
class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties):
class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaModifierUpgradable):
pl_id = "lightmap"
bl_category = "Render"
bl_label = "Lightmap"
bl_description = "Auto-Bake Lightmap"
bl_label = "Bake Lighting"
bl_description = "Auto-Bake Static Lighting"
deprecated_properties = {"render_layers"}
quality = EnumProperty(name="Quality",
description="Resolution of lightmap",
@ -156,15 +158,28 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties):
("256", "256px", "256x256 pixels"),
("512", "512px", "512x512 pixels"),
("1024", "1024px", "1024x1024 pixels"),
("2048", "2048px", "2048x2048 pixels"),
])
bake_type = EnumProperty(name="Bake To",
description="Destination for baked lighting data",
items=[
("lightmap", "Lightmap Texture", "Bakes lighting to a lightmap texture"),
("vcol", "Vertex Colors", "Bakes lighting to vertex colors"),
],
options=set())
render_layers = BoolVectorProperty(name="Layers",
description="Render layers to use for baking",
options=set(),
description="DEPRECATED: Render layers to use for baking",
options={"HIDDEN"},
subtype="LAYER",
size=_NUM_RENDER_LAYERS,
default=((True,) * _NUM_RENDER_LAYERS))
bake_pass_name = StringProperty(name="Bake Pass",
description="Pass in which to bake lighting",
options=set())
lights = PointerProperty(name="Light Group",
description="Group that defines the collection of lights to bake",
type=bpy.types.Group)
@ -172,7 +187,26 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties):
uv_map = StringProperty(name="UV Texture",
description="UV Texture used as the basis for the lightmap")
@property
def bake_lightmap(self):
if not self.enabled:
return False
age = bpy.context.scene.world.plasma_age
if age.export_active:
if age.lighting_method == "force_lightmap":
return True
elif self.bake_type == "lightmap" and age.lighting_method == "bake":
return True
else:
return False
else:
return self.bake_type == "lightmap"
def export(self, exporter, bo, so):
# If we're exporting vertex colors, who gives a rat's behind?
if not self.bake_lightmap:
return
lightmap_im = bpy.data.images.get("{}_LIGHTMAPGEN.png".format(bo.name))
# If no lightmap image is found, then either lightmap generation failed (error raised by oven)
@ -229,16 +263,38 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties):
def key_name(self):
return "{}_LIGHTMAPGEN".format(self.id_data.name)
@property
def latest_version(self):
return 2
@property
def resolution(self):
return int(self.quality)
def upgrade(self):
# In version 1, bake passes were assigned on a per modifier basis by setting
# the view layers on the modifier. Version 2 moves them into a global list
# that can be selected by name in the modifier
if self.current_version < 2:
bake_passes = bpy.context.scene.plasma_scene.bake_passes
render_layers = tuple(self.render_layers)
# Try to find a render pass matching, if possible...
bake_pass = next((i for i in bake_passes if tuple(i.render_layers) == render_layers), None)
if bake_pass is None:
bake_pass = bake_passes.add()
bake_pass.display_name = "Pass {}".format(len(bake_passes))
bake_pass.render_layers = render_layers
self.bake_pass_name = bake_pass.display_name
self.property_unset("render_layers")
self.current_version = 2
class PlasmaLightingMod(PlasmaModifierProperties):
pl_id = "lighting"
bl_category = "Render"
bl_label = "Lighting"
bl_label = "Lighting Info"
bl_description = "Fine tune Plasma lighting settings"
force_rt_lights = BoolProperty(name="Force RT Lighting",
@ -252,10 +308,10 @@ class PlasmaLightingMod(PlasmaModifierProperties):
@property
def allow_preshade(self):
bo = self.id_data
if bo.plasma_modifiers.water_basic.enabled:
mods = self.id_data.plasma_modifiers
if mods.water_basic.enabled:
return False
if bo.plasma_modifiers.lightmap.enabled:
if mods.lightmap.bake_lightmap:
return False
return True
@ -284,12 +340,12 @@ class PlasmaLightingMod(PlasmaModifierProperties):
@property
def want_rt_lights(self):
"""Gets whether or not this object ought to be lit dynamically"""
bo = self.id_data
if bo.plasma_modifiers.lightmap.enabled:
mods = self.id_data.plasma_modifiers
if mods.lightmap.enabled and mods.lightmap.bake_type == "lightmap":
return False
if bo.plasma_modifiers.water_basic.enabled:
if mods.water_basic.enabled:
return True
if bo.plasma_object.has_transform_animation:
if self.id_data.plasma_object.has_transform_animation:
return True
return False

46
korman/properties/prop_scene.py

@ -0,0 +1,46 @@
# This file is part of Korman.
#
# Korman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Korman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bpy.props import *
from ..exporter.etlight import _NUM_RENDER_LAYERS
class PlasmaBakePass(bpy.types.PropertyGroup):
def _get_display_name(self):
return self.name
def _set_display_name(self, value):
for i in bpy.data.objects:
lm = i.plasma_modifiers.lightmap
if lm.bake_pass_name == self.name:
lm.bake_pass_name = value
self.name = value
display_name = StringProperty(name="Pass Name",
get=_get_display_name,
set=_set_display_name,
options=set())
render_layers = BoolVectorProperty(name="Layers to Bake",
description="Render layers to use for baking",
options=set(),
subtype="LAYER",
size=_NUM_RENDER_LAYERS,
default=((True,) * _NUM_RENDER_LAYERS))
class PlasmaScene(bpy.types.PropertyGroup):
bake_passes = CollectionProperty(type=PlasmaBakePass)
active_pass_index = IntProperty(options={"HIDDEN"})

1
korman/ui/__init__.py

@ -20,6 +20,7 @@ from .ui_list import *
from .ui_menus import *
from .ui_modifiers import *
from .ui_object import *
from .ui_render_layer import *
from .ui_texture import *
from .ui_toolbox import *
from .ui_world import *

30
korman/ui/modifiers/render.py

@ -16,6 +16,7 @@
import bpy
from .. import ui_list
from ...exporter.mesh import _VERTEX_COLOR_LAYERS
def fademod(modifier, layout, context):
layout.prop(modifier, "fader_type")
@ -73,27 +74,42 @@ def lighting(modifier, layout, context):
col.label("Other Plasma lights {} be cast at runtime.".format("will" if modifier.rt_lights else "will NOT"),
icon="LAYER_USED")
map_type = "a lightmap" if lightmap.enabled and lightmap.bake_type == "lightmap" else "vertex colors"
if lightmap.enabled and lightmap.lights:
col.label(" All '{}' lights will be baked to a lightmap".format(lightmap.lights),
col.label("All '{}' lights will be baked to {}".format(lightmap.lights.name, map_type),
icon="LAYER_USED")
elif have_static_lights:
light_type = "Blender-only" if modifier.rt_lights else "unanimated"
map_type = "a lightmap" if lightmap.enabled else "vertex colors"
col.label("Other {} lights will be baked to {}.".format(light_type, map_type), icon="LAYER_USED")
else:
col.label("No static lights will be baked.", icon="LAYER_USED")
def lightmap(modifier, layout, context):
layout.row(align=True).prop(modifier, "quality", expand=True)
layout.prop(modifier, "render_layers", text="Active Render Layers")
pl_scene = context.scene.plasma_scene
is_texture = modifier.bake_type == "lightmap"
layout.prop(modifier, "bake_type")
if modifier.bake_type == "vcol":
col_layer = next((i for i in modifier.id_data.data.vertex_colors if i.name.lower() in _VERTEX_COLOR_LAYERS), None)
if col_layer is not None:
layout.label("Mesh color layer '{}' will override this lighting.".format(col_layer.name), icon="ERROR")
col = layout.column()
col.active = is_texture
col.prop(modifier, "quality")
layout.prop_search(modifier, "bake_pass_name", pl_scene, "bake_passes", icon="RENDERLAYERS")
layout.prop(modifier, "lights")
layout.prop_search(modifier, "uv_map", context.active_object.data, "uv_textures")
col = layout.column()
col.active = is_texture
col.prop_search(modifier, "uv_map", context.active_object.data, "uv_textures")
# Lightmaps can only be applied to objects with opaque materials.
if any((i.use_transparency for i in modifier.id_data.data.materials if i is not None)):
if is_texture and any((i.use_transparency for i in modifier.id_data.data.materials if i is not None)):
layout.label("Transparent objects cannot be lightmapped.", icon="ERROR")
else:
operator = layout.operator("object.plasma_lightmap_preview", "Preview Lightmap", icon="RENDER_STILL")
col = layout.column()
col.active = is_texture
operator = col.operator("object.plasma_lightmap_preview", "Preview Lightmap", icon="RENDER_STILL")
# Kind of clever stuff to show the user a preview...
# We can't show images, so we make a hidden ImageTexture called LIGHTMAPGEN_PREVIEW. We check

53
korman/ui/ui_render_layer.py

@ -0,0 +1,53 @@
# This file is part of Korman.
#
# Korman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Korman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import ui_list
class RenderLayerButtonsPanel:
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "render_layer"
@classmethod
def poll(cls, context):
return context.scene.render.engine == "PLASMA_GAME"
class BakePassUI(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
layout.prop(item, "display_name", emboss=False, text="", icon="RENDERLAYERS")
class PlasmaBakePassPanel(RenderLayerButtonsPanel, bpy.types.Panel):
bl_label = "Plasma Bake Passes"
def draw(self, context):
layout = self.layout
scene = context.scene.plasma_scene
ui_list.draw_list(layout, "BakePassUI", "scene", scene, "bake_passes",
"active_pass_index", name_prefix="Pass",
name_prop="display_name", rows=3, maxrows=3)
active_pass_index = scene.active_pass_index
try:
bake_pass = scene.bake_passes[active_pass_index]
except:
pass
else:
box = layout.box()
box.prop(bake_pass, "display_name")
box.prop(bake_pass, "render_layers")

12
korman/ui/ui_world.py

@ -133,18 +133,18 @@ class PlasmaAgePanel(AgeButtonsPanel, bpy.types.Panel):
col = split.column()
col.label("Export Settings:")
col.prop(age, "texcache_method", text="")
col.prop(age, "bake_lighting")
cons_ui = col.column()
cons_ui.enabled = ConsoleToggler.is_platform_supported()
cons_ui.prop(age, "verbose")
cons_ui.prop(age, "show_console")
col.enabled = ConsoleToggler.is_platform_supported()
col.prop(age, "verbose")
col.prop(age, "show_console")
col = split.column()
col.label("Plasma Settings:")
col.prop(age, "age_sdl")
col.prop(age, "use_texture_page")
layout.separator()
layout.prop(age, "lighting_method")
layout.prop(age, "texcache_method")
class PlasmaEnvironmentPanel(AgeButtonsPanel, bpy.types.Panel):

Loading…
Cancel
Save