Skip to content

Commit 7bb01af

Browse files
authored
Cleanup volume (#4726)
* add indexedabsorption and additive volume algorithms to WGLMakie * fix mip for negative values * fix RGB(A) volume plots no accepting 3(4) component data * use absorption in absorptionrgba, indexedabsorption * document what algorithm do * update clip planes to test volume with more fitting data * avoid newline in docstirng list * fix automatic x/y/z values for Color data * fix and test absorption * update attribute docs * add rgba, index examples * update changelog * don't show algorithm docs twice * fix typo * skip volume test in CairoMakie * fix merge error
1 parent b7cc362 commit 7bb01af

File tree

12 files changed

+196
-83
lines changed

12 files changed

+196
-83
lines changed

Diff for: CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
## [0.22.1] - 2025-01-17
1717

1818
- Allow volume textures for mesh color, to e.g. implement a performant volume slice display [#2274](https://github.com/MakieOrg/Makie.jl/pull/2274).
19-
- Fix `alpha` use in legends and some CairoMakie cases [#4721](https://github.com/MakieOrg/Makie.jl/pull/4721).
19+
- Fixed `alpha` use in legends and some CairoMakie cases [#4721](https://github.com/MakieOrg/Makie.jl/pull/4721).
20+
- 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)
2021

2122
## [0.22.0] - 2024-12-12
2223

Diff for: CairoMakie/test/runtests.jl

+1
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ excludes = Set([
194194
"picking", # Not implemented
195195
"MetaMesh (Sponza)", # makes little sense without per pixel depth order
196196
"Mesh with 3d volume texture", # Not implemented yet
197+
"Volume absorption",
197198
])
198199

199200
functions = [:volume, :volume!, :uv_mesh]

Diff for: GLMakie/assets/shader/volume.frag

+18-33
Original file line numberDiff line numberDiff line change
@@ -35,45 +35,30 @@ const float max_distance = 1.3;
3535
const int num_samples = 200;
3636
const float step_size = max_distance / float(num_samples);
3737

38-
float _normalize(float val, float from, float to)
39-
{
40-
return (val-from) / (to - from);
41-
}
38+
float _normalize(float val, float from, float to) { return (val-from) / (to - from);}
4239

43-
vec4 color_lookup(float intensity, Nothing color_map, Nothing norm, vec4 color)
44-
{
40+
vec4 color_lookup(float intensity, Nothing color_map, Nothing norm, vec4 color) {
4541
return color;
4642
}
47-
48-
vec4 color_lookup(float intensity, samplerBuffer color_ramp, vec2 norm, Nothing color)
49-
{
43+
vec4 color_lookup(float intensity, samplerBuffer color_ramp, vec2 norm, Nothing color) {
5044
return texelFetch(color_ramp, int(_normalize(intensity, norm.x, norm.y)*textureSize(color_ramp)));
5145
}
52-
53-
vec4 color_lookup(float intensity, samplerBuffer color_ramp, Nothing norm, Nothing color)
54-
{
46+
vec4 color_lookup(float intensity, samplerBuffer color_ramp, Nothing norm, Nothing color) {
5547
return vec4(0); // stub method
5648
}
57-
58-
vec4 color_lookup(float intensity, sampler1D color_ramp, vec2 norm, Nothing color)
59-
{
49+
vec4 color_lookup(float intensity, sampler1D color_ramp, vec2 norm, Nothing color) {
6050
return texture(color_ramp, _normalize(intensity, norm.x, norm.y));
6151
}
62-
63-
vec4 color_lookup(samplerBuffer colormap, int index)
64-
{
65-
return texelFetch(colormap, index);
52+
vec4 color_lookup(vec4 data_color, Nothing color_ramp, Nothing norm, Nothing color) {
53+
return data_color; // stub method
6654
}
67-
68-
vec4 color_lookup(sampler1D colormap, int index)
69-
{
70-
return texelFetch(colormap, index, 0);
55+
vec4 color_lookup(float intensity, Nothing color_ramp, Nothing norm, Nothing color) {
56+
return vec4(0); // stub method
7157
}
7258

73-
vec4 color_lookup(Nothing colormap, int index)
74-
{
75-
return vec4(0);
76-
}
59+
vec4 color_lookup(samplerBuffer colormap, int index) { return texelFetch(colormap, index); }
60+
vec4 color_lookup(sampler1D colormap, int index) { return texelFetch(colormap, index, 0); }
61+
vec4 color_lookup(Nothing colormap, int index) { return vec4(0); }
7762

7863
vec3 gennormal(vec3 uvw, float d, vec3 o)
7964
{
@@ -184,7 +169,7 @@ vec4 absorptionrgba(vec3 front, vec3 dir)
184169
int i = 0;
185170
for (i; i < num_samples ; ++i) {
186171
vec4 density = texture(volumedata, pos);
187-
float opacity = step_size * density.a;
172+
float opacity = step_size * density.a * absorption;
188173
T *= 1.0-opacity;
189174
if (T <= 0.01)
190175
break;
@@ -204,7 +189,7 @@ vec4 volumeindexedrgba(vec3 front, vec3 dir)
204189
for (i; i < num_samples; ++i) {
205190
int index = int(texture(volumedata, pos).x) - 1;
206191
vec4 density = color_lookup(color_map, index);
207-
float opacity = step_size*density.a;
192+
float opacity = step_size*density.a * absorption;
208193
Lo += (T*opacity)*density.rgb;
209194
T *= 1.0 - opacity;
210195
if (T <= 0.01)
@@ -284,9 +269,9 @@ vec4 isosurface(vec3 front, vec3 dir)
284269

285270
vec4 mip(vec3 front, vec3 dir)
286271
{
287-
vec3 pos = front;
288-
int i = 0;
289-
float maximum = 0.0;
272+
vec3 pos = front + dir;
273+
int i = 1;
274+
float maximum = texture(volumedata, front).x;
290275
for (i; i < num_samples; ++i, pos += dir){
291276
float density = texture(volumedata, pos).x;
292277
if(maximum < density)
@@ -315,7 +300,7 @@ bool process_clip_planes(inout vec3 p1, inout vec3 p2)
315300
p2 = p1;
316301
return true;
317302
}
318-
303+
319304
// one outside - shorten segment
320305
else if (d1 < 0.0)
321306
// solve 0 = m * t + b = (d2 - d1) * t + d1 with t in (0, 1)

Diff for: GLMakie/src/drawing_primitives.jl

+3
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,9 @@ function draw_atomic(screen::Screen, scene::Scene, plot::Volume)
959959
if haskey(gl_attributes, :intensity)
960960
intensity = pop!(gl_attributes, :intensity)
961961
return draw_volume(screen, Tex(intensity), gl_attributes)
962+
elseif haskey(gl_attributes, :color)
963+
color = pop!(gl_attributes, :color)
964+
return draw_volume(screen, Tex(color), gl_attributes)
962965
else
963966
return draw_volume(screen, Tex(plot[4]), gl_attributes)
964967
end

Diff for: GLMakie/src/glshaders/image_like.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ function draw_volume(screen, main::VolumeTypes, data::Dict)
6262
volumedata = main => Texture
6363
model = Mat4f(I)
6464
modelinv = const_lift(inv, model)
65-
color_map = default(Vector{RGBA}, s) => Texture
66-
color_norm = color_map === nothing ? nothing : const_lift(extrema2f0, main)
67-
color = color_map === nothing ? default(RGBA, s) : nothing
65+
color_map = nothing => Texture
66+
color_norm = nothing
67+
color = nothing => Texture
6868

6969
algorithm = MaximumIntensityProjection
7070
absorption = 1f0

Diff for: MakieCore/src/basic_plots.jl

+20-14
Original file line numberDiff line numberDiff line change
@@ -275,32 +275,38 @@ end
275275
volume(volume_data)
276276
volume(x, y, z, volume_data)
277277
278-
Plots a volume, with optional physical dimensions `x, y, z`.
279-
Available algorithms are:
280-
* `:iso` => IsoValue
281-
* `:absorption` => Absorption
282-
* `:mip` => MaximumIntensityProjection
283-
* `:absorptionrgba` => AbsorptionRGBA
284-
* `:additive` => AdditiveRGBA
285-
* `:indexedabsorption` => IndexedAbsorptionRGBA
278+
Plots a volume with optional physical dimensions `x, y, z`.
279+
280+
All volume plots are derived from casting rays for each drawn pixel. These rays
281+
intersect with the volume data to derive some color, usually based on the given
282+
colormap. How exactly the color is derived depends on the algorithm used.
286283
"""
287284
@recipe Volume (
288285
x::EndPoints,
289286
y::EndPoints,
290287
z::EndPoints,
291-
volume::AbstractArray{Float32,3}
288+
# TODO: consider using RGB{N0f8}, RGBA{N0f8} instead of Vec/RGB(A){Float32}
289+
volume::AbstractArray{<: Union{Float32, Vec3f, RGB{Float32}, Vec4f, RGBA{Float32}}, 3}
292290
) begin
293-
"Sets the volume algorithm that is used."
291+
"""
292+
Sets the volume algorithm that is used. Available algorithms are:
293+
* `:iso`: Shows an isovalue surface within the given float data. For this only samples within `isovalue - isorange .. isovalue + isorange` are included in the final color of a pixel.
294+
* `:absorption`: Accumulates color based on the float values sampled from volume data. At each ray step (starting from the front) a value is sampled from the volume data and then used to sample the colormap. The resulting color is weighted by the ray step size and blended the previously accumulated color. The weight of each step can be adjusted with the multiplicative `absorption` attribute.
295+
* `:mip`: Shows the maximum intensity projection of the given float data. This derives the color of a pixel from the largest value sampled from the respective ray.
296+
* `:absorptionrgba`: This algorithm matches :absorption, but samples colors directly from RGBA volume data. For each ray step a color is sampled from the data, weighted by the ray step size and blended with the previously accumulated color. Also considers `absorption`.
297+
* `:additive`: Accumulates colors using `accumulated_color = 1 - (1 - accumulated_color) * (1 - sampled_color)` where `sampled_color` is a sample of volume data at the current ray step.
298+
* `:indexedabsorption`: This algorithm acts the same as :absorption, but interprets the volume data as indices. They are used as direct indices to the colormap. Also considers `absorption`.
299+
"""
294300
algorithm = :mip
295-
"Sets the target value for the IsoValue algorithm."
301+
"Sets the target value for the :iso algorithm. `accepted = isovalue - isorange < value < isovalue + isorange`"
296302
isovalue = 0.5
297-
"Sets the range of values picked up by the IsoValue algorithm."
303+
"Sets the maximum accepted distance from the isovalue for the :iso algorithm. `accepted = isovalue - isorange < value < isovalue + isorange`"
298304
isorange = 0.05
299305
"Sets whether the volume data should be sampled with interpolation."
300306
interpolate = true
301-
"Enables depth write for Volume, so that volume correctly occludes other objects."
307+
"Enables depth write for :iso so that volume correctly occludes other objects."
302308
enable_depth = true
303-
"Absorption multiplier for algorithm=:absorption. This changes how much light each voxel absorbs."
309+
"Absorption multiplier for algorithm = :absorption, :absorptionrgba and :indexedabsorption. This changes how much light each voxel absorbs."
304310
absorption = 1f0
305311
mixin_generic_plot_attributes()...
306312
mixin_shading_attributes()...

Diff for: ReferenceTests/src/tests/examples3d.jl

+23-7
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ end
7777
f
7878
end
7979

80+
@reference_test "Volume absorption" begin
81+
f = Figure(size = (600, 300))
82+
r = range(-5, 5, length=31)
83+
data = [cos(x*x + y*y + z*z)^2 for x in r, y in r, z in r]
84+
absorption = 5.0
85+
volume(f[1, 1], data, algorithm = :absorption; absorption)
86+
volume(f[1, 2], 128 .+ 120 .* data, algorithm = :indexedabsorption; absorption)
87+
volume(f[1, 3], HSV.(180 .* data, 0.8, 0.9), algorithm = :absorptionrgba; absorption)
88+
f
89+
end
90+
8091
@reference_test "Textured meshscatter" begin
8192
catmesh = loadasset("cat.obj")
8293
img = loadasset("diffusemap.png")
@@ -661,11 +672,15 @@ end
661672
end
662673

663674
@reference_test "Clip planes - volume" begin
664-
f = Figure(size = (600, 400))
675+
f = Figure(size = (600, 400), backgroundcolor = :black)
665676
r = -10:10
666677
data = [1 - (1 + cos(x^2) + cos(y^2) + cos(z^2)) for x in r, y in r, z in r]
667-
clip_planes = [Plane3f(Vec3f(-1), 0.0)]
678+
index_data = round.(Int, 10 .* abs.(data))
679+
N = maximum(index_data)
680+
density_data = 0.005 .* abs.(data)
681+
rgba_data = [RGBAf(cos(x^2)^2, cos(y^2)^2, cos(z^2)^2, 0.5 + 0.5 * sin(x^2 + y^2 + z^2)) for x in r, y in r, z in r]
668682

683+
clip_planes = [Plane3f(Vec3f(-1), 0.0)]
669684
attr = (clip_planes = clip_planes, axis = (show_axis = false,))
670685

671686
volume(f[1, 1], -10..10, -10..10, -10..10, data; attr...,
@@ -675,13 +690,14 @@ end
675690

676691
volume(f[1, 2], -10..10, -10..10, -10..10, data; attr...,
677692
algorithm = :mip)
678-
volume(f[2, 2], -10..10, -10..10, -10..10, data; attr...,
693+
volume(f[2, 2], -10..10, -10..10, -10..10, rgba_data; attr...,
679694
algorithm = :absorptionrgba)
680695

681-
volume(f[1, 3], -10..10, -10..10, -10..10, data; attr...,
682-
algorithm = :additive)
683-
volume(f[2, 3], -10..10, -10..10, -10..10, data; attr...,
684-
algorithm = :indexedabsorption)
696+
# TODO: doesn't work as intended anymore?
697+
volume(f[1, 3], -10..10, -10..10, -10..10, rgba_data; attr...,
698+
algorithm = :additive, alpha = 0.01)
699+
volume(f[2, 3], -10..10, -10..10, -10..10, index_data; attr...,
700+
algorithm = :indexedabsorption, colormap = Makie.resample(to_colormap(:viridis), N))
685701

686702
f
687703
end

Diff for: WGLMakie/assets/volume.frag

+59-14
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,27 @@ const float step_size = max_distance / float(num_samples);
1313

1414
uniform vec4 clip_planes[8];
1515

16-
float _normalize(float val, float from, float to)
17-
{
18-
return (val-from) / (to - from);
19-
}
16+
float _normalize(float val, float from, float to) { return (val-from) / (to - from); }
2017

21-
vec4 color_lookup(float intensity, sampler2D color_ramp, vec2 norm)
22-
{
18+
vec4 color_lookup(float intensity, sampler2D color_ramp, vec2 norm) {
2319
return texture(color_ramp, vec2(_normalize(intensity, norm.x, norm.y), 0.0));
2420
}
21+
vec4 color_lookup(vec4 color, bool color_ramp, bool norm) {
22+
return color; // stub method
23+
}
24+
vec4 color_lookup(float intensity, bool color_ramp, bool norm) {
25+
return vec4(0); // stub method
26+
}
27+
28+
vec4 color_lookup(sampler2D colormap, int index) {
29+
return texelFetch(colormap, ivec2(index, 0), 0);
30+
}
31+
vec4 color_lookup(bool colormap, vec4 color) {
32+
return color; // stub method
33+
}
34+
vec4 color_lookup(bool colormap, int index) {
35+
return vec4(0); // stub method
36+
}
2537

2638
vec3 gennormal(vec3 uvw, float d)
2739
{
@@ -110,15 +122,15 @@ vec4 volume(vec3 front, vec3 dir)
110122
}
111123

112124

113-
vec4 volumergba(vec3 front, vec3 dir)
125+
vec4 absorptionrgba(vec3 front, vec3 dir)
114126
{
115127
vec3 pos = front;
116128
float T = 1.0;
117129
vec3 Lo = vec3(0.0);
118130
int i = 0;
119131
for (i; i < num_samples ; ++i) {
120132
vec4 density = texture(volumedata, pos);
121-
float opacity = step_size * density.a;
133+
float opacity = step_size * density.a * absorption;
122134
T *= 1.0 - opacity;
123135
if (T <= 0.01)
124136
break;
@@ -179,9 +191,9 @@ vec4 isosurface(vec3 front, vec3 dir)
179191

180192
vec4 mip(vec3 front, vec3 dir)
181193
{
182-
vec3 pos = front;
183-
int i = 0;
184-
float maximum = 0.0;
194+
vec3 pos = front + dir;
195+
int i = 1;
196+
float maximum = texture(volumedata, front).x;
185197
for (i; i < num_samples; ++i, pos += dir){
186198
float density = texture(volumedata, pos).x;
187199
if(maximum < density)
@@ -190,6 +202,38 @@ vec4 mip(vec3 front, vec3 dir)
190202
return color_lookup(maximum, colormap, colorrange);
191203
}
192204

205+
vec4 additivergba(vec3 front, vec3 dir)
206+
{
207+
vec3 pos = front;
208+
vec4 integrated_color = vec4(0., 0., 0., 0.);
209+
int i = 0;
210+
for (i; i < num_samples ; ++i) {
211+
vec4 density = texture(volumedata, pos);
212+
integrated_color = 1.0 - (1.0 - integrated_color) * (1.0 - density);
213+
pos += dir;
214+
}
215+
return integrated_color;
216+
}
217+
218+
vec4 volumeindexedrgba(vec3 front, vec3 dir)
219+
{
220+
vec3 pos = front;
221+
float T = 1.0;
222+
vec3 Lo = vec3(0.0);
223+
int i = 0;
224+
for (i; i < num_samples; ++i) {
225+
int index = int(texture(volumedata, pos).x) - 1;
226+
vec4 density = color_lookup(colormap, index);
227+
float opacity = step_size * density.a * absorption;
228+
Lo += (T*opacity)*density.rgb;
229+
T *= 1.0 - opacity;
230+
if (T <= 0.01)
231+
break;
232+
pos += dir;
233+
}
234+
return vec4(Lo, 1.0 - T);
235+
}
236+
193237
uniform uint objectid;
194238

195239
const float typemax = 100000000000000000000000000000000000000.0;
@@ -290,10 +334,11 @@ void main()
290334
else if(algorithm == uint(2))
291335
color = mip(start, step_in_dir);
292336
else if(algorithm == uint(3))
293-
color = volumergba(start, step_in_dir);
337+
color = absorptionrgba(start, step_in_dir);
294338
else if(algorithm == uint(4))
295-
color = vec4(0.0);
296-
// color = volumeindexedrgba(start, step_in_dir);
339+
color = additivergba(start, step_in_dir);
340+
else if(algorithm == uint(5))
341+
color = volumeindexedrgba(start, step_in_dir);
297342
else
298343
color = contours(start, step_in_dir);
299344

Diff for: WGLMakie/src/meshes.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function handle_color!(plot, uniforms, buffers, uniform_color_name = :uniform_co
4444
) do uvt, pv, res, model, pattern
4545
return Makie.pattern_uv_transform(uvt, pv * model, res, pattern, true)
4646
end
47-
elseif color[] isa AbstractMatrix
47+
elseif color[] isa Union{AbstractMatrix, AbstractArray{<: Any, 3}}
4848
uniforms[uniform_color_name] = Sampler(convert_texture(color); minfilter=minfilter)
4949
elseif color[] isa Makie.ColorMapping
5050
if color[].color_scaled[] isa AbstractVector

0 commit comments

Comments
 (0)