Browse Source

Generalize the lightmap modifier

The lightmap modifier is now a more general "Bake Lighting" modifier
that can control how vertex colors are baked as well. The default vertex
color baking behavior is unaffected. It is now identical to adding a
Bake Lighting modifier, changing it to bake vertex colors without
specifying a pass.
pull/122/head
Adam Johnson 6 years ago
parent
commit
c0cdf46460
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 5
      korman/exporter/convert.py
  2. 32
      korman/exporter/etlight.py
  3. 6
      korman/exporter/mesh.py
  4. 30
      korman/operators/op_export.py
  5. 48
      korman/properties/modifiers/render.py
  6. 27
      korman/ui/modifiers/render.py
  7. 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.mesh.add_progress_presteps(self.report)
self.report.progress_add_step("Collecting Objects") self.report.progress_add_step("Collecting Objects")
self.report.progress_add_step("Harvesting Actors") 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) etlight.LightBaker.add_progress_steps(self.report)
self.report.progress_add_step("Exporting Scene Objects") self.report.progress_add_step("Exporting Scene Objects")
self.report.progress_add_step("Exporting Logic Nodes") 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. # 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! # Indeed, in PyPRP it was a manual step. So... BAKE NAO!
if self._op.bake_lighting:
self._bake_static_lighting() self._bake_static_lighting()
# Step 3: Export all the things! # Step 3: Export all the things!
@ -113,6 +112,8 @@ class Exporter:
self.report.save() self.report.save()
def _bake_static_lighting(self): def _bake_static_lighting(self):
lighting_method = self._op.lighting_method
if lighting_method != "skip":
oven = etlight.LightBaker(self.report) oven = etlight.LightBaker(self.report)
oven.bake_static_lighting(self._objects) oven.bake_static_lighting(self._objects)

32
korman/exporter/etlight.py

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

6
korman/exporter/mesh.py

