Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add voxel plot type #3527

Merged
merged 69 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
cba7911
create voxel rendering prototype
ffreyer Dec 31, 2023
1a86c05
enable lighting
ffreyer Dec 31, 2023
86ddecf
prototype voxel id generation & color handling
ffreyer Jan 1, 2024
bbc2c8f
add is_air attribute
ffreyer Jan 1, 2024
de8f800
prototype texture mapping
ffreyer Jan 1, 2024
233bc49
fix shader reloading
ffreyer Jan 2, 2024
067da42
fix texture mapping
ffreyer Jan 2, 2024
65482f4
Merge branch 'master' into ff/voxel
ffreyer Jan 2, 2024
8293f03
implement local updates
ffreyer Jan 2, 2024
3126fca
optimize render order (depthsorting = false)
ffreyer Jan 2, 2024
1344c39
add depthsorting = true
ffreyer Jan 2, 2024
f2da9be
render z planes first
ffreyer Jan 2, 2024
f732687
add lowclip and highclip
ffreyer Jan 3, 2024
00df3d1
add refimg tests + some fixes
ffreyer Jan 3, 2024
855a05b
fix colorrange
ffreyer Jan 3, 2024
44fce26
fix local chunk update
ffreyer Jan 3, 2024
eef32a8
handle colorrange more efficiently
ffreyer Jan 3, 2024
42c67ad
handle voxel id data more efficiently
ffreyer Jan 3, 2024
717d4d1
docstring & formatting
ffreyer Jan 3, 2024
856199c
switch back to lrbt order for uvmap
ffreyer Jan 3, 2024
fa412fc
add docs
ffreyer Jan 3, 2024
9e7fae6
try fix tests
ffreyer Jan 3, 2024
2fabaaf
fix show
ffreyer Jan 3, 2024
4acf7bb
Merge branch 'master' into ff/voxel
ffreyer Jan 3, 2024
e437b94
fix test?
ffreyer Jan 4, 2024
b7a81f6
add missing dimensions
ffreyer Jan 4, 2024
16a3ec1
add arguments for placement and scale
ffreyer Jan 4, 2024
368452c
allow Colon
ffreyer Jan 4, 2024
b142fc2
add Colon() to local_update
ffreyer Jan 4, 2024
184d803
minor cleanup
ffreyer Jan 5, 2024
31e4fc8
prototype WGLMakie version
ffreyer Jan 5, 2024
e634cbf
Merge branch 'master' into ff/voxel
ffreyer Jan 5, 2024
1fe834a
add fallback in CairoMakie
ffreyer Jan 5, 2024
bc8ed59
add RPRMakie fallback
ffreyer Jan 5, 2024
d46ce61
skip invisible voxels
ffreyer Jan 5, 2024
9b68130
fix typo
ffreyer Jan 5, 2024
12415ed
rename voxel -> voxels
ffreyer Jan 5, 2024
f589bc5
update docs, fix placement
ffreyer Jan 5, 2024
62f27bc
update news
ffreyer Jan 5, 2024
995e9d3
fix Colorbar for voxels
ffreyer Jan 5, 2024
dbec749
enable tests
ffreyer Jan 5, 2024
13a4992
fix texture rotation
ffreyer Jan 6, 2024
fd5d879
cleanup print
ffreyer Jan 6, 2024
53f38ed
cleanup comment
ffreyer Jan 6, 2024
6dece75
generalize array access
ffreyer Jan 6, 2024
b2f4eb5
debug WGLMakie
ffreyer Jan 7, 2024
894f350
get voxels rendering in WGLMakie
ffreyer Jan 7, 2024
d62c5b8
fix texture mapping
ffreyer Jan 7, 2024
bbb9b51
activate tests
ffreyer Jan 7, 2024
1e793e3
fix moving planes, cleanup prints
ffreyer Jan 7, 2024
c6efec9
Merge branch 'master' into ff/voxel
ffreyer Jan 7, 2024
6d6cb6b
add unit tests
ffreyer Jan 7, 2024
2885ae8
add gap attribute
ffreyer Jan 11, 2024
0ccdd53
tests & docs
ffreyer Jan 11, 2024
e528631
mention potential issues with picking
ffreyer Jan 11, 2024
e919bf5
Merge branch 'master' into ff/voxel
ffreyer Feb 4, 2024
2e2a904
fix WGLMakie picking
ffreyer Feb 4, 2024
bc3b3a1
fix depthsorting/gap handling
ffreyer Feb 4, 2024
dc2d81c
switch to integer mod
ffreyer Feb 16, 2024
5127a0c
fix render order
ffreyer Feb 16, 2024
c8494bc
Merge branch 'master' into ff/voxel
ffreyer Feb 16, 2024
a207075
Merge branch 'breaking-0.21' into ff/voxel
SimonDanisch Feb 23, 2024
5c6f4ff
Merge branch 'breaking-0.21' into ff/voxel
SimonDanisch Feb 28, 2024
6f4a544
use RNG
SimonDanisch Feb 28, 2024
a17405d
fix 1.6 3d array syntax
SimonDanisch Feb 28, 2024
b0ed597
fix refimage
SimonDanisch Feb 28, 2024
925cb24
Update CHANGELOG.md
SimonDanisch Feb 28, 2024
585c358
Merge branch 'breaking-0.21' into ff/voxel
SimonDanisch Mar 8, 2024
94b2204
fix julia 1.6
SimonDanisch Mar 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions GLMakie/assets/shader/voxel.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#version 330 core
// {{GLSL VERSION}}
// {{GLSL_EXTENSIONS}}

