Armature: "Raytrace" bones endpoint spheres.

Here is how it works:
We render a high poly disc that we orient & scale towards the camera so that
it covers the same pixel of the sphere it's supposed to represent.

Then the pixel shader raytrace the sphere (effectively starting from
the poly disc depth) and outputs the depth to gl_FragDepth.

This approach has many benefit:
- high quality obviously: per pixel accurate depth!
- compatible with MSAA: since the sphere horizon is delimited by polygons,
  we get the coverage computed by the rasterizer. However we still gets
  aliasing if the sphere intersect directly other meshes.
- virtually no overdraw: there is no backface to shade but we still get
  overdraw because by little triangle [gpus rasterize pixel by groups of 4].
- allows early depth test: since the poly disc is set at the nearest depth
  we can output, we can use GL_ARB_conservative_depth to enable early depth
  test and discard pixels that are already behind geometry.
- can draw outline pretty easily without geometry shader.
This commit is contained in:
Clément Foucault 2018-04-27 16:27:47 +02:00
parent a56561dcd2
commit 8c2a6f957a
8 changed files with 380 additions and 1 deletions

View File

@ -221,6 +221,9 @@ data_to_c_simple(modes/shaders/common_globals_lib.glsl SRC)
data_to_c_simple(modes/shaders/common_view_lib.glsl SRC)
data_to_c_simple(modes/shaders/common_fxaa_lib.glsl SRC)
data_to_c_simple(modes/shaders/common_fullscreen_vert.glsl SRC)
data_to_c_simple(modes/shaders/armature_sphere_vert.glsl SRC)
data_to_c_simple(modes/shaders/armature_sphere_frag.glsl SRC)
data_to_c_simple(modes/shaders/armature_sphere_outline_vert.glsl SRC)
data_to_c_simple(modes/shaders/armature_shape_outline_vert.glsl SRC)
data_to_c_simple(modes/shaders/armature_shape_outline_geom.glsl SRC)
data_to_c_simple(modes/shaders/edit_mesh_overlay_frag.glsl SRC)

View File

