From a76d27f6942ce4497e95462fd57ca67ab5c9cc6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 30 Apr 2018 22:35:30 +0200 Subject: Armature: Add envelope outline shader. --- source/blender/draw/intern/draw_cache.c | 93 +++++------- source/blender/draw/intern/draw_cache.h | 2 +- .../shaders/armature_envelope_outline_vert.glsl | 165 +++++++++++++++++++++ 3 files changed, 206 insertions(+), 54 deletions(-) create mode 100644 source/blender/draw/modes/shaders/armature_envelope_outline_vert.glsl diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c index 1b41ff311a8..db0f891c730 100644 --- a/source/blender/draw/intern/draw_cache.c +++ b/source/blender/draw/intern/draw_cache.c @@ -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; - - 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); - } - - /* Vertices */ - Gwn_VertBuf *vbo = GWN_vertbuf_create_with_format(&format); - GWN_vertbuf_data_alloc(vbo, 4); - - /* 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}); - - 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; + 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, CIRCLE_RESOL); + GWN_vertbuf_data_alloc(vbo, (CIRCLE_RESOL + 1) * 2); - /* 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); + 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)); - /* { 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}); + /* 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); - 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) { diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h index b2270ba57c7..22a5588599f 100644 --- a/source/blender/draw/intern/draw_cache.h +++ b/source/blender/draw/intern/draw_cache.h @@ -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); diff --git a/source/blender/draw/modes/shaders/armature_envelope_outline_vert.glsl b/source/blender/draw/modes/shaders/armature_envelope_outline_vert.glsl new file mode 100644 index 00000000000..2c38ffebca4 --- /dev/null +++ b/source/blender/draw/modes/shaders/armature_envelope_outline_vert.glsl @@ -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; +} -- cgit v1.2.3