// debug FLAGS
// #define DEBUG_RENDER_ORDER 2 // (0, 1, 2) - dimensions

struct Nothing{ //Nothing type, to encode if some variable doesn't contain any data
bool _; //empty structs are not allowed
};

// Sets which shading procedures to use
{{shading}}

flat in vec3 o_normal;
in vec3 o_uvw;
flat in int o_side;
in vec2 o_tex_uv;

#ifdef DEBUG_RENDER_ORDER
flat in float plane_render_idx; // debug
#endif

uniform isampler3D voxel_id;
uniform uint objectid;

{{uv_map_type}} uv_map;
{{color_map_type}} color_map;
{{color_type}} color;

vec4 debug_color(uint id) {
return vec4(
float((id & uint(225)) >> uint(5)) / 5.0,
float((id & uint(25)) >> uint(3)) / 3.0,
float((id & uint(7)) >> uint(1)) / 3.0,
1.0
);
}
vec4 debug_color(int id) { return debug_color(uint(id)); }

// unused but compilation requires it
vec4 get_lrbt(Nothing uv_map, int id, int side) {
return vec4(0,0,1,1);
}
vec4 get_lrbt(sampler1D uv_map, int id, int side) {
return texelFetch(uv_map, id-1, 0);
}
vec4 get_lrbt(sampler2D uv_map, int id, int side) {
return texelFetch(uv_map, ivec2(id-1, side), 0);
}

vec4 get_color_from_texture(sampler2D color, int id) {
vec4 lrbt = get_lrbt(uv_map, id, o_side);
// compute uv normalized to voxel
vec2 voxel_uv = mod(o_tex_uv, 1.0);
voxel_uv = mix(lrbt.xy, lrbt.zw, voxel_uv);
return texture(color, voxel_uv);
}


vec4 get_color(Nothing color, Nothing color_map, int id) {
return debug_color(id);
}
vec4 get_color(Nothing color, sampler1D color_map, int id) {
return texelFetch(color_map, id-1, 0);
}
vec4 get_color(sampler1D color, sampler1D color_map, int id) {
return texelFetch(color, id-1, 0);
}
vec4 get_color(sampler1D color, Nothing color_map, int id) {
return texelFetch(color, id-1, 0);
}
vec4 get_color(sampler2D color, sampler1D color_map, int id) {
return get_color_from_texture(color, id);
}
vec4 get_color(sampler2D color, Nothing color_map, int id) {
return get_color_from_texture(color, id);
}


void write2framebuffer(vec4 color, uvec2 id);

#ifndef NO_SHADING
vec3 illuminate(vec3 normal, vec3 base_color);
#endif

void main()
{
// grab voxel id
int id = int(texture(voxel_id, o_uvw).x);

// id is invisible so we simply discard
if (id == 0) {
discard;
}

// otherwise we draw. For now just some color...
vec4 voxel_color = get_color(color, color_map, id);

#ifdef DEBUG_RENDER_ORDER
if (mod(o_side, 3) != DEBUG_RENDER_ORDER)
discard;
voxel_color = vec4(plane_render_idx, 0, 0, id == 0 ? 0.01 : 1.0);
#endif

#ifndef NO_SHADING
voxel_color.rgb = illuminate(o_normal, voxel_color.rgb);
#endif

// TODO: index into 3d array
ivec3 size = ivec3(textureSize(voxel_id, 0).xyz);
ivec3 idx = ivec3(o_uvw * size);
int lin = 1 + idx.x + size.x * (idx.y + size.y * idx.z);

// draw
write2framebuffer(voxel_color, uvec2(objectid, lin));
ffreyer marked this conversation as resolved.
Show resolved Hide resolved
}
144 changes: 144 additions & 0 deletions GLMakie/assets/shader/voxel.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#version 330 core
// {{GLSL_VERSION}}
// {{GLSL_EXTENSIONS}}

// debug FLAGS
// #define DEBUG_RENDER_ORDER

in vec2 vertices;

flat out vec3 o_normal;
out vec3 o_uvw;
flat out int o_side;
out vec2 o_tex_uv;

