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

Volume rendering compositing changes and wireframe rendering to vis chunks in volume rendering mode #503

Merged
merged 19 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a39f5bc
feat: VR front-to-back compositing and chunk vis
seankmartin Nov 22, 2023
a2219e7
chore: add comment about opacity correction source
seankmartin Nov 22, 2023
ec6fa49
refactor: clearer naming for compositing
seankmartin Nov 23, 2023
66b5292
chore: fix formatting
seankmartin Nov 23, 2023
2a30856
Merge branch 'master' into feature/volume-rendering-compositing
seankmartin Jan 23, 2024
1c448b2
feat: add gain parameter to VR and python binds
seankmartin Jan 24, 2024
afd90ef
refactor: pull OIT emit function out as standalone
seankmartin Jan 24, 2024
36134e1
feat: perform OIT along rays during VR
seankmartin Jan 24, 2024
15a749e
test: ensure that color and revealage are independent of sampling rate
seankmartin Jan 24, 2024
19897dc
feat: add depth culling to VR rays during marching
seankmartin Jan 24, 2024
0fa1a2d
chore: format file
seankmartin Jan 24, 2024
a280d7b
fix: break composite loop if VR ray goes behind opaque
seankmartin Jan 29, 2024
70df5f1
fix: remove console.log during testing
seankmartin Jan 31, 2024
4e7dc33
feat: volume rendering chunk vis in wireframe mode
seankmartin Jan 31, 2024
cf48e5d
refactor: rename gain to volumeRenderingGain
seankmartin Feb 5, 2024
1baaced
feat: change gain scale from linear to exponential
seankmartin Feb 5, 2024
2100f9a
refactor: rename to depthBufferTexture (remove ID)
seankmartin Feb 5, 2024
62691c4
format: run python formatting
seankmartin Feb 5, 2024
5b95530
fix: default volume rendering slider gain of 0 (e^0 passed to shader)
seankmartin Feb 6, 2024
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
2 changes: 2 additions & 0 deletions python/neuroglancer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
PlaceEllipsoidTool, # noqa: F401
BlendTool, # noqa: F401
OpacityTool, # noqa: F401
VolumeRenderingTool, # noqa: F401
GainTool, # noqa: F401
VolumeRenderingDepthSamplesTool, # noqa: F401
CrossSectionRenderScaleTool, # noqa: F401
SelectedAlphaTool, # noqa: F401
Expand Down
16 changes: 16 additions & 0 deletions python/neuroglancer/viewer_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ class OpacityTool(Tool):
TOOL_TYPE = "opacity"


@export_tool
class GainTool(Tool):
__slots__ = ()
TOOL_TYPE = "gain"


@export_tool
class VolumeRenderingTool(Tool):
__slots__ = ()
TOOL_TYPE = "volumeRendering"


@export_tool
class VolumeRenderingDepthSamplesTool(Tool):
__slots__ = ()
Expand Down Expand Up @@ -543,6 +555,10 @@ def __init__(self, *args, **kwargs):
)
opacity = wrapped_property("opacity", optional(float, 0.5))
blend = wrapped_property("blend", optional(str))
gain = wrapped_property("gain", optional(float, 1))
volume_rendering = volumeRendering = wrapped_property(
"volumeRendering", optional(bool, False)
)
volume_rendering_depth_samples = volumeRenderingDepthSamples = wrapped_property(
"volumeRenderingDepthSamples", optional(float, 64)
)
Expand Down
16 changes: 16 additions & 0 deletions src/image_user_layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ import {
ShaderControls,
} from "#/widget/shader_controls";
import { Tab } from "#/widget/tab_view";
import { trackableFiniteFloat } from "#/trackable_finite_float";

const OPACITY_JSON_KEY = "opacity";
const GAIN_JSON_KEY = "gain";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename to volumeRenderingGain since it only applies to volume rendering?

Also when I've created this control manually as a UI control, I've typically found it convenient to give it an exponential scale so that it can more easily cover a larger range, i.e. the slider ranges from -10 to 10 and the actual gain is exp(gain). But that could also be handled purely in the slider control. Maybe you've also found the linear gain to work okay.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks! Linear worked pretty well, but I prefer the exponential scale after trying it out, thanks for the suggestion on that.
Not sure if we should hint at the scale in the UI somehow now, or if the name Gain (3D) is clear enough. Happy either way, but let me know if you have thoughts on that

