Browse Source

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!
pull/6/head
Adam Johnson 10 years ago
parent
commit
65d1d71416
  1. 47
      korman/exporter/material.py
  2. 10
      korman/exporter/mesh.py
  3. 29
      korman/helpers.py
  4. 1
      korman/operators/__init__.py
  5. 1
      korman/operators/op_export.py
  6. 106
      korman/operators/op_lightmap.py
  7. 1
      korman/properties/modifiers/__init__.py
  8. 82
      korman/properties/modifiers/render.py
  9. 1
      korman/ui/modifiers/__init__.py
  10. 31
      korman/ui/modifiers/render.py

47
korman/exporter/material.py

@ -102,11 +102,19 @@ class _GLTexture:
class _Texture: class _Texture:
def __init__(self, texture): def __init__(self, texture=None, image=None):
self.image = texture.image assert (texture or image)
self.calc_alpha = texture.use_calculate_alpha
self.mipmap = texture.use_mipmap if texture is not None:
self.use_alpha = texture.use_alpha 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): def __eq__(self, other):
if not isinstance(other, _Texture): if not isinstance(other, _Texture):
@ -121,10 +129,7 @@ class _Texture:
return hash(self.image.name) ^ hash(self.calc_alpha) return hash(self.image.name) ^ hash(self.calc_alpha)
def __str__(self): def __str__(self):
if self.mipmap: name = self._change_extension(self.image.name, ".dds")
name = self._change_extension(self.image.name, ".dds")
else:
name = self._change_extension(self.image.name, ".bmp")
if self.calc_alpha: if self.calc_alpha:
name = "ALPHAGEN_{}".format(name) name = "ALPHAGEN_{}".format(name)
return name return name
@ -149,6 +154,7 @@ class _Texture:
class MaterialConverter: class MaterialConverter:
def __init__(self, exporter): def __init__(self, exporter):
self._obj2mat = {}
self._exporter = weakref.ref(exporter) self._exporter = weakref.ref(exporter)
self._pending = {} self._pending = {}
@ -166,6 +172,12 @@ class MaterialConverter:
self._propagate_material_settings(bm, layer) self._propagate_material_settings(bm, layer)
hsgmat.addLayer(layer.key) 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... # Looks like we're done...
return hsgmat.key return hsgmat.key
@ -214,7 +226,7 @@ class MaterialConverter:
if texture.image is None: if texture.image is None:
bitmap = self.add_object(plDynamicTextMap, name="{}_DynText".format(layer.key.name), bl=bo) bitmap = self.add_object(plDynamicTextMap, name="{}_DynText".format(layer.key.name), bl=bo)
else: else:
key = _Texture(texture) key = _Texture(texture=texture)
if key not in self._pending: if key not in self._pending:
print(" Stashing '{}' for conversion as '{}'".format(texture.image.name, str(key))) print(" Stashing '{}' for conversion as '{}'".format(texture.image.name, str(key)))
self._pending[key] = [layer,] self._pending[key] = [layer,]
@ -226,6 +238,16 @@ class MaterialConverter:
# We'll allow this, just for sanity's sake... # We'll allow this, just for sanity's sake...
pass 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): def finalize(self):
for key, layers in self._pending.items(): for key, layers in self._pending.items():
name = str(key) name = str(key)
@ -241,7 +263,7 @@ class MaterialConverter:
# Some basic mipmap settings. # Some basic mipmap settings.
numLevels = math.floor(math.log(max(eWidth, eHeight), 2)) + 1 if key.mipmap else 1 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 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 # Grab the image data from OpenGL and stuff it into the plBitmap
@ -286,6 +308,9 @@ class MaterialConverter:
mipmap = pages[page] mipmap = pages[page]
layer.texture = mipmap.key layer.texture = mipmap.key
def get_materials(self, bo):
return self._obj2mat[bo]
@property @property
def _mgr(self): def _mgr(self):
return self._exporter().mgr return self._exporter().mgr

10
korman/exporter/mesh.py

@ -217,7 +217,15 @@ class MeshConverter:
return diface.key return diface.key
def _export_mesh(self, bo): 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 = bo.data
mesh.update(calc_tessface=True) mesh.update(calc_tessface=True)

29
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 <http://www.gnu.org/licenses/>.
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)

1
korman/operators/__init__.py

@ -14,6 +14,7 @@
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
from . import op_export as exporter from . import op_export as exporter
from . import op_lightmap as lightmap
from . import op_modifier as modifier from . import op_modifier as modifier
from . import op_world as world from . import op_world as world

1
korman/operators/op_export.py

@ -23,7 +23,6 @@ class ExportOperator(bpy.types.Operator):
bl_idname = "export.plasma_age" bl_idname = "export.plasma_age"
bl_label = "Export Age" bl_label = "Export Age"
bl_options = {"BLOCKING"}
# Export specific props # Export specific props
version = bpy.props.EnumProperty( version = bpy.props.EnumProperty(

106
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 <http://www.gnu.org/licenses/>.
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"}

1
korman/properties/modifiers/__init__.py

@ -20,6 +20,7 @@ from .base import PlasmaModifierProperties
from .logic import * from .logic import *
from .physics import * from .physics import *
from .region import * from .region import *
from .render import *
class PlasmaModifiers(bpy.types.PropertyGroup): class PlasmaModifiers(bpy.types.PropertyGroup):
def determine_next_id(self): def determine_next_id(self):

82
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 <http://www.gnu.org/licenses/>.
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)

1
korman/ui/modifiers/__init__.py

@ -16,3 +16,4 @@
from .logic import * from .logic import *
from .physics import * from .physics import *
from .region import * from .region import *
from .render import *

31
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 <http://www.gnu.org/licenses/>.
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)
Loading…
Cancel
Save