From 65d1d7141636f18f567443b4b6b9a61e2437ccc3 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 5 Jun 2015 20:14:43 -0400 Subject: [PATCH] Rudimentary Lightmap Baking Still lots of considerations: - Need to make faux lightgroups to avoid baking Plasma RT lights - Still need to bake vertex colors - Still need to export Plasma RT lights (D'oh!) But, this is definitely a step in the right direction! --- korman/exporter/material.py | 47 ++++++++--- korman/exporter/mesh.py | 10 ++- korman/helpers.py | 29 +++++++ korman/operators/__init__.py | 1 + korman/operators/op_export.py | 1 - korman/operators/op_lightmap.py | 106 ++++++++++++++++++++++++ korman/properties/modifiers/__init__.py | 1 + korman/properties/modifiers/render.py | 82 ++++++++++++++++++ korman/ui/modifiers/__init__.py | 1 + korman/ui/modifiers/render.py | 31 +++++++ 10 files changed, 296 insertions(+), 13 deletions(-) create mode 100644 korman/helpers.py create mode 100644 korman/operators/op_lightmap.py create mode 100644 korman/properties/modifiers/render.py create mode 100644 korman/ui/modifiers/render.py diff --git a/korman/exporter/material.py b/korman/exporter/material.py index d30fe4f..388e2bb 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -102,11 +102,19 @@ class _GLTexture: class _Texture: - def __init__(self, texture): - self.image = texture.image - self.calc_alpha = texture.use_calculate_alpha - self.mipmap = texture.use_mipmap - self.use_alpha = texture.use_alpha + def __init__(self, texture=None, image=None): + assert (texture or image) + + if texture is not None: + self.image = texture.image + self.calc_alpha = texture.use_calculate_alpha + self.mipmap = texture.use_mipmap + self.use_alpha = texture.use_alpha + if image is not None: + self.image = image + self.calc_alpha = False + self.mipmap = False + self.use_alpha = image.use_alpha def __eq__(self, other): if not isinstance(other, _Texture): @@ -121,10 +129,7 @@ class _Texture: return hash(self.image.name) ^ hash(self.calc_alpha) def __str__(self): - if self.mipmap: - name = self._change_extension(self.image.name, ".dds") - else: - name = self._change_extension(self.image.name, ".bmp") + name = self._change_extension(self.image.name, ".dds") if self.calc_alpha: name = "ALPHAGEN_{}".format(name) return name @@ -149,6 +154,7 @@ class _Texture: class MaterialConverter: def __init__(self, exporter): + self._obj2mat = {} self._exporter = weakref.ref(exporter) self._pending = {} @@ -166,6 +172,12 @@ class MaterialConverter: self._propagate_material_settings(bm, layer) hsgmat.addLayer(layer.key) + # Cache this material for later + if bo in self._obj2mat: + self._obj2mat[bo].append(hsgmat.key) + else: + self._obj2mat[bo] = [hsgmat.key] + # Looks like we're done... return hsgmat.key @@ -214,7 +226,7 @@ class MaterialConverter: if texture.image is None: bitmap = self.add_object(plDynamicTextMap, name="{}_DynText".format(layer.key.name), bl=bo) else: - key = _Texture(texture) + key = _Texture(texture=texture) if key not in self._pending: print(" Stashing '{}' for conversion as '{}'".format(texture.image.name, str(key))) self._pending[key] = [layer,] @@ -226,6 +238,16 @@ class MaterialConverter: # We'll allow this, just for sanity's sake... pass + def export_prepared_layer(self, layer, image): + """This exports an externally prepared layer and image""" + key = _Texture(image=image) + if key not in self._pending: + print(" Stashing '{}' for conversion as '{}'".format(image.name, str(key))) + self._pending[key] = [layer,] + else: + print(" Found another user of '{}'".format(image.name)) + self._pending[key].append(layer) + def finalize(self): for key, layers in self._pending.items(): name = str(key) @@ -241,7 +263,7 @@ class MaterialConverter: # Some basic mipmap settings. numLevels = math.floor(math.log(max(eWidth, eHeight), 2)) + 1 if key.mipmap else 1 - compression = plBitmap.kDirectXCompression if key.mipmap else plBitmap.kUncompressed + compression = plBitmap.kDirectXCompression dxt = plBitmap.kDXT5 if key.use_alpha or key.calc_alpha else plBitmap.kDXT1 # Grab the image data from OpenGL and stuff it into the plBitmap @@ -286,6 +308,9 @@ class MaterialConverter: mipmap = pages[page] layer.texture = mipmap.key + def get_materials(self, bo): + return self._obj2mat[bo] + @property def _mgr(self): return self._exporter().mgr diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 39e5dec..ba82b59 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -217,7 +217,15 @@ class MeshConverter: return diface.key def _export_mesh(self, bo): - # First, we need to grab the object's mesh... + # Step 0.8: If this mesh wants to be light mapped, we need to go ahead and generate it. + if bo.plasma_modifiers.lightmap.enabled: + print(" Baking lightmap...") + print("====") + bpy.context.scene.objects.active = bo + bpy.ops.object.plasma_lightmap_autobake() + print("====") + + # Step 0.9: Update the mesh mesh = bo.data mesh.update(calc_tessface=True) diff --git a/korman/helpers.py b/korman/helpers.py new file mode 100644 index 0000000..28c2bb8 --- /dev/null +++ b/korman/helpers.py @@ -0,0 +1,29 @@ +# 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 . + +class GoodNeighbor: + """Leave Things the Way You Found Them! (TM)""" + + def __enter__(self): + self._tracking = {} + return self + + def track(self, cls, attr, value): + self._tracking[(cls, attr)] = getattr(cls, attr) + setattr(cls, attr, value) + + def __exit__(self, type, value, traceback): + for (cls, attr), value in self._tracking.items(): + setattr(cls, attr, value) diff --git a/korman/operators/__init__.py b/korman/operators/__init__.py index 12cf63f..f0c38fe 100644 --- a/korman/operators/__init__.py +++ b/korman/operators/__init__.py @@ -14,6 +14,7 @@ # along with Korman. If not, see . from . import op_export as exporter +from . import op_lightmap as lightmap from . import op_modifier as modifier from . import op_world as world diff --git a/korman/operators/op_export.py b/korman/operators/op_export.py index 565e175..16961f4 100644 --- a/korman/operators/op_export.py +++ b/korman/operators/op_export.py @@ -23,7 +23,6 @@ class ExportOperator(bpy.types.Operator): bl_idname = "export.plasma_age" bl_label = "Export Age" - bl_options = {"BLOCKING"} # Export specific props version = bpy.props.EnumProperty( diff --git a/korman/operators/op_lightmap.py b/korman/operators/op_lightmap.py new file mode 100644 index 0000000..c7957b7 --- /dev/null +++ b/korman/operators/op_lightmap.py @@ -0,0 +1,106 @@ +# 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 . + +import bpy +from ..helpers import GoodNeighbor + +class _LightmapOperator: + @classmethod + def poll(cls, context): + if context.object is not None: + return context.scene.render.engine == "PLASMA_GAME" + + +class LightmapAutobakeOperator(_LightmapOperator, bpy.types.Operator): + bl_idname = "object.plasma_lightmap_autobake" + bl_label = "Bake Lightmap" + bl_options = {"INTERNAL"} + + def execute(self, context): + with GoodNeighbor() as toggle: + # We need to ensure that we bake onto the "BlahObject_LIGHTMAPGEN" image + obj = context.active_object + data_images = bpy.data.images + im_name = "{}_LIGHTMAPGEN".format(obj.name) + size = obj.plasma_modifiers.lightmap.resolution + + im = data_images.get(im_name) + if im is None: + im = data_images.new(im_name, width=size, height=size) + elif im.size != (size, size): + # Force delete and recreate the image because the size is out of date + im.user_clear() + data_images.remove(im) + im = data_images.new(im_name, width=size, height=size) + + # This just wraps Blender's internal lightmap UV whatchacallit... + # We want to ensure that we use the UV Layer "LIGHTMAPGEN" and fetch the size from + # the lightmap modifier. What fun... + mesh = context.active_object.data + mesh.update() + + # Search for LIGHTMAPGEN + for uvtex in mesh.uv_textures: + if uvtex.name == "LIGHTMAPGEN": + toggle.track(mesh.uv_textures, "active", uvtex) + break + else: + # Gotta make it + uvtex = mesh.uv_textures.new("LIGHTMAPGEN") + toggle.track(mesh.uv_textures, "active", uvtex) + + # Now, enter edit mode on this mesh and unwrap. + bpy.ops.object.mode_set(mode="EDIT") + bpy.ops.mesh.select_all(action="SELECT") + bpy.ops.uv.lightmap_pack(PREF_CONTEXT="ALL_FACES", PREF_IMG_PX_SIZE=size) + bpy.ops.object.mode_set(mode="OBJECT") + + # Associate the image with all the new UVs + for i in mesh.uv_textures.active.data: + i.image = im + + # Bake settings + render = context.scene.render + toggle.track(render, "bake_type", "FULL") + toggle.track(render, "use_bake_to_vertex_color", False) + + for mat in obj.data.materials: + for tex in mat.texture_slots: + if tex is not None and tex.use: + toggle.track(tex, "use", False) + + # Now, we *finally* bake the lightmap... + # FIXME: Don't bake Plasma RT lights + bpy.ops.object.bake_image() + + # Done! + return {"FINISHED"} + + +class LightmapAutobakePreviewOperator(_LightmapOperator, bpy.types.Operator): + bl_idname = "object.plasma_lightmap_preview" + bl_label = "Preview Lightmap" + bl_options = {"INTERNAL"} + + def execute(self, context): + bpy.ops.object.plasma_lightmap_autobake() + + tex = bpy.data.textures.get("LIGHTMAPGEN_PREVIEW") + if tex is None: + tex = bpy.data.textures.new("LIGHTMAPGEN_PREVIEW", "IMAGE") + tex.extension = "CLIP" + tex.image = bpy.data.images["{}_LIGHTMAPGEN".format(context.active_object.name)] + + return {"FINISHED"} diff --git a/korman/properties/modifiers/__init__.py b/korman/properties/modifiers/__init__.py index ff03ec3..401b0c7 100644 --- a/korman/properties/modifiers/__init__.py +++ b/korman/properties/modifiers/__init__.py @@ -20,6 +20,7 @@ from .base import PlasmaModifierProperties from .logic import * from .physics import * from .region import * +from .render import * class PlasmaModifiers(bpy.types.PropertyGroup): def determine_next_id(self): diff --git a/korman/properties/modifiers/render.py b/korman/properties/modifiers/render.py new file mode 100644 index 0000000..bfdc0b6 --- /dev/null +++ b/korman/properties/modifiers/render.py @@ -0,0 +1,82 @@ +# 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 . + + +import bpy +from bpy.props import * +from PyHSPlasma import * + +from .base import PlasmaModifierProperties + +class PlasmaLightMapGen(PlasmaModifierProperties): + pl_id = "lightmap" + + bl_category = "Render" + bl_label = "Lightmap" + bl_description = "Auto-Bake Lightmap" + + quality = EnumProperty(name="Quality", + description="Resolution of lightmap", + items=[ + ("128", "128px", "128x128 pixels"), + ("256", "256px", "256x256 pixels"), + ("512", "512px", "512x512 pixels"), + ("1024", "1024px", "1024x1024 pixels"), + ]) + + def created(self, obj): + self.display_name = "{}_LIGHTMAPGEN".format(obj.name) + + def export(self, exporter, bo, so): + mat_mgr = exporter.mesh.material + materials = mat_mgr.get_materials(bo) + lightmap_im = bpy.data.images.get("{}_LIGHTMAPGEN".format(bo.name)) + + # Find the stupid UVTex + uvw_src = 0 + for i, uvtex in enumerate(bo.data.tessface_uv_textures): + if uvtex.name == "LIGHTMAPGEN": + uvw_src = i + break + else: + # TODO: raise exception + pass + + for matKey in materials: + layer = exporter.mgr.add_object(plLayer, name="{}_LIGHTMAPGEN".format(matKey.name), so=so) + layer.UVWSrc = uvw_src + + # Colors science'd from PRPs + layer.ambient = hsColorRGBA(1.0, 1.0, 1.0) + layer.preshade = hsColorRGBA(0.5, 0.5, 0.5) + layer.runtime = hsColorRGBA(0.5, 0.5, 0.5) + + # GMatState + gstate = layer.state + gstate.blendFlags |= hsGMatState.kBlendMult + gstate.clampFlags |= (hsGMatState.kClampTextureU | hsGMatState.kClampTextureV) + gstate.ZFlags |= hsGMatState.kZNoZWrite + gstate.miscFlags |= hsGMatState.kMiscLightMap + + mat = matKey.object + mat.compFlags |= hsGMaterial.kCompIsLightMapped + mat.addPiggyBack(layer.key) + + # Mmm... cheating + mat_mgr.export_prepared_layer(layer, lightmap_im) + + @property + def resolution(self): + return int(self.quality) diff --git a/korman/ui/modifiers/__init__.py b/korman/ui/modifiers/__init__.py index 83edafa..06220b9 100644 --- a/korman/ui/modifiers/__init__.py +++ b/korman/ui/modifiers/__init__.py @@ -16,3 +16,4 @@ from .logic import * from .physics import * from .region import * +from .render import * diff --git a/korman/ui/modifiers/render.py b/korman/ui/modifiers/render.py new file mode 100644 index 0000000..ac4936e --- /dev/null +++ b/korman/ui/modifiers/render.py @@ -0,0 +1,31 @@ +# 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 . + +import bpy + +def lightmap(modifier, layout, context): + col = layout.column(align=True) + col.row(align=True).prop(modifier, "quality", expand=True) + 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 + # the backing image name to see if it's for this lightmap. If so, you have a preview. If not, + # well... It was nice knowing you! + tex = bpy.data.textures.get("LIGHTMAPGEN_PREVIEW") + if tex is not None: + im_name = "{}_LIGHTMAPGEN".format(context.active_object.name) + if tex.image.name == im_name: + layout.template_preview(tex, show_buttons=False)