@ -195,7 +195,8 @@ class MeshConverter(_MeshManager):
# Lightmapping requires its own LIGHTMAPGEN channel # Lightmapping requires its own LIGHTMAPGEN channel
# NOTE: the LIGHTMAPGEN texture has already been created, so it is in num_user_texs # 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 num_user_texs -= 1
max_user_texs -= 1 max_user_texs -= 1
@ -261,12 +262,13 @@ class MeshConverter(_MeshManager):
bumpmap = self.material.get_bump_layer(bo) bumpmap = self.material.get_bump_layer(bo)
# Locate relevant vertex color layers now... # Locate relevant vertex color layers now...
lm = bo.plasma_modifiers.lightmap
color, alpha = None, None color, alpha = None, None
for vcol_layer in mesh.tessface_vertex_colors: for vcol_layer in mesh.tessface_vertex_colors:
name = vcol_layer.name.lower() name = vcol_layer.name.lower()
if name in _VERTEX_COLOR_LAYERS: if name in _VERTEX_COLOR_LAYERS:
color = vcol_layer.data 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 color = vcol_layer.data
elif name == "alpha": elif name == "alpha":
alpha = vcol_layer.data 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", "description": "Profiles the exporter using cProfile",
"default": False}), "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", "verbose": (BoolProperty, {"name": "Display Verbose Log",
"description": "Shows the verbose export log in the console", "description": "Shows the verbose export log in the console",
"default": False}), "default": False}),
@ -58,6 +54,18 @@ class ExportOperator(bpy.types.Operator):
("use", "Use Texture Cache", "Use (and update, if needed) cached textures."), ("use", "Use Texture Cache", "Use (and update, if needed) cached textures."),
("rebuild", "Rebuild Texture Cache", "Rebuilds the texture cache from scratch.")], ("rebuild", "Rebuild Texture Cache", "Rebuilds the texture cache from scratch.")],
"default": "use"}), "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... # 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 :( # The crazy mess we're doing with props on the fly means we have to explicitly draw them :(
layout.prop(self, "version") layout.prop(self, "version")
layout.prop(age, "texcache_method", text="") layout.prop(age, "texcache_method", text="")
layout.prop(age, "bake_lighting") layout.prop(age, "lighting_method")
row = layout.row() row = layout.row()
row.enabled = ConsoleToggler.is_platform_supported() row.enabled = ConsoleToggler.is_platform_supported()
row.prop(age, "show_console") row.prop(age, "show_console")
@ -89,6 +97,12 @@ class ExportOperator(bpy.types.Operator):
return getattr(bpy.context.scene.world.plasma_age, attr) return getattr(bpy.context.scene.world.plasma_age, attr)
raise AttributeError(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 @property
def has_reports(self): def has_reports(self):
return hasattr(self.report) return hasattr(self.report)
@ -120,6 +134,7 @@ class ExportOperator(bpy.types.Operator):
with _UiHelper(context) as _ui: with _UiHelper(context) as _ui:
e = exporter.Exporter(self) e = exporter.Exporter(self)
try: try:
self.export_active = True
if self.profile_export: if self.profile_export:
profile = path.with_name("{}_cProfile".format(ageName)) profile = path.with_name("{}_cProfile".format(ageName))
profile = cProfile.runctx("e.run()", globals(), locals(), str(profile)) 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 = stats.sort_stats("time", "calls")
stats.print_stats() stats.print_stats()
return {"FINISHED"} return {"FINISHED"}
finally:
self.export_active = False
def invoke(self, context, event): def invoke(self, context, event):
# Called when a user hits "export" from the menu # 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(): for name, (prop, options) in cls._properties.items():
# Hide these settings from being seen on the age properties # Hide these settings from being seen on the age properties
age_options = dict(options) age_options = dict(options)
age_options["options"] = {"HIDDEN"} bl_options = age_options.setdefault("options", set())
bl_options.add("HIDDEN")
# Now do the majick # Now do the majick
setattr(PlasmaAge, name, prop(**age_options)) setattr(PlasmaAge, name, prop(**age_options))

48
korman/properties/modifiers/render.py

@ -146,8 +146,8 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
pl_id = "lightmap" pl_id = "lightmap"
bl_category = "Render" bl_category = "Render"
bl_label = "Lightmap" bl_label = "Bake Lighting"
bl_description = "Auto-Bake Lightmap" bl_description = "Auto-Bake Static Lighting"
deprecated_properties = {"render_layers"} deprecated_properties = {"render_layers"}
@ -158,8 +158,17 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
("256", "256px", "256x256 pixels"), ("256", "256px", "256x256 pixels"),
("512", "512px", "512x512 pixels"), ("512", "512px", "512x512 pixels"),
("1024", "1024px", "1024x1024 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", render_layers = BoolVectorProperty(name="Layers",
description="DEPRECATED: Render layers to use for baking", description="DEPRECATED: Render layers to use for baking",
options={"HIDDEN"}, options={"HIDDEN"},
@ -178,7 +187,26 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
uv_map = StringProperty(name="UV Texture", uv_map = StringProperty(name="UV Texture",
description="UV Texture used as the basis for the lightmap") 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): 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)) 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) # If no lightmap image is found, then either lightmap generation failed (error raised by oven)
@ -266,7 +294,7 @@ class PlasmaLightingMod(PlasmaModifierProperties):
pl_id = "lighting" pl_id = "lighting"
bl_category = "Render" bl_category = "Render"
bl_label = "Lighting" bl_label = "Lighting Info"
bl_description = "Fine tune Plasma lighting settings" bl_description = "Fine tune Plasma lighting settings"
force_rt_lights = BoolProperty(name="Force RT Lighting", force_rt_lights = BoolProperty(name="Force RT Lighting",
@ -280,10 +308,10 @@ class PlasmaLightingMod(PlasmaModifierProperties):
@property @property
def allow_preshade(self): def allow_preshade(self):
bo = self.id_data mods = self.id_data.plasma_modifiers
if bo.plasma_modifiers.water_basic.enabled: if mods.water_basic.enabled:
return False return False
if bo.plasma_modifiers.lightmap.enabled: if mods.lightmap.bake_lightmap:
return False return False
return True return True
@ -312,12 +340,12 @@ class PlasmaLightingMod(PlasmaModifierProperties):
@property @property
def want_rt_lights(self): def want_rt_lights(self):
"""Gets whether or not this object ought to be lit dynamically""" """Gets whether or not this object ought to be lit dynamically"""
bo = self.id_data mods = self.id_data.plasma_modifiers
if bo.plasma_modifiers.lightmap.enabled: if mods.lightmap.enabled and mods.lightmap.bake_type == "lightmap":
return False return False
if bo.plasma_modifiers.water_basic.enabled: if mods.water_basic.enabled:
return True return True
if bo.plasma_object.has_transform_animation: if self.id_data.plasma_object.has_transform_animation:
return True return True
return False return False

27
korman/ui/modifiers/render.py

@ -16,6 +16,7 @@
import bpy import bpy
from .. import ui_list from .. import ui_list
from ...exporter.mesh import _VERTEX_COLOR_LAYERS
def fademod(modifier, layout, context): def fademod(modifier, layout, context):
layout.prop(modifier, "fader_type") layout.prop(modifier, "fader_type")
@ -73,28 +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"), col.label("Other Plasma lights {} be cast at runtime.".format("will" if modifier.rt_lights else "will NOT"),
icon="LAYER_USED") icon="LAYER_USED")
map_type = "a lightmap" if lightmap.enabled and lightmap.bake_type == "lightmap" else "vertex colors"
if lightmap.enabled and lightmap.lights: 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") icon="LAYER_USED")
elif have_static_lights: elif have_static_lights:
light_type = "Blender-only" if modifier.rt_lights else "unanimated" 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") col.label("Other {} lights will be baked to {}.".format(light_type, map_type), icon="LAYER_USED")
else: else:
col.label("No static lights will be baked.", icon="LAYER_USED") col.label("No static lights will be baked.", icon="LAYER_USED")
def lightmap(modifier, layout, context): def lightmap(modifier, layout, context):
pl_scene = context.scene.plasma_scene pl_scene = context.scene.plasma_scene
layout.row(align=True).prop(modifier, "quality", expand=True) 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_search(modifier, "bake_pass_name", pl_scene, "bake_passes", icon="RENDERLAYERS")
layout.prop(modifier, "lights") 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. # 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") layout.label("Transparent objects cannot be lightmapped.", icon="ERROR")
else: 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... # 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 # We can't show images, so we make a hidden ImageTexture called LIGHTMAPGEN_PREVIEW. We check

12
korman/ui/ui_world.py

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

Loading…
Cancel
Save