#ifdef DEBUG_RENDER_ORDER
flat out float plane_render_idx;
#endif

out vec3 o_camdir;
out vec3 o_world_pos;

uniform mat4 model;
uniform mat3 world_normalmatrix;
uniform mat4 projectionview;
uniform vec3 eyeposition;
uniform vec3 view_direction;
uniform isampler3D voxel_id;
uniform float depth_shift;
uniform bool depthsorting;

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)
mat2x3(1, 0, 0, 0, 0, 1), // xy -> x_z (y normal)
mat2x3(1, 0, 0, 0, 1, 0) // xy -> xy_ (z normal)
);

void main() {
/* How this works:
To simplify lets consider a 2d grid of pixel where the voxel surface would
be the square outline of around a data point x.
+---+---+---+
| x | x | x |
+---+---+---+
| x | x | x |
+---+---+---+
| x | x | x |
+---+---+---+
Naively we would draw 4 lines for each point x, coloring them based on the
data attached to x. This would result in 4 * N^2 lines with N^2 = number of
pixels. We can do much better though by drawing a line for each column and
row of pixels:
1 +---+---+---+
| x | x | x |
2 +---+---+---+
| x | x | x |
3 +---+---+---+
| x | x | x |
4 +---+---+---+
5 6 7 8
This results in 2 * (N+1) lines. We can adjust the color of the line by
sampling a Texture containing the information previously attached to vertices.

Generalized to 3D voxels, lines become planes and the texture becomes 3D.
We draw the planes through instancing. So first we will need to map the
instance id to a dimension (xy, xz or yz plane) and an offset (in z, y or
x direction respectively).
Note that the render order of planes can make a significant impact on
performance. It may be worth it to adjust this based on eyeposition.

For now we alternate x, y, z planes and start from the center.
*/

// TODO: might be better for transparent rendering to alternate xyz?
ivec3 size = textureSize(voxel_id, 0);
int dim = 2, id = gl_InstanceID;
if (gl_InstanceID > size.z + size.y + 1) {
dim = 0;
id = gl_InstanceID - (size.z + size.y + 2);
} else if (gl_InstanceID > size.z) {
dim = 1;
id = gl_InstanceID - (size.z + 1);
}

#ifdef DEBUG_RENDER_ORDER
plane_render_idx = float(id) / float(size[dim]-1);
#endif

// plane placement
// Figure out which plane to start with
vec3 offset = 0.5 * vec3(size);
vec3 normal = world_normalmatrix * unit_vecs[dim];
int dir = int(sign(dot(view_direction, normal)));
vec3 displacement;
if (depthsorting) {
// depthsorted should start far away from viewer so every plane draws
displacement = -dir * (id - offset[dim]) * unit_vecs[dim];
} else {
// no sorting should start at viewer and expand in view direction so
// that depth test can quickly eliminate unnecessary fragments
vec4 origin = model * vec4(-offset, 1);
float dist = dot(eyeposition - origin.xyz / origin.w, normal) / dot(normal, normal);
int start = clamp(int(dist), 0, size[dim]);
// this should work better with integer modulo...
displacement = (mod(start + dir * id, size[dim] + 0.001) - offset[dim]) * unit_vecs[dim];
}

// place plane vertices
vec3 voxel_pos = size * (orientations[dim] * vertices) + displacement;
vec4 world_pos = model * vec4(voxel_pos, 1.0f);
o_world_pos = world_pos.xyz;
gl_Position = projectionview * world_pos;
gl_Position.z += gl_Position.w * depth_shift;

// For each plane the normal is constant and its direction is given by the
// `displacement` direction, i.e. `n = unit_vecs[dim]`. We just need to derive
// whether it's +n or -n.
// If we assume the viewer to be outside of a voxel, the normal direction
// should always be facing them. Thus:
o_camdir = eyeposition - world_pos.xyz / world_pos.w;
float normal_dir = sign(dot(o_camdir, normal));
o_normal = normalize(normal_dir * normal);

// The texture coordinate can also be derived. `voxel_pos` effectively gives
// an integer index into the chunk, shifted to be centered. We can convert
// this to a float index into the voxel_id texture by normalizing.
// The minor ceveat here is that because planes are drawn between voxels we
// would be sampling between voxels like this. To fix this we want to shift
// the uvw coordinate to the relevant voxel center, which we can do using the
// normal direction.
// Here we want to shift in -normal direction to get a front face. Consider
// this example with 1, 2 solid, 0 air and v the viewer:
// | 1 | 2 | 0 | v
// If we shift in +normal direction (towards viewer) the planes would sample
// from the id closer to the viewer, drawing a backface.
o_uvw = (voxel_pos - 0.5 * o_normal) / size + 0.5;

// normal in: -x -y -z +x +y +z direction
o_side = dim + 3 * int(0.5 + 0.5 * normal_dir);

// map voxel_pos (-w/2 .. w/2 scale) back to 2d (scaled 0 .. w)
// if the normal is negative invert range (w .. 0)
o_tex_uv = transpose(orientations[dim]) * (normal_dir * voxel_pos + offset);
}
2 changes: 1 addition & 1 deletion GLMakie/src/GLAbstraction/GLShader.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ function compile_shader(source::ShaderSource, template_src::String)
glShaderSource(shaderid, template_src)
glCompileShader(shaderid)
if !GLAbstraction.iscompiled(shaderid)
GLAbstraction.print_with_lines(String(source))
GLAbstraction.print_with_lines(template_src)
@warn("shader $(name) didn't compile. \n$(GLAbstraction.getinfolog(shaderid))")
end
return Shader(name, Vector{UInt8}(template_src), source.typ, shaderid)
Expand Down
22 changes: 13 additions & 9 deletions GLMakie/src/GLAbstraction/GLTexture.jl
Original file line number Diff line number Diff line change
Expand Up @@ -381,21 +381,25 @@ function gpu_resize!(t::Texture{T, ND}, newdims::NTuple{ND, Int}) where {T, ND}
return t
end

