Armature: Add envelope outline shader.
This commit is contained in:
@@ -86,8 +86,7 @@ static struct DRWShapeCache {
|
||||
Gwn_Batch *drw_bone_wire_wire;
|
||||
Gwn_Batch *drw_bone_envelope;
|
||||
Gwn_Batch *drw_bone_envelope_distance;
|
||||
Gwn_Batch *drw_bone_envelope_wire;
|
||||
Gwn_Batch *drw_bone_envelope_head_wire;
|
||||
Gwn_Batch *drw_bone_envelope_outline;
|
||||
Gwn_Batch *drw_bone_point;
|
||||
Gwn_Batch *drw_bone_point_wire;
|
||||
Gwn_Batch *drw_bone_arrows;
|
||||
@@ -1959,71 +1958,59 @@ Gwn_Batch *DRW_cache_bone_envelope_solid_get(void)
|
||||
return SHC.drw_bone_envelope;
|
||||
}
|
||||
|
||||
/* Bone body. */
|
||||
Gwn_Batch *DRW_cache_bone_envelope_wire_outline_get(void)
|
||||
Gwn_Batch *DRW_cache_bone_envelope_outline_get(void)
|
||||
{
|
||||
if (!SHC.drw_bone_envelope_wire) {
|
||||
unsigned int v_idx = 0;
|
||||
if (!SHC.drw_bone_envelope_outline) {
|
||||
# define CIRCLE_RESOL 64
|
||||
float v0[2], v1[2], v2[2];
|
||||
const float radius = 1.0f;
|
||||
|
||||
/* Position Only 2D format */
|
||||
static Gwn_VertFormat format = { 0 };
|
||||
static unsigned int pos_id;
|
||||
static struct { uint pos0, pos1, pos2; } attr_id;
|
||||
if (format.attrib_ct == 0) {
|
||||
pos_id = GWN_vertformat_attr_add(&format, "pos", GWN_COMP_F32, 4, GWN_FETCH_FLOAT);
|
||||
attr_id.pos0 = GWN_vertformat_attr_add(&format, "pos0", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
|
||||
attr_id.pos1 = GWN_vertformat_attr_add(&format, "pos1", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
|
||||
attr_id.pos2 = GWN_vertformat_attr_add(&format, "pos2", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
|
||||
}
|
||||
|
||||
/* Vertices */
|
||||
Gwn_VertBuf *vbo = GWN_vertbuf_create_with_format(&format);
|
||||
GWN_vertbuf_data_alloc(vbo, 4);
|
||||
GWN_vertbuf_data_alloc(vbo, (CIRCLE_RESOL + 1) * 2);
|
||||
|
||||
/* Two lines between head and tail circles. */
|
||||
/* Encoded lines, vertex shader gives them final correct value. */
|
||||
/* { X, Y, head/tail, inner/outer border } */
|
||||
GWN_vertbuf_attr_set(vbo, pos_id, v_idx++, (const float[4]){ 1.0f, 0.0f, 0.0f, 0.0f});
|
||||
GWN_vertbuf_attr_set(vbo, pos_id, v_idx++, (const float[4]){ 1.0f, 0.0f, 1.0f, 0.0f});
|
||||
GWN_vertbuf_attr_set(vbo, pos_id, v_idx++, (const float[4]){-1.0f, 0.0f, 0.0f, 0.0f});
|
||||
GWN_vertbuf_attr_set(vbo, pos_id, v_idx++, (const float[4]){-1.0f, 0.0f, 1.0f, 0.0f});
|
||||
v0[0] = radius * sinf((2.0f * M_PI * -2) / ((float)CIRCLE_RESOL));
|
||||
v0[1] = radius * cosf((2.0f * M_PI * -2) / ((float)CIRCLE_RESOL));
|
||||
v1[0] = radius * sinf((2.0f * M_PI * -1) / ((float)CIRCLE_RESOL));
|
||||
v1[1] = radius * cosf((2.0f * M_PI * -1) / ((float)CIRCLE_RESOL));
|
||||
|
||||
SHC.drw_bone_envelope_wire = GWN_batch_create_ex(GWN_PRIM_LINES, vbo, NULL, GWN_BATCH_OWNS_VBO);
|
||||
}
|
||||
return SHC.drw_bone_envelope_wire;
|
||||
}
|
||||
|
||||
|
||||
/* Bone head and tail. */
|
||||
Gwn_Batch *DRW_cache_bone_envelope_head_wire_outline_get(void)
|
||||
{
|
||||
#define CIRCLE_RESOL 32 /* Must be multiple of 2 */
|
||||
if (!SHC.drw_bone_envelope_head_wire) {
|
||||
unsigned int v_idx = 0;
|
||||
|
||||
static Gwn_VertFormat format = { 0 };
|
||||
static unsigned int pos_id;
|
||||
if (format.attrib_ct == 0) {
|
||||
pos_id = GWN_vertformat_attr_add(&format, "pos", GWN_COMP_F32, 4, GWN_FETCH_FLOAT);
|
||||
/* Output 4 verts for each position. See shader for explanation. */
|
||||
unsigned int v = 0;
|
||||
for (int a = 0; a < CIRCLE_RESOL; a++) {
|
||||
v2[0] = radius * sinf((2.0f * M_PI * a) / ((float)CIRCLE_RESOL));
|
||||
v2[1] = radius * cosf((2.0f * M_PI * a) / ((float)CIRCLE_RESOL));
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos0, v , v0);
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos1, v , v1);
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos2, v++, v2);
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos0, v , v0);
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos1, v , v1);
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos2, v++, v2);
|
||||
copy_v2_v2(v0, v1);
|
||||
copy_v2_v2(v1, v2);
|
||||
}
|
||||
v2[0] = 0.0f;
|
||||
v2[1] = radius;
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos0, v , v0);
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos1, v , v1);
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos2, v++, v2);
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos0, v , v0);
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos1, v , v1);
|
||||
GWN_vertbuf_attr_set(vbo, attr_id.pos2, v++, v2);
|
||||
|
||||
/* Vertices */
|
||||
Gwn_VertBuf *vbo = GWN_vertbuf_create_with_format(&format);
|
||||
GWN_vertbuf_data_alloc(vbo, CIRCLE_RESOL);
|
||||
|
||||
/* Encoded lines, vertex shader gives them final correct value. */
|
||||
/* Only head circle (tail is drawn in disp_tail_mat space as a head one by draw_armature.c's draw_point()). */
|
||||
for (int i = 0; i < CIRCLE_RESOL; i++) {
|
||||
const float alpha = 2.0f * M_PI * i / CIRCLE_RESOL;
|
||||
const float x = cosf(alpha);
|
||||
const float y = -sinf(alpha);
|
||||
|
||||
/* { X, Y, head/tail, inner/outer border } */
|
||||
GWN_vertbuf_attr_set(vbo, pos_id, v_idx++, (const float[4]){ x, y, 0.0f, 0.0f});
|
||||
}
|
||||
|
||||
SHC.drw_bone_envelope_head_wire = GWN_batch_create_ex(GWN_PRIM_LINE_LOOP, vbo, NULL, GWN_BATCH_OWNS_VBO);
|
||||
SHC.drw_bone_envelope_outline = GWN_batch_create_ex(GWN_PRIM_TRI_STRIP, vbo, NULL, GWN_BATCH_OWNS_VBO);
|
||||
# undef CIRCLE_RESOL
|
||||
}
|
||||
return SHC.drw_bone_envelope_head_wire;
|
||||
#undef CIRCLE_RESOL
|
||||
return SHC.drw_bone_envelope_outline;
|
||||
}
|
||||
|
||||
|
||||
Gwn_Batch *DRW_cache_bone_point_get(void)
|
||||
{
|
||||
if (!SHC.drw_bone_point) {
|
||||
|
||||
@@ -101,7 +101,7 @@ struct Gwn_Batch *DRW_cache_bone_box_get(void);
|
||||
struct Gwn_Batch *DRW_cache_bone_box_wire_outline_get(void);
|
||||
struct Gwn_Batch *DRW_cache_bone_wire_wire_outline_get(void);
|
||||
struct Gwn_Batch *DRW_cache_bone_envelope_solid_get(void);
|
||||
struct Gwn_Batch *DRW_cache_bone_envelope_wire_outline_get(void);
|
||||
struct Gwn_Batch *DRW_cache_bone_envelope_outline_get(void);
|
||||
struct Gwn_Batch *DRW_cache_bone_envelope_head_wire_outline_get(void);
|
||||
struct Gwn_Batch *DRW_cache_bone_point_get(void);
|
||||
struct Gwn_Batch *DRW_cache_bone_point_wire_outline_get(void);
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
|
||||
uniform mat4 ViewMatrix;
|
||||
uniform mat4 ViewMatrixInverse;
|
||||
uniform mat4 ViewProjectionMatrix;
|
||||
uniform mat4 ProjectionMatrix;
|
||||
|
||||
uniform vec2 viewportSize;
|
||||
uniform float lineThickness = 3.0;
|
||||
|
||||
/* ---- Instanciated Attribs ---- */
|
||||
in vec2 pos0;
|
||||
in vec2 pos1;
|
||||
in vec2 pos2;
|
||||
|
||||
/* ---- Per instance Attribs ---- */
|
||||
/* Assumed to be in world coordinate already. */
|
||||
in vec4 headSphere;
|
||||
in vec4 tailSphere;
|
||||
in vec4 color;
|
||||
in vec3 xAxis;
|
||||
|
||||
flat out vec4 finalColor;
|
||||
|
||||
/* project to screen space */
|
||||
vec2 proj(vec4 pos)
|
||||
{
|
||||
return (0.5 * (pos.xy / pos.w) + 0.5) * viewportSize;
|
||||
}
|
||||
|
||||
vec2 compute_dir(vec2 v0, vec2 v1, vec2 v2)
|
||||
{
|
||||
vec2 dir = normalize(v2 - v0);
|
||||
dir = vec2(dir.y, -dir.x);
|
||||
return dir;
|
||||
}
|
||||
|
||||
mat3 compute_mat(vec4 sphere, vec3 bone_vec, out float z_ofs)
|
||||
{
|
||||
bool is_persp = (ProjectionMatrix[3][3] == 0.0);
|
||||
vec3 cam_ray = (is_persp) ? sphere.xyz - ViewMatrixInverse[3].xyz
|
||||
: -ViewMatrixInverse[2].xyz;
|
||||
|
||||
/* Sphere center distance from the camera (persp) in world space. */
|
||||
float cam_dist = length(cam_ray);
|
||||
|
||||
/* Compute view aligned orthonormal space. */
|
||||
vec3 z_axis = cam_ray / cam_dist;
|
||||
vec3 x_axis = normalize(cross(bone_vec, z_axis));
|
||||
vec3 y_axis = cross(z_axis, x_axis);
|
||||
z_ofs = 0.0;
|
||||
|
||||
if (is_persp) {
|
||||
/* For perspective, the projected sphere radius
|
||||
* can be bigger than the center disc. Compute the
|
||||
* max angular size and compensate by sliding the disc
|
||||
* towards the camera and scale it accordingly. */
|
||||
const float half_pi = 3.1415926 * 0.5;
|
||||
float rad = sphere.w;
|
||||
/* Let be :
|
||||
* V the view vector origin.
|
||||
* O the sphere origin.
|
||||
* T the point on the target circle.
|
||||
* We compute the angle between (OV) and (OT). */
|
||||
float a = half_pi - asin(rad / cam_dist);
|
||||
float cos_b = cos(a);
|
||||
float sin_b = sqrt(clamp(1.0 - cos_b * cos_b, 0.0, 1.0));
|
||||
|
||||
x_axis *= sin_b;
|
||||
y_axis *= sin_b;
|
||||
z_ofs = -rad * cos_b;
|
||||
}
|
||||
|
||||
return mat3(x_axis, y_axis, z_axis);
|
||||
}
|
||||
|
||||
struct Bone { vec3 p1, vec; float vec_rsq, h_bias, h_scale; };
|
||||
|
||||
bool bone_blend_starts(vec3 p, Bone b)
|
||||
{
|
||||
/* Simple capsule sdf with a minor touch and optimisations. */
|
||||
vec3 pa = p - b.p1;
|
||||
float h = dot(pa, b.vec) * b.vec_rsq;
|
||||
h = h * b.h_scale + b.h_bias; /* comment this line for sharp transition. */
|
||||
return h > 0.0; /* we just want to know when the head sphere starts interpolating. */
|
||||
// h = clamp(h, 0.0, 1.0);
|
||||
// return length(pa - b.vec * h) - (b.r1 + b.rdif * h);
|
||||
}
|
||||
|
||||
vec3 get_outline_point(
|
||||
vec2 pos, vec4 sph_near, vec4 sph_far,
|
||||
mat3 mat_near, mat3 mat_far, float z_ofs_near, float z_ofs_far, Bone b)
|
||||
{
|
||||
/* Compute outline position on the nearest sphere and check
|
||||
* if it penetrates the capsule body. If it does, put this
|
||||
* vertex on the farthest sphere. */
|
||||
vec3 wpos = sph_near.xyz + mat_near * vec3(pos * sph_near.w, z_ofs_near);
|
||||
if (bone_blend_starts(wpos, b)) {
|
||||
wpos = sph_far.xyz + mat_far * vec3(pos * sph_far.w, z_ofs_far);
|
||||
}
|
||||
return wpos;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
float dst_head = distance(headSphere.xyz, ViewMatrixInverse[3].xyz);
|
||||
float dst_tail = distance(tailSphere.xyz, ViewMatrixInverse[3].xyz);
|
||||
// float dst_head = -dot(headSphere.xyz, ViewMatrix[2].xyz);
|
||||
// float dst_tail = -dot(tailSphere.xyz, ViewMatrix[2].xyz);
|
||||
|
||||
vec4 sph_near, sph_far;
|
||||
if ((dst_head > dst_tail) && (ProjectionMatrix[3][3] == 0.0)) {
|
||||
sph_near = tailSphere;
|
||||
sph_far = headSphere;
|
||||
}
|
||||
else {
|
||||
sph_near = headSphere;
|
||||
sph_far = tailSphere;
|
||||
}
|
||||
|
||||
Bone b;
|
||||
/* Precompute everything we can to speedup iterations. */
|
||||
b.p1 = sph_near.xyz;
|
||||
b.vec = sph_far.xyz - sph_near.xyz;
|
||||
float vec_lsq = max(1e-8, dot(b.vec, b.vec));
|
||||
b.vec_rsq = 1.0 / vec_lsq;
|
||||
float sinb = (sph_far.w - sph_near.w) * b.vec_rsq;
|
||||
float ofs1 = sinb * sph_near.w;
|
||||
float ofs2 = sinb * sph_far.w;
|
||||
b.h_scale = 1.0 - ofs1 + ofs2;
|
||||
b.h_bias = ofs1 * b.h_scale;
|
||||
|
||||
vec3 bone_vec = (sph_far.xyz - sph_near.xyz) + 1e-8;
|
||||
|
||||
float z_ofs_near, z_ofs_far;
|
||||
mat3 mat_near = compute_mat(sph_near, bone_vec, z_ofs_near);
|
||||
mat3 mat_far = compute_mat(sph_far, bone_vec, z_ofs_far);
|
||||
|
||||
vec3 wpos0 = get_outline_point(pos0, sph_near, sph_far, mat_near, mat_far, z_ofs_near, z_ofs_far, b);
|
||||
vec3 wpos1 = get_outline_point(pos1, sph_near, sph_far, mat_near, mat_far, z_ofs_near, z_ofs_far, b);
|
||||
vec3 wpos2 = get_outline_point(pos2, sph_near, sph_far, mat_near, mat_far, z_ofs_near, z_ofs_far, b);
|
||||
|
||||
vec4 V = ViewMatrix * vec4(wpos1, 1.0);
|
||||
float pres_fac = (ProjectionMatrix[3][3] == 0.0) ? abs(V.z) : 1.0;
|
||||
|
||||
vec4 p0 = ViewProjectionMatrix * vec4(wpos0, 1.0);
|
||||
vec4 p1 = ProjectionMatrix * V;
|
||||
vec4 p2 = ViewProjectionMatrix * vec4(wpos2, 1.0);
|
||||
|
||||
/* compute position from 3 vertex because the change in direction
|
||||
* can happen very quicky and lead to very thin edges. */
|
||||
vec2 ss0 = proj(p0);
|
||||
vec2 ss1 = proj(p1);
|
||||
vec2 ss2 = proj(p2);
|
||||
vec2 edge_dir = compute_dir(ss0, ss1, ss2);
|
||||
|
||||
bool outer = ((gl_VertexID & 1) == 1);
|
||||
vec2 t = lineThickness / viewportSize;
|
||||
t *= pres_fac;
|
||||
t = (outer) ? t : vec2(0.0);
|
||||
|
||||
gl_Position = p1;
|
||||
gl_Position.xy += t * edge_dir;
|
||||
|
||||
finalColor = color;
|
||||
}
|
||||
Reference in New Issue
Block a user