GPv3: Fill texture coordinates data storage #114772

Closed
casey-bianco-davis wants to merge 22 commits from casey-bianco-davis/blender:GPv3-fill-texture into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
6 changed files with 268 additions and 21 deletions

View File

@ -865,6 +865,22 @@ void BKE_grease_pencil_data_update(Depsgraph *depsgraph, Scene *scene, Object *o
void BKE_grease_pencil_duplicate_drawing_array(const GreasePencil *grease_pencil_src,
GreasePencil *grease_pencil_dst);
/*
* Returns the three points UV(1,0), UV(0,1) and UV(0,0) respectively in local-space for the stroke
* `curve_i`
*/
std::array<blender::float3, 3> get_texture_points(const blender::bke::CurvesGeometry &curves,
int curve_i);
casey-bianco-davis marked this conversation as resolved Outdated

This function is used only inside grease_pencil.cc, doesn't need to be exposed in the header.

Storing the points in a float3x3 is confusing, i would suggest returning e.g. float3[3]. Better yet, since these points are only used to compute an (orthonormal) transform the function could just do that and return a float4x4.

I had to implement the same function for the texture modifier. It won't be needed there anymore when this change goes in since the modifier would just change the linear transform.
2c9e34e168/source/blender/modifiers/intern/MOD_grease_pencil_texture.cc (L82-L101)

This function is used only inside grease_pencil.cc, doesn't need to be exposed in the header. Storing the points in a `float3x3` is confusing, i would suggest returning e.g. `float3[3]`. Better yet, since these points are only used to compute an (orthonormal) transform the function could just do that and return a float4x4. I had to implement the same function for the texture modifier. It won't be needed there anymore when this change goes in since the modifier would just change the linear transform. https://projects.blender.org/blender/blender/src/commit/2c9e34e16899d5faf138792bde0b2b4df65a2414/source/blender/modifiers/intern/MOD_grease_pencil_texture.cc#L82-L101

Currently get_texture_points is only used in the same file, but it will be used in a future PR.

I agree with not using float3x3 but I prefer std::array<float3, 3> over c-style arrays.

Currently `get_texture_points` is only used in the same file, but it will be used in a future PR. I agree with not using `float3x3` but I prefer `std::array<float3, 3>` over c-style arrays.
/* Sets the three points in local-space for the stroke `curve_i` */
void set_texture_points(blender::bke::CurvesGeometry &curves,
int curve_i,
const std::array<blender::float3, 3> texture_points);
/*
* Returns the matrix that transforms from a 3D point in local-space to a 2D point in
* texture-space for the stroke `curve_i`
*/
blender::float4x2 get_texture_matrix(const blender::bke::CurvesGeometry &curves, int curve_i);
int BKE_grease_pencil_object_material_index_get_by_name(Object *ob, const char *name);
Material *BKE_grease_pencil_object_material_new(Main *bmain,
Object *ob,

View File

@ -10,6 +10,7 @@
#include "BKE_action.h"
#include "BKE_anim_data.hh"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
@ -30,9 +31,11 @@
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_memarena.h"
#include "BLI_memory_utils.hh"
#include "BLI_offset_indices.hh"
#include "BLI_polyfill_2d.h"
#include "BLI_span.hh"
#include "BLI_stack.hh"
@ -1454,6 +1457,98 @@ void BKE_grease_pencil_duplicate_drawing_array(const GreasePencil *grease_pencil
/** \} */
/* ------------------------------------------------------------------- */
/** \name Grease Pencil texture coordinate functions
* \{ */
std::array<float3, 3> get_texture_points(const blender::bke::CurvesGeometry &curves, int curve_i)
{
using namespace blender;
using namespace blender::bke;
const AttributeAccessor attributes = curves.attributes();
/* The default is the front draw plane (XZ). */
const VArray<float3> text_u = *attributes.lookup_or_default<float3>(
"texture_u", bke::AttrDomain::Curve, float3(1.0f, 0.0f, 0.0f));
const VArray<float3> text_v = *attributes.lookup_or_default<float3>(
"texture_v", bke::AttrDomain::Curve, float3(0.0f, 0.0f, 1.0f));
const VArray<float3> text_origin = *attributes.lookup_or_default<float3>(
"texture_origin", bke::AttrDomain::Curve, float3(0.0f, 0.0f, 0.0f));
const float3 locu = text_u[curve_i];
const float3 locv = text_v[curve_i];
const float3 loco = text_origin[curve_i];
return std::array<float3, 3>{locu, locv, loco};
}
void set_texture_points(blender::bke::CurvesGeometry &curves,
int curve_i,
const std::array<float3, 3> texture_points)
{
using namespace blender;
using namespace blender::bke;
MutableAttributeAccessor attributes = curves.attributes_for_write();
SpanAttributeWriter<float3> textU = attributes.lookup_or_add_for_write_span<float3>(
"texture_u", bke::AttrDomain::Curve);
SpanAttributeWriter<float3> textV = attributes.lookup_or_add_for_write_span<float3>(
"texture_v", bke::AttrDomain::Curve);
SpanAttributeWriter<float3> textO = attributes.lookup_or_add_for_write_span<float3>(
casey-bianco-davis marked this conversation as resolved
Review

I suggest to just store the loc/rot/scale instead of three vectors. That's a bit more flexible and doesn't rely so much on this arbitrary GPv2 method of picking three points to generate the transform.

The pair of get_texture_points/set_texture_points would then be replaced by get_stroke_plane_transform/set_stroke_plane_transform.

I suggest to just store the loc/rot/scale instead of three vectors. That's a bit more flexible and doesn't rely so much on this arbitrary GPv2 method of picking three points to generate the transform. The pair of `get_texture_points`/`set_texture_points` would then be replaced by `get_stroke_plane_transform`/`set_stroke_plane_transform`.
"texture_origin", bke::AttrDomain::Curve);
textU.span[curve_i] = texture_points[0];
textV.span[curve_i] = texture_points[1];
textO.span[curve_i] = texture_points[2];
textU.finish();
textV.finish();
textO.finish();
}
blender::float4x2 get_texture_matrix(const blender::bke::CurvesGeometry &curves, int curve_i)
{
using namespace blender;
using namespace blender::math;
const std::array<float3, 3> texture_points = get_texture_points(curves, curve_i);
const float3 locu = texture_points[0];
const float3 locv = texture_points[1];
const float3 loco = texture_points[2];
const float3 diru = locu - loco;
const float3 dirv = locv - loco;
const float4x3 uv_to_pos = transpose(
float3x4(float4(diru, 0.0f), float4(dirv, 0.0f), float4(loco, 1.0f)));
/*
* We want to solve for `uv` in the equation: `pos = uv * uv_to_pos`
* Because these matrices are not square we can not use a normal inverse.
*
* Our problem has the form of: `X = A * Y`
* We can solve for `A` using: `A = X * B`
*
* Where `B` is the Right-sided inverse, calculated as:
*
* |--------------------------|
* | B = T(Y) * (Y * T(Y))^-1 |
* |--------------------------|
*
* And `T()` is transpose and `()^-1` is the inverse
*/
const float3x4 transpose_uv_to_pos = transpose(uv_to_pos);
const float3x4 right_inverse = transpose_uv_to_pos * invert(uv_to_pos * transpose_uv_to_pos);
return transpose(float2x4(right_inverse));
}
/** \} */
/* ------------------------------------------------------------------- */
/** \name Grease Pencil material functions
* \{ */

View File

@ -156,6 +156,102 @@ static void find_used_vertex_groups(const bGPDframe &gpf,
}
}
/*
* This takes the legacy UV transforms and returns the texture transformation matrix.
*/
float3x2 convert_texture_to_matrix(const float2 uv_translation,
const float uv_rotation,
const float2 uv_scale)
{
using namespace blender;
/* Bounding box data. */
const float2 minv = float2(-1.0f, -1.0f);
const float2 maxv = float2(1.0f, 1.0f);
/* Center of rotation. */
const float2 center = float2(0.5f, 0.5f);
const float2 uv_scale_inv = math::safe_rcp(uv_scale);
casey-bianco-davis marked this conversation as resolved Outdated

math::safe_rcp does 1/x with a null check

`math::safe_rcp` does 1/x with a null check
const float2 d = maxv - minv;
const float s = sin(uv_rotation);
const float c = cos(uv_rotation);
const float2x2 rot = float2x2(float2(c, s), float2(-s, c));
float3x2 textmat = float3x2::identity();
/* Apply bounding box rescaling. */
textmat[2] -= minv;
textmat = math::from_scale<float2x2>(1.0f / d) * textmat;
/* Apply translation. */
textmat[2] += uv_translation;
/* Apply rotation. */
textmat[2] -= center;
textmat = rot * textmat;
LukasTonne marked this conversation as resolved
Review

Could use math::from_origin_transform for this

Could use `math::from_origin_transform` for this

math::from_origin_transform doesn't work here because textmat is float3x2 and rot is float2x2 and math::from_origin_transform only works with same size matrices.

`math::from_origin_transform` doesn't work here because `textmat` is float3x2 and `rot` is float2x2 and `math::from_origin_transform` only works with same size matrices.
textmat[2] += center;
/* Apply scale. */
textmat = math::from_scale<float2x2>(uv_scale_inv) * textmat;
return textmat;
}
/* This gets the legacy stroke points in local-space. */
std::array<float3, 3> get_legacy_stroke_points(bGPDstroke *gps)
{
using namespace blender;
using namespace blender::math;
const bGPDspoint *points = gps->points;
const int totpoints = gps->totpoints;
if (totpoints < 2) {
/* The default is the front draw plane (XZ). */
return std::array<float3, 3>{
float3(1.0f, 0.0f, 0.0f), float3(0.0f, 0.0f, 1.0f), float3(0.0f, 0.0f, 0.0f)};
}
const bGPDspoint *point0 = &points[0];
const bGPDspoint *point1 = &points[1];
const bGPDspoint *point3 = &points[int(totpoints * 0.75f)];
const float3 pt0 = float3(point0->x, point0->y, point0->z);
const float3 pt1 = float3(point1->x, point1->y, point1->z);
const float3 pt3 = float3(point3->x, point3->y, point3->z);
/* Local X axis (p0 -> p1) */
const float3 locx = normalize(pt1 - pt0);
/* Point vector at 3/4 */
const float3 v3 = totpoints == 2 ? pt3 * 0.001f : pt3;
const float3 loc3 = v3 - pt0;
/* Vector orthogonal to polygon plane. */
const float3 normal = cross(locx, loc3);
/* Local Y axis (cross to normal/x axis). */
const float3 locy = normalize(cross(normal, locx));
/* Get local stroke points using `pt0` as origin. */
return std::array<float3, 3>{pt0 + locx, pt0 + locy, pt0};
}
/* This gets the legacy texture points in local-space. */
std::array<float3, 3> get_legacy_texture_points(bGPDstroke *gps)
{
const std::array<float3, 3> stroke_points = get_legacy_stroke_points(gps);
const float3x2 texture_tranformation_mat = convert_texture_to_matrix(
float2(gps->uv_translation), gps->uv_rotation, float2(gps->uv_scale));
const float3x3 stroke_bases = float3x3(
stroke_points[0] - stroke_points[2], stroke_points[1] - stroke_points[2], stroke_points[2]);
const float3x3 texture_bases = stroke_bases * math::invert(float3x3(texture_tranformation_mat));
const float3x3 texture_points = texture_bases +
float3x3(texture_bases[2], texture_bases[2], float3(0.0f));
return std::array<float3, 3>{texture_points[0], texture_points[1], texture_points[2]};
}
void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
const ListBase &vertex_group_names,
GreasePencilDrawing &r_drawing)
@ -259,12 +355,6 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
"hardness", AttrDomain::Curve);
SpanAttributeWriter<float> stroke_point_aspect_ratios =
attributes.lookup_or_add_for_write_span<float>("aspect_ratio", AttrDomain::Curve);
SpanAttributeWriter<float2> stroke_fill_translations =
attributes.lookup_or_add_for_write_span<float2>("fill_translation", AttrDomain::Curve);
SpanAttributeWriter<float> stroke_fill_rotations =
attributes.lookup_or_add_for_write_span<float>("fill_rotation", AttrDomain::Curve);
SpanAttributeWriter<float2> stroke_fill_scales = attributes.lookup_or_add_for_write_span<float2>(
"fill_scale", AttrDomain::Curve);
SpanAttributeWriter<ColorGeometry4f> stroke_fill_colors =
attributes.lookup_or_add_for_write_span<ColorGeometry4f>("fill_color", AttrDomain::Curve);
SpanAttributeWriter<int> stroke_materials = attributes.lookup_or_add_for_write_span<int>(
@ -280,9 +370,6 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
stroke_hardnesses.span[stroke_i] = gps->hardness;
stroke_point_aspect_ratios.span[stroke_i] = gps->aspect_ratio[0] /
max_ff(gps->aspect_ratio[1], 1e-8);
stroke_fill_translations.span[stroke_i] = float2(gps->uv_translation);
stroke_fill_rotations.span[stroke_i] = gps->uv_rotation;
stroke_fill_scales.span[stroke_i] = float2(gps->uv_scale);
stroke_fill_colors.span[stroke_i] = ColorGeometry4f(gps->vert_color_fill);
stroke_materials.span[stroke_i] = gps->mat_nr;
@ -367,6 +454,9 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
/* Unknown curve type. */
BLI_assert_unreachable();
}
const std::array<float3, 3> texture_points = get_legacy_texture_points(gps);
set_texture_points(curves, stroke_i, texture_points);
}
delta_times.finish();
@ -380,9 +470,6 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
stroke_end_caps.finish();
stroke_hardnesses.finish();
stroke_point_aspect_ratios.finish();
stroke_fill_translations.finish();
stroke_fill_rotations.finish();
stroke_fill_scales.finish();
stroke_fill_colors.finish();
stroke_materials.finish();
}