texsubimage(t::Texture{T, 1}, newvalue::Array{T, 1}, xrange::UnitRange, level=0) where {T} = glTexSubImage1D(
t.texturetype, level, first(xrange)-1, length(xrange), t.format, t.pixeltype, newvalue
)
function texsubimage(t::Texture{T, 2}, newvalue::Array{T, 2}, xrange::UnitRange, yrange::UnitRange, level=0) where T
function texsubimage(t::Texture{T, 1}, newvalue::Array{T}, xrange::UnitRange, level=0) where {T}
glTexSubImage1D(
t.texturetype, level, first(xrange)-1, length(xrange), t.format, t.pixeltype, newvalue
)
end
function texsubimage(t::Texture{T, 2}, newvalue::Array{T}, xrange::UnitRange, yrange::UnitRange, level=0) where T
glTexSubImage2D(
t.texturetype, level,
first(xrange)-1, first(yrange)-1, length(xrange), length(yrange),
t.format, t.pixeltype, newvalue
)
end
texsubimage(t::Texture{T, 3}, newvalue::Array{T, 3}, xrange::UnitRange, yrange::UnitRange, zrange::UnitRange, level=0) where {T} = glTexSubImage3D(
t.texturetype, level,
first(xrange)-1, first(yrange)-1, first(zrange)-1, length(xrange), length(yrange), length(zrange),
t.format, t.pixeltype, newvalue
)
function texsubimage(t::Texture{T, 3}, newvalue::Array{T}, xrange::UnitRange, yrange::UnitRange, zrange::UnitRange, level=0) where {T}
glTexSubImage3D(
t.texturetype, level,
first(xrange)-1, first(yrange)-1, first(zrange)-1, length(xrange), length(yrange), length(zrange),
t.format, t.pixeltype, newvalue
)
end

Base.iterate(t::TextureBuffer{T}) where {T} = iterate(t.buffer)
function Base.iterate(t::TextureBuffer{T}, state::Tuple{Ptr{T}, Int}) where T
Expand Down
20 changes: 15 additions & 5 deletions GLMakie/src/GLMakie.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,25 @@ end

const GL_ASSET_DIR = RelocatableFolders.@path joinpath(@__DIR__, "..", "assets")
const SHADER_DIR = RelocatableFolders.@path joinpath(GL_ASSET_DIR, "shader")
const LOADED_SHADERS = Dict{String, ShaderSource}()
const LOADED_SHADERS = Dict{String, Tuple{Float64, ShaderSource}}()

function loadshader(name)
# Turns out, joinpath is so slow, that it actually makes sense
# To memoize it :-O
# Turns out, loading shaders is so slow, that it actually makes sense to memoize it :-O
# when creating 1000 plots with the PlotSpec API, timing drop from 1.5s to 1s just from this change:
return get!(LOADED_SHADERS, name) do
return ShaderSource(joinpath(SHADER_DIR, name))
# Note that we need to check if the file is still valid to enable hot reloading of shaders
path = joinpath(SHADER_DIR, name)
if haskey(LOADED_SHADERS, name)
cached_time, src = LOADED_SHADERS[name]
file_time = Base.Filesystem.mtime(joinpath(SHADER_DIR, name))
# return source if valid
(file_time == cached_time) && return src
end

# replace source if invalid/add new source
mtime = Base.Filesystem.mtime(path)
src = ShaderSource(path)
LOADED_SHADERS[name] = (mtime, src)
return src
end

gl_texture_atlas() = Makie.get_texture_atlas(2048, 64)
Expand Down
Loading
Loading