GPENCIL: Add randomize options to gpencil texture mapping modifier

Updated for GPv3
This commit is contained in:
2025-10-11 23:01:05 -05:00
parent 2dbda3986c
commit 33d056039e
4 changed files with 161 additions and 11 deletions
@@ -1082,6 +1082,12 @@
.fill_rotation = 0.0f, \
.fill_offset = {0.0f, 0.0f}, \
.fill_scale = 1.0f, \
.rnd_uv_offset = 0.0f, \
.rnd_uv_rot = 0.0f, \
.rnd_uv_scale = 0.0f, \
.rnd_fill_offset = {0.0f, 0.0f}, \
.rnd_fill_rot = 0.0f, \
.rnd_fill_scale = 0.0f, \
.fit_method = GP_TEX_CONSTANT_LENGTH, \
.mode = 0, \
}
@@ -3492,6 +3492,22 @@ typedef struct GreasePencilTextureModifierData {
float fill_rotation;
float fill_offset[2];
float fill_scale;
/** Random UV Offset. */
float rnd_uv_offset;
/** Random UV Rotations. */
float rnd_uv_rot;
/** Random UV Scales. */
float rnd_uv_scale;
/** Random Fill Offset. */
float rnd_fill_offset[2];
/** Random Fill Rotations. */
float rnd_fill_rot;
/** Random FillScales. */
float rnd_fill_scale;
/** (first element is the index) random values. */
int seed;
/* Custom index for passes. */
int layer_pass;
/* Texture fit options. */
+42 -1
View File
@@ -11127,7 +11127,8 @@ static void rna_def_modifier_grease_pencil_texture(BlenderRNA *brna)
rna_def_modifier_grease_pencil_vertex_group(
srna, "rna_GreasePencilTextureModifier_vertex_group_name_set");
rna_def_modifier_panel_open_prop(srna, "open_influence_panel", 0);
rna_def_modifier_panel_open_prop(srna, "open_random_panel", 0);
rna_def_modifier_panel_open_prop(srna, "open_influence_panel", 1);
RNA_define_lib_overridable(true);
@@ -11178,6 +11179,46 @@ static void rna_def_modifier_grease_pencil_texture(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Fit Method", "");
RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update");
prop = RNA_def_property(srna, "rnd_uv_offset", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "rnd_uv_offset");
RNA_def_property_ui_text(prop, "Random UV Offset", "Value for changes in stroke UV location");
RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT);
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "rnd_uv_rot", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "rnd_uv_rot");
RNA_def_property_ui_text(prop, "Random UV Rotaiton", "Value for changes in stroke UV rotation");
RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT);
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "rnd_uv_scale", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "rnd_uv_scale");
RNA_def_property_ui_text(prop, "Random UV Scale", "Value for changes in stroke UV scale");
RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT);
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "rnd_fill_offset", PROP_FLOAT, PROP_TRANSLATION);
RNA_def_property_float_sdna(prop, NULL, "rnd_fill_offset");
RNA_def_property_ui_text(prop, "Random Fill Offset", "Value for changes in fill location");
RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT);
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "rnd_fill_rot", PROP_FLOAT, PROP_EULER);
RNA_def_property_float_sdna(prop, NULL, "rnd_fill_rot");
RNA_def_property_ui_text(prop, "Random Fill Rotation", "Value for changes in fill rotation");
RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 100, RNA_TRANSLATION_PREC_DEFAULT);
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "rnd_fill_scale", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "rnd_fill_scale");
RNA_def_property_ui_text(prop, "Random Fill Scale", "Value for changes in fill scale");
RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT);
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "seed", PROP_INT, PROP_UNSIGNED);
RNA_def_property_ui_text(prop, "Seed", "Random seed");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "mode");
RNA_def_property_enum_items(prop, mode_items);
@@ -10,6 +10,8 @@
#include "BLI_index_range.hh"
#include "BLI_math_base.hh"
#include "BLI_span.hh"
#include "BLI_hash.h"
#include "BLI_rand.h"
#include "DNA_defaults.h"
#include "DNA_modifier_types.h"
@@ -76,6 +78,8 @@ static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void
static void write_stroke_transforms(bke::greasepencil::Drawing &drawing,
const IndexMask &curves_mask,
const GreasePencilTextureModifierData &tmd,
const Object &ob,
const float offset,
const float rotation,
const float scale,
@@ -97,16 +101,41 @@ static void write_stroke_transforms(bke::greasepencil::Drawing &drawing,
curves.ensure_evaluated_lengths();
/* Make sure different modifiers get different seeds. */
const int seed = tmd.seed + BLI_hash_string(ob.id.name + 2) + BLI_hash_string(tmd.modifier.name);
const float rand_offset = BLI_hash_int_01(seed);
/* Generates a random number for loc/rot/scale channels, based on seed and a per-stroke random
* value r. */
auto get_random_channel = [&](const char channel, const double r) {
float rand = fmodf(r * 2.0f - 1.0f + rand_offset, 1.0f);
return fmodf(sin(rand * 12.9898f + channel * 78.233f) * 43758.5453f, 1.0f);
};
auto get_random_value = [&](const char channel, const int64_t curve_i, const float scale) {
const uint halton_primes[3] = {2, 3, 7};
double halton_offset[3] = {0.0f, 0.0f, 0.0f};
double r[3];
/* To ensure a nice distribution, we use halton sequence and offset using the curve index. */
BLI_halton_3d(halton_primes, halton_offset, curve_i, r);
return get_random_channel(channel, r[0]) * scale;
};
curves_mask.foreach_index(GrainSize(512), [&](int64_t curve_i) {
const IndexRange points = points_by_curve[curve_i];
const bool is_cyclic = cyclic[curve_i];
const Span<float> lengths = curves.evaluated_lengths_for_curve(curve_i, is_cyclic);
const float norm = normalize_u ? math::safe_rcp(lengths.last()) : 1.0f;
u_translations.span[curve_i] += offset;
u_scales.span[curve_i] *= scale * norm;
/* Randomness factors for loc/rot/scale per curve. */
const float loc_factor = get_random_value(0, curve_i, tmd.rnd_uv_offset);
const float rot_factor = get_random_value(1, curve_i, tmd.rnd_uv_rot);
const float scale_factor = get_random_value(2, curve_i, tmd.rnd_uv_scale);
u_translations.span[curve_i] += offset + loc_factor;
u_scales.span[curve_i] *= (scale + scale_factor) * norm;
for (const int point_i : points) {
rotations.span[point_i] += rotation;
rotations.span[point_i] += rotation + rot_factor;
}
});
@@ -162,6 +191,8 @@ static void get_legacy_stroke_matrix(const Span<float3> positions,
static void write_fill_transforms(bke::greasepencil::Drawing &drawing,
const IndexMask &curves_mask,
const GreasePencilTextureModifierData &tmd,
const Object &ob,
const float2 &offset,
const float rotation,
const float scale)
@@ -198,9 +229,45 @@ static void write_fill_transforms(bke::greasepencil::Drawing &drawing,
const Span<float3> positions = curves.positions();
Array<float4x2> texture_matrices(drawing.texture_matrices());
/* Make sure different modifiers get different seeds. */
const int seed = tmd.seed + BLI_hash_string(ob.id.name + 2) + BLI_hash_string(tmd.modifier.name);
const float rand_offset = BLI_hash_int_01(seed);
/* Generates a random number for loc/rot/scale channels, based on seed and a per-stroke random
* value r. */
auto get_random_channel = [&](const char channel, const double r) {
float rand = fmodf(r * 2.0f - 1.0f + rand_offset, 1.0f);
return fmodf(sin(rand * 12.9898f + channel * 78.233f) * 43758.5453f, 1.0f);
};
auto get_random_value = [&](const char channel, const int64_t curve_i, const float scale) {
const uint halton_primes[3] = {2, 3, 7};
double halton_offset[3] = {0.0f, 0.0f, 0.0f};
double r[3];
/* To ensure a nice distribution, we use halton sequence and offset using the curve index. */
BLI_halton_3d(halton_primes, halton_offset, curve_i, r);
return get_random_channel(channel, r[0]) * scale;
};
auto get_random_vector = [&](const char channel, const int64_t curve_i, const float2 scale) {
const uint halton_primes[2] = {2, 3};
double halton_offset[2] = {0.0f, 0.0f};
double r[2];
/* To ensure a nice distribution, we use halton sequence and offset using the curve index. */
BLI_halton_2d(halton_primes, halton_offset, curve_i, r);
return float2(get_random_channel(channel, r[0]) * scale[0],
get_random_channel(channel, r[1]) * scale[1]);
};
curves_mask.foreach_index(GrainSize(512), [&](int64_t curve_i) {
const IndexRange points = curves.points_by_curve()[curve_i];
float4x2 &texture_matrix = texture_matrices[curve_i];
/* Randomness factors for loc/rot/scale per curve. */
const float2 loc_factor = get_random_vector(0, curve_i, tmd.rnd_fill_offset);
const float rot_factor = get_random_value(1, curve_i, tmd.rnd_fill_rot);
const float scale_factor = get_random_value(2, curve_i, tmd.rnd_fill_scale);
/* Factor out the stroke-to-layer transform part used by GPv2.
* This may not be the same as the transform used by GPv3 for concave shapes due to a
* simplistic normal calculation, but we want to achieve the same effect as GPv2 so have to use
@@ -222,9 +289,9 @@ static void write_fill_transforms(bke::greasepencil::Drawing &drawing,
const float legacy_uv_rotation = uv_rotation;
const float2 legacy_uv_scale = 0.5f * uv_scale;
const float2 legacy_uv_translation_new = legacy_uv_translation + offset;
const float legacy_uv_rotation_new = legacy_uv_rotation + rotation;
const float2 legacy_uv_scale_new = legacy_uv_scale * scale;
const float2 legacy_uv_translation_new = legacy_uv_translation + offset + loc_factor;
const float legacy_uv_rotation_new = legacy_uv_rotation + rotation + rot_factor;
const float2 legacy_uv_scale_new = legacy_uv_scale * (scale + scale_factor);
const float2 uv_translation_new =
(rotate_by_angle(legacy_uv_translation_new, legacy_uv_rotation_new) + 0.5f) *
@@ -249,6 +316,12 @@ static void modify_curves(const GreasePencilTextureModifierData &tmd,
const ModifierEvalContext &ctx,
bke::greasepencil::Drawing &drawing)
{
bke::CurvesGeometry &curves = drawing.strokes_for_write();
if (curves.points_num() == 0) {
return;
}
IndexMaskMemory mask_memory;
const IndexMask curves_mask = modifier::greasepencil::get_filtered_stroke_mask(
ctx.object, drawing.strokes(), tmd.influence, mask_memory);
@@ -257,17 +330,17 @@ static void modify_curves(const GreasePencilTextureModifierData &tmd,
switch (GreasePencilTextureModifierMode(tmd.mode)) {
case MOD_GREASE_PENCIL_TEXTURE_STROKE:
write_stroke_transforms(
drawing, curves_mask, tmd.uv_offset, tmd.alignment_rotation, tmd.uv_scale, normalize_u);
drawing, curves_mask, tmd, *ctx.object, tmd.uv_offset, tmd.alignment_rotation, tmd.uv_scale, normalize_u);
break;
case MOD_GREASE_PENCIL_TEXTURE_FILL:
write_fill_transforms(
drawing, curves_mask, tmd.fill_offset, tmd.fill_rotation, tmd.fill_scale);
drawing, curves_mask, tmd, *ctx.object, tmd.fill_offset, tmd.fill_rotation, tmd.fill_scale);
break;
case MOD_GREASE_PENCIL_TEXTURE_STROKE_AND_FILL:
write_stroke_transforms(
drawing, curves_mask, tmd.uv_offset, tmd.alignment_rotation, tmd.uv_scale, normalize_u);
drawing, curves_mask, tmd, *ctx.object, tmd.uv_offset, tmd.alignment_rotation, tmd.uv_scale, normalize_u);
write_fill_transforms(
drawing, curves_mask, tmd.fill_offset, tmd.fill_rotation, tmd.fill_scale);
drawing, curves_mask, tmd, *ctx.object, tmd.fill_offset, tmd.fill_rotation, tmd.fill_scale);
break;
}
}
@@ -329,6 +402,20 @@ static void panel_draw(const bContext *C, Panel *panel)
uiItemR(col, ptr, "fill_scale", UI_ITEM_NONE, IFACE_("Scale"), ICON_NONE);
}
if (uiLayout *random_layout = uiLayoutPanelProp(
C, layout, ptr, "open_random_panel", IFACE_("Randomize")))
{
uiLayout *subcol = uiLayoutColumn(random_layout, false);
uiItemR(subcol, ptr, "rnd_uv_offset", UI_ITEM_NONE, IFACE_("Stroke Offset"), ICON_NONE);
uiItemR(subcol, ptr, "rnd_uv_rot", UI_ITEM_NONE, IFACE_("Stroke Rotation"), ICON_NONE);
uiItemR(subcol, ptr, "rnd_uv_scale", UI_ITEM_NONE, IFACE_("Stroke Scale"), ICON_NONE);
uiItemR(subcol, ptr, "rnd_fill_rot", UI_ITEM_NONE, IFACE_("Fill Rot"), ICON_NONE);
uiItemR(subcol, ptr, "rnd_fill_offset", UI_ITEM_NONE, IFACE_("Fill Offset"), ICON_NONE);
uiItemR(subcol, ptr, "rnd_fill_scale", UI_ITEM_NONE, IFACE_("Fill Scale"), ICON_NONE);
uiItemR(subcol, ptr, "seed", UI_ITEM_NONE, nullptr, ICON_NONE);
}
if (uiLayout *influence_panel = uiLayoutPanelProp(
C, layout, ptr, "open_influence_panel", IFACE_("Influence")))
{