diff --git a/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt b/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt index b87842d4f7..8dec7e9531 100644 --- a/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt +++ b/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt @@ -75,7 +75,9 @@ class RAIVolume(@Transient val ds: VolumeDataSource, options: VolumeViewerOption val s = source.spimSource.getSource(0, 0) 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()) - max.sub(min) + val d = max.sub(min) + logger.info("Dimensions are $d") + d } else { Vector3i(1, 1, 1) } diff --git a/src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt b/src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt index 950440faaf..7aabb30ba8 100644 --- a/src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt +++ b/src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt @@ -122,7 +122,42 @@ class VolumeManager( var maxAllowedStepInVoxels = 1.0 /** Numeric factor by which the step size may degrade on the far plane. */ - var farPlaneDegradation = 2.0 + var farPlaneDegradation = 1.0 + + /** Amount of randomisation for ray start and ray steps. 0.0 turns this off, 1.0 is the default, + * values larger than 1.0 might lead to noisy renderings. */ + var shuffleDegree = 1.0f + set(value) { + field = value + shaderProperties["shuffleDegree"] = value + } + + var maxOcclusionDistance = 4.0f + set(value) { + field = value + shaderProperties["maxOcclusionDistance"] = value + } + + var kernelSize = 8.0f + set(value) { + field = value + shaderProperties["kernelSize"] = value + } + + var occlusionSteps = 10 + set(value) { + field = value + shaderProperties["occlusionSteps"] = value + } + + var aoDebug = 0 + set(value) { + field = value + shaderProperties["aoDebug"] = value + } + + + // TODO: What happens when changing this? And should it change the mode for the current node only // or for all VolumeManager-managed nodes? @@ -175,6 +210,7 @@ class VolumeManager( shaderProperties["transform"] = Matrix4f() shaderProperties["viewportSize"] = Vector2f() shaderProperties["dsp"] = Vector2f() + shaderProperties["shuffleDegree"] = shuffleDegree val oldKeys = material().textures.filter { it.key !in customTextures }.keys val texturesToKeep = material().textures.filter { it.key in customTextures } oldKeys.forEach { @@ -264,7 +300,11 @@ class VolumeManager( segments[SegmentType.FragmentShader] = SegmentTemplate( this.javaClass, "BDVVolume.frag", - "intersectBoundingBox", "vis", "SampleVolume", "Convert", "Accumulate" + "intersectBoundingBox", + "vis", + "SampleVolume", + "Convert", + "Accumulate" ) segments[SegmentType.MaxDepth] = SegmentTemplate( this.javaClass, @@ -466,7 +506,7 @@ class VolumeManager( currentProg.setUniform(i, "colorMap", state.colorMap) currentProg.setUniform(i, "slicingPlanes", 4, state.node.slicingArray()) currentProg.setUniform(i, "slicingMode", state.node.slicingMode.id) - currentProg.setUniform(i,"usedSlicingPlanes", + currentProg.setUniform(i, "usedSlicingPlanes", min(state.node.slicingPlaneEquations.size, Volume.MAX_SUPPORTED_SLICING_PLANES)) currentProg.setUniform(i, "sceneGraphVisibility", if (state.node.visible) 1 else 0) diff --git a/src/main/resources/graphics/scenery/volumes/AccumulateBlockVolume.frag b/src/main/resources/graphics/scenery/volumes/AccumulateBlockVolume.frag index 57cf783525..511a804515 100644 --- a/src/main/resources/graphics/scenery/volumes/AccumulateBlockVolume.frag +++ b/src/main/resources/graphics/scenery/volumes/AccumulateBlockVolume.frag @@ -14,7 +14,11 @@ if (vis) v.rgb = v.rgb + (1.0f - v.a) * newColor * adjusted_alpha; v.a = v.a + (1.0f - v.a) * adjusted_alpha; + v.rgb = mix(previous.rgb, v.rgb, 0.5); + if(v.a >= 1.0f) { break; } + + previous = v; } diff --git a/src/main/resources/graphics/scenery/volumes/BDVVolume.frag b/src/main/resources/graphics/scenery/volumes/BDVVolume.frag index 51c95468b2..b230be0576 100644 --- a/src/main/resources/graphics/scenery/volumes/BDVVolume.frag +++ b/src/main/resources/graphics/scenery/volumes/BDVVolume.frag @@ -1,5 +1,13 @@ +#extension GL_EXT_control_flow_attributes : enable +#extension GL_EXT_debug_printf : enable +#extension SPV_KHR_non_semantic_info : enable out vec4 FragColor; uniform vec2 viewportSize; +uniform float shuffleDegree; +uniform float maxOcclusionDistance; +uniform float kernelSize; +uniform int occlusionSteps; +uniform int aoDebug; uniform vec2 dsp; uniform float fwnw; uniform float nw; @@ -71,6 +79,22 @@ float adjustOpacity(float a, float modifiedStepLength) { uniform bool fixedStepSize; uniform float stepsPerVoxel; +float rand(vec2 co){ + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +#define PI 3.14159265359 +vec3 randomSpherePoint(vec3 rand) { + float ang1 = (rand.x + 1.0) * PI; // [-1..1) -> [0..2*PI) + float u = rand.y; // [-1..1), cos and acos(2v-1) cancel each other out, so we arrive at [-1..1) + float u2 = u * u; + float sqrt1MinusU2 = sqrt(1.0 - u2); + float x = sqrt1MinusU2 * cos(ang1); + float y = sqrt1MinusU2 * sin(ang1); + float z = u; + return vec3(x, y, z); +} + // --------------------- // $insert{Convert} // $insert{SampleVolume} @@ -88,6 +112,9 @@ void main() mat4 t = transform; vec2 uv = Vertex.textureCoord * 2.0 - vec2(1.0); + vec3 shuffle = vec3(rand(uv), rand(uv.yx), rand(uv.xy/uv.yx)); + uv = uv + (shuffle.xy * 0.001f * shuffleDegree); + vec2 depthUV = (vrParameters.stereoEnabled ^ 1) * Vertex.textureCoord + vrParameters.stereoEnabled * vec2((Vertex.textureCoord.x/2.0 + currentEye.eye * 0.5), Vertex.textureCoord.y); depthUV = depthUV * 2.0 - vec2(1.0); @@ -101,9 +128,6 @@ void main() vec4 wback = ipv * back; wback *= 1 / wback.w; - vec4 direc = Vertex.inverseView * normalize(wback-wfront); - direc.w = 0.0f; - // -- bounding box intersection for all volumes ---------- float tnear = 1, tfar = 0, tmax = getMaxDepth( depthUV ); float n, f; @@ -153,6 +177,7 @@ void main() float step_prev = step - stepWidth; vec4 wprev = mix(wfront, wback, step_prev); vec4 v = vec4( 0 ); + vec4 previous = vec4(0.0f); for ( int i = 0; i < numSteps; ++i) { vec4 wpos = mix( wfront, wback, step ); @@ -170,9 +195,9 @@ void main() wprev = wpos; if(fixedStepSize) { - step += stepWidth; + step += stepWidth * (1.0f+shuffleDegree*shuffle.x/2.0f); } else { - step += nw + step * fwnw; + step += nw + step * fwnw * (1.0f+shuffleDegree*shuffle.x/2.0f); } } FragColor = v; diff --git a/src/main/resources/graphics/scenery/volumes/SampleBlockVolume.frag b/src/main/resources/graphics/scenery/volumes/SampleBlockVolume.frag index e7364a24d9..a49d37faf1 100644 --- a/src/main/resources/graphics/scenery/volumes/SampleBlockVolume.frag +++ b/src/main/resources/graphics/scenery/volumes/SampleBlockVolume.frag @@ -21,6 +21,73 @@ uniform vec3 blockScales[ NUM_BLOCK_SCALES ]; uniform vec3 lutSize; uniform vec3 lutOffset; +const vec2 poisson16[] = vec2[]( +vec2( -0.94201624, -0.39906216 ), +vec2( 0.94558609, -0.76890725 ), +vec2( -0.094184101, -0.92938870 ), +vec2( 0.34495938, 0.29387760 ), +vec2( -0.91588581, 0.45771432 ), +vec2( -0.81544232, -0.87912464 ), +vec2( -0.38277543, 0.27676845 ), +vec2( 0.97484398, 0.75648379 ), +vec2( 0.44323325, -0.97511554 ), +vec2( 0.53742981, -0.47373420 ), +vec2( -0.26496911, -0.41893023 ), +vec2( 0.79197514, 0.19090188 ), +vec2( -0.24188840, 0.99706507 ), +vec2( -0.81409955, 0.91437590 ), +vec2( 0.19984126, 0.78641367 ), +vec2( 0.14383161, -0.14100790 ) +); + +vec3 getUVW(vec3 pos, + vec3 cacheSize, + vec3 blockSize, + vec3 paddedBlockSize, + vec3 padOffset +) { + + vec3 q = floor( pos / blockSize ) - lutOffset + 0.5; + + uvec4 lutv = texture( lutSampler, q / lutSize ); + vec3 B0 = lutv.xyz * paddedBlockSize + padOffset; + vec3 sj = blockScales[ lutv.w ]; + + vec3 c0 = B0 + mod( pos * sj, blockSize ) + 0.5 * sj; + // + 0.5 ( sj - 1 ) + 0.5 for tex coord offset + + return c0/cacheSize; +} + + +vec3 getGradient(float v, sampler3D volumeCache, vec3 pos, float kernelSize, + vec3 cacheSize, + vec3 blockSize, + vec3 paddedBlockSize, + vec3 padOffset +) { + +// const vec3 offset = vec3(kernelSize)/textureSize(volumeCache, 0); + const vec3 offset = vec3(kernelSize);///(sourcemax-sourcemin); + + vec3 uvw0 = getUVW(pos + vec3(offset.x, 0.0f, 0.0f), cacheSize, blockSize, paddedBlockSize, padOffset); + vec3 uvw1 = getUVW(pos + vec3(0.0f, offset.y, 0.0f), cacheSize, blockSize, paddedBlockSize, padOffset); + vec3 uvw2 = getUVW(pos + vec3(0.0f, 0.0f, offset.z), cacheSize, blockSize, paddedBlockSize, padOffset); + +// debugPrintfEXT("offset=%f, uv0 = %v3f, uv1 = %v3f, uv2 = %v3f", offset, uvw0, uvw1, uvw2); + + float raw = 0.0f; + raw = convert(texture( volumeCache, uvw0).r); + float v0 = texture(transferFunction, vec2(raw + 0.000001f, 0.5f)).r; + raw = convert(texture( volumeCache, uvw1).r); + float v1 = texture(transferFunction, vec2(raw + 0.000001f, 0.5f)).r; + raw = convert(texture( volumeCache, uvw2).r); + float v2 = texture(transferFunction, vec2(raw + 0.000001f, 0.5f)).r; + + return vec3(v - v0, v - v1, v - v2); +} + + vec4 sampleVolume( vec4 wpos, sampler3D volumeCache, vec3 cacheSize, vec3 blockSize, vec3 paddedBlockSize, vec3 padOffset ) { bool cropping = slicingMode == 1 || slicingMode == 3; @@ -48,20 +115,42 @@ vec4 sampleVolume( vec4 wpos, sampler3D volumeCache, vec3 cacheSize, vec3 blockS } vec3 pos = (im * wpos).xyz + 0.5; + vec3 uvw = getUVW(pos, cacheSize, blockSize, paddedBlockSize, padOffset); + float rawsample = convert(texture( volumeCache, uvw ).r); + float tf = texture(transferFunction, vec2(rawsample + 0.001f, 0.5f)).r; + vec3 cmapplied = tf * texture(colorMap, vec2(rawsample + 0.001f, 0.5f)).rgb; - vec3 q = floor( pos / blockSize ) - lutOffset + 0.5; + float shadowing = 0.0f; + float shadowDist = 0.0f; - uvec4 lutv = texture( lutSampler, q / lutSize ); - vec3 B0 = lutv.xyz * paddedBlockSize + padOffset; - vec3 sj = blockScales[ lutv.w ]; + if(tf > 0.0f && occlusionSteps > 0) { + [[unroll]] for(int s = 0; s < occlusionSteps; s++) { + vec3 lpos = pos + vec3(poisson16[s], (poisson16[s].x + poisson16[s].y)/2.0) * kernelSize; + vec3 N = normalize(getGradient(tf, volumeCache, lpos, kernelSize, cacheSize, + blockSize, + paddedBlockSize, + padOffset + )); + vec3 sampleDir = normalize(lpos - pos); - vec3 c0 = B0 + mod( pos * sj, blockSize ) + 0.5 * sj; - // + 0.5 ( sj - 1 ) + 0.5 for tex coord offset + float NdotS = max(dot(N, sampleDir), 0.0); + float dist = distance(pos, lpos); + + float a = smoothstep(0.0f, maxOcclusionDistance*2.0, dist); + + shadowDist += a * NdotS/occlusionSteps; + if(s == 3) { +// debugPrintfEXT("N=%v3f NdotS=%f dist=%f lpos=%v3f wpos=%v3f", N, NdotS, dist, lpos, pos); + } + } + + shadowing = clamp(shadowDist, 0.0, 1.0); +// debugPrintfEXT("sd=%f s=%f", shadowDist, shadowing); + } - float rawsample = convert(texture( volumeCache, c0 / cacheSize ).r); - float tf = texture(transferFunction, vec2(rawsample + 0.001f, 0.5f)).r; - vec3 cmapplied = tf * texture(colorMap, vec2(rawsample + 0.001f, 0.5f)).rgb; +// vec3 color = mix(cmapplied * (1.0f-shadowing), vec3(1.0f-shadowing), float(aoDebug)); int intransparent = int( slicing && isInSlice) ; - return vec4(cmapplied*tf,1) * intransparent + vec4(cmapplied, tf) * (1-intransparent); +// return vec4(cmapplied*tf,1) * intransparent + vec4(cmapplied * (1.0f - shadowing), tf * (1.0f-shadowing)) * (1-intransparent); + return vec4(cmapplied*tf,1) * intransparent + vec4(vec3(1.0f - shadowing), tf * (1.0f-shadowing)) * (1-intransparent); } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/BDVExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/BDVExample.kt index d274ea722e..8a276f0528 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/BDVExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/BDVExample.kt @@ -11,6 +11,7 @@ import graphics.scenery.backends.Renderer import graphics.scenery.volumes.Colormap import graphics.scenery.volumes.TransferFunction import graphics.scenery.volumes.Volume +import net.imagej.lut.LUTService import net.imagej.ops.OpService import net.imglib2.histogram.Histogram1d import org.scijava.Context @@ -54,7 +55,7 @@ class BDVExample: SceneryBase("BDV Rendering example", 1280, 720) { // If file is null, we'll use one of our example datasets. if(file == null) { - file = File(getDemoFilesPath() + "/volumes/t1-head.xml") + file = File(getDemoFilesPath() + "/volumes/visible-male.xml") } files.add(file.absolutePath) } @@ -73,7 +74,7 @@ class BDVExample: SceneryBase("BDV Rendering example", 1280, 720) { val options = VolumeViewerOptions().maxCacheSizeInMB(maxCacheSize) val v = Volume.fromSpimData(XmlIoSpimDataMinimal().load(files.first()), hub, options) v.name = "volume" - v.colormap = Colormap.get("hot") + v.colormap = Colormap.get("jet") // we set some known properties here for the T1 head example dataset if(files.first().endsWith("t1-head.xml")) { @@ -82,6 +83,8 @@ class BDVExample: SceneryBase("BDV Rendering example", 1280, 720) { v.spatial().scale = Vector3f(0.2f) v.setTransferFunctionRange(0.0f, 2000.0f) } + v.transferFunction = TransferFunction.ramp(0.2f, 0.15f) + v.multiResolutionLevelLimits = 0 to 1 v.viewerState.sources.firstOrNull()?.spimSource?.getSource(0, 0)?.let { rai -> var h: Any?