annotation and gizmo shaders rework for smoothed lines without bgl #2897

Reworked annotation and gizmo shaders to work without `bgl` and support smooth lines.

Moved LIB_GLSL and DEF_GLSL to shaders module since it's basically the same code and this way it'll be easier to keep track of it.
This commit is contained in:
Andrej730
2023-03-31 19:33:54 +05:00
parent f321cc81c8
commit 10315e0e29
3 changed files with 418 additions and 571 deletions
File diff suppressed because it is too large Load Diff
@@ -18,10 +18,9 @@
import bpy
import blf
import math
import gpu, bgl
import gpu
from bpy import types
from mathutils import Vector, Matrix
from mathutils import Vector
from mathutils import geometry
from bpy_extras import view3d_utils
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
@@ -500,7 +499,7 @@ class ExtrusionWidget(types.GizmoGroup):
gz = self.guides = self.gizmos.new("BIM_GT_extrusion_guides")
gz.matrix_basis = basis
gz.color = gz.color_highlight = tuple(theme.gizmo_secondary)
gz.alpha = gz.alpha_highlight = 0.5
gz.alpha = gz.alpha_highlight = 0.75
gz.use_draw_modal = True
gz.target_set_prop("depth", prop, "value")
gz.scale_value = scale_value
@@ -16,20 +16,186 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bgl
from mathutils import Matrix
from gpu.types import GPUShader
from gpu_extras.batch import batch_for_shader
import gpu
BASE_DEF_GLSL = """
#define PI 3.141592653589793
#define MAX_POINTS 64
#define CIRCLE_SEGS 12
#define SMOOTH_WIDTH 1.0
#define lineSmooth true
"""
# since `bgl` is deprecated, creating smoothing lines became tricky
# Notes for creating shaders with smoothed lines:
# in geom shader - use triangle_strip, DEFAULT_SETUP, do_edge_verts or do_vertex to emit vertices
# in frag shader - use lineWidth uniform, smoothline flaot in, smoothing shader code from base shader
# mind the vertex limit since emitting vertices for smoothed lines produces twice as much vertices
BASE_LIB_GLSL = """
// TODO: redefine as macor instead
uniform vec2 winsize;
uniform float lineWidth;
#define half_winsize (winsize / 2)
// convert camera to window
#define C2W(v) vec4(v.x * half_winsize.x / v.w, v.y * half_winsize.y / v.w, v.z / v.w, 1)
// convert window to camera
#define W2C(v) vec4(v.x * v.w / half_winsize.x, v.y * v.w / half_winsize.y, v.z * v.w, 1)
void emitSegment(vec4 p0, vec4 p1) {
gl_Position = p0;
EmitVertex();
gl_Position = p1;
EmitVertex();
EndPrimitive();
}
void emitTriangle(vec4 p1, vec4 p2, vec4 p3) {
gl_Position = p1;
EmitVertex();
gl_Position = p2;
EmitVertex();
gl_Position = p3;
EmitVertex();
EndPrimitive();
}
#define matCLIP2WIN() vec4(winsize.x/2, winsize.y/2, 1, 1)
#define CLIP2WIN(v) (clip2win * (v) / (v).w)
#define matWIN2CLIP() vec4(2/winsize.x, 2/winsize.y, 1, 1)
#define WIN2CLIP(v) (win2clip * (v) * (v).w)
#define DEFAULT_SETUP() vec4 clip2win = matCLIP2WIN(), win2clip = matWIN2CLIP(); vec2 EDGE_DIR
out float smoothline;
void arrow_head(in vec4 dir, in float size, in float angle, out vec4 head[3]) {
vec4 nose = dir * size;
float c = cos(angle), s = sin(angle);
head[0] = nose;
head[1] = vec4(mat2(c, -s, +s, c) * nose.xy, 0, 0);
head[2] = vec4(mat2(c, +s, -s, c) * nose.xy, 0, 0);
}
void circle_head(in float size, out vec4 head[CIRCLE_SEGS]) {
float angle_d = PI * 2 / CIRCLE_SEGS;
for(int i = 0; i<CIRCLE_SEGS; i++) {
float angle = angle_d * i;
head[i] = vec4(cos(angle), sin(angle), 0, 0) * size;
}
}
bool check_counterclockwise(in vec4 A, in vec4 B, in vec4 C) {
return (C.y-A.y) * (B.x-A.x) > (B.y-A.y) * (C.x-A.x);
}
void angle_circle_head(
in vec4 circle_start, in float circle_angle,
in bool counterclockwise,
out vec4 head[CIRCLE_SEGS+1], out float angle_segs) {
// 1 added to CIRCLE_SEGS because we're number of vertices
// for n segments is n+1
float angle_d;
angle_d = PI * 2 / CIRCLE_SEGS; // 30d
// need to bottom clamp it to 1, otherwise it causes Blender crash at extruding the curve
angle_segs = max(1, ceil(circle_angle / angle_d));
angle_d = circle_angle / angle_segs;
for(int i = 0; i < (angle_segs + 1); i++) {
float angle = angle_d * i;
if (counterclockwise) {
head[i] = vec4(
circle_start.x * cos(-angle) + circle_start.y * sin(-angle),
circle_start.x * -sin(-angle) + circle_start.y * cos(-angle),
0, 0
);
} else {
head[i] = vec4(
circle_start.x * cos(angle) + circle_start.y * sin(angle),
circle_start.x * -sin(angle) + circle_start.y * cos(angle),
0, 0
);
}
}
}
void cross_head(in vec4 dir, in float size, out vec4 head[3]) {
vec4 nose = dir * size;
float c = cos(PI/2), s = sin(PI/2);
head[0] = nose;
head[1] = vec4(mat2(c, -s, +s, c) * nose.xy, 0, 0);
head[2] = vec4(mat2(c, +s, -s, c) * nose.xy, 0, 0);
}
// do_vertex_util is custom EmitVertex method to support line smoothing
// `e` - edge xy direction in win/clip space (directions are invariant in both spaces)
#define do_vertex(pos, e) (do_vertex_util(pos, vec2(-(e).y, (e).x) / winsize.xy))
#define do_vertex_win(pos, e) ( do_vertex( WIN2CLIP( pos ), e ) )
// if vertex is shared by two segments of the line still need to emit it twice
// to avoid smoothing artifacts
// don't forget to initialize `vec2 EDGE_DIR` for macro to work
// `pos0` / `pos1` - vertex position in clip space
#define do_edge_verts(pos0, pos1) ( EDGE_DIR = normalize( ( (pos1) - (pos0) ).xy ), do_vertex( pos0, EDGE_DIR ), do_vertex( pos1, EDGE_DIR) )
#define do_edge_verts_win(pos0, pos1) ( do_edge_verts( WIN2CLIP(pos0), WIN2CLIP(pos1) ) )
void do_vertex_util(vec4 pos, vec2 ofs)
{
float final_line_width = lineWidth + SMOOTH_WIDTH * float(lineSmooth);
ofs *= final_line_width;
smoothline = final_line_width * 0.5;
gl_Position = pos;
gl_Position.xy += ofs * pos.w;
EmitVertex();
smoothline = -final_line_width * 0.5;
gl_Position = pos;
gl_Position.xy -= ofs * pos.w;
EmitVertex();
}
// geometry utils
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
vec4 nose = side * length;
vec4 ear = dir * width;
head[0] = side * -radius;
head[1] = nose * -.5;
head[2] = vec4(0) + ear;
head[3] = nose * .5;
head[4] = side * radius;
}
void do_triangle_head(vec4 pos_w, vec4 head[5]) {
DEFAULT_SETUP();
do_edge_verts_win(pos_w + head[0], pos_w + head[1]);
do_edge_verts_win(pos_w + head[1], pos_w + head[2]);
do_edge_verts_win(pos_w + head[2], pos_w + head[3]);
do_edge_verts_win(pos_w + head[3], pos_w + head[4]);
}
void do_circle_head(vec4 pos_w, vec4 head[CIRCLE_SEGS]) {
DEFAULT_SETUP();
for(int i=0; i<CIRCLE_SEGS-1; i++) {
do_edge_verts_win(pos_w + head[i], pos_w + head[i+1]);
EmitVertex();
}
do_edge_verts_win( pos_w + head[CIRCLE_SEGS-1], pos_w + head[0] );
}
"""
class BaseShader:
"""Wrapper for GPUShader
To use for viewport decorations with geometry generated on GPU side.
The Geometry shader works in clipping coords (aftre projecting before division and window scaling).
To make window-scale geometry, vectors should be calculated in window space and than back-projected to clipping spae.
Provide `winSize` uniform vector with halfsize of window, and use W2C and C2W macros.
Provide `half_winsize` uniform vector with halfsize of window, and use W2C and C2W macros.
Replace glsl code in derived classes.
Beware of unused attributes and uniforms: glsl compiler will optimize them out and blender fail with an exception.
@@ -44,9 +210,7 @@ class BaseShader:
TYPE = None # should be LINES|POINTS|etc
DEF_GLSL = """
#define PI 3.141592653589793
"""
DEF_GLSL = BASE_DEF_GLSL
VERT_GLSL = """
uniform mat4 viewMatrix;
@@ -64,46 +228,45 @@ class BaseShader:
"""
# prepended to geom_glsl
LIB_GLSL = """
uniform vec2 winSize;
// convert camera to window
#define C2W(v) vec4(v.x * winSize.x / v.w, v.y * winSize.y / v.w, v.z / v.w, 1)
// convert window to camera
#define W2C(v) vec4(v.x * v.w / winSize.x, v.y * v.w / winSize.y, v.z * v.w, 1)
void emitSegment(vec4 p0, vec4 p1) {
gl_Position = p0;
EmitVertex();
gl_Position = p1;
EmitVertex();
EndPrimitive();
}
void emitTriangle(vec4 p1, vec4 p2, vec4 p3) {
gl_Position = p1;
EmitVertex();
gl_Position = p2;
EmitVertex();
gl_Position = p3;
EmitVertex();
EndPrimitive();
}
"""
LIB_GLSL = BASE_LIB_GLSL
GEOM_GLSL = """
uniform float viewportDrawingScale;
layout(lines) in;
layout(triangle_strip, max_vertices = 4) out;
void main() {
// default setup for macro to work
vec4 clip2win = matCLIP2WIN();
vec4 win2clip = matWIN2CLIP();
vec2 EDGE_DIR;
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
do_edge_verts(p0, p1);
EndPrimitive();
}
"""
FRAG_GLSL = """
uniform vec4 color;
uniform float lineWidth;
in float smoothline;
out vec4 fragColor;
void main() {
vec2 co = gl_FragCoord.xy;
fragColor = color;
if (lineSmooth) {
fragColor.a *= clamp((lineWidth + SMOOTH_WIDTH) * 0.5 - abs(smoothline), 0.0, 1.0);
}
}
"""
def __init__(self):
# NB: libcode arg doesn't work
# TODO: rename to .shader
self.prog = GPUShader(
vertexcode=self.VERT_GLSL,
fragcode=self.FRAG_GLSL,
@@ -122,25 +285,28 @@ class BaseShader:
def glenable(self):
gpu.state.blend_set("ALPHA")
bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA)
bgl.glBlendEquation(bgl.GL_FUNC_ADD)
# gpu.state.depth_test_set('LESS_EQUAL')
# bgl.glDepthMask(True)
gpu.state.depth_test_set("LESS_EQUAL")
def uniform_region(self, ctx):
region = ctx.region
region3d = ctx.region_data
try:
self.prog.uniform_float("viewMatrix", region3d.perspective_matrix)
except ValueError: # unused uniform
pass
try:
self.prog.uniform_float("winSize", (region.width / 2, region.height / 2))
except ValueError: # unused uniform
pass
uniform_floats = {
"viewMatrix": region3d.perspective_matrix,
"winsize": (region.width, region.height),
"lineWidth": 2.5,
}
for name, value in uniform_floats.items():
try:
self.prog.uniform_float(name, value)
# TODO: shouldn't just try'n'catch them
# because they may indicate errors in code
except ValueError: # unused uniform
pass
# TODO: add smoothing if this shades is going to be used
class BaseLinesShader(BaseShader):
"""Draws line segments with gaps around vertices at endpoints"""
@@ -185,7 +351,6 @@ class BaseLinesShader(BaseShader):
def glenable(self):
super().glenable()
bgl.glEnable(bgl.GL_LINE_SMOOTH)
class GizmoShader(BaseShader):
@@ -207,6 +372,7 @@ class GizmoShader(BaseShader):
"""
# TODO: add smoothing if this shader is going to be used
class DotsGizmoShader(GizmoShader):
"""Draws circles of radius 1 around points"""
@@ -274,24 +440,26 @@ class ExtrusionGuidesShader(GizmoShader):
uniform mat4 ModelViewProjectionMatrix;
layout(lines) in;
layout(line_strip, max_vertices=10) out;
layout(triangle_strip, max_vertices=MAX_POINTS) out;
void main() {
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
vec4 p0w = C2W(p0), p1w = C2W(p1);
// default setup for macro to work
vec2 EDGE_DIR;
emitSegment(p0, p1);
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
do_edge_verts(p0, p1);
EndPrimitive();
vec4 bx = ModelViewProjectionMatrix[0] * CROSS_SIZE;
vec4 by = ModelViewProjectionMatrix[1] * CROSS_SIZE;
emitSegment(p0 - bx, p0 + bx);
emitSegment(p0 - by, p0 + by);
emitSegment(p1 - bx, p1 + bx);
emitSegment(p1 - by, p1 + by);
do_edge_verts(p0 - bx, p0 + bx);
EndPrimitive();
do_edge_verts(p0 - by, p0 + by);
EndPrimitive();
do_edge_verts(p1 - bx, p1 + bx);
EndPrimitive();
do_edge_verts(p1 - by, p1 + by);
EndPrimitive();
}
"""
def glenable(self):
super().glenable()
bgl.glEnable(bgl.GL_LINE_SMOOTH)