diff options
-rw-r--r-- | intern/cycles/blender/addon/ui.py | 36 | ||||
-rw-r--r-- | source/blender/blenloader/intern/versioning_legacy.c | 3 | ||||
-rw-r--r-- | source/blender/editors/object/object_bake.c | 15 | ||||
-rw-r--r-- | source/blender/editors/object/object_bake_api.c | 38 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_scene_defaults.h | 4 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_scene_types.h | 13 | ||||
-rw-r--r-- | source/blender/makesdna/intern/dna_rename_defs.h | 1 | ||||
-rw-r--r-- | source/blender/makesrna/RNA_enum_items.h | 1 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_scene.c | 33 | ||||
-rw-r--r-- | source/blender/render/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/render/RE_bake.h | 8 | ||||
-rw-r--r-- | source/blender/render/RE_multires_bake.h | 3 | ||||
-rw-r--r-- | source/blender/render/RE_texture_margin.h | 45 | ||||
-rw-r--r-- | source/blender/render/intern/bake.c | 19 | ||||
-rw-r--r-- | source/blender/render/intern/multires_bake.c | 23 | ||||
-rw-r--r-- | source/blender/render/intern/texture_margin.cc | 569 |
16 files changed, 783 insertions, 30 deletions
diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index e4fbc898070..ddef3f63641 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -1803,18 +1803,45 @@ class CYCLES_RENDER_PT_bake_output(CyclesButtonsPanel, Panel): rd = scene.render if rd.use_bake_multires: - layout.prop(rd, "bake_margin") layout.prop(rd, "use_bake_clear", text="Clear Image") - if rd.bake_type == 'DISPLACEMENT': layout.prop(rd, "use_bake_lores_mesh") else: layout.prop(cbk, "target") - if cbk.target == 'IMAGE_TEXTURES': - layout.prop(cbk, "margin") layout.prop(cbk, "use_clear", text="Clear Image") +class CYCLES_RENDER_PT_bake_output_margin(CyclesButtonsPanel, Panel): + bl_label = "Margin" + bl_context = "render" + bl_parent_id = "CYCLES_RENDER_PT_bake_output" + COMPAT_ENGINES = {'CYCLES'} + + @classmethod + def poll(cls, context): + scene = context.scene + cbk = scene.render.bake + return cbk.target == 'IMAGE_TEXTURES' + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + scene = context.scene + cscene = scene.cycles + cbk = scene.render.bake + rd = scene.render + + if rd.use_bake_multires: + layout.prop(rd, "bake_margin_type", text="Type") + layout.prop(rd, "bake_margin", text="Size") + else: + if cbk.target == 'IMAGE_TEXTURES': + layout.prop(cbk, "margin_type", text="Type") + layout.prop(cbk, "margin", text="Size") + + class CYCLES_RENDER_PT_debug(CyclesDebugButtonsPanel, Panel): bl_label = "Debug" @@ -2183,6 +2210,7 @@ classes = ( CYCLES_RENDER_PT_bake_influence, CYCLES_RENDER_PT_bake_selected_to_active, CYCLES_RENDER_PT_bake_output, + CYCLES_RENDER_PT_bake_output_margin, CYCLES_RENDER_PT_debug, node_panel(CYCLES_MATERIAL_PT_settings), node_panel(CYCLES_MATERIAL_PT_settings_surface), diff --git a/source/blender/blenloader/intern/versioning_legacy.c b/source/blender/blenloader/intern/versioning_legacy.c index 2fceb42262e..94720ad0b0a 100644 --- a/source/blender/blenloader/intern/versioning_legacy.c +++ b/source/blender/blenloader/intern/versioning_legacy.c @@ -1859,7 +1859,8 @@ void blo_do_versions_pre250(FileData *fd, Library *lib, Main *bmain) if (bmain->subversionfile < 4) { for (sce = bmain->scenes.first; sce; sce = sce->id.next) { sce->r.bake_mode = 1; /* prevent to include render stuff here */ - sce->r.bake_filter = 16; + sce->r.bake_margin = 16; + sce->r.bake_margin_type = R_BAKE_ADJACENT_FACES; sce->r.bake_flag = R_BAKE_CLEAR; } } diff --git a/source/blender/editors/object/object_bake.c b/source/blender/editors/object/object_bake.c index 1b6b0c78037..a5e6e7f0852 100644 --- a/source/blender/editors/object/object_bake.c +++ b/source/blender/editors/object/object_bake.c @@ -108,8 +108,10 @@ typedef struct { ListBase data; /** Clear the images before baking */ bool bake_clear; - /** Bake-filter, aka margin */ - int bake_filter; + /** margin size in pixels*/ + int bake_margin; + /** margin type */ + char bake_margin_type; /** mode of baking (displacement, normals, AO) */ short mode; /** Use low-resolution mesh when baking displacement maps */ @@ -372,7 +374,8 @@ static int multiresbake_image_exec_locked(bContext *C, wmOperator *op) /* copy data stored in job descriptor */ bkr.scene = scene; - bkr.bake_filter = scene->r.bake_filter; + bkr.bake_margin = scene->r.bake_margin; + bkr.bake_margin_type = scene->r.bake_margin_type; bkr.mode = scene->r.bake_mode; bkr.use_lores_mesh = scene->r.bake_flag & R_BAKE_LORES_MESH; bkr.bias = scene->r.bake_biasdist; @@ -416,7 +419,8 @@ static void init_multiresbake_job(bContext *C, MultiresBakeJob *bkj) /* backup scene settings, so their changing in UI would take no effect on baker */ bkj->scene = scene; - bkj->bake_filter = scene->r.bake_filter; + bkj->bake_margin = scene->r.bake_margin; + bkj->bake_margin_type = scene->r.bake_margin_type; bkj->mode = scene->r.bake_mode; bkj->use_lores_mesh = scene->r.bake_flag & R_BAKE_LORES_MESH; bkj->bake_clear = scene->r.bake_flag & R_BAKE_CLEAR; @@ -477,7 +481,8 @@ static void multiresbake_startjob(void *bkv, short *stop, short *do_update, floa /* copy data stored in job descriptor */ bkr.scene = bkj->scene; - bkr.bake_filter = bkj->bake_filter; + bkr.bake_margin = bkj->bake_margin; + bkr.bake_margin_type = bkj->bake_margin_type; bkr.mode = bkj->mode; bkr.use_lores_mesh = bkj->use_lores_mesh; bkr.user_scale = bkj->user_scale; diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c index 0de34e21462..d56d0edd5a2 100644 --- a/source/blender/editors/object/object_bake_api.c +++ b/source/blender/editors/object/object_bake_api.c @@ -89,6 +89,7 @@ typedef struct BakeAPIRender { eScenePassType pass_type; int pass_filter; int margin; + eBakeMarginType margin_type; bool is_clear; bool is_selected_to_active; @@ -184,8 +185,11 @@ static bool write_internal_bake_pixels(Image *image, const int width, const int height, const int margin, + const char margin_type, const bool is_clear, - const bool is_noncolor) + const bool is_noncolor, + Mesh const *mesh, + char const *uv_layer) { ImBuf *ibuf; void *lock; @@ -281,7 +285,7 @@ static bool write_internal_bake_pixels(Image *image, /* margins */ if (margin > 0) { - RE_bake_margin(ibuf, mask_buffer, margin); + RE_bake_margin(ibuf, mask_buffer, margin, margin_type, mesh, uv_layer); } ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; @@ -327,8 +331,11 @@ static bool write_external_bake_pixels(const char *filepath, const int width, const int height, const int margin, + const int margin_type, ImageFormatData *im_format, - const bool is_noncolor) + const bool is_noncolor, + Mesh const *mesh, + char const *uv_layer) { ImBuf *ibuf = NULL; bool ok = false; @@ -385,7 +392,7 @@ static bool write_external_bake_pixels(const char *filepath, mask_buffer = MEM_callocN(sizeof(char) * num_pixels, "Bake Mask"); RE_bake_mask_fill(pixel_array, num_pixels, mask_buffer); - RE_bake_margin(ibuf, mask_buffer, margin); + RE_bake_margin(ibuf, mask_buffer, margin, margin_type, mesh, uv_layer); if (mask_buffer) { MEM_freeN(mask_buffer); @@ -770,6 +777,7 @@ static bool bake_targets_output_internal(const BakeAPIRender *bkr, ReportList *reports) { bool all_ok = true; + const Mesh *me = (Mesh *)ob->data; for (int i = 0; i < targets->num_images; i++) { BakeImage *bk_image = &targets->images[i]; @@ -780,8 +788,11 @@ static bool bake_targets_output_internal(const BakeAPIRender *bkr, bk_image->width, bk_image->height, bkr->margin, + bkr->margin_type, bkr->is_clear, - targets->is_noncolor); + targets->is_noncolor, + me, + bkr->uv_layer); /* might be read by UI to set active image for display */ bake_update_image(bkr->area, bk_image->image); @@ -895,8 +906,11 @@ static bool bake_targets_output_external(const BakeAPIRender *bkr, bk_image->width, bk_image->height, bkr->margin, + bkr->margin_type, &bake->im_format, - targets->is_noncolor); + targets->is_noncolor, + me, + bkr->uv_layer); if (!ok) { BKE_reportf(reports, RPT_ERROR, "Problem saving baked map in \"%s\"", name); @@ -1625,6 +1639,7 @@ static void bake_init_api_data(wmOperator *op, bContext *C, BakeAPIRender *bkr) bkr->pass_type = RNA_enum_get(op->ptr, "type"); bkr->pass_filter = RNA_enum_get(op->ptr, "pass_filter"); bkr->margin = RNA_int_get(op->ptr, "margin"); + bkr->margin_type = RNA_enum_get(op->ptr, "margin_type"); bkr->save_mode = (eBakeSaveMode)RNA_enum_get(op->ptr, "save_mode"); bkr->target = (eBakeTarget)RNA_enum_get(op->ptr, "target"); @@ -1818,6 +1833,11 @@ static void bake_set_props(wmOperator *op, Scene *scene) RNA_property_int_set(op->ptr, prop, bake->margin); } + prop = RNA_struct_find_property(op->ptr, "margin_type"); + if (!RNA_property_is_set(op->ptr, prop)) { + RNA_property_enum_set(op->ptr, prop, bake->margin_type); + } + prop = RNA_struct_find_property(op->ptr, "use_selected_to_active"); if (!RNA_property_is_set(op->ptr, prop)) { RNA_property_boolean_set(op->ptr, prop, (bake->flag & R_BAKE_TO_ACTIVE) != 0); @@ -2008,6 +2028,12 @@ void OBJECT_OT_bake(wmOperatorType *ot) "Extends the baked result as a post process filter", 0, 64); + RNA_def_enum(ot->srna, + "margin_type", + rna_enum_bake_margin_type_items, + R_BAKE_EXTEND, + "Margin Type", + "Which algorithm to use to generate the margin"); RNA_def_boolean(ot->srna, "use_selected_to_active", false, diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index d2c4f22bc23..7ba054e3133 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -47,6 +47,7 @@ .width = 512, \ .height = 512, \ .margin = 16, \ + .margin_type = R_BAKE_ADJACENT_FACES, \ .normal_space = R_BAKE_SPACE_TANGENT, \ .normal_swizzle = {R_BAKE_POSX, R_BAKE_POSY, R_BAKE_POSZ}, \ } @@ -102,7 +103,8 @@ .dither_intensity = 1.0f, \ \ .bake_mode = 0, \ - .bake_filter = 16, \ + .bake_margin = 16, \ + .bake_margin_type = R_BAKE_ADJACENT_FACES, \ .bake_flag = R_BAKE_CLEAR, \ .bake_samples = 256, \ .bake_biasdist = 0.001f, \ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index a491e131d71..864358e040c 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -563,11 +563,18 @@ typedef struct BakeData { char target; char save_mode; - char _pad[6]; + char margin_type; + char _pad[5]; struct Object *cage_object; } BakeData; +/** #BakeData.margin_type (char) */ +typedef enum eBakeMarginType { + R_BAKE_ADJACENT_FACES = 0, + R_BAKE_EXTEND = 1, +} eBakeMarginType; + /** #BakeData.normal_swizzle (char) */ typedef enum eBakeNormalSwizzle { R_BAKE_POSX = 0, @@ -715,7 +722,9 @@ typedef struct RenderData { /* Bake Render options */ short bake_mode, bake_flag; - short bake_filter, bake_samples; + short bake_margin, bake_samples; + short bake_margin_type; + char _pad9[6]; float bake_biasdist, bake_user_scale; /* path to render output */ diff --git a/source/blender/makesdna/intern/dna_rename_defs.h b/source/blender/makesdna/intern/dna_rename_defs.h index c5769d7eee4..cb8052856a7 100644 --- a/source/blender/makesdna/intern/dna_rename_defs.h +++ b/source/blender/makesdna/intern/dna_rename_defs.h @@ -107,6 +107,7 @@ DNA_STRUCT_RENAME_ELEM(ParticleSettings, dup_group, instance_collection) DNA_STRUCT_RENAME_ELEM(ParticleSettings, dup_ob, instance_object) DNA_STRUCT_RENAME_ELEM(ParticleSettings, dupliweights, instance_weights) DNA_STRUCT_RENAME_ELEM(RigidBodyWorld, steps_per_second, substeps_per_frame) +DNA_STRUCT_RENAME_ELEM(RenderData, bake_filter, bake_margin) DNA_STRUCT_RENAME_ELEM(SpaceSeq, overlay_type, overlay_frame_type) DNA_STRUCT_RENAME_ELEM(SurfaceDeformModifierData, numverts, num_bind_verts) DNA_STRUCT_RENAME_ELEM(Text, name, filepath) diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h index 531af92c544..baa9ddba1be 100644 --- a/source/blender/makesrna/RNA_enum_items.h +++ b/source/blender/makesrna/RNA_enum_items.h @@ -72,6 +72,7 @@ DEF_ENUM(rna_enum_image_generated_type_items) DEF_ENUM(rna_enum_normal_space_items) DEF_ENUM(rna_enum_normal_swizzle_items) DEF_ENUM(rna_enum_bake_save_mode_items) +DEF_ENUM(rna_enum_bake_margin_type_items) DEF_ENUM(rna_enum_bake_target_items) DEF_ENUM(rna_enum_views_format_items) diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 2cda19737da..5bfbd42866b 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -432,6 +432,16 @@ const EnumPropertyItem rna_enum_normal_swizzle_items[] = { {0, NULL, 0, NULL, NULL}, }; +const EnumPropertyItem rna_enum_bake_margin_type_items[] = { + {R_BAKE_ADJACENT_FACES, + "ADJACENT_FACES", + 0, + "Adjacent Faces", + "Use pixels from adjacent faces across UV seams"}, + {R_BAKE_EXTEND, "EXTEND", 0, "Extend", "Extend border pixels outwards"}, + {0, NULL, 0, NULL, NULL}, +}; + const EnumPropertyItem rna_enum_bake_target_items[] = { {R_BAKE_TARGET_IMAGE_TEXTURES, "IMAGE_TEXTURES", @@ -5055,6 +5065,11 @@ static void rna_def_bake_data(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Margin", "Extends the baked result as a post process filter"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); + prop = RNA_def_property(srna, "margin_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_bake_margin_type_items); + RNA_def_property_ui_text(prop, "Margin Type", "Algorithm to extend the baked result"); + RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); + prop = RNA_def_property(srna, "max_ray_distance", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_range(prop, 0.0, FLT_MAX); RNA_def_property_ui_range(prop, 0.0, 1.0, 1, 3); @@ -5846,6 +5861,16 @@ static void rna_def_scene_render_data(BlenderRNA *brna) {0, NULL, 0, NULL, NULL}, }; + static const EnumPropertyItem bake_margin_type_items[] = { + {R_BAKE_ADJACENT_FACES, + "ADJACENT_FACES", + 0, + "Adjacent Faces", + "Use pixels from adjacent faces across UV seams"}, + {R_BAKE_EXTEND, "EXTEND", 0, "Extend", "Extend border pixels outwards"}, + {0, NULL, 0, NULL, NULL}, + }; + static const EnumPropertyItem pixel_size_items[] = { {0, "AUTO", 0, "Automatic", "Automatic pixel size, depends on the user interface scale"}, {1, "1", 0, "1x", "Render at full resolution"}, @@ -6261,11 +6286,17 @@ static void rna_def_scene_render_data(BlenderRNA *brna) RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); prop = RNA_def_property(srna, "bake_margin", PROP_INT, PROP_PIXEL); - RNA_def_property_int_sdna(prop, NULL, "bake_filter"); + RNA_def_property_int_sdna(prop, NULL, "bake_margin"); RNA_def_property_range(prop, 0, 64); RNA_def_property_ui_text(prop, "Margin", "Extends the baked result as a post process filter"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); + prop = RNA_def_property(srna, "bake_margin_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "bake_margin_type"); + RNA_def_property_enum_items(prop, bake_margin_type_items); + RNA_def_property_ui_text(prop, "Margin Type", "Algorithm to generate the margin"); + RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); + prop = RNA_def_property(srna, "bake_bias", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "bake_biasdist"); RNA_def_property_range(prop, 0.0, 1000.0); diff --git a/source/blender/render/CMakeLists.txt b/source/blender/render/CMakeLists.txt index 494415a4077..a7c1b12982c 100644 --- a/source/blender/render/CMakeLists.txt +++ b/source/blender/render/CMakeLists.txt @@ -49,6 +49,7 @@ set(SRC intern/pipeline.c intern/render_result.c intern/texture_image.c + intern/texture_margin.cc intern/texture_pointdensity.c intern/texture_procedural.c intern/zbuf.c @@ -58,6 +59,7 @@ set(SRC RE_multires_bake.h RE_pipeline.h RE_texture.h + RE_texture_margin.h intern/pipeline.h intern/render_result.h diff --git a/source/blender/render/RE_bake.h b/source/blender/render/RE_bake.h index b7ce3da71ff..d61c0a8bf90 100644 --- a/source/blender/render/RE_bake.h +++ b/source/blender/render/RE_bake.h @@ -27,6 +27,7 @@ struct Depsgraph; struct ImBuf; struct Mesh; struct Render; +struct MLoopUV; #ifdef __cplusplus extern "C" { @@ -112,7 +113,12 @@ void RE_bake_pixels_populate(struct Mesh *me, void RE_bake_mask_fill(const BakePixel pixel_array[], size_t num_pixels, char *mask); -void RE_bake_margin(struct ImBuf *ibuf, char *mask, int margin); +void RE_bake_margin(struct ImBuf *ibuf, + char *mask, + int margin, + char margin_type, + struct Mesh const *me, + char const *uv_layer); void RE_bake_normal_world_to_object(const BakePixel pixel_array[], size_t num_pixels, diff --git a/source/blender/render/RE_multires_bake.h b/source/blender/render/RE_multires_bake.h index 42ee2c57fbb..6df80c27c40 100644 --- a/source/blender/render/RE_multires_bake.h +++ b/source/blender/render/RE_multires_bake.h @@ -33,7 +33,8 @@ extern "C" { typedef struct MultiresBakeRender { Scene *scene; DerivedMesh *lores_dm, *hires_dm; - int bake_filter; /* Bake-filter, aka margin */ + int bake_margin; + char bake_margin_type; int lvl, tot_lvl; short mode; bool use_lores_mesh; /* Use low-resolution mesh when baking displacement maps */ diff --git a/source/blender/render/RE_texture_margin.h b/source/blender/render/RE_texture_margin.h new file mode 100644 index 00000000000..0533b93a286 --- /dev/null +++ b/source/blender/render/RE_texture_margin.h @@ -0,0 +1,45 @@ +/* + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ +#pragma once + +/** \file + * \ingroup bke + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct Mesh; +struct IMBuf; +struct MLoopUV; +struct ImBuf; +struct DerivedMesh; + +void RE_generate_texturemargin_adjacentfaces( + struct ImBuf *ibuf, char *mask, const int margin, struct Mesh const *me, char const *uv_layer); + +void RE_generate_texturemargin_adjacentfaces_dm(struct ImBuf *ibuf, + char *mask, + const int margin, + struct DerivedMesh *dm); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/render/intern/bake.c b/source/blender/render/intern/bake.c index 2d7f964a968..93d2f721cc5 100644 --- a/source/blender/render/intern/bake.c +++ b/source/blender/render/intern/bake.c @@ -83,6 +83,7 @@ #include "IMB_imbuf_types.h" #include "RE_bake.h" +#include "RE_texture_margin.h" /* local include */ #include "render_types.h" @@ -154,10 +155,24 @@ void RE_bake_mask_fill(const BakePixel pixel_array[], const size_t num_pixels, c } } -void RE_bake_margin(ImBuf *ibuf, char *mask, const int margin) +void RE_bake_margin(ImBuf *ibuf, + char *mask, + const int margin, + const char margin_type, + Mesh const *me, + char const *uv_layer) { /* margin */ - IMB_filter_extend(ibuf, mask, margin); + switch (margin_type) { + case R_BAKE_ADJACENT_FACES: + RE_generate_texturemargin_adjacentfaces(ibuf, mask, margin, me, uv_layer); + break; + default: + /* fall through */ + case R_BAKE_EXTEND: + IMB_filter_extend(ibuf, mask, margin); + break; + } if (ibuf->planes != R_IMF_PLANES_RGBA) { /* clear alpha added by filtering */ diff --git a/source/blender/render/intern/multires_bake.c b/source/blender/render/intern/multires_bake.c index d3e7dca2035..5cf328a3a73 100644 --- a/source/blender/render/intern/multires_bake.c +++ b/source/blender/render/intern/multires_bake.c @@ -47,6 +47,7 @@ #include "RE_multires_bake.h" #include "RE_pipeline.h" #include "RE_texture.h" +#include "RE_texture_margin.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -1296,14 +1297,23 @@ static void apply_ao_callback(DerivedMesh *lores_dm, /* ******$***************** Post processing ************************* */ -static void bake_ibuf_filter(ImBuf *ibuf, char *mask, const int filter) +static void bake_ibuf_filter( + ImBuf *ibuf, char *mask, const int margin, const char margin_type, DerivedMesh *dm) { /* must check before filtering */ const bool is_new_alpha = (ibuf->planes != R_IMF_PLANES_RGBA) && BKE_imbuf_alpha_test(ibuf); - /* Margin */ - if (filter) { - IMB_filter_extend(ibuf, mask, filter); + if (margin) { + switch (margin_type) { + case R_BAKE_ADJACENT_FACES: + RE_generate_texturemargin_adjacentfaces_dm(ibuf, mask, margin, dm); + break; + default: + /* fall through */ + case R_BAKE_EXTEND: + IMB_filter_extend(ibuf, mask, margin); + break; + } } /* if the bake results in new alpha then change the image setting */ @@ -1311,7 +1321,7 @@ static void bake_ibuf_filter(ImBuf *ibuf, char *mask, const int filter) ibuf->planes = R_IMF_PLANES_RGBA; } else { - if (filter && ibuf->planes != R_IMF_PLANES_RGBA) { + if (margin && ibuf->planes != R_IMF_PLANES_RGBA) { /* clear alpha added by filtering */ IMB_rectfill_alpha(ibuf, 1.0f); } @@ -1460,7 +1470,8 @@ static void finish_images(MultiresBakeRender *bkr, MultiresBakeResult *result) result->height_max); } - bake_ibuf_filter(ibuf, userdata->mask_buffer, bkr->bake_filter); + bake_ibuf_filter( + ibuf, userdata->mask_buffer, bkr->bake_margin, bkr->bake_margin_type, bkr->lores_dm); ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; BKE_image_mark_dirty(ima, ibuf); diff --git a/source/blender/render/intern/texture_margin.cc b/source/blender/render/intern/texture_margin.cc new file mode 100644 index 00000000000..3798d7dbe87 --- /dev/null +++ b/source/blender/render/intern/texture_margin.cc @@ -0,0 +1,569 @@ +/* + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup render + */ + +#include "BLI_assert.h" +#include "BLI_math_geom.h" +#include "BLI_math_vec_types.hh" +#include "BLI_math_vector.hh" +#include "BLI_vector.hh" + +#include "BKE_DerivedMesh.h" +#include "BKE_mesh.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "MEM_guardedalloc.h" + +#include "zbuf.h" // for rasterizer + +#include "RE_texture_margin.h" + +#include <algorithm> +#include <math.h> +#include <valarray> + +namespace blender::render::texturemargin { + +/* The map class contains both a pixel map which maps out polygon indices for all UV-polygons and + * adjacency tables. + */ +class TextureMarginMap { + static const int directions[4][2]; + + /* Maps UV-edges to their corresponding UV-edge. */ + Vector<int> loop_adjacency_map_; + /* Maps UV-edges to their corresponding polygon. */ + Vector<int> loop_to_poly_map_; + + int w_, h_; + Vector<uint32_t> pixel_data_; + ZSpan zspan_; + uint32_t value_to_store_; + char *mask_; + + MPoly const *mpoly_; + MLoop const *mloop_; + MLoopUV const *mloopuv_; + int totpoly_; + int totloop_; + int totedge_; + + public: + TextureMarginMap(size_t w, + size_t h, + MPoly const *mpoly, + MLoop const *mloop, + MLoopUV const *mloopuv, + int totpoly, + int totloop, + int totedge) + : w_(w), + h_(h), + mpoly_(mpoly), + mloop_(mloop), + mloopuv_(mloopuv), + totpoly_(totpoly), + totloop_(totloop), + totedge_(totedge) + { + pixel_data_.resize(w_ * h_, 0xFFFFFFFF); + + zbuf_alloc_span(&zspan_, w_, h_); + + build_tables(); + } + + ~TextureMarginMap() + { + zbuf_free_span(&zspan_); + } + + inline void set_pixel(int x, int y, uint32_t value) + { + BLI_assert(x < w_); + BLI_assert(x >= 0); + pixel_data_[y * w_ + x] = value; + } + + inline uint32_t get_pixel(int x, int y) const + { + if (x < 0 || y < 0 || x >= w_ || y >= h_) { + return 0xFFFFFFFF; + } + + return pixel_data_[y * w_ + x]; + } + + void rasterize_tri(float *v1, float *v2, float *v3, uint32_t value, char *mask) + { + /* NOTE: This is not thread safe, because the value to be written by the rasterizer is + * a class member. If this is ever made multithreaded each therad needs to get it's own. + */ + value_to_store_ = value; + mask_ = mask; + zspan_scanconvert( + &zspan_, this, &(v1[0]), &(v2[0]), &(v3[0]), TextureMarginMap::zscan_store_pixel); + } + + static void zscan_store_pixel(void *map, int x, int y, float, float) + { + /* NOTE: Not thread safe, see comment above. + * + */ + TextureMarginMap *m = static_cast<TextureMarginMap *>(map); + m->set_pixel(x, y, m->value_to_store_); + if (m->mask_) { + m->mask_[y * m->w_ + x] = 1; + } + } + +/* The map contains 2 kinds of pixels: DijkstraPixels and polygon indices. The top bit determines + * what kind it is. With the top bit set, it is a 'dijkstra' pixel. The bottom 3 bits encode the + * direction of the shortest path and the remaining 28 bits are used to store the distance. If + * the top bit is not set, the rest of the bits is used to store the polygon index. + */ +#define PackDijkstraPixel(dist, dir) (0x80000000 + ((dist) << 3) + (dir)) +#define DijkstraPixelGetDistance(dp) (((dp) ^ 0x80000000) >> 3) +#define DijkstraPixelGetDirection(dp) ((dp)&0x7) +#define IsDijkstraPixel(dp) ((dp)&0x80000000) +#define DijkstraPixelIsUnset(dp) ((dp) == 0xFFFFFFFF) + + /* Use dijkstra's algorithm to 'grow' a border around the polygons marked in the map. + * For each pixel mark which direction is the shortest way to a polygon. + */ + void grow_dijkstra(int margin) + { + class DijkstraActivePixel { + public: + DijkstraActivePixel(int dist, int _x, int _y) : distance(dist), x(_x), y(_y) + { + } + int distance; + int x, y; + }; + auto cmp_dijkstrapixel_fun = [](DijkstraActivePixel const &a1, DijkstraActivePixel const &a2) { + return a1.distance > a2.distance; + }; + + Vector<DijkstraActivePixel> active_pixels; + for (int y = 0; y < h_; y++) { + for (int x = 0; x < w_; x++) { + if (DijkstraPixelIsUnset(get_pixel(x, y))) { + for (int i = 0; i < 4; i++) { + int xx = x - directions[i][0]; + int yy = y - directions[i][1]; + + if (xx >= 0 && xx < w_ && yy >= 0 && yy < w_ && !IsDijkstraPixel(get_pixel(xx, yy))) { + set_pixel(x, y, PackDijkstraPixel(1, i)); + active_pixels.append(DijkstraActivePixel(1, x, y)); + break; + } + } + } + } + } + + // std::make_heap(active_pixels.begin(), active_pixels.end(), cmp_dijkstrapixel_fun); + // Not strictly needed because at this point it already is a heap. + + while (active_pixels.size()) { + std::pop_heap(active_pixels.begin(), active_pixels.end(), cmp_dijkstrapixel_fun); + DijkstraActivePixel p = active_pixels.pop_last(); + + int dist = p.distance; + + dist++; + if (dist < margin) { + for (int i = 0; i < 4; i++) { + int x = p.x + directions[i][0]; + int y = p.y + directions[i][1]; + if (x >= 0 && x < w_ && y >= 0 && y < h_) { + uint32_t dp = get_pixel(x, y); + if (IsDijkstraPixel(dp) && (DijkstraPixelGetDistance(dp) > dist)) { + BLI_assert(abs((int)DijkstraPixelGetDirection(dp) - (int)i) != 2); + set_pixel(x, y, PackDijkstraPixel(dist, i)); + active_pixels.append(DijkstraActivePixel(dist, x, y)); + std::push_heap(active_pixels.begin(), active_pixels.end(), cmp_dijkstrapixel_fun); + } + } + } + } + } + } + + /* Walk over the map and for margin pixels follow the direction stored in the bottom 3 + * bits back to the polygon. + * Then look up the pixel from the next polygon. + */ + void lookup_pixels(ImBuf *ibuf, char *mask, int maxPolygonSteps) + { + for (int y = 0; y < h_; y++) { + for (int x = 0; x < w_; x++) { + uint32_t dp = get_pixel(x, y); + if (IsDijkstraPixel(dp) && !DijkstraPixelIsUnset(dp)) { + int dist = DijkstraPixelGetDistance(dp); + int direction = DijkstraPixelGetDirection(dp); + + int xx = x; + int yy = y; + + /* Follow the dijkstra directions to find the polygon this margin pixels belongs to. */ + while (dist > 0) { + xx -= directions[direction][0]; + yy -= directions[direction][1]; + dp = get_pixel(xx, yy); + dist--; + BLI_assert(!dist || (dist == DijkstraPixelGetDistance(dp))); + direction = DijkstraPixelGetDirection(dp); + } + + uint32_t poly = get_pixel(xx, yy); + + BLI_assert(!IsDijkstraPixel(poly)); + + float destX, destY; + + int other_poly; + bool found_pixel_in_polygon = false; + if (lookup_pixel(x, y, poly, &destX, &destY, &other_poly)) { + + for (int i = 0; i < maxPolygonSteps; i++) { + /* Force to pixel grid. */ + int nx = (int)round(destX); + int ny = (int)round(destY); + uint32_t polygon_from_map = get_pixel(nx, ny); + if (other_poly == polygon_from_map) { + found_pixel_in_polygon = true; + break; + } + + /* Look up again, but starting from the polygon we were expected to land in. */ + lookup_pixel(nx, ny, other_poly, &destX, &destY, &other_poly); + } + + if (found_pixel_in_polygon) { + bilinear_interpolation(ibuf, ibuf, destX, destY, x, y); + /* Add our new pixels to the assigned pixel map. */ + mask[y * w_ + x] = 1; + } + } + } + else if (DijkstraPixelIsUnset(dp) || !IsDijkstraPixel(dp)) { + /* These are not margin pixels, make sure the extend filter which is run after this step + * leaves them alone. + */ + mask[y * w_ + x] = 1; + } + } + } + } + + private: + float2 uv_to_xy(MLoopUV const &mloopuv) const + { + float2 ret; + ret.x = ((mloopuv.uv[0] * w_) - (0.5f + 0.001f)); + ret.y = ((mloopuv.uv[1] * h_) - (0.5f + 0.001f)); + return ret; + } + + void build_tables() + { + loop_to_poly_map_.resize(totloop_); + for (int i = 0; i < totpoly_; i++) { + for (int j = 0; j < mpoly_[i].totloop; j++) { + int l = j + mpoly_[i].loopstart; + loop_to_poly_map_[l] = i; + } + } + + loop_adjacency_map_.resize(totloop_, -1); + + Vector<int> tmpmap; + tmpmap.resize(totedge_, -1); + + for (size_t i = 0; i < totloop_; i++) { + int edge = mloop_[i].e; + if (tmpmap[edge] == -1) { + loop_adjacency_map_[i] = -1; + tmpmap[edge] = i; + } + else { + BLI_assert(tmpmap[edge] >= 0); + loop_adjacency_map_[i] = tmpmap[edge]; + loop_adjacency_map_[tmpmap[edge]] = i; + } + } + } + + /* Find which edge of the src_poly is closest to x,y. Look up it's adjacent UV-edge and polygon. + * Then return the location of the equivalent pixel in the other polygon. + * Returns true if a new pixel location was found, false if it wasn't, which can happen if the + * margin pixel is on a corner, or the UV-edge doesnt have an adjacent polygon. + */ + bool lookup_pixel( + float x, float y, int src_poly, float *r_destx, float *r_desty, int *r_other_poly) + { + float2 point(x, y); + + *r_destx = *r_desty = 0; + + int found_edge = -1; + float found_dist = -1; + float found_t = 0; + + /* Find the closest edge on which the point x,y can be projected. + */ + for (size_t i = 0; i < mpoly_[src_poly].totloop; i++) { + int l1 = mpoly_[src_poly].loopstart + i; + int l2 = l1 + 1; + if (l2 >= mpoly_[src_poly].loopstart + mpoly_[src_poly].totloop) { + l2 = mpoly_[src_poly].loopstart; + } + /* edge points */ + float2 edgepoint1 = uv_to_xy(mloopuv_[l1]); + float2 edgepoint2 = uv_to_xy(mloopuv_[l2]); + /* Vector AB is the vector from the first edge point to the second edge point. + * Vector AP is the vector from the first edge point to our point under investigation. */ + float2 ab = edgepoint2 - edgepoint1; + float2 ap = point - edgepoint1; + + /* Project ap onto ab. */ + float dotv = math::dot(ab, ap); + + float ablensq = math::length_squared(ab); + + float t = dotv / ablensq; + + if (t >= 0.0 && t <= 1.0) { + + /* Find the point on the edge closest to P */ + float2 reflect_point = edgepoint1 + (t * ab); + /* This is the vector to P, so 90 degrees out from the edge. */ + float2 reflect_vec = reflect_point - point; + + float reflectLen = sqrt(reflect_vec[0] * reflect_vec[0] + reflect_vec[1] * reflect_vec[1]); + float cross = ab[0] * reflect_vec[1] - ab[1] * reflect_vec[0]; + /* Only if P is on the outside of the edge, which means the cross product is positive, + * we consider this edge. + */ + bool valid = (cross > 0.0); + + if (valid && (found_dist < 0 || reflectLen < found_dist)) { + /* Stother_ab the info of the closest edge so far. */ + found_dist = reflectLen; + found_t = t; + found_edge = i + mpoly_[src_poly].loopstart; + } + } + } + + if (found_edge < 0) { + return false; + } + + /* Get the 'other' edge. I.E. the UV edge from the neighbour polygon. */ + int other_edge = loop_adjacency_map_[found_edge]; + + if (other_edge < 0) { + return false; + } + + int dst_poly = loop_to_poly_map_[other_edge]; + + if (r_other_poly) { + *r_other_poly = dst_poly; + } + + int other_edge2 = other_edge + 1; + if (other_edge2 >= mpoly_[dst_poly].loopstart + mpoly_[dst_poly].totloop) { + other_edge2 = mpoly_[dst_poly].loopstart; + } + + float2 other_edgepoint1 = uv_to_xy(mloopuv_[other_edge]); + float2 other_edgepoint2 = uv_to_xy(mloopuv_[other_edge2]); + + /* Calculate the vector from the oder edges last point to it's first point. */ + float2 other_ab = other_edgepoint1 - other_edgepoint2; + float2 other_reflect_point = other_edgepoint2 + (found_t * other_ab); + float2 perpendicular_other_ab; + perpendicular_other_ab.x = other_ab.y; + perpendicular_other_ab.y = -other_ab.x; + + /* The new point is dound_dist distance from other_reflect_point at a 90 degree angle to + * other_ab */ + float2 new_point = other_reflect_point + (found_dist / math::length(perpendicular_other_ab)) * + perpendicular_other_ab; + + *r_destx = new_point.x; + *r_desty = new_point.y; + + return true; + } +}; // class TextureMarginMap + +const int TextureMarginMap::directions[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}}; + +static void generate_margin(ImBuf *ibuf, + char *mask, + const int margin, + const Mesh *me, + DerivedMesh *dm, + char const *uv_layer) +{ + + MPoly *mpoly; + MLoop *mloop; + MLoopUV const *mloopuv; + int totpoly, totloop, totedge; + + int tottri; + MLoopTri const *looptri; + MLoopTri *looptri_mem = NULL; + + if (me) { + BLI_assert(dm == NULL); + totpoly = me->totpoly; + totloop = me->totloop; + totedge = me->totedge; + mpoly = me->mpoly; + mloop = me->mloop; + + if ((uv_layer == NULL) || (uv_layer[0] == '\0')) { + mloopuv = static_cast<MLoopUV const *>(CustomData_get_layer(&me->ldata, CD_MLOOPUV)); + } + else { + int uv_id = CustomData_get_named_layer(&me->ldata, CD_MLOOPUV, uv_layer); + mloopuv = static_cast<MLoopUV const *>( + CustomData_get_layer_n(&me->ldata, CD_MLOOPUV, uv_id)); + } + + tottri = poly_to_tri_count(me->totpoly, me->totloop); + looptri_mem = static_cast<MLoopTri *>(MEM_mallocN(sizeof(*looptri) * tottri, __func__)); + BKE_mesh_recalc_looptri( + me->mloop, me->mpoly, me->mvert, me->totloop, me->totpoly, looptri_mem); + looptri = looptri_mem; + } + else { + BLI_assert(dm != NULL); + BLI_assert(me == NULL); + BLI_assert(mloopuv == NULL); + totpoly = dm->getNumPolys(dm); + totedge = dm->getNumEdges(dm); + totloop = dm->getNumLoops(dm); + mpoly = dm->getPolyArray(dm); + mloop = dm->getLoopArray(dm); + mloopuv = (MLoopUV const *)dm->getLoopDataArray(dm, CD_MLOOPUV); + + looptri = dm->getLoopTriArray(dm); + tottri = dm->getNumLoopTri(dm); + } + + TextureMarginMap map(ibuf->x, ibuf->y, mpoly, mloop, mloopuv, totpoly, totloop, totedge); + + bool draw_new_mask = false; + /* Now the map contains 3 sorts of values: 0xFFFFFFFF for empty pixels, 0x80000000 + polyindex + * for margin pixels, just polyindex for poly pixels. + */ + if (mask) { + mask = (char *)MEM_dupallocN(mask); + } + else { + mask = (char *)MEM_callocN(sizeof(char) * ibuf->x * ibuf->y, __func__); + draw_new_mask = true; + } + + for (int i = 0; i < tottri; i++) { + const MLoopTri *lt = &looptri[i]; + float vec[3][2]; + + for (int a = 0; a < 3; a++) { + const float *uv = mloopuv[lt->tri[a]].uv; + + /* NOTE(campbell): workaround for pixel aligned UVs which are common and can screw up our + * intersection tests where a pixel gets in between 2 faces or the middle of a quad, + * camera aligned quads also have this problem but they are less common. + * Add a small offset to the UVs, fixes bug T18685. */ + vec[a][0] = uv[0] * (float)ibuf->x - (0.5f + 0.001f); + vec[a][1] = uv[1] * (float)ibuf->y - (0.5f + 0.002f); + } + + BLI_assert(lt->poly < 0x80000000); // NOTE: we need the top bit for the dijkstra distance map + map.rasterize_tri(vec[0], vec[1], vec[2], lt->poly, draw_new_mask ? mask : NULL); + } + + char *tmpmask = (char *)MEM_dupallocN(mask); + /* Extend (with averaging) by 2 pixels. Those will be overwritten, but it + * helps linear interpolations on the edges of polygons. */ + IMB_filter_extend(ibuf, tmpmask, 2); + MEM_freeN(tmpmask); + + map.grow_dijkstra(margin); + + /* Looking further than 3 polygons away leads to so much cumulative rounding + * that it isn't worth it. So hardcode it to 3. + */ + map.lookup_pixels(ibuf, mask, 3); + + /* Use the extend filter to fill in the missing pixels at the corners, not strictly correct, but + * the visual difference seems very minimal. This also catches pixels we missed because of very + * narrow polygons. + */ + IMB_filter_extend(ibuf, mask, margin); + + MEM_freeN(mask); + + if (looptri_mem) { + MEM_freeN(looptri_mem); + } +} + +} // namespace blender::render::texturemargin + +/** + * Generate a margin around the textures uv islands by copying pixels from the adjacent polygon. + * + * \param ibuf: the texture image. + * \param mask: pixels with a mask value of 1 are not written to. + * \param margin: the size of the margin in pixels. + * \param me: the mesh to use the polygons of. + * \param mloopuv: the uv data to use. + */ + +void RE_generate_texturemargin_adjacentfaces( + ImBuf *ibuf, char *mask, const int margin, const Mesh *me, char const *uv_layer) +{ + blender::render::texturemargin::generate_margin(ibuf, mask, margin, me, NULL, uv_layer); +} + +void RE_generate_texturemargin_adjacentfaces_dm(ImBuf *ibuf, + char *mask, + const int margin, + DerivedMesh *dm) +{ + blender::render::texturemargin::generate_margin(ibuf, mask, margin, NULL, dm, NULL); +} |