From b360bb3cdc7ea3f9f5aba94d11e4f9d4f90c42a7 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Wed, 20 Oct 2021 15:04:44 +0300 Subject: [PATCH] Make it possible to set up and export some common effects in Blender A sky effect can now be enabled in world properties. If sky is enabled and any materials use IBL, en environment map effect is added. If any lights have shadows enabled, a shadow map effect is exported. --- blender/io_mspgl/export_scene.py | 100 ++++++++++++++++++++++++++++++- blender/io_mspgl/properties.py | 47 ++++++++++++++- blender/io_mspgl/scene.py | 18 +++++- 3 files changed, 160 insertions(+), 5 deletions(-) diff --git a/blender/io_mspgl/export_scene.py b/blender/io_mspgl/export_scene.py index 0557073c..a069af2a 100644 --- a/blender/io_mspgl/export_scene.py +++ b/blender/io_mspgl/export_scene.py @@ -1,5 +1,7 @@ import math import os +import itertools +import mathutils class SceneExporter: def export_to_file(self, context, out_fn, *, selected_only=False, visible_only=True, collection=True, skip_existing=True): @@ -131,21 +133,82 @@ class SceneExporter: any_opaque = False any_blended = False + use_ibl = False + use_shadow = False + shadowed_lights = [] + shadow_casters = [] s = scene while s: if s.instances: any_opaque = True if s.blended_instances: any_blended = True + if s.use_ibl: + use_ibl = True + if s.use_shadow: + use_shadow = True + shadowed_lights += [l.data for l in s.lights if l.data.use_shadow] + for i in itertools.chain(s.instances, s.blended_instances): + p = i.prototype + if p.material_slots and p.material_slots[0].material and p.material_slots[0].material.shadow_method!='NONE': + shadow_casters.append(i) s = s.background_set + shadowed_lights.sort(key=lambda l:l.shadow_map_size, reverse=True) main_tags = [] if any_opaque: main_tags.append("") if any_blended: + main_tags.append("blended") - self.add_content_steps(seq_res, "content", lighting_res, main_tags) + content = "content" + if use_ibl and scene.use_sky: + self.add_auxiliary_sequence(seq_res, "environment", "sky", ((0.0, 0.0, 0.0, 0.0), 1.0), main_tags, lighting_res) + + st = Statement("effect", "environment") + st.sub.append(Statement("type", Token("environment_map"))) + st.sub.append(Statement("size", 32)) + st.sub.append(Statement("roughness_levels", 2)) + st.sub.append(Statement("fixed_position", 0.0, 0.0, 0.0)) + st.sub.append(Statement("content", content)) + st.sub.append(Statement("environment", "environment_sequence")) + + seq_res.statements.append(st) + content = "environment" + + if scene.use_sky: + st = Statement("effect", "sky") + st.sub.append(Statement("type", Token("sky"))) + st.sub.append(seq_res.create_reference_statement("sun", resources[scene.sun_light.name+".light"])) + st.sub.append(Statement("content", content)) + + seq_res.statements.append(st) + content = "sky" + + if use_shadow: + self.add_auxiliary_sequence(seq_res, "shadow", "content", (None, 1.0), ["shadow"], None) + self.add_auxiliary_sequence(seq_res, "thsm", "content", (None, 1.0), ["shadow_thsm"], None) + + st = Statement("effect", "shadow_map") + st.sub.append(Statement("type", Token("shadow_map"))) + st.sub.append(Statement("size", *self.compute_shadowmap_size(shadowed_lights))) + target, radius = self.compute_bounding_sphere(shadow_casters) + st.sub.append(Statement("target", *target)) + st.sub.append(Statement("radius", radius)) + st.sub.append(Statement("content", content)) + st.sub.append(seq_res.create_reference_statement("lighting", lighting_res)) + for l in shadowed_lights: + ss = seq_res.create_reference_statement("light", resources[l.name+".light"]) + ss.sub.append(Statement("size", int(l.shadow_map_size))) + shadow_caster = "thsm_sequence" if l.type=='POINT' else "shadow_sequence" + ss.sub.append(Statement("shadow_caster", shadow_caster)) + st.sub.append(ss) + + seq_res.statements.append(st) + content = "shadow_map" + + self.add_content_steps(seq_res, content, lighting_res, main_tags) if scene.use_ao: ss = Statement("postprocessor") @@ -193,3 +256,38 @@ class SceneExporter: if lighting: st.sub.append(seq_res.create_reference_statement("lighting", lighting)) seq_res.statements.append(st) + + def add_auxiliary_sequence(self, seq_res, aux_name, content, clear_values, step_tags, lighting): + seq_name = os.path.splitext(seq_res.name)[0] + + from .datafile import Resource, Statement + aux_seq_res = Resource("{}_{}.seq".format(seq_name, aux_name), "sequence") + self.add_clear(aux_seq_res.statements, *clear_values) + aux_seq_res.statements.append(Statement("renderable", "content")) + self.add_content_steps(aux_seq_res, "content", lighting, step_tags) + + st = seq_res.create_reference_statement("sequence", aux_name+"_sequence", aux_seq_res) + st.sub.append(Statement("renderable", "content", content)) + seq_res.statements.append(st) + + def compute_shadowmap_size(self, lights): + total_area = 0 + for l in lights: + s = int(l.shadow_map_size) + total_area += s*s + + size = 1 + while size*sizetotal_area*2: + return (size, size//2) + else: + return (size, size) + + def compute_bounding_sphere(self, instances): + points = [] + for i in instances: + points += [i.matrix_world@mathutils.Vector(c) for c in i.prototype.bound_box] + + from .util import compute_bounding_sphere + return compute_bounding_sphere(points) diff --git a/blender/io_mspgl/properties.py b/blender/io_mspgl/properties.py index 7b67955a..ef401826 100644 --- a/blender/io_mspgl/properties.py +++ b/blender/io_mspgl/properties.py @@ -12,6 +12,20 @@ class MspGLSceneProperties(bpy.types.Panel): self.layout.prop(scene, "export_disposition") +class MspGLWorldProperties(bpy.types.Panel): + bl_idname = "WORLD_PT_mspgl_properties" + bl_label = "MspGL properties" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "world" + + def draw(self, context): + world = context.scene.world + + self.layout.prop(world, "use_sky") + if world.use_sky: + self.layout.prop(world, "sun_light") + class MspGLMeshProperties(bpy.types.Panel): bl_idname = "MESH_PT_mspgl_properties" bl_label = "MspGL properties" @@ -143,8 +157,25 @@ class MspGLTextureNodeProperties(bpy.types.Panel): self.layout.prop(node, "use_mipmap") self.layout.prop(node, "max_anisotropy") +class MspGLLightProperties(bpy.types.Panel): + bl_idname = "LIGHT_PT_mspgl_properties" + bl_label = "MspGL properties" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "data" + + @classmethod + def poll(cls, context): + return context.active_object.type=="LIGHT" + + def draw(self, context): + light = context.active_object.data + + if light.use_shadow: + self.layout.prop(light, "shadow_map_size") + class MspGLRenderProperties(bpy.types.Panel): - bl_idname = "WORLD_PT_mspgl_properties" + bl_idname = "RENDER_PT_mspgl_properties" bl_label = "MspGL properties" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -188,7 +219,7 @@ class MspGLUniformList(bpy.types.UIList): layout.prop(uniform, "name", text="", emboss=False, icon_value=icon) layout.label(text="({})".format(", ".join("{:.3f}".format(v) for v in uniform.values[:uniform.size]))) -classes = [MspGLSceneProperties, MspGLMeshProperties, MspGLObjectProperties, MspGLMaterialProperties, +classes = [MspGLSceneProperties, MspGLWorldProperties, MspGLMeshProperties, MspGLObjectProperties, MspGLMaterialProperties, MspGLTextureNodeProperties, MspGLLightProperties, MspGLRenderProperties, MspGLRenderMethod, MspGLRenderMethodList, MspGLUniform, MspGLUniformList] @@ -204,6 +235,9 @@ def register_properties(): bpy.types.Scene.use_hdr = bpy.props.BoolProperty(name="High dynamic range", description="Use a range render target with a floating point format", default=False) bpy.types.Scene.ao_samples = bpy.props.IntProperty(name="Ambient occlusion samples", description="Number of samples to use for ambient occlusion", min=8, max=128, default=32) + bpy.types.World.use_sky = bpy.props.BoolProperty(name="Realtime sky", description="Use a realtime rendered sky background", default=False) + bpy.types.World.sun_light = bpy.props.PointerProperty(type=bpy.types.Light, name="Sun", description="Light to use as sun for the sky") + bpy.types.Mesh.winding_test = bpy.props.BoolProperty(name="Winding test", description="Perform winding test to skip back faces") bpy.types.Mesh.smoothing = bpy.props.EnumProperty(name="Smoothing", description="Smoothing method to use", default="MSPGL", items=(("NONE", "None", "No smoothing"), @@ -245,6 +279,15 @@ def register_properties(): bpy.types.ShaderNodeTexImage.use_mipmap = bpy.props.BoolProperty(name="Use mipmaps", description="Use mipmaps (automatically generated) for the texture", default=True) bpy.types.ShaderNodeTexImage.max_anisotropy = bpy.props.FloatProperty(name="Maximum anisotropy", description="Maximum anisotropy to use in texture filtering", min=1, max=16, default=1) + bpy.types.Light.shadow_map_size = bpy.props.EnumProperty(name="Shadow map size", description="Size of shadow map to use for rendering shadows", default="4096", + items=(("256", "256", ""), + ("512", "512", ""), + ("1024", "1024", ""), + ("2048", "2048", ""), + ("4096", "4096", ""), + ("8192", "8192", ""), + ("16384", "16384", ""))) + def unregister_properties(): for c in classes: bpy.utils.unregister_class(c) diff --git a/blender/io_mspgl/scene.py b/blender/io_mspgl/scene.py index 03e239e6..e4f31067 100644 --- a/blender/io_mspgl/scene.py +++ b/blender/io_mspgl/scene.py @@ -25,6 +25,8 @@ class Scene: self.instances = [] self.blended_instances = [] self.lights = [] + self.realtime_sky = False + self.sun_light = None self.ambient_light = mathutils.Color((0.0, 0.0, 0.0)) self.exposure = scene.view_settings.exposure @@ -43,6 +45,12 @@ class Scene: s = surface_node.inputs["Strength"].default_value self.ambient_light = mathutils.Color(c[:3])*s + self.use_sky = scene.world.use_sky and scene.world.sun_light + self.sun_light = scene.world.sun_light + + self.use_shadow = False + self.use_ibl = False + objects = scene.objects[:] objects.sort(key=lambda o:o.name) if obj_filter: @@ -57,13 +65,19 @@ class Scene: clones = [c for c in objects if is_same_object(o, c)] self.prototypes.append(o) instance_list = self.instances - if o.material_slots and o.material_slots[0].material and o.material_slots[0].material.blend_method=='BLEND': - instance_list = self.blended_instances + if o.material_slots and o.material_slots[0].material: + mat = o.material_slots[0].material + if mat.blend_method=='BLEND': + instance_list = self.blended_instances + if mat.image_based_lighting: + self.use_ibl = True for c in clones: instance_list.append(Instance(c, o)) processed.add(c.name) elif o.type=='LIGHT': self.lights.append(o) + if o.data.use_shadow: + self.use_shadow = True def get_chain(self): result = [] -- 2.45.2