From f3a9110ceb96fe85fa9dbee2842ee6df11507837 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 18 Jun 2024 18:32:25 +0200 Subject: [PATCH] improve voxel clipping --- GLMakie/assets/shader/voxel.frag | 23 +++++++++ GLMakie/assets/shader/voxel.vert | 18 ------- GLMakie/src/drawing_primitives.jl | 26 ++++++++++ WGLMakie/assets/voxel.frag | 26 ++++++++-- WGLMakie/assets/voxel.vert | 8 --- WGLMakie/src/serialization.jl | 81 +++++++++++++++++++------------ 6 files changed, 122 insertions(+), 60 deletions(-) diff --git a/GLMakie/assets/shader/voxel.frag b/GLMakie/assets/shader/voxel.frag index cc5caf52c34..71f11e2c356 100644 --- a/GLMakie/assets/shader/voxel.frag +++ b/GLMakie/assets/shader/voxel.frag @@ -26,6 +26,8 @@ flat in int plane_front; uniform lowp usampler3D voxel_id; uniform uint objectid; uniform float gap; +uniform int _num_clip_planes; +uniform vec4 clip_planes[8]; {{uv_map_type}} uv_map; {{color_map_type}} color_map; @@ -81,6 +83,24 @@ vec4 get_color(sampler2D color, Nothing color_map, int id) { return get_color_from_texture(color, id); } +bool is_clipped() +{ + float d1, d2; + ivec3 size = ivec3(textureSize(voxel_id, 0).xyz); + vec3 xyz = vec3(ivec3(o_uvw * size)); + for (int i = 0; i < _num_clip_planes; i++) { + // distance from clip planes with negative clipped + d1 = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w; + d2 = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w; + + // both outside - clip everything + if (d1 < 0.0 || d2 < 0.0) { + return true; + } + } + + return false; +} void write2framebuffer(vec4 color, uvec2 id); @@ -90,6 +110,9 @@ vec3 illuminate(vec3 normal, vec3 base_color); void main() { + if (is_clipped()) + discard; + vec2 voxel_uv = mod(o_tex_uv, 1.0); if (voxel_uv.x < 0.5 * gap || voxel_uv.x > 1.0 - 0.5 * gap || voxel_uv.y < 0.5 * gap || voxel_uv.y > 1.0 - 0.5 * gap) diff --git a/GLMakie/assets/shader/voxel.vert b/GLMakie/assets/shader/voxel.vert index 05bc6a00bee..9c84745a302 100644 --- a/GLMakie/assets/shader/voxel.vert +++ b/GLMakie/assets/shader/voxel.vert @@ -31,23 +31,6 @@ uniform float depth_shift; uniform bool depthsorting; uniform float gap; -uniform int num_clip_planes; -uniform vec4 clip_planes[8]; -out float gl_ClipDistance[8]; - -void process_clip_planes(vec3 world_pos) -{ - // distance = dot(world_pos - plane.point, plane.normal) - // precalculated: dot(plane.point, plane.normal) -> plane.w - for (int i = 0; i < num_clip_planes; i++) - gl_ClipDistance[i] = dot(world_pos, clip_planes[i].xyz) - clip_planes[i].w; - - // TODO: can be skipped? - for (int i = num_clip_planes; i < 8; i++) - gl_ClipDistance[i] = 1.0; -} - - const vec3 unit_vecs[3] = vec3[]( vec3(1, 0, 0), vec3(0, 1, 0), vec3(0, 0, 1) ); const mat2x3 orientations[3] = mat2x3[]( mat2x3(0, 1, 0, 0, 0, 1), // xy -> _yz (x normal) @@ -166,7 +149,6 @@ void main() { vec3 plane_vertex = size * (orientations[dim] * vertices) + displacement; vec4 world_pos = model * vec4(plane_vertex, 1.0f); o_world_pos = world_pos.xyz; - process_clip_planes(world_pos.xyz); gl_Position = projectionview * world_pos; gl_Position.z += gl_Position.w * depth_shift; diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index c02bc59be1f..712a81a5cc0 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -916,6 +916,32 @@ function draw_atomic(screen::Screen, scene::Scene, plot::Voxels) ) end + # Handled manually without using OpenGL clipping + gl_attributes[:_num_clip_planes] = pop!(gl_attributes, :num_clip_planes) + gl_attributes[:num_clip_planes] = Observable(0) + pop!(gl_attributes, :clip_planes) + gl_attributes[:clip_planes] = map(plot, gl_attributes[:model], plot.clip_planes) do model, planes + # model/modelinv has no perspective projection so we should be fine + # with just applying it to the plane origin and transpose(inv(modelinv)) + # to plane.normal + modelinv = inv(model) + @assert modelinv[4, 4] == 1 + + output = Vector{Vec4f}(undef, 8) + for i in 1:min(length(planes), 8) + origin = modelinv * to_ndim(Point4f, planes[i].distance * planes[i].normal, 1) + normal = transpose(model) * to_ndim(Vec4f, planes[i].normal, 0) + distance = dot(Vec3f(origin[1], origin[2], origin[3]) / origin[4], + Vec3f(normal[1], normal[2], normal[3])) + output[i] = Vec4f(normal[1], normal[2], normal[3], distance) + end + for i in min(length(planes), 8)+1:8 + output[i] = Vec4f(0, 0, 0, -1e9) + end + + return output + end + # color attribute adjustments pop!(gl_attributes, :lowclip, nothing) pop!(gl_attributes, :highclip, nothing) diff --git a/WGLMakie/assets/voxel.frag b/WGLMakie/assets/voxel.frag index 070ba3875a0..3fe7a6dfeee 100644 --- a/WGLMakie/assets/voxel.frag +++ b/WGLMakie/assets/voxel.frag @@ -16,6 +16,7 @@ flat in int plane_front; #endif uniform int num_clip_planes; +uniform vec4 clip_planes[8]; vec4 debug_color(uint id) { return vec4( @@ -82,6 +83,25 @@ vec3 blinnphong(vec3 N, vec3 V, vec3 L, vec3 color){ ); } +bool is_clipped() +{ + float d1, d2; + vec3 size = vec3(textureSize(voxel_id, 0).xyz); + vec3 xyz = vec3(ivec3(o_uvw * size)); + for (int i = 0; i < num_clip_planes; i++) { + // distance from clip planes with negative clipped + d1 = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w; + d2 = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w; + + // both outside - clip everything + if (d1 < 0.0 || d2 < 0.0) { + return true; + } + } + + return false; +} + flat in uint frag_instance_id; vec4 pack_int(uint id, uint index) { vec4 unpack; @@ -91,11 +111,11 @@ vec4 pack_int(uint id, uint index) { unpack.w = float((index & uint(0x00ff)) >> 0) / 255.0; return unpack; } + void main() { - for (int i = 0; i < num_clip_planes; i++) - if (o_clip_distance[i] < 0.0) - discard; + if (is_clipped()) + discard; vec2 voxel_uv = mod(o_tex_uv, 1.0); if (voxel_uv.x < 0.5 * gap || voxel_uv.x > 1.0 - 0.5 * gap || diff --git a/WGLMakie/assets/voxel.vert b/WGLMakie/assets/voxel.vert index 236aa1c819a..a22fb2fa4e4 100644 --- a/WGLMakie/assets/voxel.vert +++ b/WGLMakie/assets/voxel.vert @@ -16,13 +16,6 @@ flat out int plane_front; out vec3 o_camdir; uniform mat4 projection, view; -uniform vec4 clip_planes[8]; -uniform int num_clip_planes; - -void process_clip_planes(vec3 world_pos) { - for (int i = 0; i < num_clip_planes; i++) - o_clip_distance[i] = dot(world_pos, clip_planes[i].xyz) - clip_planes[i].w; -} const vec3 unit_vecs[3] = vec3[]( vec3(1, 0, 0), vec3(0, 1, 0), vec3(0, 0, 1) ); const mat2x3 orientations[3] = mat2x3[]( @@ -144,7 +137,6 @@ void main() { // place plane vertices vec3 plane_vertex = vec3(size) * (orientations[dim] * get_position()) + displacement; vec4 world_pos = get_model() * vec4(plane_vertex, 1.0f); - process_clip_planes(world_pos.xyz); gl_Position = projection * view * world_pos; gl_Position.z += gl_Position.w * get_depth_shift(); diff --git a/WGLMakie/src/serialization.jl b/WGLMakie/src/serialization.jl index 38dec9ef97c..8be830ca351 100644 --- a/WGLMakie/src/serialization.jl +++ b/WGLMakie/src/serialization.jl @@ -370,37 +370,38 @@ function serialize_three(scene::Scene, @nospecialize(plot::AbstractPlot)) mesh[:cam_space] = to_value(get(plot, key, :data)) # Handle clip planes - if !(plot isa Volume) - - clip_planes = map(plot, plot.clip_planes) do planes - if length(planes) > 8 - @warn("Only up to 8 clip planes are supported. The rest are ignored!", maxlog = 1) - end + if plot isa Voxels + + clip_planes = map( + plot, plot.converted..., plot.model, plot.clip_planes + ) do xs, ys, zs, chunk, model, planes + # model/modelinv has no perspective projection so we should be fine + # with just applying it to the plane origin and transpose(inv(modelinv)) + # to plane.normal + mini = minimum.((xs, ys, zs)) + width = maximum.((xs, ys, zs)) .- mini + _model = Mat4f(model) * + Makie.scalematrix(Vec3f(width ./ size(chunk))) * + Makie.translationmatrix(Vec3f(mini)) + modelinv = inv(_model) + @assert modelinv[4, 4] == 1 output = Vector{Vec4f}(undef, 8) for i in 1:min(length(planes), 8) - output[i] = Makie.gl_plane_format(planes[i]) + origin = modelinv * to_ndim(Point4f, planes[i].distance * planes[i].normal, 1) + normal = transpose(_model) * to_ndim(Vec4f, planes[i].normal, 0) + distance = dot(Vec3f(origin[1], origin[2], origin[3]) / origin[4], + Vec3f(normal[1], normal[2], normal[3])) + output[i] = Vec4f(normal[1], normal[2], normal[3], distance) end for i in min(length(planes), 8)+1:8 - output[i] = Vec4f(0, 0, 0, -1e10) + output[i] = Vec4f(0, 0, 0, -1e9) end return output end - - uniforms[:clip_planes] = serialize_three(clip_planes[]) - on(plot, clip_planes) do value - updater[] = [:clip_planes, serialize_three(value)] - return - end - - uniforms[:num_clip_planes] = serialize_three(length(plot.clip_planes[])) - on(plot, plot.clip_planes) do planes - updater[] = [:num_clip_planes, serialize_three(length(planes))] - return - end - else + elseif plot isa Volume # TODO: better solution (ShaderAbstractions doesn't like Vector uniforms) model2 = lift(plot, plot.model, plot[1], plot[2], plot[3]) do m, xyz... @@ -433,20 +434,38 @@ function serialize_three(scene::Scene, @nospecialize(plot::AbstractPlot)) return output end - uniforms[:clip_planes] = serialize_three(clip_planes[]) - on(plot, clip_planes) do value - updater[] = [:clip_planes, serialize_three(value)] - return - end - - uniforms[:num_clip_planes] = serialize_three(length(plot.clip_planes[])) - on(plot, plot.clip_planes) do planes - updater[] = [:num_clip_planes, serialize_three(length(planes))] - return + else + + clip_planes = map(plot, plot.clip_planes) do planes + if length(planes) > 8 + @warn("Only up to 8 clip planes are supported. The rest are ignored!", maxlog = 1) + end + + output = Vector{Vec4f}(undef, 8) + for i in 1:min(length(planes), 8) + output[i] = Makie.gl_plane_format(planes[i]) + end + for i in min(length(planes), 8)+1:8 + output[i] = Vec4f(0, 0, 0, -1e10) + end + + return output end end + uniforms[:clip_planes] = serialize_three(clip_planes[]) + on(plot, clip_planes) do value + updater[] = [:clip_planes, serialize_three(value)] + return + end + + uniforms[:num_clip_planes] = serialize_three(length(plot.clip_planes[])) + on(plot, plot.clip_planes) do planes + updater[] = [:num_clip_planes, serialize_three(length(planes))] + return + end + return mesh end