From 47841c5503727a96a6bd46b78616649000995de5 Mon Sep 17 00:00:00 2001 From: Ulrik Guenther Date: Fri, 22 Sep 2023 15:40:57 +0200 Subject: [PATCH 1/4] BoundingGrid: Improve aesthetics of bounding grids, improving sizing and position of axis labels --- .../kotlin/graphics/scenery/BoundingGrid.kt | 65 +++++++++++++++---- .../backends/shaders/BoundingGrid.frag | 36 +++++----- 2 files changed, 74 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/graphics/scenery/BoundingGrid.kt b/src/main/kotlin/graphics/scenery/BoundingGrid.kt index 42182c5f0..3e42e7026 100644 --- a/src/main/kotlin/graphics/scenery/BoundingGrid.kt +++ b/src/main/kotlin/graphics/scenery/BoundingGrid.kt @@ -23,7 +23,7 @@ import java.util.* * * @author Ulrik Günther */ -open class BoundingGrid : Mesh("Bounding Grid") { +open class BoundingGrid : Mesh("Bounding Grid"), RenderingOrder { protected var labels = HashMap() /** Grid color for the bounding grid. */ @@ -44,7 +44,7 @@ open class BoundingGrid : Mesh("Bounding Grid") { /** Line width for the grid. */ @ShaderProperty - var lineWidth: Float = 1.2f + var lineWidth: Float = 1.0f set(value) { field = value updateModifiedAt() @@ -52,12 +52,25 @@ open class BoundingGrid : Mesh("Bounding Grid") { /** Whether to show only the ticks on the grid, or show the full grid. */ @ShaderProperty - var ticksOnly: Int = 1 + var ticksOnly: Int = 0 + set(value) { + field = value + if(ticksOnly == 0) { + material().cullingMode = Material.CullingMode.Front + } else { + material().cullingMode = Material.CullingMode.None + } + updateModifiedAt() + } + + @ShaderProperty + protected var boundingBoxSize: Vector3f = Vector3f(1.0f) set(value) { field = value updateModifiedAt() } + /** Slack around transparent objects, 2% by default. */ var slack = 0.02f @@ -76,9 +89,20 @@ open class BoundingGrid : Mesh("Bounding Grid") { init { setMaterial(ShaderMaterial.fromFiles("DefaultForward.vert", "BoundingGrid.frag")) { blending.transparent = true - blending.opacity = 0.8f - blending.setOverlayBlending() - cullingMode = Material.CullingMode.Front + blending.opacity = 1.0f + blending.sourceColorBlendFactor = Blending.BlendFactor.One + blending.destinationColorBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha + blending.sourceAlphaBlendFactor = Blending.BlendFactor.One + blending.destinationAlphaBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha + blending.colorBlending = Blending.BlendOp.add + blending.alphaBlending = Blending.BlendOp.add + + if(ticksOnly > 0) { + cullingMode = Material.CullingMode.None + } else { + cullingMode = Material.CullingMode.Back + } + depthTest = Material.DepthTest.LessEqual } labels = hashMapOf( @@ -88,7 +112,7 @@ open class BoundingGrid : Mesh("Bounding Grid") { "z" to TextBoard() ) - labels.forEach { s, fontBoard -> + labels.forEach { (s, fontBoard) -> fontBoard.text = s fontBoard.fontColor = Vector4f(1.0f, 1.0f, 1.0f, 1.0f) fontBoard.backgroundColor = Vector4f(0.0f, 0.0f, 0.0f, 1.0f) @@ -146,8 +170,9 @@ open class BoundingGrid : Mesh("Bounding Grid") { } val b = Box(max - min) + boundingBoxSize = max - min - logger.debug("Bounding box of $node is $maxBoundingBox") + logger.debug("Bounding box of {} is {}", node, maxBoundingBox) val center = (max - min)*0.5f @@ -166,10 +191,24 @@ open class BoundingGrid : Mesh("Bounding Grid") { boundingBox?.let { bb -> // label coordinates are relative to the bounding box - labels["0"]?.spatial()?.position = bb.min - Vector3f(0.1f, 0.0f, 0.0f) - labels["x"]?.spatial()?.position = Vector3f(2.0f * bb.max.x() + 0.1f, 0.01f, 0.01f) - center - labels["y"]?.spatial()?.position = Vector3f(-0.1f, 2.0f * bb.max.y(), 0.01f) - center - labels["z"]?.spatial()?.position = Vector3f(-0.1f, 0.01f, 2.0f * bb.max.z()) - center + val hs = 2.0f * bb.halfSize + + labels["0"]?.spatial()?.position = bb.min - 0.02f * hs + labels["x"]?.spatial()?.position = Vector3f(2.0f * bb.max.x() + 0.02f* hs.x, -0.02f * hs.y, -0.02f * hs.z) - center + labels["y"]?.spatial()?.position = Vector3f(-0.02f * hs.x, 2.0f * bb.max.y() , 0.02f * hs.z) - center + labels["z"]?.spatial()?.position = Vector3f(-0.02f * hs.x, -0.02f * hs.y, 2.0f * bb.max.z() + 0.02f * hs.z) - center + + val scale = Vector3f() + this.spatial().world.getScale(scale) + val fontScale = 0.3f + val invScale = Vector3f(1.0f/maxOf(scale.x, 0.0001f) * fontScale, + 1.0f/maxOf(scale.y, 0.0001f) * fontScale, + 1.0f/maxOf(scale.z, 0.0001f) * fontScale) + + labels["0"]?.spatial()?.scale = invScale + labels["x"]?.spatial()?.scale = invScale + labels["y"]?.spatial()?.scale = invScale + labels["z"]?.spatial()?.scale = invScale spatial { needsUpdate = true @@ -193,6 +232,8 @@ open class BoundingGrid : Mesh("Bounding Grid") { this.ticksOnly = fresh.ticksOnly } + override var renderingOrder: Int = Int.MAX_VALUE + /** * Returns this bounding box' coordinates and associated [Node] as String. */ diff --git a/src/main/resources/graphics/scenery/backends/shaders/BoundingGrid.frag b/src/main/resources/graphics/scenery/backends/shaders/BoundingGrid.frag index 9fa29b706..10b7fdf95 100644 --- a/src/main/resources/graphics/scenery/backends/shaders/BoundingGrid.frag +++ b/src/main/resources/graphics/scenery/backends/shaders/BoundingGrid.frag @@ -45,31 +45,37 @@ layout(set = 4, binding = 0) uniform ShaderProperties { float lineWidth; int ticksOnly; vec3 gridColor; + vec3 boundingBoxSize; }; layout(location = 0) out vec4 FragColor; void main() { - // draw screen-spaced antialiased grid lines, inspired by - // http://madebyevan.com/shaders/grid - here we scale the incoming - // coords by the numLines factor. For correct AA, the fwidth argument - // also has to be scaled by that factor. - vec2 coord = Vertex.TexCoord; - vec2 grid = abs(fract(coord*numLines - 0.5) - 0.5) / fwidth(coord*numLines); - // line width is determined by the minimum gradient, for thicker lines, we - // divide by lineWidth, lowering the gradient slope. - float line = min(grid.x, grid.y)/lineWidth; + vec3 coord = Vertex.FragPosition.xyz; + vec2 uv = Vertex.TexCoord; + vec3 grid = abs(fract(coord*numLines) - 0.5); + vec3 df = fwidth(coord * numLines); + vec3 grid3D = clamp((grid - df * (lineWidth - 1.0)) / df, 0.0, 1.0); + vec3 axis = vec3(1.0, 1.0, 1.0); + float line = float(length(axis) > 0.0) * pow(grid3D.x, axis.x) * pow(grid3D.y, axis.y) * pow(grid3D.z, axis.z); // if only ticks should be display, this'll discard the interior // of the bounding box quad completely, apart from the ticks. - if(ticksOnly > 0) { - if(coord.x > 0.02 && coord.x < 0.98 && coord.y > 0.02 && coord.y < 0.98) { - discard; - } - } // mix together line colors and black background. // everything apart from the lines should be transparent. - FragColor = mix(vec4(0.0), vec4(gridColor, Material.Opacity), 1.0 - min(line, 1.0)); + float alpha = 1.0 - line; + + if(ticksOnly > 0) { + vec2 bl = step(vec2(0.01), uv); + vec2 tr = step(vec2(0.01), 1.0 - uv); + alpha *= (1.0 - bl.x * bl.y * tr.x * tr.y); + } + + + if(alpha < 0.0001f) { + discard; + } + FragColor = vec4(gridColor * alpha, Material.Opacity * alpha); } From 4a19999e3b16f943fd807fd2cb6bdec2f3ecd728 Mon Sep 17 00:00:00 2001 From: Ulrik Guenther Date: Sat, 23 Sep 2023 13:59:34 +0200 Subject: [PATCH 2/4] BoundingGrid: Fix y orientation and update compiled shader --- .../kotlin/graphics/scenery/BoundingGrid.kt | 3 ++- .../backends/shaders/BoundingGrid.frag.spv | Bin 3492 -> 4368 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/graphics/scenery/BoundingGrid.kt b/src/main/kotlin/graphics/scenery/BoundingGrid.kt index 3e42e7026..d1214aec4 100644 --- a/src/main/kotlin/graphics/scenery/BoundingGrid.kt +++ b/src/main/kotlin/graphics/scenery/BoundingGrid.kt @@ -52,7 +52,7 @@ open class BoundingGrid : Mesh("Bounding Grid"), RenderingOrder { /** Whether to show only the ticks on the grid, or show the full grid. */ @ShaderProperty - var ticksOnly: Int = 0 + var ticksOnly: Int = 1 set(value) { field = value if(ticksOnly == 0) { @@ -208,6 +208,7 @@ open class BoundingGrid : Mesh("Bounding Grid"), RenderingOrder { labels["0"]?.spatial()?.scale = invScale labels["x"]?.spatial()?.scale = invScale labels["y"]?.spatial()?.scale = invScale + labels["y"]?.spatial()?.scale?.timesAssign(-1.0f) labels["z"]?.spatial()?.scale = invScale spatial { diff --git a/src/main/resources/graphics/scenery/backends/shaders/BoundingGrid.frag.spv b/src/main/resources/graphics/scenery/backends/shaders/BoundingGrid.frag.spv index 65f146c8e19f5427ad624f59ed62b1157830650f..7afb6e7b2e9cce350b1330decce606e4329d9041 100644 GIT binary patch literal 4368 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 Date: Thu, 18 Apr 2024 22:57:51 +0200 Subject: [PATCH 3/4] BoundingGrid: Fixes for Material.depthTest API change --- src/main/kotlin/graphics/scenery/BoundingGrid.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/graphics/scenery/BoundingGrid.kt b/src/main/kotlin/graphics/scenery/BoundingGrid.kt index d1214aec4..ef69934cb 100644 --- a/src/main/kotlin/graphics/scenery/BoundingGrid.kt +++ b/src/main/kotlin/graphics/scenery/BoundingGrid.kt @@ -102,7 +102,8 @@ open class BoundingGrid : Mesh("Bounding Grid"), RenderingOrder { } else { cullingMode = Material.CullingMode.Back } - depthTest = Material.DepthTest.LessEqual + depthTest = true + depthOp = Material.DepthTest.LessEqual } labels = hashMapOf( From 4250f3f1f675512f890836ce54bb5e1369b4af61 Mon Sep 17 00:00:00 2001 From: Ulrik Guenther Date: Thu, 16 May 2024 22:48:44 +0200 Subject: [PATCH 4/4] RAIVolume: In generateBoundingBox(), take scaling transforms from BDV datasets into consideration --- .../graphics/scenery/volumes/RAIVolume.kt | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt b/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt index 151289a44..5ca0151f7 100644 --- a/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt +++ b/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt @@ -9,6 +9,7 @@ import graphics.scenery.Origin import graphics.scenery.utils.extensions.minus import graphics.scenery.utils.extensions.plus import graphics.scenery.utils.extensions.times +import net.imglib2.realtransform.AffineTransform3D import net.imglib2.type.numeric.NumericType import net.imglib2.type.numeric.integer.* import net.imglib2.type.numeric.real.FloatType @@ -23,9 +24,9 @@ class RAIVolume(@Transient val ds: VolumeDataSource, options: VolumeViewerOption options, hub ) { - private constructor() : this(VolumeDataSource.RAISource(UnsignedByteType(), emptyList(), ArrayList(), 0, null), VolumeViewerOptions.options(), Hub()) { - - } + // Empty secondary constructor is necessary for network serialisation + @Suppress("unused") + private constructor() : this(VolumeDataSource.RAISource(UnsignedByteType(), emptyList(), ArrayList(), 0, null), VolumeViewerOptions.options(), Hub()) init { name = "Volume (RAI source)" @@ -44,9 +45,18 @@ class RAIVolume(@Transient val ds: VolumeDataSource, options: VolumeViewerOption } override fun generateBoundingBox(): OrientedBoundingBox { + val preScale = Vector3f(1.0f) + val source = firstSource() + + if(source != null) { + val tr = AffineTransform3D() + source.spimSource.getSourceTransform(0, 0, tr) + preScale.set(tr.get(0, 0), tr.get(1,1), tr.get(2, 2)) + } + return OrientedBoundingBox(this, - Vector3f(-0.0f, -0.0f, -0.0f), - Vector3f(getDimensions())) + Vector3f(0.0f), + Vector3f(getDimensions()) * preScale) } override fun localScale(): Vector3f { @@ -69,7 +79,7 @@ class RAIVolume(@Transient val ds: VolumeDataSource, options: VolumeViewerOption val min = Vector3i(s.min(0).toInt(), s.min(1).toInt(), s.min(2).toInt()) val max = Vector3i(s.max(0).toInt(), s.max(1).toInt(), s.max(2).toInt()) val d = max.sub(min) - logger.debug("Dimensions are $d") + logger.debug("Dimensions are {}", d) d } else { Vector3i(1, 1, 1) @@ -81,7 +91,7 @@ class RAIVolume(@Transient val ds: VolumeDataSource, options: VolumeViewerOption } /** - * Extension of [VolumeSpatial] for RAI volumes + * Extension of [Volume.VolumeSpatial] for RAI volumes */ class RAIVolumeSpatial(volume: RAIVolume): VolumeSpatial(volume) { override fun composeModel() { @@ -90,20 +100,20 @@ class RAIVolume(@Transient val ds: VolumeDataSource, options: VolumeViewerOption val volume = (node as? RAIVolume) ?: return val source = volume.firstSource() - val shift = if (source != null) { - val s = source.spimSource.getSource(0, 0) - val min = Vector3f(s.min(0).toFloat(), s.min(1).toFloat(), s.min(2).toFloat()) - val max = Vector3f(s.max(0).toFloat(), s.max(1).toFloat(), s.max(2).toFloat()) - (max - min) * (-0.5f) - } else { - Vector3f(0.0f, 0.0f, 0.0f) - } - model.translation(position) model.mul(Matrix4f().set(this.rotation)) model.scale(scale) model.scale(volume.localScale()) if (volume.origin == Origin.Center) { + val shift = if (source != null) { + val s = source.spimSource.getSource(0, 0) + val min = Vector3f(s.min(0).toFloat(), s.min(1).toFloat(), s.min(2).toFloat()) + val max = Vector3f(s.max(0).toFloat(), s.max(1).toFloat(), s.max(2).toFloat()) + (max - min) * (-0.5f) + } else { + Vector3f(0.0f, 0.0f, 0.0f) + } + model.translate(shift) } }