Fix #130855: RegionView3D matrices need to be reset before transforming points
- In "Surface" mode the depth buffer needs to be cached after constructing the base DrawingPlacement, otherwise there is no depth data for projection and the origin is used as a fallback. - The DrawingPlacement relies on the RegionView3D matrices to project from the rendered image back into 3D space. Because the boundary projection is called while the rv3d matrices are still set up for the image render it will look up the wrong pixels from the depth buffer. The solution is to make sure the viewport reset happens before projecting the boundary, i.e. by making sure the reset function is called before `process_image`. - The aspect ratio needs to be taken into account for the boundary-to-strokes transform, otherwise the placement is wrong depending on how much the region aspect deviates from square. The code for computing the zoom factor and offset has also been cleaned up and documented somewhat better, and now uses the `Bounds<float2>` struct instead of old `rctf`. Pull Request: https://projects.blender.org/blender/blender/pulls/131321
This commit is contained in:
@@ -43,7 +43,10 @@ constexpr const bool enable_debug_gpu_capture = true;
|
||||
|
||||
RegionViewData region_init(ARegion ®ion, const int2 &win_size)
|
||||
{
|
||||
const RegionViewData data = {int2{region.winx, region.winy}, region.winrct};
|
||||
RegionView3D &rv3d = *static_cast<RegionView3D *>(region.regiondata);
|
||||
|
||||
const RegionViewData data = {
|
||||
int2{region.winx, region.winy}, region.winrct, ED_view3d_mats_rv3d_backup(&rv3d)};
|
||||
|
||||
/* Resize region. */
|
||||
region.winrct.xmin = 0;
|
||||
@@ -58,9 +61,14 @@ RegionViewData region_init(ARegion ®ion, const int2 &win_size)
|
||||
|
||||
void region_reset(ARegion ®ion, const RegionViewData &data)
|
||||
{
|
||||
region.winx = data.region_winsize.x;
|
||||
region.winy = data.region_winsize.y;
|
||||
region.winrct = data.region_winrct;
|
||||
RegionView3D &rv3d = *static_cast<RegionView3D *>(region.regiondata);
|
||||
|
||||
region.winx = data.winsize.x;
|
||||
region.winy = data.winsize.y;
|
||||
region.winrct = data.winrct;
|
||||
|
||||
ED_view3d_mats_rv3d_restore(&rv3d, data.rv3d_store);
|
||||
MEM_freeN(data.rv3d_store);
|
||||
}
|
||||
|
||||
GPUOffScreen *image_render_begin(const int2 &win_size)
|
||||
|
||||
@@ -38,6 +38,7 @@ struct View3D;
|
||||
struct ViewContext;
|
||||
struct BVHTree;
|
||||
struct GreasePencilLineartModifierData;
|
||||
struct RV3DMatrixStore;
|
||||
namespace blender {
|
||||
namespace bke {
|
||||
enum class AttrDomain : int8_t;
|
||||
@@ -617,8 +618,9 @@ namespace image_render {
|
||||
|
||||
/** Region size to restore after rendering. */
|
||||
struct RegionViewData {
|
||||
int2 region_winsize;
|
||||
rcti region_winrct;
|
||||
int2 winsize;
|
||||
rcti winrct;
|
||||
RV3DMatrixStore *rv3d_store;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_bounds.hh"
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_index_mask.hh"
|
||||
#include "BLI_math_base.hh"
|
||||
@@ -579,6 +580,7 @@ static bke::CurvesGeometry boundary_to_curves(const Scene &scene,
|
||||
const FillBoundary &boundary,
|
||||
const ImageBufferAccessor &buffer,
|
||||
const ed::greasepencil::DrawingPlacement &placement,
|
||||
const float3x3 &image_to_region,
|
||||
const int material_index,
|
||||
const float hardness)
|
||||
{
|
||||
@@ -635,7 +637,9 @@ static bke::CurvesGeometry boundary_to_curves(const Scene &scene,
|
||||
for (const int point_i : curves.points_range()) {
|
||||
const int pixel_index = boundary.pixels[point_i];
|
||||
const int2 pixel_coord = buffer.coord_from_index(pixel_index);
|
||||
const float3 position = placement.project_with_shift(float2(pixel_coord));
|
||||
const float2 region_coord =
|
||||
math::transform_point(image_to_region, float3(pixel_coord, 1.0f)).xy();
|
||||
const float3 position = placement.project_with_shift(region_coord);
|
||||
positions[point_i] = position;
|
||||
|
||||
/* Calculate radius and opacity for the outline as if it was a user stroke with full pressure.
|
||||
@@ -698,6 +702,7 @@ static bke::CurvesGeometry process_image(Image &ima,
|
||||
const ViewContext &view_context,
|
||||
const Brush &brush,
|
||||
const ed::greasepencil::DrawingPlacement &placement,
|
||||
const float3x3 &image_to_region,
|
||||
const int stroke_material_index,
|
||||
const float stroke_hardness,
|
||||
const bool invert,
|
||||
@@ -757,6 +762,7 @@ static bke::CurvesGeometry process_image(Image &ima,
|
||||
boundary,
|
||||
buffer,
|
||||
placement,
|
||||
image_to_region,
|
||||
stroke_material_index,
|
||||
stroke_hardness);
|
||||
}
|
||||
@@ -843,29 +849,26 @@ static VArray<ColorGeometry4f> get_stroke_colors(const Object &object,
|
||||
return VArray<ColorGeometry4f>::ForContainer(colors);
|
||||
}
|
||||
|
||||
static rctf get_region_bounds(const ARegion ®ion)
|
||||
static Bounds<float2> get_region_bounds(const ARegion ®ion)
|
||||
{
|
||||
/* Initialize maximum bound-box size. */
|
||||
rctf region_bounds;
|
||||
BLI_rctf_init(®ion_bounds, 0, region.winx, 0, region.winy);
|
||||
return region_bounds;
|
||||
return {float2(0), float2(region.winx, region.winy)};
|
||||
}
|
||||
|
||||
/* Helper: Calc the maximum bounding box size of strokes to get the zoom level of the viewport.
|
||||
* For each stroke, the 2D projected bounding box is calculated and using this data, the total
|
||||
* object bounding box (all strokes) is calculated. */
|
||||
static rctf get_boundary_bounds(const ARegion ®ion,
|
||||
const RegionView3D &rv3d,
|
||||
const Object &object,
|
||||
const Object &object_eval,
|
||||
const VArray<bool> &boundary_layers,
|
||||
const Span<DrawingInfo> src_drawings)
|
||||
static std::optional<Bounds<float2>> get_boundary_bounds(const ARegion ®ion,
|
||||
const RegionView3D &rv3d,
|
||||
const Object &object,
|
||||
const Object &object_eval,
|
||||
const VArray<bool> &boundary_layers,
|
||||
const Span<DrawingInfo> src_drawings)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
using bke::greasepencil::Layer;
|
||||
|
||||
rctf bounds;
|
||||
BLI_rctf_init_minmax(&bounds);
|
||||
std::optional<Bounds<float2>> boundary_bounds;
|
||||
|
||||
BLI_assert(object.type == OB_GREASE_PENCIL);
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
|
||||
@@ -917,15 +920,14 @@ static rctf get_boundary_bounds(const ARegion ®ion,
|
||||
®ion, pos_world, pos_view, V3D_PROJ_TEST_NOP);
|
||||
if (result == V3D_PROJ_RET_OK) {
|
||||
const float pixels = radii[point_i] / ED_view3d_pixel_size(&rv3d, pos_world);
|
||||
rctf point_rect;
|
||||
BLI_rctf_init_pt_radius(&point_rect, pos_view, pixels);
|
||||
BLI_rctf_union(&bounds, &point_rect);
|
||||
Bounds<float2> point_bounds = {pos_view - float2(pixels), pos_view + float2(pixels)};
|
||||
boundary_bounds = bounds::merge(boundary_bounds, {point_bounds});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return bounds;
|
||||
return boundary_bounds;
|
||||
}
|
||||
|
||||
static auto fit_strokes_to_view(const ViewContext &view_context,
|
||||
@@ -942,98 +944,79 @@ static auto fit_strokes_to_view(const ViewContext &view_context,
|
||||
|
||||
switch (fit_method) {
|
||||
case FillToolFitMethod::None:
|
||||
return std::make_pair(float2(1.0f), float2(0.0f));
|
||||
return std::make_tuple(float2(1.0f), float2(0.0f), float3x3::identity());
|
||||
|
||||
case FillToolFitMethod::FitToView: {
|
||||
const Object &object_eval = *DEG_get_evaluated_object(view_context.depsgraph,
|
||||
view_context.obact);
|
||||
/* Zoom and offset based on bounds, to fit all strokes within the render. */
|
||||
const rctf bounds = get_boundary_bounds(*view_context.region,
|
||||
*view_context.rv3d,
|
||||
*view_context.obact,
|
||||
object_eval,
|
||||
boundary_layers,
|
||||
src_drawings);
|
||||
const rctf region_bounds = get_region_bounds(*view_context.region);
|
||||
UNUSED_VARS(bounds, region_bounds);
|
||||
const float2 bounds_max = float2(bounds.xmax, bounds.ymax);
|
||||
const float2 bounds_min = float2(bounds.xmin, bounds.ymin);
|
||||
const std::optional<Bounds<float2>> boundary_bounds = get_boundary_bounds(
|
||||
*view_context.region,
|
||||
*view_context.rv3d,
|
||||
*view_context.obact,
|
||||
object_eval,
|
||||
boundary_layers,
|
||||
src_drawings);
|
||||
if (!boundary_bounds) {
|
||||
return std::make_tuple(float2(1.0f), float2(0.0f), float3x3::identity());
|
||||
}
|
||||
|
||||
/* Include fill point for computing zoom. */
|
||||
const float2 fill_bounds_min = math::min(bounds_min, fill_point) - margin;
|
||||
const float2 fill_bounds_max = math::max(bounds_max, fill_point) + margin;
|
||||
const float2 fill_bounds_center = 0.5f * (fill_bounds_min + fill_bounds_max);
|
||||
const float2 fill_bounds_extent = fill_bounds_max - fill_bounds_min;
|
||||
const Bounds<float2> fill_bounds = [&]() {
|
||||
Bounds<float2> result = bounds::merge(*boundary_bounds, Bounds<float2>(fill_point));
|
||||
result.pad(margin);
|
||||
return result;
|
||||
}();
|
||||
|
||||
const float2 region_max = float2(region_bounds.xmax, region_bounds.ymax);
|
||||
const float2 region_min = float2(region_bounds.xmin, region_bounds.ymin);
|
||||
const float2 region_center = 0.5f * (region_min + region_max);
|
||||
const float2 region_extent = region_max - region_min;
|
||||
|
||||
const float2 zoom_factors = math::clamp(math::safe_divide(fill_bounds_extent, region_extent),
|
||||
float2(min_zoom_factor),
|
||||
float2(max_zoom_factor));
|
||||
const Bounds<float2> region_bounds = get_region_bounds(*view_context.region);
|
||||
const float2 zoom_factors = math::clamp(
|
||||
math::safe_divide(fill_bounds.size(), region_bounds.size()),
|
||||
float2(min_zoom_factor),
|
||||
float2(max_zoom_factor));
|
||||
/* Use the most zoomed out factor for uniform scale. */
|
||||
const float2 zoom = uniform_zoom ? float2(math::reduce_max(zoom_factors)) : zoom_factors;
|
||||
|
||||
/* Clamp offset to always include the center point. */
|
||||
const float2 offset_center = fill_bounds_center - region_center;
|
||||
const float2 offset_min = fill_point + 0.5f * fill_bounds_extent - region_center;
|
||||
const float2 offset_max = fill_point - 0.5f * fill_bounds_extent - region_center;
|
||||
const float2 region_offset = float2(
|
||||
fill_point.x < bounds_min.x ?
|
||||
offset_min.x :
|
||||
(fill_point.x > bounds_max.x ? offset_max.x : offset_center.x),
|
||||
fill_point.y < bounds_min.y ?
|
||||
offset_min.y :
|
||||
(fill_point.y > bounds_max.y ? offset_max.y : offset_center.y));
|
||||
const float2 offset = math::safe_divide(region_offset, region_extent);
|
||||
/* Actual rendered bounds based on the final zoom factor. */
|
||||
const Bounds<float2> render_bounds = {
|
||||
fill_bounds.center() - 0.5f * region_bounds.size() * zoom.x,
|
||||
fill_bounds.center() + 0.5f * region_bounds.size() * zoom.y};
|
||||
|
||||
return std::make_pair(zoom, offset);
|
||||
/* Center offset for View3d matrices (strokes to pixels). */
|
||||
const float2 offset = math::safe_divide(render_bounds.center() - region_bounds.center(),
|
||||
region_bounds.size());
|
||||
/* Corner offset for boundary transform (pixels to strokes). */
|
||||
const float3x3 image_to_region = math::from_loc_scale<float3x3>(
|
||||
render_bounds.min - region_bounds.min, zoom);
|
||||
|
||||
return std::make_tuple(zoom, offset, image_to_region);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(float2(1.0f), float2(0.0f));
|
||||
return std::make_tuple(float2(1.0f), float2(0.0f), float3x3::identity());
|
||||
}
|
||||
|
||||
bke::CurvesGeometry fill_strokes(const ViewContext &view_context,
|
||||
const Brush &brush,
|
||||
const Scene &scene,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const VArray<bool> &boundary_layers,
|
||||
const Span<DrawingInfo> src_drawings,
|
||||
const bool invert,
|
||||
const std::optional<float> alpha_threshold,
|
||||
const float2 &fill_point,
|
||||
const ExtensionData &extensions,
|
||||
const FillToolFitMethod fit_method,
|
||||
const int stroke_material_index,
|
||||
const bool keep_images)
|
||||
static Image *render_strokes(const ViewContext &view_context,
|
||||
const Brush &brush,
|
||||
const Scene &scene,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const VArray<bool> &boundary_layers,
|
||||
const Span<DrawingInfo> src_drawings,
|
||||
const std::optional<float> alpha_threshold,
|
||||
const float2 &fill_point,
|
||||
const ExtensionData &extensions,
|
||||
const ed::greasepencil::DrawingPlacement &placement,
|
||||
const float2 &zoom,
|
||||
const float2 &offset)
|
||||
{
|
||||
using bke::greasepencil::Layer;
|
||||
|
||||
ARegion ®ion = *view_context.region;
|
||||
View3D &view3d = *view_context.v3d;
|
||||
RegionView3D &rv3d = *view_context.rv3d;
|
||||
Depsgraph &depsgraph = *view_context.depsgraph;
|
||||
Object &object = *view_context.obact;
|
||||
|
||||
BLI_assert(object.type == OB_GREASE_PENCIL);
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
|
||||
const Object &object_eval = *DEG_get_evaluated_object(&depsgraph, &object);
|
||||
const ed::greasepencil::DrawingPlacement placement(scene, region, view3d, object_eval, &layer);
|
||||
|
||||
/* Zoom and offset based on bounds, to fit all strokes within the render. */
|
||||
const bool uniform_zoom = true;
|
||||
const float max_zoom_factor = 5.0f;
|
||||
const float2 margin = float2(20);
|
||||
const auto [zoom, offset] = fit_strokes_to_view(view_context,
|
||||
boundary_layers,
|
||||
src_drawings,
|
||||
fit_method,
|
||||
fill_point,
|
||||
uniform_zoom,
|
||||
max_zoom_factor,
|
||||
margin);
|
||||
/* Scale stroke radius by half to hide gaps between filled areas and boundaries. */
|
||||
const float radius_scale = (brush.gpencil_settings->fill_draw_mode == GP_FILL_DMODE_CONTROL) ?
|
||||
0.0f :
|
||||
@@ -1128,7 +1111,65 @@ bke::CurvesGeometry fill_strokes(const ViewContext &view_context,
|
||||
GPU_depth_mask(false);
|
||||
GPU_blend(GPU_BLEND_NONE);
|
||||
|
||||
Image *ima = image_render::image_render_end(*view_context.bmain, offscreen_buffer);
|
||||
return image_render::image_render_end(*view_context.bmain, offscreen_buffer);
|
||||
}
|
||||
|
||||
bke::CurvesGeometry fill_strokes(const ViewContext &view_context,
|
||||
const Brush &brush,
|
||||
const Scene &scene,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const VArray<bool> &boundary_layers,
|
||||
const Span<DrawingInfo> src_drawings,
|
||||
const bool invert,
|
||||
const std::optional<float> alpha_threshold,
|
||||
const float2 &fill_point,
|
||||
const ExtensionData &extensions,
|
||||
const FillToolFitMethod fit_method,
|
||||
const int stroke_material_index,
|
||||
const bool keep_images)
|
||||
{
|
||||
ARegion ®ion = *view_context.region;
|
||||
View3D &view3d = *view_context.v3d;
|
||||
Depsgraph &depsgraph = *view_context.depsgraph;
|
||||
Object &object = *view_context.obact;
|
||||
|
||||
BLI_assert(object.type == OB_GREASE_PENCIL);
|
||||
const Object &object_eval = *DEG_get_evaluated_object(&depsgraph, &object);
|
||||
|
||||
/* Zoom and offset based on bounds, to fit all strokes within the render. */
|
||||
const bool uniform_zoom = true;
|
||||
const float max_zoom_factor = 5.0f;
|
||||
const float2 margin = float2(20);
|
||||
const auto [zoom, offset, image_to_region] = fit_strokes_to_view(view_context,
|
||||
boundary_layers,
|
||||
src_drawings,
|
||||
fit_method,
|
||||
fill_point,
|
||||
uniform_zoom,
|
||||
max_zoom_factor,
|
||||
margin);
|
||||
|
||||
ed::greasepencil::DrawingPlacement placement(scene, region, view3d, object_eval, &layer);
|
||||
if (placement.use_project_to_surface()) {
|
||||
placement.cache_viewport_depths(&depsgraph, ®ion, &view3d);
|
||||
}
|
||||
else if (placement.use_project_to_nearest_stroke()) {
|
||||
placement.cache_viewport_depths(&depsgraph, ®ion, &view3d);
|
||||
placement.set_origin_to_nearest_stroke(fill_point);
|
||||
}
|
||||
|
||||
Image *ima = render_strokes(view_context,
|
||||
brush,
|
||||
scene,
|
||||
layer,
|
||||
boundary_layers,
|
||||
src_drawings,
|
||||
alpha_threshold,
|
||||
fill_point,
|
||||
extensions,
|
||||
placement,
|
||||
zoom,
|
||||
offset);
|
||||
if (!ima) {
|
||||
return {};
|
||||
}
|
||||
@@ -1141,6 +1182,7 @@ bke::CurvesGeometry fill_strokes(const ViewContext &view_context,
|
||||
view_context,
|
||||
brush,
|
||||
placement,
|
||||
image_to_region,
|
||||
stroke_material_index,
|
||||
stroke_hardness,
|
||||
invert,
|
||||
|
||||
Reference in New Issue
Block a user