const BLEND_JSON_KEY = "blend";
const SHADER_JSON_KEY = "shader";
const SHADER_CONTROLS_JSON_KEY = "shaderControls";
Expand All @@ -119,6 +121,7 @@ const [
] = getVolumeRenderingDepthSamplesBoundsLogScale();
export class ImageUserLayer extends Base {
opacity = trackableAlphaValue(0.5);
gain = trackableFiniteFloat(1);
blendMode = trackableBlendModeValue();
fragmentMain = getTrackableFragmentMain();
shaderError = makeWatchableShaderError();
Expand Down Expand Up @@ -199,6 +202,7 @@ export class ImageUserLayer extends Base {
isLocalDimension;
this.blendMode.changed.add(this.specificationChanged.dispatch);
this.opacity.changed.add(this.specificationChanged.dispatch);
this.gain.changed.add(this.specificationChanged.dispatch);
this.fragmentMain.changed.add(this.specificationChanged.dispatch);
this.shaderControlState.changed.add(this.specificationChanged.dispatch);
this.sliceViewRenderScaleTarget.changed.add(
Expand Down Expand Up @@ -252,6 +256,7 @@ export class ImageUserLayer extends Base {
);
const volumeRenderLayer = context.registerDisposer(
new VolumeRenderingRenderLayer({
gain: this.gain,
multiscaleSource: volume,
shaderControlState: this.shaderControlState,
shaderError: this.shaderError,
Expand Down Expand Up @@ -285,6 +290,7 @@ export class ImageUserLayer extends Base {
restoreState(specification: any) {
super.restoreState(specification);
this.opacity.restoreState(specification[OPACITY_JSON_KEY]);
this.gain.restoreState(specification[GAIN_JSON_KEY]);
verifyOptionalObjectProperty(specification, BLEND_JSON_KEY, (blendValue) =>
this.blendMode.restoreState(blendValue),
);
Expand All @@ -306,6 +312,7 @@ export class ImageUserLayer extends Base {
toJSON() {
const x = super.toJSON();
x[OPACITY_JSON_KEY] = this.opacity.toJSON();
x[GAIN_JSON_KEY] = this.gain.toJSON();
x[BLEND_JSON_KEY] = this.blendMode.toJSON();
x[SHADER_JSON_KEY] = this.fragmentMain.toJSON();
x[SHADER_CONTROLS_JSON_KEY] = this.shaderControlState.toJSON();
Expand Down Expand Up @@ -451,6 +458,15 @@ const LAYER_CONTROLS: LayerControlDefinition<ImageUserLayer>[] = [
toolJson: VOLUME_RENDERING_JSON_KEY,
...checkboxLayerControl((layer) => layer.volumeRendering),
},
{
label: "Gain (3D)",
toolJson: GAIN_JSON_KEY,
isValid: (layer) => layer.volumeRendering,
...rangeLayerControl((layer) => ({
value: layer.gain,
options: { min: 0.01, max: 16.0, step: 0.01 },
})),
},
{
label: "Resolution (3D)",
toolJson: VOLUME_RENDERING_DEPTH_SAMPLES_JSON_KEY,
Expand Down
16 changes: 11 additions & 5 deletions src/perspective_view/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,26 @@ void emit(vec4 color, highp uint pickId) {
* http://casual-effects.blogspot.com/2015/03/implemented-weighted-blended-order.html
*/
export const glsl_computeOITWeight = `
float computeOITWeight(float alpha) {
float computeOITWeight(float alpha, float depth) {
float a = min(1.0, alpha) * 8.0 + 0.01;
float b = -gl_FragCoord.z * 0.95 + 1.0;
float b = -depth * 0.95 + 1.0;
return a * a * a * b * b * b;
}
`;

// Color must be premultiplied by alpha.
// Can use emitAccumAndRevealage() to emit a pre-weighted OIT result.
export const glsl_perspectivePanelEmitOIT = [
glsl_computeOITWeight,
`
void emitAccumAndRevealage(vec4 accum, float revealage, highp uint pickId) {
v4f_fragData0 = vec4(accum.rgb, revealage);
v4f_fragData1 = vec4(accum.a, 0.0, 0.0, 0.0);
}
void emit(vec4 color, highp uint pickId) {
float weight = computeOITWeight(color.a);
float weight = computeOITWeight(color.a, gl_FragCoord.z);
vec4 accum = color * weight;
v4f_fragData0 = vec4(accum.rgb, color.a);
v4f_fragData1 = vec4(accum.a, 0.0, 0.0, 0.0);
emitAccumAndRevealage(accum, color.a, pickId);
}
`,
];
Expand Down Expand Up @@ -846,6 +850,8 @@ export class PerspectivePanel extends RenderedDataPanel {
renderContext.emitPickID = false;
for (const [renderLayer, attachment] of visibleLayers) {
if (renderLayer.isTransparent) {
renderContext.depthBufferTextureID =
this.offscreenFramebuffer.colorBuffers[OffscreenTextures.Z].texture;
renderLayer.draw(renderContext, attachment);
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/perspective_view/render_layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ export interface PerspectiveViewRenderContext
* Specifies whether there was a previous pick ID pass.
*/
alreadyEmittedPickID: boolean;

/**
* Specifies the ID of the depth frame buffer texture to query during rendering.
*/
depthBufferTextureID?: WebGLTexture | null;
seankmartin marked this conversation as resolved.
Show resolved Hide resolved
}

export class PerspectiveViewRenderLayer<
Expand Down
73 changes: 73 additions & 0 deletions src/volume_rendering/volume_render_layer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { fragmentShaderTest } from "#/webgl/shader_testing";
import { glsl_computeOITWeight } from "#/perspective_view/panel";
import { glsl_emitRGBAVolumeRendering } from "#/volume_rendering/volume_render_layer";
import { vec3 } from "gl-matrix";

describe("volume rendering compositing", () => {
const steps = [16, 22, 32, 37, 64, 100, 128, 256, 512, 551, 1024, 2048];
const revealages = new Float32Array(steps.length);
it("combines uniform colors the same regardless of sampling rate", () => {
fragmentShaderTest(
{
inputSteps: "float",
},
{
outputValue1: "float",
outputValue2: "float",
outputValue3: "float",
outputValue4: "float",
revealage: "float",
},
(tester) => {
const { builder } = tester;
builder.addFragmentCode(glsl_computeOITWeight);
builder.addFragmentCode(`
vec4 color = vec4(0.1, 0.3, 0.5, 0.1);
float idealSamplingRate = 512.0;
float uGain = 0.01;
float uBrightnessFactor;
vec4 outputColor;
float depthAtRayPosition;
`);
builder.addFragmentCode(glsl_emitRGBAVolumeRendering);
builder.setFragmentMain(`
outputColor = vec4(0.0);
revealage = 1.0;
uBrightnessFactor = idealSamplingRate / inputSteps;
for (int i = 0; i < int(inputSteps); ++i) {
depthAtRayPosition = mix(0.0, 1.0, float(i) / (inputSteps - 1.0));
emitRGBA(color);
}
outputValue1 = outputColor.r;
outputValue2 = outputColor.g;
outputValue3 = outputColor.b;
outputValue4 = outputColor.a;
`);
for (let i = 0; i < steps.length; ++i) {
const inputSteps = steps[i];
tester.execute({ inputSteps });
const values = tester.values;
const {
revealage,
outputValue1,
outputValue2,
outputValue3,
outputValue4,
} = values;
const color = vec3.fromValues(
outputValue1 / outputValue4,
outputValue2 / outputValue4,
outputValue3 / outputValue4,
);
expect(color[0]).toBeCloseTo(0.1, 5);
expect(color[1]).toBeCloseTo(0.3, 5);
expect(color[2]).toBeCloseTo(0.5, 5);
revealages[i] = revealage;
}
for (let i = 1; i < revealages.length; ++i) {
expect(revealages[i]).toBeCloseTo(revealages[i - 1], 2);
}
},
);
});
});
Loading
Loading