Skip to content

Commit 3b2a8a3

Browse files
authored
fix(volume_rendering): Volume rendering fixes for max projection - picking, blending, and performance (#584)
* fix: ensure picking value from max projection is < 1 * fix: better blending of multiple VR layers with max enabled * fix: clarify comment * fix: userIntensity always overwrites default * fix: no longer double draw opaque layers * chore: remove log to check fix * refactor: remove non-functioning continue statement * feat: emit a single ID for picking in VR layer * refactor: rename uniform to uPickId * feat: register VR layer to get pick ID * refactor: remove unused demo code around pick ID * refactor: clarify role of copy shaders in perspective panel * fix: don't copy over the max projection picking buffer for each max layer Do one copy if there are any max layers instead * refactor: clarify buffer names * fix: VR tool from Python
1 parent 2d7862a commit 3b2a8a3

File tree

4 files changed

+89
-60
lines changed

4 files changed

+89
-60
lines changed

python/neuroglancer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
PlaceEllipsoidTool, # noqa: F401
5050
BlendTool, # noqa: F401
5151
OpacityTool, # noqa: F401
52-
VolumeRenderingModeTool, # noqa: F401
52+
VolumeRenderingTool, # noqa: F401
5353
VolumeRenderingGainTool, # noqa: F401
5454
VolumeRenderingDepthSamplesTool, # noqa: F401
5555
CrossSectionRenderScaleTool, # noqa: F401

python/neuroglancer/viewer_state.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,9 @@ class OpacityTool(Tool):
165165

166166

167167
@export_tool
168-
class VolumeRenderingModeTool(Tool):
168+
class VolumeRenderingTool(Tool):
169169
__slots__ = ()
170-
TOOL_TYPE = "volumeRenderingMode"
170+
TOOL_TYPE = "volumeRendering"
171171

172172

173173
@export_tool

src/perspective_view/panel.ts

Lines changed: 74 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -153,21 +153,26 @@ export function perspectivePanelEmitOIT(builder: ShaderBuilder) {
153153
}
154154

155155
export function maxProjectionEmit(builder: ShaderBuilder) {
156-
builder.addOutputBuffer("vec4", "v4f_fragData0", 0);
157-
builder.addOutputBuffer("highp vec4", "v4f_fragData1", 1);
158-
builder.addOutputBuffer("highp vec4", "v4f_fragData2", 2);
156+
builder.addOutputBuffer("vec4", "out_color", 0);
157+
builder.addOutputBuffer("highp vec4", "out_z", 1);
158+
builder.addOutputBuffer("highp vec4", "out_intensity", 2);
159+
builder.addOutputBuffer("highp vec4", "out_pickId", 3);
159160
builder.addFragmentCode(`
160-
void emit(vec4 color, float depth, float pick) {
161-
v4f_fragData0 = color;
162-
v4f_fragData1 = vec4(1.0 - depth, 1.0 - depth, 1.0 - depth, 1.0);
163-
v4f_fragData2 = vec4(pick, pick, pick, 1.0);
161+
void emit(vec4 color, float depth, float intensity, highp uint pickId) {
162+
float pickIdFloat = float(pickId);
163+
float bufferDepth = 1.0 - depth;
164+
out_color = color;
165+
out_z = vec4(bufferDepth, bufferDepth, bufferDepth, 1.0);
166+
out_intensity = vec4(intensity, intensity, intensity, 1.0);
167+
out_pickId = vec4(pickIdFloat, pickIdFloat, pickIdFloat, 1.0);
164168
}`);
165169
}
166170

167171
const tempVec3 = vec3.create();
168172
const tempVec4 = vec4.create();
169173
const tempMat4 = mat4.create();
170174

175+
// Copy the OIT values to the main color buffer
171176
function defineTransparencyCopyShader(builder: ShaderBuilder) {
172177
builder.addOutputBuffer("vec4", "v4f_fragColor", null);
173178
builder.setFragmentMain(`
@@ -180,31 +185,45 @@ v4f_fragColor = vec4(accum.rgb / accum.a, revealage);
180185
`);
181186
}
182187

188+
// Copy the max projection color to the OIT buffer
183189
function defineMaxProjectionColorCopyShader(builder: ShaderBuilder) {
184-
builder.addOutputBuffer("vec4", "v4f_fragColor", null);
190+
builder.addOutputBuffer("vec4", "v4f_fragData0", 0);
191+
builder.addOutputBuffer("vec4", "v4f_fragData1", 1);
192+
builder.addFragmentCode(glsl_perspectivePanelEmitOIT);
185193
builder.setFragmentMain(`
186-
v4f_fragColor = getValue0();
194+
vec4 color = getValue0();
195+
float bufferDepth = getValue1().r;
196+
float weight = computeOITWeight(color.a, 1.0 - bufferDepth);
197+
vec4 accum = color * weight;
198+
float revealage = color.a;
199+
200+
emitAccumAndRevealage(accum, revealage, 0u);
187201
`);
188202
}
189203

204+
// Copy the max projection depth and pick values to the main buffer
190205
function defineMaxProjectionPickCopyShader(builder: ShaderBuilder) {
191-
builder.addOutputBuffer("vec4", "v4f_fragData0", 0);
192-
builder.addOutputBuffer("highp vec4", "v4f_fragData1", 1);
193-
builder.addOutputBuffer("highp vec4", "v4f_fragData2", 2);
206+
builder.addOutputBuffer("vec4", "out_color", 0);
207+
builder.addOutputBuffer("highp vec4", "out_z", 1);
208+
builder.addOutputBuffer("highp vec4", "out_pickId", 2);
194209
builder.setFragmentMain(`
195-
v4f_fragData0 = vec4(0.0);
196-
v4f_fragData1 = getValue0();
197-
v4f_fragData2 = getValue1();
210+
out_color = vec4(0.0);
211+
out_z = getValue0();
212+
out_pickId = getValue1();
198213
`);
199214
}
200215

216+
// Copy the max projection depth and picking to the max projection pick buffer.
217+
// Note that the depth is set as the intensity value from the render layer.
218+
// This is to combine max projection picking data via depth testing
219+
// on the maximum intensity value of the data.
201220
function defineMaxProjectionToPickCopyShader(builder: ShaderBuilder) {
202-
builder.addOutputBuffer("highp vec4", "v4f_fragData0", 0);
203-
builder.addOutputBuffer("highp vec4", "v4f_fragData1", 1);
221+
builder.addOutputBuffer("highp vec4", "out_z", 0);
222+
builder.addOutputBuffer("highp vec4", "out_pickId", 1);
204223
builder.setFragmentMain(`
205-
v4f_fragData0 = getValue0();
206-
v4f_fragData1 = getValue1();
207-
gl_FragDepth = v4f_fragData1.r;
224+
out_z = getValue0();
225+
out_pickId = getValue2();
226+
gl_FragDepth = getValue1().r;
208227
`);
209228
}
210229

@@ -306,13 +325,13 @@ export class PerspectivePanel extends RenderedDataPanel {
306325
OffscreenCopyHelper.get(this.gl, defineTransparencyCopyShader, 2),
307326
);
308327
protected maxProjectionColorCopyHelper = this.registerDisposer(
309-
OffscreenCopyHelper.get(this.gl, defineMaxProjectionColorCopyShader, 1),
328+
OffscreenCopyHelper.get(this.gl, defineMaxProjectionColorCopyShader, 2),
310329
);
311330
protected maxProjectionPickCopyHelper = this.registerDisposer(
312331
OffscreenCopyHelper.get(this.gl, defineMaxProjectionPickCopyShader, 2),
313332
);
314333
protected maxProjectionToPickCopyHelper = this.registerDisposer(
315-
OffscreenCopyHelper.get(this.gl, defineMaxProjectionToPickCopyShader, 2),
334+
OffscreenCopyHelper.get(this.gl, defineMaxProjectionToPickCopyShader, 3),
316335
);
317336

318337
private sharedObject: PerspectiveViewState;
@@ -730,6 +749,12 @@ export class PerspectivePanel extends RenderedDataPanel {
730749
WebGL2RenderingContext.RED,
731750
WebGL2RenderingContext.FLOAT,
732751
),
752+
new TextureBuffer(
753+
this.gl,
754+
WebGL2RenderingContext.R32F,
755+
WebGL2RenderingContext.RED,
756+
WebGL2RenderingContext.FLOAT,
757+
),
733758
],
734759
depthBuffer: new DepthStencilRenderbuffer(this.gl),
735760
}),
@@ -1004,6 +1029,7 @@ export class PerspectivePanel extends RenderedDataPanel {
10041029
renderContext.depthBufferTexture =
10051030
this.offscreenFramebuffer.colorBuffers[OffscreenTextures.Z].texture;
10061031
}
1032+
// Draw max projection layers
10071033
if (
10081034
renderLayer.isVolumeRendering &&
10091035
isProjectionLayer(renderLayer as VolumeRenderingRenderLayer)
@@ -1021,21 +1047,26 @@ export class PerspectivePanel extends RenderedDataPanel {
10211047
bindMaxProjectionPickingBuffer();
10221048
this.maxProjectionToPickCopyHelper.draw(
10231049
this.maxProjectionConfiguration.colorBuffers[1 /*depth*/].texture,
1024-
this.maxProjectionConfiguration.colorBuffers[2 /*pick*/].texture,
1050+
this.maxProjectionConfiguration.colorBuffers[2 /*intensity*/]
1051+
.texture,
1052+
this.maxProjectionConfiguration.colorBuffers[3 /*pick*/].texture,
10251053
);
10261054

1027-
// Copy max projection color result to color only buffer
1055+
// Copy max projection color result to the transparent buffer with OIT
10281056
// Depth testing off to combine max layers into one color via blend
1029-
this.offscreenFramebuffer.bindSingle(OffscreenTextures.COLOR);
1057+
renderContext.bindFramebuffer();
10301058
gl.depthMask(false);
10311059
gl.disable(WebGL2RenderingContext.DEPTH_TEST);
10321060
gl.enable(WebGL2RenderingContext.BLEND);
1033-
gl.blendFunc(
1061+
gl.blendFuncSeparate(
1062+
WebGL2RenderingContext.ONE,
10341063
WebGL2RenderingContext.ONE,
1064+
WebGL2RenderingContext.ZERO,
10351065
WebGL2RenderingContext.ONE_MINUS_SRC_ALPHA,
10361066
);
10371067
this.maxProjectionColorCopyHelper.draw(
10381068
this.maxProjectionConfiguration.colorBuffers[0 /*color*/].texture,
1069+
this.maxProjectionConfiguration.colorBuffers[1 /*depth*/].texture,
10391070
);
10401071

10411072
// Reset the max projection buffer
@@ -1052,19 +1083,15 @@ export class PerspectivePanel extends RenderedDataPanel {
10521083
gl.clearDepth(1.0);
10531084
gl.clearColor(0.0, 0.0, 0.0, 1.0);
10541085
gl.depthMask(false);
1055-
gl.blendFuncSeparate(
1056-
WebGL2RenderingContext.ONE,
1057-
WebGL2RenderingContext.ONE,
1058-
WebGL2RenderingContext.ZERO,
1059-
WebGL2RenderingContext.ONE_MINUS_SRC_ALPHA,
1060-
);
10611086
gl.enable(WebGL2RenderingContext.DEPTH_TEST);
10621087
gl.depthFunc(WebGL2RenderingContext.LESS);
10631088
renderContext.emitter = perspectivePanelEmitOIT;
10641089
renderContext.bindFramebuffer();
1065-
continue;
10661090
}
1067-
renderLayer.draw(renderContext, attachment);
1091+
// Draw regular transparent layers
1092+
else if (renderLayer.isTransparent) {
1093+
renderLayer.draw(renderContext, attachment);
1094+
}
10681095
}
10691096
// Copy transparent rendering result back to primary buffer.
10701097
gl.disable(WebGL2RenderingContext.DEPTH_TEST);
@@ -1109,27 +1136,21 @@ export class PerspectivePanel extends RenderedDataPanel {
11091136
/*dppass=*/ WebGL2RenderingContext.REPLACE,
11101137
);
11111138
gl.stencilMask(2);
1139+
if (hasMaxProjection) {
1140+
this.maxProjectionPickCopyHelper.draw(
1141+
this.maxProjectionPickConfiguration.colorBuffers[0].texture /*depth*/,
1142+
this.maxProjectionPickConfiguration.colorBuffers[1].texture /*pick*/,
1143+
);
1144+
}
11121145
for (const [renderLayer, attachment] of visibleLayers) {
1113-
if (!renderLayer.isTransparent || !renderLayer.transparentPickEnabled) {
1146+
if (
1147+
!renderLayer.isTransparent ||
1148+
!renderLayer.transparentPickEnabled ||
1149+
renderLayer.isVolumeRendering
1150+
) {
1151+
// Skip non-transparent layers and transparent layers with transparentPickEnabled=false.
1152+
// Volume rendering layers are handled separately and are combined in a pick buffer
11141153
continue;
1115-
}
1116-
// For max projection layers, can copy over the pick buffer directly.
1117-
if (renderLayer.isVolumeRendering) {
1118-
if (isProjectionLayer(renderLayer as VolumeRenderingRenderLayer)) {
1119-
this.maxProjectionPickCopyHelper.draw(
1120-
this.maxProjectionPickConfiguration.colorBuffers[0]
1121-
.texture /*depth*/,
1122-
this.maxProjectionPickConfiguration.colorBuffers[1]
1123-
.texture /*pick*/,
1124-
);
1125-
}
1126-
// Draw picking for non min/max volume rendering layers
1127-
else {
1128-
// Currently volume rendering layers have no picking support
1129-
// Outside of min/max mode
1130-
continue;
1131-
}
1132-
// other transparent layers are drawn as usual
11331154
} else {
11341155
renderLayer.draw(renderContext, attachment);
11351156
}

src/volume_rendering/volume_render_layer.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,16 +254,18 @@ void emitIntensity(float value) {
254254
float savedDepth = 0.0;
255255
float savedIntensity = 0.0;
256256
vec4 newColor = vec4(0.0);
257+
float userEmittedIntensity = -100.0;
257258
`);
258259
glsl_emitIntensity = `
259260
float convertIntensity(float value) {
260261
return clamp(${glsl_intensityConversion}, 0.0, 1.0);
261262
}
262263
void emitIntensity(float value) {
263-
defaultMaxProjectionIntensity = value;
264+
userEmittedIntensity = value;
264265
}
265266
float getIntensity() {
266-
return convertIntensity(defaultMaxProjectionIntensity);
267+
float intensity = userEmittedIntensity > -100.0 ? userEmittedIntensity : defaultMaxProjectionIntensity;
268+
return convertIntensity(intensity);
267269
}
268270
`;
269271
glsl_rgbaEmit = `
@@ -281,8 +283,9 @@ void emitRGBA(vec4 rgba) {
281283
savedIntensity = intensityChanged ? newIntensity : savedIntensity;
282284
savedDepth = intensityChanged ? depthAtRayPosition : savedDepth;
283285
outputColor = intensityChanged ? newColor : outputColor;
284-
emit(outputColor, savedDepth, savedIntensity);
286+
emit(outputColor, savedDepth, savedIntensity, uPickId);
285287
defaultMaxProjectionIntensity = 0.0;
288+
userEmittedIntensity = -100.0;
286289
`;
287290
}
288291
emitter(builder);
@@ -308,6 +311,7 @@ void emitRGBA(vec4 rgba) {
308311

309312
builder.addUniform("highp float", "uBrightnessFactor");
310313
builder.addUniform("highp float", "uGain");
314+
builder.addUniform("highp uint", "uPickId");
311315
builder.addVarying("highp vec4", "vNormalizedPosition");
312316
builder.addTextureSampler(
313317
"sampler2D",
@@ -365,7 +369,7 @@ vec2 computeUVFromClipSpace(vec4 clipSpacePosition) {
365369
`;
366370
if (isProjectionMode(shaderParametersState.mode)) {
367371
glsl_emitWireframe = `
368-
emit(outputColor, 1.0, uChunkNumber);
372+
emit(outputColor, 1.0, uChunkNumber, uPickId);
369373
`;
370374
}
371375
builder.setFragmentMainFunction(`
@@ -621,6 +625,9 @@ void main() {
621625
gl.enable(WebGL2RenderingContext.CULL_FACE);
622626
gl.cullFace(WebGL2RenderingContext.FRONT);
623627

628+
const pickId = isProjectionMode(this.mode.value)
629+
? renderContext.pickIDs.register(this)
630+
: 0;
624631
forEachVisibleVolumeRenderingChunk(
625632
renderContext.projectionParameters,
626633
this.localPosition.value,
@@ -798,6 +805,7 @@ void main() {
798805
}
799806
newSource = false;
800807
gl.uniform3fv(shader.uniform("uTranslation"), chunkPosition);
808+
gl.uniform1ui(shader.uniform("uPickId"), pickId);
801809
drawBoxes(gl, 1, 1);
802810
++presentCount;
803811
} else {

0 commit comments

Comments
 (0)