View File

@ -77,6 +77,10 @@ void main()
vec2 uvs = (use_clip) ? clamp(gp_interp.uv, 0.0, 1.0) : gp_interp.uv;
bool premul = flag_test(gp_interp_flat.mat_flag, GP_FILL_TEXTURE_PREMUL);
col = texture_read_as_linearrgb(gpFillTexture, premul, uvs);
/* TODO: Remove this once we can render textures. */
/* Debug color. (Because textures are not yet implemented) */
col = debug_texture(gp_interp.uv);
}
else if (flag_test(gp_interp_flat.mat_flag, GP_FILL_GRADIENT_USE)) {
bool radial = flag_test(gp_interp_flat.mat_flag, GP_FILL_GRADIENT_RADIAL);

View File

@ -553,10 +553,11 @@ static void grease_pencil_geom_batch_ensure(Object &object,
int point_i,
int idx,
float length,
float4x2 texture_matrix,
GreasePencilStrokeVert &s_vert,
GreasePencilColorVert &c_vert) {
copy_v3_v3(s_vert.pos,
math::transform_point(layer_space_to_object_space, positions[point_i]));
float3 pos = math::transform_point(layer_space_to_object_space, positions[point_i]);
copy_v3_v3(s_vert.pos, pos);
s_vert.radius = radii[point_i] * ((end_cap == GP_STROKE_CAP_TYPE_ROUND) ? 1.0f : -1.0f);
s_vert.opacity = opacities[point_i] *
((start_cap == GP_STROKE_CAP_TYPE_ROUND) ? 1.0f : -1.0f);
@ -567,8 +568,7 @@ static void grease_pencil_geom_batch_ensure(Object &object,
s_vert.packed_asp_hard_rot = pack_rotation_aspect_hardness(
rotations[point_i], stroke_point_aspect_ratios[curve_i], stroke_hardnesses[curve_i]);
s_vert.u_stroke = length;
/* TODO: Populate fill UVs. */
s_vert.uv_fill[0] = s_vert.uv_fill[1] = 0;
copy_v2_v2(s_vert.uv_fill, texture_matrix * float4(pos, 1.0f));
LukasTonne marked this conversation as resolved
Review

For applying a transform to a point use
math::transform_point(texture_matrix, pos)

For applying a transform to a point use `math::transform_point(texture_matrix, pos)`

math::transform_point does not work here because the texture matrix is 4x2, projecting points from 3D local-space to 2D texture-space. Whereas math::transform_point only works with 3D to 3D transformations.

`math::transform_point` does not work here because the texture matrix is 4x2, projecting points from 3D local-space to 2D texture-space. Whereas `math::transform_point` only works with 3D to 3D transformations.
Review

Ah, that's alright then, thanks for explaining.

Ah, that's alright then, thanks for explaining.
copy_v4_v4(c_vert.vcol, vertex_colors[point_i]);
copy_v4_v4(c_vert.fcol, stroke_fill_colors[curve_i]);
@ -588,6 +588,7 @@ static void grease_pencil_geom_batch_ensure(Object &object,
IndexRange verts_range = IndexRange(verts_start_offset, num_verts);
MutableSpan<GreasePencilStrokeVert> verts_slice = verts.slice(verts_range);
MutableSpan<GreasePencilColorVert> cols_slice = cols.slice(verts_range);
const float4x2 texture_matrix = get_texture_matrix(curves, curve_i);
const Span<float> lengths = curves.evaluated_lengths_for_curve(curve_i, is_cyclic);
@ -616,6 +617,7 @@ static void grease_pencil_geom_batch_ensure(Object &object,
points[i],
idx,
length,
texture_matrix,
verts_slice[idx],
cols_slice[idx]);
}
@ -630,6 +632,7 @@ static void grease_pencil_geom_batch_ensure(Object &object,
points[0],
idx,
length,
texture_matrix,
verts_slice[idx],
cols_slice[idx]);
}

View File

@ -9,6 +9,7 @@
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_material.h"
#include "BKE_scene.hh"
#include "BLI_length_parameterize.hh"
#include "BLI_math_color.h"
@ -116,6 +117,7 @@ class PaintOperation : public GreasePencilStrokeOperation {
Vector<float2> screen_space_smoothed_coords_;
/* The start index of the smoothing window. */
int active_smooth_start_index_ = 0;
std::array<float3, 3> texture_points_;
/* Helper class to project screen space coordinates to 3d. */
ed::greasepencil::DrawingPlacement placement_;
@ -269,11 +271,20 @@ struct PaintOperationExecutor {
bke::AttrDomain::Point,
{"position", "radius", "opacity", "vertex_color"},
curves.points_range().take_back(1));
bke::fill_attribute_range_default(
attributes,
bke::AttrDomain::Curve,
{"curve_type", "material_index", "cyclic", "hardness", "start_cap", "end_cap"},
curves.curves_range().take_back(1));
bke::fill_attribute_range_default(attributes,
bke::AttrDomain::Curve,
{"curve_type",
"material_index",
"cyclic",
"hardness",
"start_cap",
"end_cap",
"texture_u",
"texture_v",
"texture_origin"},
curves.curves_range().take_back(1));
set_texture_points(curves, curves.curves_range().last(), self.texture_points_);
drawing_->tag_topology_changed();
}
@ -483,6 +494,35 @@ void PaintOperation::on_stroke_begin(const bContext &C, const InputSample &start
placement_.set_origin_to_nearest_stroke(start_sample.mouse_position);
}
/* Align texture with the drawing plane. */
switch (scene->toolsettings->gp_sculpt.lock_axis) {
case GP_LOCKAXIS_VIEW:
texture_points_ = std::array<float3, 3>{placement_.project(float2(region->winx, 0.0f)),
placement_.project(float2(0.0f, region->winy)),
placement_.project(float2(0.0f, 0.0f))};
break;
case GP_LOCKAXIS_Y:
texture_points_ = std::array<float3, 3>{
float3(1.0f, 0.0f, 0.0f), float3(0.0f, 0.0f, 1.0f), float3(0.0f)};
break;
case GP_LOCKAXIS_X:
texture_points_ = std::array<float3, 3>{
float3(0.0f, 1.0f, 0.0f), float3(0.0f, 0.0f, 1.0f), float3(0.0f)};
break;
case GP_LOCKAXIS_Z:
texture_points_ = std::array<float3, 3>{
float3(1.0f, 0.0f, 0.0f), float3(0.0f, 1.0f, 0.0f), float3(0.0f)};
break;
case GP_LOCKAXIS_CURSOR: {
float3x3 mat;
BKE_scene_cursor_rot_to_mat3(&scene->cursor, mat.ptr());
float3 loc = float3(scene->cursor.location);
texture_points_ = std::array<float3, 3>{
mat * float3(1.0f, 0.0f, 0.0f) + loc, mat * float3(0.0f, 1.0f, 0.0f) + loc, loc};
break;
}
}
Material *material = BKE_grease_pencil_object_material_ensure_from_active_input_brush(
CTX_data_main(&C), object, brush);
const int material_index = BKE_object_material_index_get(object, material);
@ -574,6 +614,8 @@ void PaintOperation::process_stroke_end(bke::greasepencil::Drawing &drawing)
curves.resize(curves.points_num() - points_to_remove, curves.curves_num());
curves.offsets_for_write().last() = curves.points_num();
}
set_texture_points(curves, curves.curves_range().last(), this->texture_points_);
}
void PaintOperation::on_stroke_done(const bContext &C)