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

Replace voxel uvmap interface with uv_transform interface #4758

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Added option `lowres_background=true` to Resampler, and renamed `resolution` to `max_resolution` [#4745](https://github.com/MakieOrg/Makie.jl/pull/4745).
- Added option `throttle=0.0` to `async_latest`, to allow throttling while skipping latest updates [#4745](https://github.com/MakieOrg/Makie.jl/pull/4745).
- Fixed issue with `WGLMakie.voxels` not rendering on linux with firefox [#4756](https://github.com/MakieOrg/Makie.jl/pull/4756)
- Updated `voxels` to use `uv_transform` interface instead of `uvmap` to give more control over texture mapping (i.e. to allow rotations) [#4758](https://github.com/MakieOrg/Makie.jl/pull/4758)
- Cleaned up surface handling in GLMakie: Surface cells are now discarded when there is a nan in x, y or z. Fixed incorrect normal if x or y is nan [#4735](https://github.com/MakieOrg/Makie.jl/pull/4735)
- Cleaned up `volume` plots: Added `:indexedabsorption` and `:additive` to WGLMakie, generalized `:mip` to include negative values, fixed missing conversions for rgba algorithms (`:additive`, `:absorptionrgba`), fixed missing conversion for `absorption` attribute & extended it to `:indexedabsorption` and `absorptionrgba`, added tests and improved docs. [#4726](https://github.com/MakieOrg/Makie.jl/pull/4726)

Expand Down
3 changes: 2 additions & 1 deletion CairoMakie/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ excludes = Set([
"scatter with glow", # some are missing
"scatter with stroke", # stroke acts inward in CairoMakie, outwards in W/GLMakie
"Textured meshscatter", # not yet implemented
"Voxel - texture mapping", # not yet implemented
"Voxel - texture mapping", # textures not implemented
"Voxel uvs", # textures not implemented
"Miter Joints for line rendering", # CairoMakie does not show overlap here
"picking", # Not implemented
"MetaMesh (Sponza)", # makes little sense without per pixel depth order
Expand Down
36 changes: 25 additions & 11 deletions GLMakie/assets/shader/voxel.frag
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

// debug FLAGS
// #define DEBUG_RENDER_ORDER 0 // (0, 1, 2) - dimensions
// #define DEBUG_UV
{{DEBUG_FLAG_DEFINE}}

struct Nothing{ //Nothing type, to encode if some variable doesn't contain any data
bool _; //empty structs are not allowed
Expand All @@ -29,7 +31,7 @@ uniform float gap;
uniform int _num_clip_planes;
uniform vec4 clip_planes[8];

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

Expand All @@ -44,26 +46,33 @@ vec4 debug_color(uint id) {
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);
mat3x2 get_uv_transform_mat(Nothing uv_transform, int id, int side) {
return mat3x2(1,0,0,1,0,0);
}
vec4 get_lrbt(sampler1D uv_map, int id, int side) {
return texelFetch(uv_map, id-1, 0);
mat3x2 get_uv_transform_mat(sampler2D uv_transform, int id, int side) {
vec2 part1 = texelFetch(uv_transform, ivec2(0, id-1), 0).xy;
vec2 part2 = texelFetch(uv_transform, ivec2(1, id-1), 0).xy;
vec2 part3 = texelFetch(uv_transform, ivec2(2, id-1), 0).xy;
return mat3x2(part1, part2, part3);
}
vec4 get_lrbt(sampler2D uv_map, int id, int side) {
return texelFetch(uv_map, ivec2(id-1, side), 0);
mat3x2 get_uv_transform_mat(sampler3D uv_transform, int id, int side) {
vec2 part1 = texelFetch(uv_transform, ivec3(0, id-1, side), 0).xy;
vec2 part2 = texelFetch(uv_transform, ivec3(1, id-1, side), 0).xy;
vec2 part3 = texelFetch(uv_transform, ivec3(2, id-1, side), 0).xy;
return mat3x2(part1, part2, part3);
}

vec4 get_color_from_texture(sampler2D color, int id) {
vec4 lrbt = get_lrbt(uv_map, id, o_side);
mat3x2 uvt = get_uv_transform_mat(uv_transform, id, o_side);
// compute uv normalized to voxel
// TODO: float precision causes this to wrap sometimes (e.g. 5.999..7.0002)
vec2 voxel_uv = mod(o_tex_uv, 1.0);
voxel_uv = mix(lrbt.xz, lrbt.yw, voxel_uv);
// correct for shrinking due to gap
voxel_uv = (voxel_uv - vec2(0.5 * gap)) / vec2(1.0 - gap);
voxel_uv = uvt * vec3(voxel_uv, 1);
return texture(color, voxel_uv);
}


vec4 get_color(Nothing color, Nothing color_map, int id) {
return debug_color(id);
}
Expand Down Expand Up @@ -93,7 +102,7 @@ bool is_clipped()
// distance between clip plane and center
d = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w;

if (d < 0.0)
if (d < 0.0)
return true;
}

Expand All @@ -111,6 +120,7 @@ void main()
if (is_clipped())
discard;

// TODO: Should uv's be scaled with gap > 0 so voxels still span a 0..1 uv space?
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)
Expand All @@ -127,6 +137,10 @@ void main()
// otherwise we draw. For now just some color...
vec4 voxel_color = get_color(color, color_map, id);

#ifdef DEBUG_UV
voxel_color = vec4(voxel_uv, 0, 1);
#endif

#ifdef DEBUG_RENDER_ORDER
if (plane_dim != DEBUG_RENDER_ORDER)
discard;
Expand Down
12 changes: 10 additions & 2 deletions GLMakie/assets/shader/voxel.vert
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// debug FLAGS
// #define DEBUG_RENDER_ORDER
{{DEBUG_FLAG_DEFINE}}

in vec2 vertices;

Expand Down Expand Up @@ -179,6 +180,13 @@ void main() {
o_side = dim + 3 * int(0.5 + 0.5 * normal_dir);

// map plane_vertex (-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]) * (vec3(-normal_dir, normal_dir, 1.0) * plane_vertex);
// use normal_dir to invert u/v direction based on which side is viewed
o_tex_uv = vec2(0);
if (dim == 0) { // x normal, yz planes
o_tex_uv = vec2(normal_dir, 1.0) * plane_vertex.yz;
} else if (dim == 1) { // y normal, xz planes
o_tex_uv = vec2(-normal_dir, 1.0) * plane_vertex.xz;
} else { // (dim == 2) z normal, xy planes
o_tex_uv = vec2(1.0, normal_dir) * plane_vertex.xy;
}
}
27 changes: 24 additions & 3 deletions GLMakie/src/drawing_primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1047,15 +1047,36 @@ function draw_atomic(screen::Screen, scene::Scene, plot::Voxels)
get!(gl_attributes, :color_map, nothing)

# process texture mapping
uv_map = pop!(gl_attributes, :uvmap)
if !isnothing(to_value(uv_map))
gl_attributes[:uv_map] = Texture(screen.glscreen, uv_map, minfilter = :nearest)
uv_map = pop!(gl_attributes, :uvmap, nothing)
uv_transform = pop!(gl_attributes, :uv_transform)

if !isnothing(to_value(uv_map)) || !isnothing(to_value(uv_transform))
if !(to_value(gl_attributes[:color]) isa Matrix{<: Colorant})
error("Could not create render object for voxel plot due to incomplete texture mapping. `uv_transform` has been provided without an image being passed as `color`.")
end

if !isnothing(to_value(uv_transform))
# new
packed = map(Makie.pack_voxel_uv_transform, uv_transform)
else
# old, deprecated
@warn "Voxel uvmap has been deprecated in favor of the more general `uv_transform`. Use `map(lrbt -> (Point2f(lrbt[1], lrbt[3]), Vec2f(lrbt[2] - lrbt[1], lrbt[4] - lrbt[3])), uvmap)`."
packed = map(uv_map) do uvmap
raw_uvt = Makie.uvmap_to_uv_transform(uvmap)
converted_uvt = Makie.convert_attribute(raw_uvt, Makie.key"uv_transform"())
return Makie.pack_voxel_uv_transform(converted_uvt)
end
end
gl_attributes[:uv_transform] = Texture(screen.glscreen, packed, minfilter = :nearest)

interp = to_value(pop!(gl_attributes, :interpolate))
interp = interp ? :linear : :nearest
color = gl_attributes[:color]
gl_attributes[:color] = Texture(screen.glscreen, color, minfilter = interp)
elseif !isnothing(to_value(gl_attributes[:color]))
if to_value(gl_attributes[:color]) isa Matrix{<: Colorant}
error("Could not create render object for voxel plot due to incomplete texture mapping. An image has been passed as `color` but not `uv_transform` was provided.")
end
gl_attributes[:color] = Texture(screen.glscreen, gl_attributes[:color], minfilter = :nearest)
end

Expand Down
5 changes: 3 additions & 2 deletions GLMakie/src/glshaders/voxel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function draw_voxels(screen, main::VolumeTypes, data::Dict)
backlight = 0f0
color = nothing => Texture
color_map = nothing => Texture
uv_map = nothing => Texture
uv_transform = nothing => Texture
shader = GLVisualizeShader(
screen,
"voxel.vert",
Expand All @@ -25,7 +25,8 @@ function draw_voxels(screen, main::VolumeTypes, data::Dict)
"MAX_LIGHTS" => "#define MAX_LIGHTS $(screen.config.max_lights)",
"MAX_LIGHT_PARAMETERS" => "#define MAX_LIGHT_PARAMETERS $(screen.config.max_light_parameters)",
"buffers" => output_buffers(screen, to_value(transparency)),
"buffer_writes" => output_buffer_writes(screen, to_value(transparency))
"buffer_writes" => output_buffer_writes(screen, to_value(transparency)),
"DEBUG_FLAG_DEFINE" => to_value(get(data, :debug, ""))
)
)
end
Expand Down
13 changes: 11 additions & 2 deletions MakieCore/src/basic_plots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -619,10 +619,19 @@ representation and may behave a bit differently than usual.
"A function that controls which values in the input data are mapped to invisible (air) voxels."
is_air = x -> isnothing(x) || ismissing(x) || isnan(x)
"""
Defines a map from voxel ids (and optionally sides) to uv coordinates. These uv coordinates
are then used to sample a 2D texture passed through `color` for texture mapping.
Deprecated - use uv_transform
"""
uvmap = nothing
"""
To use texture mapping `uv_transform` needs to be defined and `color` needs to be an image.
The `uv_transform` can be given as a `Vector` where each index maps to a `UInt8` voxel id (skipping 0),
or as a `Matrix` where the second index maps to a side following the order `(-x, -y, -z, +x, +y, +z)`.
Each element acts as a `Mat{2, 3, Float32}` which is applied to `Vec3f(uv, 1)`, where uv's are generated to run from 0..1 for each voxel.
The result is then used to sample the texture.
UV transforms have a bunch of shorthands you can use, for example `(Point2f(x, y), Vec2f(xscale, yscale))`.
They are listed in `?Makie.uv_transform`.
"""
uv_transform = nothing
"Controls whether the texture map is sampled with interpolation (i.e. smoothly) or not (i.e. pixelated)."
interpolate = false
"""
Expand Down
10 changes: 10 additions & 0 deletions ReferenceTests/src/tests/primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,16 @@ end
fig
end

@testset "Voxel uvs" begin
texture = FileIO.load(Makie.assetpath("debug_texture.png"))
f,a,p = voxels(ones(UInt8, 3,3,3), uv_transform = [I], color = texture)
st = Stepper(f)
step!(st)
update_cam!(a.scene, 5pi/4, -pi/4)
step!(st)
st
end

@reference_test "Voxel - colors and colormap" begin
# test direct mapping of ids to colors & upsampling of vector colormap
fig = Figure(size = (800, 400))
Expand Down
63 changes: 44 additions & 19 deletions WGLMakie/assets/voxel.frag
Original file line number Diff line number Diff line change
Expand Up @@ -31,33 +31,58 @@ vec4 debug_color(uint id) {
}
vec4 debug_color(int id) { return debug_color(uint(id)); }

vec4 get_color(bool color, bool color_map, bool uv_map, int id) {
// unused but compilation requires it
mat3x2 get_uv_transform_mat(bool uv_transform, int id, int side) {
return mat3x2(1,0,0,1,0,0);
}
mat3x2 get_uv_transform_mat(sampler2D uv_transform, int id, int side) {
vec2 part1 = texelFetch(uv_transform, ivec2(0, id-1), 0).xy;
vec2 part2 = texelFetch(uv_transform, ivec2(1, id-1), 0).xy;
vec2 part3 = texelFetch(uv_transform, ivec2(2, id-1), 0).xy;
return mat3x2(part1, part2, part3);
}
mat3x2 get_uv_transform_mat(sampler3D uv_transform, int id, int side) {
vec2 part1 = texelFetch(uv_transform, ivec3(0, id-1, side), 0).xy;
vec2 part2 = texelFetch(uv_transform, ivec3(1, id-1, side), 0).xy;
vec2 part3 = texelFetch(uv_transform, ivec3(2, id-1, side), 0).xy;
return mat3x2(part1, part2, part3);
}


vec4 get_color_from_texture(sampler2D color, int id) {
mat3x2 uvt = get_uv_transform_mat(uv_transform, id, o_side);
// compute uv normalized to voxel
// TODO: float precision causes this to wrap sometimes (e.g. 5.999..7.0002)
vec2 voxel_uv = mod(o_tex_uv, 1.0);
// correct for shrinking due to gap
voxel_uv = (voxel_uv - vec2(0.5 * gap)) / vec2(1.0 - gap);
voxel_uv = uvt * vec3(voxel_uv, 1);
return texture(color, voxel_uv);
}

vec4 get_color(bool color, bool color_map, bool uv_transform, int id) {
return debug_color(id);
}
vec4 get_color(bool color, sampler2D color_map, bool uv_map, int id) {
vec4 get_color(bool color, sampler2D color_map, bool uv_transform, int id) {
return texelFetch(color_map, ivec2(id-1, 0), 0);
}
vec4 get_color(sampler2D color, sampler2D color_map, bool uv_map, int id) {
vec4 get_color(sampler2D color, sampler2D color_map, bool uv_transform, int id) {
return texelFetch(color, ivec2(id-1, 0), 0);
}
vec4 get_color(sampler2D color, bool color_map, bool uv_map, int id) {
vec4 get_color(sampler2D color, bool color_map, bool uv_transform, int id) {
return texelFetch(color, ivec2(id-1, 0), 0);
}
vec4 get_color(sampler2D color, sampler2D color_map, sampler2D uv_map, int id) {
vec4 lrbt = texelFetch(uv_map, ivec2(id-1, o_side), 0);
// compute uv normalized to voxel
// TODO: float precision causes this to wrap sometimes (e.g. 5.999..7.0002)
vec2 voxel_uv = mod(o_tex_uv, 1.0);
voxel_uv = mix(lrbt.xz, lrbt.yw, voxel_uv);
return texture(color, voxel_uv);
vec4 get_color(sampler2D color, sampler2D color_map, sampler2D uv_transform, int id) {
return get_color_from_texture(color, id);
}
vec4 get_color(sampler2D color, bool color_map, sampler2D uv_map, int id) {
vec4 lrbt = texelFetch(uv_map, ivec2(id-1, o_side), 0);
// compute uv normalized to voxel
// TODO: float precision causes this to wrap sometimes (e.g. 5.999..7.0002)
vec2 voxel_uv = mod(o_tex_uv, 1.0);
voxel_uv = mix(lrbt.xz, lrbt.yw, voxel_uv);
return texture(color, voxel_uv);
vec4 get_color(sampler2D color, bool color_map, sampler2D uv_transform, int id) {
return get_color_from_texture(color, id);
}
vec4 get_color(sampler2D color, sampler2D color_map, sampler3D uv_transform, int id) {
return get_color_from_texture(color, id);
}
vec4 get_color(sampler2D color, bool color_map, sampler3D uv_transform, int id) {
return get_color_from_texture(color, id);
}

// Smoothes out edge around 0 light intensity, see GLMakie
Expand Down Expand Up @@ -136,7 +161,7 @@ void main()
}

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

#ifdef DEBUG_RENDER_ORDER
if (plane_dim != DEBUG_RENDER_ORDER)
Expand Down
11 changes: 9 additions & 2 deletions WGLMakie/assets/voxel.vert
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ void main() {
o_side = dim + 3 * int(0.5 + 0.5 * normal_dir);

// map plane_vertex (-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]) * (vec3(-normal_dir, normal_dir, 1.0) * plane_vertex);
// use normal_dir to invert u/v direction based on which side is viewed
o_tex_uv = vec2(0);
if (dim == 0) { // x normal, yz planes
o_tex_uv = vec2(normal_dir, 1.0) * plane_vertex.yz;
} else if (dim == 1) { // y normal, xz planes
o_tex_uv = vec2(-normal_dir, 1.0) * plane_vertex.xz;
} else { // (dim == 2) z normal, xy planes
o_tex_uv = vec2(1.0, normal_dir) * plane_vertex.xy;
}
}
42 changes: 26 additions & 16 deletions WGLMakie/src/voxel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,32 +55,42 @@ function create_shader(scene::Scene, plot::Makie.Voxels)
end

maybe_color_mapping = plot.calculated_colors[]
uv_map = plot.uvmap
uv_map = get(plot.attributes, :uvmap, nothing)
uv_transform = plot.uv_transform
if maybe_color_mapping isa Makie.ColorMapping
uniform_dict[:color_map] = Sampler(maybe_color_mapping.colormap, minfilter = :nearest)
uniform_dict[:uv_map] = false
uniform_dict[:uv_transform] = false
uniform_dict[:color] = false
elseif !isnothing(to_value(uv_map))
elseif !isnothing(to_value(uv_transform)) || !isnothing(to_value(uv_map))
if !(to_value(maybe_color_mapping) isa Matrix{<: Colorant})
error("Could not create render object for voxel plot due to incomplete texture mapping. `uv_transform` has been provided without an image being passed as `color`.")
end

uniform_dict[:color_map] = false
# WebGL doesn't have sampler1D so we need to pad id -> uv mappings to
# (id, side) -> uv mappings
wgl_uv_map = map(plot, uv_map) do uv_map
if uv_map isa Vector
new_map = Matrix{Vec4f}(undef, length(uv_map), 6)
for col in 1:6
new_map[:, col] .= uv_map
end
return new_map
else
return uv_map

if !isnothing(to_value(uv_transform))
# new
packed = map(plot, uv_transform) do uvt
x = Makie.convert_attribute(uvt, Makie.key"uv_transform"())
return Makie.pack_voxel_uv_transform(x)
end
else
# old, deprecated
@warn "Voxel uvmap has been deprecated in favor of the more general `uv_transform`. Use `map(lrbt -> (Point2f(lrbt[1], lrbt[3]), Vec2f(lrbt[2] - lrbt[1], lrbt[4] - lrbt[3])), uvmap)`."
packed = map(uv_map) do uvmap
raw_uvt = Makie.uvmap_to_uv_transform(uvmap)
converted_uvt = Makie.convert_attribute(raw_uvt, Makie.key"uv_transform"())
return Makie.pack_voxel_uv_transform(converted_uvt)
end
end
uniform_dict[:uv_map] = Sampler(wgl_uv_map, minfilter = :nearest)
uniform_dict[:uv_transform] = Sampler(packed, minfilter = :nearest)
interp = to_value(plot.interpolate) ? :linear : :nearest
uniform_dict[:color] = Sampler(maybe_color_mapping, minfilter = interp)
elseif to_value(maybe_color_mapping) isa Matrix{<: Colorant}
error("Could not create render object for voxel plot due to incomplete texture mapping. An image has been passed as `color` but not `uv_transform` was provided.")
else
uniform_dict[:color_map] = false
uniform_dict[:uv_map] = false
uniform_dict[:uv_transform] = false
uniform_dict[:color] = Sampler(maybe_color_mapping, minfilter = :nearest)
end

Expand Down
Binary file added assets/debug_texture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading