From 52e4aaa21c179bccbfce671722742410ae7bdd58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 12 Feb 2021 20:12:55 +0100 Subject: [PATCH] Add button to create a new custom material --- .../custom_mat_deferred.frag.glsl | 31 ++++ .../custom_mat_deferred.vert.glsl | 20 +++ .../custom_mat_forward.frag.glsl | 12 ++ .../custom_mat_forward.vert.glsl | 11 ++ blender/arm/props_ui.py | 151 +++++++++++++++++- 5 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl create mode 100644 Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl create mode 100644 Shaders/custom_mat_presets/custom_mat_forward.frag.glsl create mode 100644 Shaders/custom_mat_presets/custom_mat_forward.vert.glsl diff --git a/Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl b/Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl new file mode 100644 index 00000000..9b1deadd --- /dev/null +++ b/Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl @@ -0,0 +1,31 @@ +#version 450 + +// Include functions for gbuffer operations (packFloat2() etc.) +#include "../std/gbuffer.glsl" + +// World-space normal from the vertex shader stage +in vec3 wnormal; + +// Gbuffer output. Deferred rendering uses the following layout: +// [0]: normal x normal y roughness metallic/matID +// [1]: base color r base color g base color b occlusion/specular +out vec4 fragColor[2]; + +void main() { + // Pack normals into 2 components to fit into the gbuffer + vec3 n = normalize(wnormal); + n /= (abs(n.x) + abs(n.y) + abs(n.z)); + n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy); + + // Define PBR material values + vec3 basecol = vec3(1.0); + float roughness = 0.0; + float metallic = 0.0; + float occlusion = 1.0; + float specular = 1.0; + uint materialId = 0; + + // Store in gbuffer (see layout table above) + fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, materialId)); + fragColor[1] = vec4(basecol.rgb, packFloat2(occlusion, specular)); +} diff --git a/Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl b/Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl new file mode 100644 index 00000000..a5d66425 --- /dev/null +++ b/Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl @@ -0,0 +1,20 @@ +#version 450 + +// World to view projection matrix to correctly position the vertex on screen +uniform mat4 WVP; +// Matrix to transform normals from local into world space +uniform mat3 N; + +// Position and normal vector in local space for the current vertex +// Armory packs the vertex data to preserve memory, so nor.z values are +// saved in pos.w +in vec4 pos; // pos.xyz, nor.w +in vec2 nor; // nor.xy + +// Normal vector in world space +out vec3 wnormal; + +void main() { + wnormal = normalize(N * vec3(nor.xy, pos.w)); + gl_Position = WVP * vec4(pos.xyz, 1.0); +} diff --git a/Shaders/custom_mat_presets/custom_mat_forward.frag.glsl b/Shaders/custom_mat_presets/custom_mat_forward.frag.glsl new file mode 100644 index 00000000..a38e1ea0 --- /dev/null +++ b/Shaders/custom_mat_presets/custom_mat_forward.frag.glsl @@ -0,0 +1,12 @@ +#version 450 + +// World-space normal from the vertex shader stage +in vec3 wnormal; + +// Color of each fragment on the screen +out vec4 fragColor; + +void main() { + // Shadeless white color + fragColor = vec4(1.0); +} diff --git a/Shaders/custom_mat_presets/custom_mat_forward.vert.glsl b/Shaders/custom_mat_presets/custom_mat_forward.vert.glsl new file mode 100644 index 00000000..12f10a5d --- /dev/null +++ b/Shaders/custom_mat_presets/custom_mat_forward.vert.glsl @@ -0,0 +1,11 @@ +#version 450 + +// World to view projection matrix to correctly position the vertex on screen +uniform mat4 WVP; + +// Position and normal vector in local space for the current vertex +in vec3 pos; + +void main() { + gl_Position = WVP * vec4(pos, 1.0); +} diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index d58b3012..4a364b95 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1,5 +1,6 @@ +import json import os -import time +import shutil import bpy from bpy.props import * @@ -286,6 +287,146 @@ class InvalidateMaterialCacheButton(bpy.types.Operator): context.material.signature = '' return{'FINISHED'} + +class ARM_OT_NewCustomMaterial(bpy.types.Operator): + bl_idname = "arm.new_custom_material" + bl_label = "New Custom Material" + bl_description = "Add a new custom material. This will create all the necessary files and folders" + + def poll_mat_name(self, context): + project_dir = arm.utils.get_fp() + shader_dir_dst = os.path.join(project_dir, 'Shaders') + mat_name = arm.utils.safestr(self.mat_name) + + self.mat_exists = os.path.isdir(os.path.join(project_dir, 'Bundled', mat_name)) + + vert_exists = os.path.isfile(os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl')) + frag_exists = os.path.isfile(os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl')) + self.shader_exists = vert_exists or frag_exists + + mat_name: StringProperty( + name='Material Name', description='The name of the new material', + default='MyCustomMaterial', + update=poll_mat_name) + mode: EnumProperty( + name='Target RP', description='Choose for which render path mode the new material is created', + default='deferred', + items=[('deferred', 'Deferred', 'Create the material for a deferred render path'), + ('forward', 'Forward', 'Create the material for a forward render path')]) + mat_exists: BoolProperty( + name='Material Already Exists', + default=False, + options={'HIDDEN', 'SKIP_SAVE'}) + shader_exists: BoolProperty( + name='Shaders Already Exist', + default=False, + options={'HIDDEN', 'SKIP_SAVE'}) + + def invoke(self, context, event): + if not bpy.data.is_saved: + self.report({'INFO'}, "Please save your file first") + return {"CANCELLED"} + + # Try to set deferred/forward based on the selected render path + try: + self.mode = 'forward' if arm.utils.get_rp().rp_renderer == 'Forward' else 'deferred' + except IndexError: + # No render path, use default (deferred) + pass + + self.poll_mat_name(context) + wm = context.window_manager + return wm.invoke_props_dialog(self, width=300) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + layout.prop(self, 'mat_name') + layout.prop(self, 'mode', expand=True) + + if self.mat_exists: + box = layout.box() + box.alert = True + col = box.column(align=True) + col.label(text='A custom material with that name already exists,', icon='ERROR') + col.label(text='clicking on \'OK\' will override the material!', icon='BLANK1') + + if self.shader_exists: + box = layout.box() + box.alert = True + col = box.column(align=True) + col.label(text='Shader file(s) with that name already exists,', icon='ERROR') + col.label(text='clicking on \'OK\' will override the shader(s)!', icon='BLANK1') + + def execute(self, context): + if self.mat_name == '': + return {'CANCELLED'} + + project_dir = arm.utils.get_fp() + shader_dir_src = os.path.join(arm.utils.get_sdk_path(), 'armory', 'Shaders', 'custom_mat_presets') + shader_dir_dst = os.path.join(project_dir, 'Shaders') + mat_name = arm.utils.safestr(self.mat_name) + mat_dir = os.path.join(project_dir, 'Bundled', mat_name) + + os.makedirs(mat_dir, exist_ok=True) + os.makedirs(shader_dir_dst, exist_ok=True) + + # Shader data + if self.mode == 'forward': + col_attachments = ['RGBA64'] + constants = [{'link': '_worldViewProjectionMatrix', 'name': 'WVP', 'type': 'mat4'}] + vertex_elems = [{'name': 'pos', 'data': 'short4norm'}] + else: + col_attachments = ['RGBA64', 'RGBA64'] + constants = [ + {'link': '_worldViewProjectionMatrix', 'name': 'WVP', 'type': 'mat4'}, + {'link': '_normalMatrix', 'name': 'N', 'type': 'mat3'} + ] + vertex_elems = [ + {'name': 'pos', 'data': 'short4norm'}, + {'name': 'nor', 'data': 'short2norm'} + ] + + con = { + 'color_attachments': col_attachments, + 'compare_mode': 'less', + 'constants': constants, + 'cull_mode': 'clockwise', + 'depth_write': True, + 'fragment_shader': f'{mat_name}.frag', + 'name': 'mesh', + 'texture_units': [], + 'vertex_shader': f'{mat_name}.vert', + 'vertex_elements': vertex_elems + } + data = { + 'shader_datas': [{ + 'contexts': [con], + 'name': f'{mat_name}' + }] + } + + # Save shader data file + with open(os.path.join(mat_dir, f'{mat_name}.json'), 'w') as datafile: + json.dump(data, datafile, indent=4, sort_keys=True) + + # Copy preset shaders to project + if self.mode == 'forward': + shutil.copy(os.path.join(shader_dir_src, 'custom_mat_forward.frag.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl')) + shutil.copy(os.path.join(shader_dir_src, 'custom_mat_forward.vert.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl')) + else: + shutil.copy(os.path.join(shader_dir_src, 'custom_mat_deferred.frag.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl')) + shutil.copy(os.path.join(shader_dir_src, 'custom_mat_deferred.vert.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl')) + + # True if called from the material properties tab, else it was called from the search menu + if hasattr(context, 'material') and context.material is not None: + context.material.arm_custom_material = mat_name + + return{'FINISHED'} + + class ARM_PT_MaterialPropsPanel(bpy.types.Panel): bl_label = "Armory Props" bl_space_type = "PROPERTIES" @@ -317,7 +458,9 @@ class ARM_PT_MaterialPropsPanel(bpy.types.Panel): columnb.enabled = mat.arm_discard columnb.prop(mat, 'arm_discard_opacity') columnb.prop(mat, 'arm_discard_opacity_shadows') - layout.prop(mat, 'arm_custom_material') + row = layout.row(align=True) + row.prop(mat, 'arm_custom_material') + row.operator('arm.new_custom_material', text='', icon='ADD') layout.prop(mat, 'arm_skip_context') layout.prop(mat, 'arm_particle_fade') layout.prop(mat, 'arm_billboard') @@ -2546,6 +2689,7 @@ def register(): bpy.utils.register_class(ARM_PT_WorldPropsPanel) bpy.utils.register_class(InvalidateCacheButton) bpy.utils.register_class(InvalidateMaterialCacheButton) + bpy.utils.register_class(ARM_OT_NewCustomMaterial) bpy.utils.register_class(ARM_PT_MaterialPropsPanel) bpy.utils.register_class(ARM_PT_MaterialBlendingPropsPanel) bpy.utils.register_class(ARM_PT_MaterialDriverPropsPanel) @@ -2626,6 +2770,7 @@ def unregister(): bpy.utils.unregister_class(ARM_PT_ScenePropsPanel) bpy.utils.unregister_class(InvalidateCacheButton) bpy.utils.unregister_class(InvalidateMaterialCacheButton) + bpy.utils.unregister_class(ARM_OT_NewCustomMaterial) bpy.utils.unregister_class(ARM_PT_MaterialDriverPropsPanel) bpy.utils.unregister_class(ARM_PT_MaterialBlendingPropsPanel) bpy.utils.unregister_class(ARM_PT_MaterialPropsPanel) @@ -2671,4 +2816,4 @@ def unregister(): bpy.utils.unregister_class(ArmProxyApplyAllButton) bpy.utils.unregister_class(ArmSyncProxyButton) bpy.utils.unregister_class(ArmPrintTraitsButton) - bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) \ No newline at end of file + bpy.utils.unregister_class(ARM_PT_MaterialNodePanel)