Anim: Per bone wire width for custom shapes #120176

Open
Christoph Lendenfeld wants to merge 36 commits from ChrisLend/blender:bone_wire_width into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
14 changed files with 245 additions and 15 deletions

View File

@ -413,6 +413,7 @@ class BONE_PT_display_custom_shape(BoneButtonsPanel, Panel):
sub.separator()
sub.prop(bone, "show_wire", text="Wireframe")
sub.prop(pchan, "custom_shape_wire_width")
class BONE_PT_inverse_kinematics(BoneButtonsPanel, Panel):

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 26
#define BLENDER_FILE_SUBVERSION 27
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@ -645,6 +645,7 @@ bPoseChannel *BKE_pose_channel_ensure(bPose *pose, const char *name)
copy_v3_fl(chan->custom_scale_xyz, 1.0f);
zero_v3(chan->custom_translation);
zero_v3(chan->custom_rotation_euler);
chan->custom_shape_wire_width = 1.0f;
/* init vars to prevent math errors */
unit_qt(chan->quat);

View File

@ -3328,6 +3328,17 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 27)) {
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
if (!ob->pose) {
continue;
}
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
pchan->custom_shape_wire_width = 1.0;
}
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@ -748,6 +748,9 @@ set(GLSL_SRC
engines/overlay/shaders/overlay_armature_shape_solid_frag.glsl
engines/overlay/shaders/overlay_armature_shape_solid_vert.glsl
engines/overlay/shaders/overlay_armature_shape_wire_vert.glsl
engines/overlay/shaders/overlay_armature_shape_wire_vert_no_geom.glsl
engines/overlay/shaders/overlay_armature_shape_wire_frag.glsl
engines/overlay/shaders/overlay_armature_shape_wire_geom.glsl
engines/overlay/shaders/overlay_armature_sphere_outline_vert.glsl
engines/overlay/shaders/overlay_armature_sphere_solid_frag.glsl
engines/overlay/shaders/overlay_armature_sphere_solid_vert.glsl

View File

@ -432,11 +432,16 @@ void OVERLAY_armature_cache_init(OVERLAY_Data *vedata)
else {
cb->transp.point_outline = cb->solid.point_outline;
}
DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
GPUTexture **depth_tex = &dtxl->depth;
const bool do_smooth_wire = U.gpu_flag & USER_GPU_FLAG_OVERLAY_SMOOTH_WIRE;
sh = OVERLAY_shader_armature_shape(true);
cb->solid.custom_outline = grp = DRW_shgroup_create(sh, armature_ps);
DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo);
DRW_shgroup_uniform_float_copy(grp, "alpha", 1.0f);
DRW_shgroup_uniform_bool_copy(grp, "do_smooth_wire", do_smooth_wire);
DRW_shgroup_uniform_texture_ref(grp, "depthTex", depth_tex);
cb->solid.box_outline = BUF_INSTANCE(grp, format, DRW_cache_bone_box_wire_get());
cb->solid.octa_outline = BUF_INSTANCE(grp, format, DRW_cache_bone_octahedral_wire_get());
@ -458,10 +463,13 @@ void OVERLAY_armature_cache_init(OVERLAY_Data *vedata)
cb->solid.custom_wire = grp = DRW_shgroup_create(sh, armature_ps);
DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo);
DRW_shgroup_uniform_float_copy(grp, "alpha", 1.0f);
DRW_shgroup_uniform_bool_copy(grp, "do_smooth_wire", do_smooth_wire);
DRW_shgroup_uniform_float_copy(grp, "width_compression", WIRE_WIDTH_COMPRESSION);
DRW_shgroup_uniform_texture_ref(grp, "depthTex", depth_tex);
DRW_shgroup_state_enable(grp, DRW_STATE_BLEND_ALPHA);
if (use_wire_alpha) {
cb->transp.custom_wire = grp = DRW_shgroup_create(sh, armature_ps);
DRW_shgroup_state_enable(grp, DRW_STATE_BLEND_ALPHA);
DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo);
DRW_shgroup_uniform_float_copy(grp, "alpha", wire_alpha);
}
@ -847,6 +855,7 @@ static void drw_shgroup_bone_custom_solid_mesh(const ArmatureDrawContext *ctx,
const float bone_color[4],
const float hint_color[4],
const float outline_color[4],
const float wire_width,
Object *custom)
{
using namespace blender::draw;
@ -880,7 +889,8 @@ static void drw_shgroup_bone_custom_solid_mesh(const ArmatureDrawContext *ctx,
if (loose_edges) {
buf = custom_bone_instance_shgroup(ctx, ctx->custom_wire, loose_edges);
OVERLAY_bone_instance_data_set_color_hint(&inst_data, outline_color);
OVERLAY_bone_instance_data_set_color(&inst_data, outline_color);
inst_data.color_a = encode_2f_to_float(outline_color[0], outline_color[1]);

eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeuw. Not your code, but just that this is necessary. Please do add what the floats are clamped to (I'm guessing 1.0, but better to have this explicit).

eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeuw. Not your code, but just that this is necessary. Please do add what the floats are clamped to (I'm guessing 1.0, but better to have this explicit).

I am not sure what to do here. It is clamping to 2, but I am only using the 0-1 range.
Adding a comment here means it can go out of sync with the code easily.

I am not sure what to do here. It is clamping to 2, but I am only using the 0-1 range. Adding a comment here means it can go out of sync with the code easily.
inst_data.color_b = encode_2f_to_float(outline_color[2], wire_width / WIRE_WIDTH_COMPRESSION);

Define this maximum as a constexpr in a header somewhere, then use it in the various places, instead of having 16.0 that needs to be synced up.

Define this maximum as a `constexpr` in a header somewhere, then use it in the various places, instead of having `16.0` that needs to be synced up.

moved it to DNA_action_types.h just above where bPoseChannel is defined

moved it to `DNA_action_types.h` just above where `bPoseChannel` is defined
DRW_buffer_add_entry_struct(buf, inst_data.mat);
}
@ -892,6 +902,7 @@ static void drw_shgroup_bone_custom_mesh_wire(const ArmatureDrawContext *ctx,
Mesh *mesh,
const float (*bone_mat)[4],
const float color[4],
const float wire_width,
Object *custom)
{
using namespace blender::draw;
@ -905,7 +916,8 @@ static void drw_shgroup_bone_custom_mesh_wire(const ArmatureDrawContext *ctx,
BoneInstanceData inst_data;
mul_m4_m4m4(inst_data.mat, ctx->ob->object_to_world().ptr(), bone_mat);
OVERLAY_bone_instance_data_set_color_hint(&inst_data, color);
OVERLAY_bone_instance_data_set_color(&inst_data, color);
inst_data.color_a = encode_2f_to_float(color[0], color[1]);
inst_data.color_b = encode_2f_to_float(color[2], wire_width / WIRE_WIDTH_COMPRESSION);
DRW_buffer_add_entry_struct(buf, inst_data.mat);
}
@ -953,6 +965,7 @@ static void drw_shgroup_bone_custom_solid(const ArmatureDrawContext *ctx,
const float bone_color[4],
const float hint_color[4],
const float outline_color[4],
const float wire_width,
Object *custom)
{
/* The custom object is not an evaluated object, so its object->data field hasn't been replaced
@ -962,7 +975,7 @@ static void drw_shgroup_bone_custom_solid(const ArmatureDrawContext *ctx,
Mesh *mesh = BKE_object_get_evaluated_mesh_no_subsurf(custom);
if (mesh != nullptr) {
drw_shgroup_bone_custom_solid_mesh(
ctx, mesh, bone_mat, bone_color, hint_color, outline_color, custom);
ctx, mesh, bone_mat, bone_color, hint_color, outline_color, wire_width, custom);
return;
}
@ -975,12 +988,13 @@ static void drw_shgroup_bone_custom_solid(const ArmatureDrawContext *ctx,
static void drw_shgroup_bone_custom_wire(const ArmatureDrawContext *ctx,
const float (*bone_mat)[4],
const float color[4],
const float wire_width,
Object *custom)
{
/* See comments in #drw_shgroup_bone_custom_solid. */
Mesh *mesh = BKE_object_get_evaluated_mesh_no_subsurf(custom);
if (mesh != nullptr) {
drw_shgroup_bone_custom_mesh_wire(ctx, mesh, bone_mat, color, custom);
drw_shgroup_bone_custom_mesh_wire(ctx, mesh, bone_mat, color, wire_width, custom);
return;
}
@ -2122,10 +2136,17 @@ class ArmatureBoneDrawStrategyCustomShape : public ArmatureBoneDrawStrategy {
}
}
if ((boneflag & BONE_DRAWWIRE) == 0 && (boneflag & BONE_DRAW_LOCKED_WEIGHT) == 0) {
drw_shgroup_bone_custom_solid(ctx, disp_mat, col_solid, col_hint, col_wire, pchan->custom);
drw_shgroup_bone_custom_solid(ctx,
disp_mat,
col_solid,
col_hint,
col_wire,
pchan->custom_shape_wire_width,
pchan->custom);
}
else {
drw_shgroup_bone_custom_wire(ctx, disp_mat, col_wire, pchan->custom);
drw_shgroup_bone_custom_wire(
ctx, disp_mat, col_wire, pchan->custom_shape_wire_width, pchan->custom);
}
if (select_id != -1) {

View File

@ -136,21 +136,54 @@ GPU_SHADER_CREATE_INFO(overlay_armature_shape_solid_clipped)
.do_static_compilation(true)
.additional_info("overlay_armature_shape_solid", "drw_clipped");
GPU_SHADER_INTERFACE_INFO(overlay_armature_shape_wire_iface, "geometry_in")
.smooth(Type::VEC4, "finalColor")
.flat(Type::FLOAT, "wire_width");
GPU_SHADER_INTERFACE_INFO(overlay_armature_shape_wire_geom_iface, "geometry_out")
.flat(Type::VEC4, "finalColor")
.flat(Type::FLOAT, "wire_width");
GPU_SHADER_INTERFACE_INFO(overlay_armature_shape_wire_geom_noperspective_iface,
"geometry_noperspective_out")
.no_perspective(Type::FLOAT, "edgeCoord");
GPU_SHADER_CREATE_INFO(overlay_armature_shape_wire)
.do_static_compilation(true)
.push_constant(Type::BOOL, "do_smooth_wire")
.push_constant(Type::FLOAT, "width_compression")
.sampler(0, ImageType::DEPTH_2D, "depthTex")
.vertex_in(0, Type::VEC3, "pos")
.vertex_in(1, Type::VEC3, "nor")
/* Per instance. */
.vertex_in(2, Type::MAT4, "inst_obmat")
.vertex_out(overlay_armature_wire_iface)
.vertex_out(overlay_armature_shape_wire_iface)
.vertex_source("overlay_armature_shape_wire_vert.glsl")
.fragment_source("overlay_armature_wire_frag.glsl")
.geometry_out(overlay_armature_shape_wire_geom_iface)
.geometry_out(overlay_armature_shape_wire_geom_noperspective_iface)
.geometry_layout(PrimitiveIn::LINES, PrimitiveOut::TRIANGLE_STRIP, 4)
.geometry_source("overlay_armature_shape_wire_geom.glsl")
.fragment_source("overlay_armature_shape_wire_frag.glsl")
.additional_info("overlay_frag_output", "overlay_armature_common", "draw_globals");
GPU_SHADER_CREATE_INFO(overlay_armature_shape_wire_clipped)
.do_static_compilation(true)
.additional_info("overlay_armature_shape_wire", "drw_clipped");
GPU_SHADER_CREATE_INFO(overlay_armature_shape_wire_no_geom)
.metal_backend_only(true)
.do_static_compilation(true)
.push_constant(Type::BOOL, "do_smooth_wire")
.sampler(0, ImageType::DEPTH_2D, "depthTex")
.vertex_in(0, Type::VEC3, "pos")
.vertex_in(1, Type::VEC3, "nor")
.vertex_in(2, Type::MAT4, "inst_obmat")
.vertex_out(overlay_armature_shape_wire_geom_iface)
.vertex_out(overlay_armature_shape_wire_geom_noperspective_iface)
.vertex_source("overlay_armature_shape_wire_vert_no_geom.glsl")
.fragment_source("overlay_armature_shape_wire_frag.glsl")
.additional_info("overlay_frag_output", "overlay_armature_common", "draw_globals");
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors

if i may, i think 2023should be replaced by 2024

if i may, i think `2023`should be replaced by `2024`

Good catch. I think the currently preferred approach is to just use the year at which the file was first created, so 2019-20232024.

Good catch. I think the currently preferred approach is to just use the year at which the file was first created, so `2019-2023` → `2024`.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(select_lib.glsl)
/**
* We want to know how much a pixel is covered by a line.
* We replace the square pixel with a circle of the same area and try to find the intersection
* area. The area we search is the circular segment. https://en.wikipedia.org/wiki/Circular_segment
* The formula for the area uses inverse trig function and is quite complex. Instead,
* we approximate it by using the smooth-step function and a 1.05 factor to the disc radius.
*/
#define M_1_SQRTPI 0.5641895835477563 /* `1/sqrt(pi)`. */
#define DISC_RADIUS (M_1_SQRTPI * 1.05)
#define GRID_LINE_SMOOTH_START (0.5 - DISC_RADIUS)
#define GRID_LINE_SMOOTH_END (0.5 + DISC_RADIUS)
bool test_occlusion()
{
return gl_FragCoord.z > texelFetch(depthTex, ivec2(gl_FragCoord.xy), 0).r;
}
float edge_step(float dist)
{
if (do_smooth_wire) {
return smoothstep(GRID_LINE_SMOOTH_START, GRID_LINE_SMOOTH_END, dist);
}
else {
return step(0.5, dist);
}
}
void main()
{
const float dist = abs(geometry_noperspective_out.edgeCoord) - geometry_out.wire_width / 2.0;
const float mix_w = clamp(edge_step(dist), 0.0, 1.0);
fragColor = mix(vec4(geometry_out.finalColor.rgb, alpha), vec4(0), mix_w);
fragColor.a *= 1.0 - mix_w;
fragColor.a *= test_occlusion() ? alpha : 1.0;
select_id_output(select_id);
lineOutput = vec4(0);
}

View File

@ -0,0 +1,73 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma BLENDER_REQUIRE(common_view_clipping_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
void do_vertex(vec4 color, vec4 pos, float coord, vec2 offset)
{
geometry_out.finalColor = color;
geometry_noperspective_out.edgeCoord = coord;
gl_Position = pos;
/* Multiply offset by 2 because gl_Position range is [-1..1]. */

The comment seems a bit incomplete. It's nice that that range is [-1…1], but without knowing what the range of offset is, this doesn't help much.

The comment seems a bit incomplete. It's nice that that range is `[-1…1]`, but without knowing what the range of `offset` is, this doesn't help much.

About this and the comment below. I copied that comment from the _geom.glsl file I used as a reference to construct this.
I see how that isn't clear from the PR but I am still uncertain how I should handle that.
Change the comment only in this file or make a refactor that changes it in all

About this and the comment below. I copied that comment from the _geom.glsl file I used as a reference to construct this. I see how that isn't clear from the PR but I am still uncertain how I should handle that. Change the comment only in this file or make a refactor that changes it in all
gl_Position.xy += offset * 2.0 * pos.w;
/* Correct but fails due to an AMD compiler bug, see: #62792.

Move the comment underneath the #if 0 so that it's clearer that it's scoped to that.

Also issue #62792 has been closed as 'resolved' years ago, so not sure why this is referenced here.

Move the comment underneath the `#if 0` so that it's clearer that it's scoped to that. Also issue #62792 has been closed as 'resolved' years ago, so not sure why this is referenced here.
* Do inline instead. */
#if 0
view_clipping_distances_set(gl_in[i]);
#endif
gpu_EmitVertex();
}
void main()
{
/* Clip line against near plane to avoid deformed lines. */
vec4 pos0 = gl_in[0].gl_Position;
vec4 pos1 = gl_in[1].gl_Position;
const vec2 pz_ndc = vec2(pos0.z / pos0.w, pos1.z / pos1.w);
const bvec2 clipped = lessThan(pz_ndc, vec2(-1.0));
if (all(clipped)) {
/* Totally clipped. */
return;
}
const vec4 pos01 = pos0 - pos1;
const float ofs = abs((pz_ndc.y + 1.0) / (pz_ndc.x - pz_ndc.y));
if (clipped.y) {
pos1 += pos01 * ofs;
}
else if (clipped.x) {
pos0 -= pos01 * (1.0 - ofs);
}
vec2 screen_space_pos[2];
screen_space_pos[0] = pos0.xy / pos0.w;
screen_space_pos[1] = pos1.xy / pos1.w;
const float wire_width = geometry_in[0].wire_width;
geometry_out.wire_width = wire_width;
float half_size = max(wire_width / 2.0, 0.5);
if (do_smooth_wire) {
/* Add 1px for AA */
half_size += 0.5;
}
const vec2 line = (screen_space_pos[0] - screen_space_pos[1]) * sizeViewport.xy;
const vec2 line_norm = normalize(vec2(line[1], -line[0]));
vec2 edge_ofs = (half_size * line_norm) * sizeViewportInv;
/* Due to an AMD glitch, this line was moved out of the `do_vertex`
* function (see #62792). */
view_clipping_distances_set(gl_in[0]);
const vec4 final_color = geometry_in[0].finalColor;
do_vertex(final_color, pos0, half_size, edge_ofs);
do_vertex(final_color, pos0, -half_size, -edge_ofs);
view_clipping_distances_set(gl_in[1]);
do_vertex(final_color, pos1, half_size, edge_ofs);
do_vertex(final_color, pos1, -half_size, -edge_ofs);
EndPrimitive();
}

View File

@ -13,10 +13,10 @@ void main()
vec3 world_pos = (model_mat * vec4(pos, 1.0)).xyz;
gl_Position = point_world_to_ndc(world_pos);
finalColor.rgb = mix(state_color.rgb, bone_color.rgb, 0.5);
finalColor.a = 1.0;
edgeStart = edgePos = ((gl_Position.xy / gl_Position.w) * 0.5 + 0.5) * sizeViewport.xy;
geometry_in.finalColor.rgb = mix(state_color.rgb, bone_color.rgb, 0.5);
geometry_in.finalColor.a = 1.0;
/* Because the packing clamps the value, the wire width is passed in compressed. */
geometry_in.wire_width = bone_color.a * width_compression;

AFAIK it should be possible to #include a file in both GLSL and C++, so that this constant can be shared from a single definition. If that's not possible, at least mention WIRE_WIDTH_COMPRESSION here so that it's clear where this value comes from.

AFAIK it should be possible to `#include` a file in both GLSL and C++, so that this constant can be shared from a single definition. If that's not possible, at least mention `WIRE_WIDTH_COMPRESSION` here so that it's clear where this value comes from.
view_clipping_distances(world_pos);
}

View File

@ -0,0 +1,24 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma BLENDER_REQUIRE(common_view_clipping_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
void main()
{
vec4 bone_color, state_color;
mat4 model_mat = extract_matrix_packed_data(inst_obmat, state_color, bone_color);
vec3 world_pos = (model_mat * vec4(pos, 1.0)).xyz;
gl_Position = point_world_to_ndc(world_pos);
/* Due to packing, the wire width is passed in compressed. If the RNA range is increased, this
* needs to change as well. */
const float wire_width = bone_color.a * width_compression;

Same as above, should be using the constant, or as a fallback reference it.

Same as above, should be using the constant, or as a fallback reference it.
geometry_out.wire_width = wire_width;
geometry_out.finalColor.rgb = mix(state_color.rgb, bone_color.rgb, 0.5);
geometry_out.finalColor.a = 1.0;
view_clipping_distances(world_pos);
}

View File

@ -499,6 +499,7 @@ void gpu_shader_create_info_init()
/* Overlay Armature Shape outline. */
overlay_armature_shape_outline = overlay_armature_shape_outline_no_geom;
overlay_armature_shape_outline_clipped = overlay_armature_shape_outline_clipped_no_geom;
overlay_armature_shape_wire = overlay_armature_shape_wire_no_geom;
/* Overlay Motion Path Line. */
overlay_motion_path_line = overlay_motion_path_line_no_geom;

View File

@ -244,6 +244,12 @@ typedef struct bPoseChannel_Runtime {
/* PoseChannel ------------------------------------ */
/* Used in the armature drawing code. Due to the encoding clamping the passed in floats, the wire
* width needs to be scaled down. */
#ifdef __cplusplus

Also mention that this is used in some GLSL files. If those explicitly name the constant, it shouldn't be necessary to list the filenames here (as those can get outdated).

Also mention that this is used in some GLSL files. If those explicitly name the constant, it shouldn't be necessary to list the filenames here (as those can get outdated).

+This should be protected by #ifdef __cplusplus

+This should be protected by `#ifdef __cplusplus`

@mod_moder can you explain why?

@mod_moder can you explain why?

This is C header, but constexpr is CPP thing.

This is C header, but `constexpr` is CPP thing.
static constexpr float WIRE_WIDTH_COMPRESSION = 16.0f;
#endif
/**
* PoseChannel
*
@ -311,6 +317,7 @@ typedef struct bPoseChannel {
float custom_scale_xyz[3];
float custom_translation[3];
float custom_rotation_euler[3];
float custom_shape_wire_width;
/** Transforms - written in by actions or transform. */
float loc[3];
@ -328,7 +335,7 @@ typedef struct bPoseChannel {
float rotAxis[3], rotAngle;
/** #eRotationModes - rotation representation to use. */
short rotmode;
char _pad[2];
char _pad[6];
/**
* Matrix result of location/rotation/scale components, and evaluation of

View File

@ -1162,6 +1162,14 @@ static void rna_def_pose_channel(BlenderRNA *brna)
prop, nullptr, "rna_PoseChannel_custom_shape_transform_set", nullptr, nullptr);
RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update");
prop = RNA_def_property(srna, "custom_shape_wire_width", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "custom_shape_wire_width");
RNA_def_property_ui_text(
prop, "Custom Shape Wire Width", "Adjust the line thickness of custom shapes");
RNA_def_property_range(prop, 1.0f, WIRE_WIDTH_COMPRESSION);
RNA_def_property_ui_range(prop, 1.0f, 10.0f, 1, 1);

This comment is now outdated.

This comment is now outdated.
RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update");
prop = RNA_def_property(srna, "color", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "BoneColor");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);