Skip to content

Commit

Permalink
Volume: Introduce ray shuffling to limit banding artifacts
Browse files Browse the repository at this point in the history
  • Loading branch information
skalarproduktraum committed Mar 15, 2024
1 parent 0d98cac commit b8a7704
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 21 deletions.
4 changes: 3 additions & 1 deletion src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
46 changes: 43 additions & 3 deletions src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -264,7 +300,11 @@ class VolumeManager(
segments[SegmentType.FragmentShader] = SegmentTemplate(
this.javaClass,
"BDVVolume.frag",
"intersectBoundingBox", "vis", "SampleVolume", "Convert", "Accumulate"
"intersectBoundingBox",

Check warning on line 303 in src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt#L303

Multiple occurrences of the same string literal within a single file detected. Prefer extracting the string literal into a property or constant.
"vis",
"SampleVolume",
"Convert",
"Accumulate"
)
segments[SegmentType.MaxDepth] = SegmentTemplate(
this.javaClass,
Expand Down Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
35 changes: 30 additions & 5 deletions src/main/resources/graphics/scenery/volumes/BDVVolume.frag
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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}
Expand All @@ -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);

Expand All @@ -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;
Expand Down Expand Up @@ -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 );
Expand All @@ -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;
Expand Down
109 changes: 99 additions & 10 deletions src/main/resources/graphics/scenery/volumes/SampleBlockVolume.frag
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand All @@ -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")) {
Expand All @@ -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?
Expand Down

0 comments on commit b8a7704

Please sign in to comment.