@ -250,8 +250,12 @@ static void drw_shgroup_bone_custom_wire(const float (*bone_mat)[4], const float
static void drw_shgroup_bone_point_solid(const float (*bone_mat)[4], const float color[4])
{
if (g_data.bone_point_solid == NULL) {
struct Gwn_Batch *geom = DRW_cache_bone_point_get();
#if 0 /* old style geometry sphere */
struct Gwn_Batch *geom = DRW_cache_bone_point_get()
g_data.bone_point_solid = shgroup_instance_solid(g_data.pass_bone_solid, geom);
#else /* new style raytraced sphere */
g_data.bone_point_solid = shgroup_instance_armature_sphere(g_data.pass_bone_solid);
#endif
}
float final_bonemat[4][4];
mul_m4_m4m4(final_bonemat, g_data.ob->obmat, bone_mat);
@ -261,8 +265,12 @@ static void drw_shgroup_bone_point_solid(const float (*bone_mat)[4], const float
static void drw_shgroup_bone_point_wire(const float (*bone_mat)[4], const float color[4])
{
if (g_data.bone_point_wire == NULL) {
#if 0 /* old style 3 axis circles */
struct Gwn_Batch *geom = DRW_cache_bone_point_wire_outline_get();
g_data.bone_point_wire = shgroup_instance_wire(g_data.pass_bone_wire, geom);
#else /* new style contour outline */
g_data.bone_point_wire = shgroup_instance_armature_sphere_outline(g_data.pass_bone_wire);
#endif
}
float final_bonemat[4][4];
mul_m4_m4m4(final_bonemat, g_data.ob->obmat, bone_mat);

View File

@ -2082,6 +2082,7 @@ Gwn_Batch *DRW_cache_bone_envelope_head_wire_outline_get(void)
Gwn_Batch *DRW_cache_bone_point_get(void)
{
if (!SHC.drw_bone_point) {
#if 0 /* old style geometry sphere */
const int lon_res = 16;
const int lat_res = 8;
const float rad = 0.05f;
@ -2119,6 +2120,30 @@ Gwn_Batch *DRW_cache_bone_point_get(void)
}
SHC.drw_bone_point = GWN_batch_create_ex(GWN_PRIM_TRIS, vbo, NULL, GWN_BATCH_OWNS_VBO);
#else
# define CIRCLE_RESOL 64
float v[2];
const float radius = 0.05f;
/* Position Only 2D format */
static Gwn_VertFormat format = { 0 };
static struct { uint pos; } attr_id;
if (format.attrib_ct == 0) {
attr_id.pos = GWN_vertformat_attr_add(&format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
}
Gwn_VertBuf *vbo = GWN_vertbuf_create_with_format(&format);
GWN_vertbuf_data_alloc(vbo, CIRCLE_RESOL);
for (int a = 0; a < CIRCLE_RESOL; a++) {
v[0] = radius * sinf((2.0f * M_PI * a) / ((float)CIRCLE_RESOL));
v[1] = radius * cosf((2.0f * M_PI * a) / ((float)CIRCLE_RESOL));
GWN_vertbuf_attr_set(vbo, attr_id.pos, a, v);
}
SHC.drw_bone_point = GWN_batch_create_ex(GWN_PRIM_TRI_FAN, vbo, NULL, GWN_BATCH_OWNS_VBO);
# undef CIRCLE_RESOL
#endif
}
return SHC.drw_bone_point;
}
@ -2126,8 +2151,48 @@ Gwn_Batch *DRW_cache_bone_point_get(void)
Gwn_Batch *DRW_cache_bone_point_wire_outline_get(void)
{
if (!SHC.drw_bone_point_wire) {
#if 0 /* old style geometry sphere */
Gwn_VertBuf *vbo = sphere_wire_vbo(0.05f);
SHC.drw_bone_point_wire = GWN_batch_create_ex(GWN_PRIM_LINES, vbo, NULL, GWN_BATCH_OWNS_VBO);
#else
# define CIRCLE_RESOL 64
float v0[2], v1[2];
const float radius = 0.05f;
/* Position Only 2D format */
static Gwn_VertFormat format = { 0 };
static struct { uint pos0, pos1; } attr_id;
if (format.attrib_ct == 0) {
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, 3, GWN_FETCH_FLOAT);
}
Gwn_VertBuf *vbo = GWN_vertbuf_create_with_format(&format);
GWN_vertbuf_data_alloc(vbo, (CIRCLE_RESOL + 1) * 2);
v0[0] = radius * sinf((2.0f * M_PI * -1) / ((float)CIRCLE_RESOL));
v0[1] = radius * cosf((2.0f * M_PI * -1) / ((float)CIRCLE_RESOL));
unsigned int v = 0;
for (int a = 0; a < CIRCLE_RESOL; a++) {
v1[0] = radius * sinf((2.0f * M_PI * a) / ((float)CIRCLE_RESOL));
v1[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.pos0, v , v0);
GWN_vertbuf_attr_set(vbo, attr_id.pos1, v++, v1);
copy_v2_v2(v0, v1);
}
v1[0] = 0.0f;
v1[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.pos0, v , v0);
GWN_vertbuf_attr_set(vbo, attr_id.pos1, v++, v1);
SHC.drw_bone_point_wire = GWN_batch_create_ex(GWN_PRIM_TRI_STRIP, vbo, NULL, GWN_BATCH_OWNS_VBO);
# undef CIRCLE_RESOL
#endif
}
return SHC.drw_bone_point_wire;
}

View File

@ -156,12 +156,17 @@ void DRW_globals_update(void)
/* ********************************* SHGROUP ************************************* */
extern char datatoc_armature_sphere_vert_glsl[];
extern char datatoc_armature_sphere_frag_glsl[];
extern char datatoc_armature_sphere_outline_vert_glsl[];
extern char datatoc_armature_shape_outline_vert_glsl[];
extern char datatoc_armature_shape_outline_geom_glsl[];
extern char datatoc_gpu_shader_flat_color_frag_glsl[];
static struct {
struct GPUShader *shape_outline;
struct GPUShader *bone_sphere;
struct GPUShader *bone_sphere_outline;
} g_armature_shaders = {NULL};
static struct {
@ -491,6 +496,47 @@ DRWShadingGroup *shgroup_instance_armature_shape_outline(DRWPass *pass, struct G
return grp;
}
DRWShadingGroup *shgroup_instance_armature_sphere(DRWPass *pass)
{
if (g_armature_shaders.bone_sphere == NULL) {
g_armature_shaders.bone_sphere = DRW_shader_create(
datatoc_armature_sphere_vert_glsl, NULL,
datatoc_armature_sphere_frag_glsl, NULL);
}
/* TODO own format? */
DRW_shgroup_instance_format(g_formats.instance_color, {
{"InstanceModelMatrix", DRW_ATTRIB_FLOAT, 16},
{"color" , DRW_ATTRIB_FLOAT, 4}
});
DRWShadingGroup *grp = DRW_shgroup_instance_create(g_armature_shaders.bone_sphere,
pass, DRW_cache_bone_point_get(), g_formats.instance_color);
return grp;
}
DRWShadingGroup *shgroup_instance_armature_sphere_outline(DRWPass *pass)
{
if (g_armature_shaders.bone_sphere_outline == NULL) {
g_armature_shaders.bone_sphere_outline = DRW_shader_create(
datatoc_armature_sphere_outline_vert_glsl, NULL,
datatoc_gpu_shader_flat_color_frag_glsl, NULL);
}
/* TODO own format? */
DRW_shgroup_instance_format(g_formats.instance_color, {
{"InstanceModelMatrix", DRW_ATTRIB_FLOAT, 16},
{"color" , DRW_ATTRIB_FLOAT, 4}
});
DRWShadingGroup *grp = DRW_shgroup_instance_create(g_armature_shaders.bone_sphere_outline,
pass, DRW_cache_bone_point_wire_outline_get(),
g_formats.instance_color);
DRW_shgroup_uniform_vec2(grp, "viewportSize", DRW_viewport_size_get(), 1);
return grp;
}

View File

@ -122,6 +122,8 @@ struct DRWShadingGroup *shgroup_instance_bone_envelope_wire(struct DRWPass *pass
struct DRWShadingGroup *shgroup_instance_bone_envelope_solid(struct DRWPass *pass, struct Gwn_Batch *geom);
struct DRWShadingGroup *shgroup_instance_mball_handles(struct DRWPass *pass, struct Gwn_Batch *geom);
struct DRWShadingGroup *shgroup_instance_armature_shape_outline(struct DRWPass *pass, struct Gwn_Batch *geom);
struct DRWShadingGroup *shgroup_instance_armature_sphere(struct DRWPass *pass);
struct DRWShadingGroup *shgroup_instance_armature_sphere_outline(struct DRWPass *pass);
int DRW_object_wire_theme_get(
struct Object *ob, struct ViewLayer *view_layer, float **r_color);

View File

@ -0,0 +1,74 @@
#extension GL_ARB_conservative_depth : enable
uniform mat4 ViewMatrixInverse;
uniform mat4 ProjectionMatrix;
flat in vec3 solidColor;
flat in mat4 sphereMatrix;
in vec3 viewPosition;
#ifdef GL_ARB_conservative_depth
/* Saves a lot of overdraw! */
layout(depth_greater) out float gl_FragDepth;
#endif
out vec4 fragColor;
#define cameraPos ViewMatrixInverse[3].xyz
float get_depth_from_view_z(float z)
{
if (ProjectionMatrix[3][3] == 0.0) {
z = (-ProjectionMatrix[3][2] / z) - ProjectionMatrix[2][2];
}
else {
z = z * ProjectionMatrix[2][2] / (1.0 - ProjectionMatrix[3][2]);
}
return z * 0.5 + 0.5;
}
void main()
{
const float sphere_radius = 0.05;
bool is_perp = (ProjectionMatrix[3][3] == 0.0);
vec3 ray_ori_view = (is_perp) ? vec3(0.0) : viewPosition.xyz;
vec3 ray_dir_view = (is_perp) ? viewPosition : vec3(0.0, 0.0, -1.0);
/* Single matrix mul without branch. */
vec4 mul_vec = (is_perp) ? vec4(ray_dir_view, 0.0) : vec4(ray_ori_view, 1.0);
vec3 mul_res = (sphereMatrix * mul_vec).xyz;
/* Reminder :
* sphereMatrix[3] is the view space origin in sphere space (sph_ori -> view_ori).
* sphereMatrix[2] is the view space Z axis in sphere space. */
/* convert to sphere local space */
vec3 ray_ori = (is_perp) ? sphereMatrix[3].xyz : mul_res;
vec3 ray_dir = (is_perp) ? mul_res : -sphereMatrix[2].xyz;
float ray_len = length(ray_dir);
ray_dir /= ray_len;
/* Line to sphere intersect */
const float sphere_radius_sqr = sphere_radius * sphere_radius;
float b = dot(ray_ori, ray_dir);
float c = dot(ray_ori, ray_ori) - sphere_radius_sqr;
float h = b * b - c;
float t = -sqrt(max(0.0, h)) - b;
/* Compute dot product for lighting */
vec3 p = ray_dir * t + ray_ori; /* Point on sphere */
vec3 n = normalize(p); /* Normal is just the point in sphere space, normalized. */
vec3 l = normalize(sphereMatrix[2].xyz); /* Just the view Z axis in the sphere space. */
float col = clamp(dot(n, l), 0.0, 1.0);
/* 2x2 dither pattern to smooth the lighting. */
float dither = (0.5 + dot(vec2(ivec2(gl_FragCoord.xy) & ivec2(1)), vec2(1.0, 2.0))) * 0.25;
dither *= (1.0 / 255.0); /* Assume 8bit per color buffer. */
fragColor = vec4(col * solidColor + dither, 1.0);
t /= ray_len;
gl_FragDepth = get_depth_from_view_z(ray_dir_view.z * t + ray_ori_view.z);
}

View File

@ -0,0 +1,102 @@
uniform mat4 ViewMatrix;
uniform mat4 ProjectionMatrix;
uniform vec2 viewportSize;
uniform float lineThickness = 3.0;
/* ---- Instanciated Attribs ---- */
in vec2 pos0;
in vec2 pos1;
/* ---- Per instance Attribs ---- */
in mat4 InstanceModelMatrix;
in vec4 color;
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 c)
{
vec2 dir = normalize(v1 - v0);
dir = vec2(dir.y, -dir.x);
/* The model matrix can be scaled negativly.
* Use projected sphere center to determine
* the outline direction. */
vec2 cv = c - v0;
dir = (dot(dir, cv) > 0.0) ? -dir : dir;
return dir;
}
void main()
{
mat4 model_view_matrix = ViewMatrix * InstanceModelMatrix;
mat4 sphereMatrix = inverse(model_view_matrix);
bool is_persp = (ProjectionMatrix[3][3] == 0.0);
/* This is the local space camera ray (not normalize).
* In perspective mode it's also the viewspace position
* of the sphere center. */
vec3 cam_ray = (is_persp) ? model_view_matrix[3].xyz : vec3(0.0, 0.0, -1.0);
cam_ray = mat3(sphereMatrix) * cam_ray;
/* Sphere center distance from the camera (persp) in local space. */
float cam_dist = length(cam_ray);
/* Compute view aligned orthonormal space. */
vec3 z_axis = cam_ray / cam_dist;
vec3 x_axis = normalize(cross(sphereMatrix[1].xyz, z_axis));
vec3 y_axis = cross(z_axis, x_axis);
float 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;
const float rad = 0.05;
/* Let be (in local space):
* 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;
}
/* Camera oriented position (but still in local space) */
vec3 cam_pos0 = x_axis * pos0.x + y_axis * pos0.y + z_axis * z_ofs;
vec3 cam_pos1 = x_axis * pos1.x + y_axis * pos1.y + z_axis * z_ofs;
vec4 V = model_view_matrix * vec4(cam_pos0, 1.0);
vec4 p0 = ProjectionMatrix * V;
vec4 p1 = ProjectionMatrix * (model_view_matrix * vec4(cam_pos1, 1.0));
vec4 c = ProjectionMatrix * vec4(model_view_matrix[3].xyz, 1.0);
vec2 ssc = proj(c);
vec2 ss0 = proj(p0);
vec2 ss1 = proj(p1);
vec2 edge_dir = compute_dir(ss0, ss1, ssc);
bool outer = ((gl_VertexID & 1) == 1);
vec2 t = lineThickness / viewportSize;
t *= (is_persp) ? abs(V.z) : 1.0;
t = (outer) ? t : vec2(0.0);
gl_Position = p0;
gl_Position.xy += t * edge_dir;
finalColor = color;
}

View File

@ -0,0 +1,79 @@
uniform mat4 ViewMatrix;
uniform mat4 ProjectionMatrix;
/* ---- Instanciated Attribs ---- */
in vec2 pos;
/* ---- Per instance Attribs ---- */
in mat4 InstanceModelMatrix;
in vec3 color;
flat out vec3 solidColor;
flat out mat4 sphereMatrix;
out vec3 viewPosition;
/* Sphere radius */
const float rad = 0.05;
void main()
{
mat4 model_view_matrix = ViewMatrix * InstanceModelMatrix;
sphereMatrix = inverse(model_view_matrix);
bool is_persp = (ProjectionMatrix[3][3] == 0.0);
/* This is the local space camera ray (not normalize).
* In perspective mode it's also the viewspace position
* of the sphere center. */
vec3 cam_ray = (is_persp) ? model_view_matrix[3].xyz : vec3(0.0, 0.0, -1.0);
cam_ray = mat3(sphereMatrix) * cam_ray;
/* Sphere center distance from the camera (persp) in local space. */
float cam_dist = length(cam_ray);
/* Compute view aligned orthonormal space. */
vec3 z_axis = cam_ray / cam_dist;
vec3 x_axis = normalize(cross(sphereMatrix[1].xyz, z_axis));
vec3 y_axis = cross(z_axis, x_axis);
float z_ofs = -rad - 1e-8; /* offset to the front of the sphere */
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;
/* Let be (in local space):
* 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));
#if 1
/* Instead of choosing the biggest circle in screenspace,
* we choose the nearest with the same angular size. This
* permit us to leverage GL_ARB_conservative_depth in the
* fragment shader. */
float minor = cam_dist - rad;
float major = cam_dist - cos_b * rad;
float fac = minor / major;
sin_b *= fac;
#else
z_ofs = -rad * cos_b;
#endif
x_axis *= sin_b;
y_axis *= sin_b;
}
/* Camera oriented position (but still in local space) */
vec3 cam_pos = x_axis * pos.x + y_axis * pos.y + z_axis * z_ofs;
vec4 V = model_view_matrix * vec4(cam_pos, 1.0);
gl_Position = ProjectionMatrix * V;
viewPosition = V.xyz;
solidColor = color;
}