Baking: new method to generate margin, based on adjacent faces

This significantly reduces discontinuities on UV seams, by giving a better
match of the texture filtered colors on both sides of the seam. It works by
using pixels from adjacent faces across the UV seam.

This new option is called "Adjacent Faces" and is the default. The old option
is called "Extend", and extends border pixels outwards.

Differential Revision: https://developer.blender.org/D13303
This commit is contained in:
Martijn Versteegh 2022-01-17 18:00:35 +01:00 committed by Brecht Van Lommel
parent 8af22719d0
commit 449db0ab1e
Notes: blender-bot 2023-06-20 16:34:56 +02:00
Referenced by issue #104059, Bake doesn't work well
Referenced by issue #96942, Bake Normal problem when adjoining faces have different UV orientation
Referenced by issue #109057, Regression: baking multi texture targets on single object is broken (adjacent faces option, Extend is fine)
16 changed files with 783 additions and 30 deletions

View File

@ -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),

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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, \

View File

@ -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 */

View File

@ -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)

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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,

View File

@ -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 */

View File

@ -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

View File

@ -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 */

View File

@ -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);

View File

@ -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);
}