Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ The "frontend" UI thread handles user actions and rendering, while the "backend"
- [Compressed segmentation format](src/sliceview/compressed_segmentation/README.md)
- [Data chunk management](src/chunk_manager/)
- [On-GPU hashing](src/gpu_hash/)
- [Volume rendering](src/volume_rendering/)
- [Screen-space ambient occlusion (SSAO) rendering](src/ssao/)

# Building

Expand Down
5 changes: 5 additions & 0 deletions python/neuroglancer/viewer_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -1916,6 +1916,11 @@ class ViewerState(JsonObjectWrapper):
"showAxisLines", optional(bool, True)
)
wire_frame = wireFrame = wrapped_property("wireFrame", optional(bool, False))
ssao = wrapped_property("ssao", optional(bool, False))
ssao_intensity = ssaoIntensity = wrapped_property(
"ssaoIntensity", optional(float, 1.8)
)
ssao_radius = ssaoRadius = wrapped_property("ssaoRadius", optional(float, 0.05))
enable_adaptive_downsampling = enableAdaptiveDownsampling = wrapped_property(
"enableAdaptiveDownsampling", optional(bool, True)
)
Expand Down
6 changes: 6 additions & 0 deletions src/data_panel_layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ export interface ViewerUIState
crossSectionBackgroundColor: TrackableRGB;
perspectiveViewBackgroundColor: TrackableRGB;
hideCrossSectionBackground3D: TrackableBoolean;
ssao: WatchableValueInterface<boolean>;
ssaoIntensity: WatchableValueInterface<number>;
ssaoRadius: WatchableValueInterface<number>;
pickRadius: TrackableValue<number>;
}

Expand Down Expand Up @@ -184,6 +187,9 @@ export function getCommonViewerState(viewer: ViewerUIState) {
visibility: viewer.visibility,
scaleBarOptions: viewer.scaleBarOptions,
hideCrossSectionBackground3D: viewer.hideCrossSectionBackground3D,
ssao: viewer.ssao,
ssaoIntensity: viewer.ssaoIntensity,
ssaoRadius: viewer.ssaoRadius,
pickRadius: viewer.pickRadius,
};
}
Expand Down
12 changes: 12 additions & 0 deletions src/layer_group_viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ export interface LayerGroupViewerState {
crossSectionBackgroundColor: TrackableRGB;
perspectiveViewBackgroundColor: TrackableRGB;
hideCrossSectionBackground3D: TrackableBoolean;
ssao: WatchableValueInterface<boolean>;
ssaoIntensity: WatchableValueInterface<number>;
ssaoRadius: WatchableValueInterface<number>;
pickRadius: TrackableValue<number>;
}

Expand Down Expand Up @@ -391,6 +394,15 @@ export class LayerGroupViewer extends RefCounted {
get scaleBarOptions() {
return this.viewerState.scaleBarOptions;
}
get ssao() {
return this.viewerState.ssao;
}
get ssaoIntensity() {
return this.viewerState.ssaoIntensity;
}
get ssaoRadius() {
return this.viewerState.ssaoRadius;
}
layerPanel: LayerBar | undefined;
layout: DataPanelLayoutContainer;
toolBinder: LocalToolBinder<this>;
Expand Down
3 changes: 3 additions & 0 deletions src/layer_groups_layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,9 @@ function getCommonViewerState(viewer: Viewer) {
crossSectionBackgroundColor: viewer.crossSectionBackgroundColor,
perspectiveViewBackgroundColor: viewer.perspectiveViewBackgroundColor,
hideCrossSectionBackground3D: viewer.hideCrossSectionBackground3D,
ssao: viewer.ssao,
ssaoIntensity: viewer.ssaoIntensity,
ssaoRadius: viewer.ssaoRadius,
pickRadius: viewer.uiConfiguration.pickRadius,
};
}
Expand Down
53 changes: 51 additions & 2 deletions src/mesh/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
validateOctree,
} from "#src/mesh/multiscale.js";
import type { PerspectivePanel } from "#src/perspective_view/panel.js";
import { perspectivePanelEmitWithNormals } from "#src/perspective_view/panel.js";
import type {
PerspectiveViewReadyRenderContext,
PerspectiveViewRenderContext,
Expand Down Expand Up @@ -294,6 +295,16 @@ export class MeshShaderManager {
mat3.invert(tempMat3, tempMat3);
mat3.transpose(tempMat3, tempMat3);
gl.uniformMatrix3fv(shader.uniform("uNormalMatrix"), false, tempMat3);
if (renderContext.emitNormals) {
// Combined model→view normal transform for SSAO. Uses `modelMat`
// directly, bypassing `uNormalMatrix`'s `canonicalVoxelFactors` scaling,
// which gives wrong oblique normals on anisotropic data.
mat4.multiply(tempMat4, projectionParameters.viewMatrix, modelMat);
mat4.invert(tempMat4, tempMat4);
mat3FromMat4(tempMat3, tempMat4);
mat3.transpose(tempMat3, tempMat3);
gl.uniformMatrix3fv(shader.uniform("uViewNormalMatrix"), false, tempMat3);
}
}

drawFragmentHelper(
Expand Down Expand Up @@ -358,7 +369,8 @@ export class MeshShaderManager {
return parameterizedEmitterDependentShaderGetter(layer, layer.gl, {
memoizeKey: `mesh/MeshShaderManager/${this.fragmentRelativeVertices}/${this.vertexPositionFormat}`,
parameters: silhouetteRenderingEnabled,
defineShader: (builder, silhouetteRenderingEnabled) => {
defineShader: (builder, silhouetteRenderingEnabled, _, emitter) => {
const emitNormals = emitter === perspectivePanelEmitWithNormals;
this.vertexPositionHandler.defineShader(builder);
builder.addAttribute("highp vec2", "aVertexNormal");
builder.addVarying("highp vec4", "vColor");
Expand All @@ -367,6 +379,12 @@ export class MeshShaderManager {
builder.addUniform("highp mat3", "uNormalMatrix");
builder.addUniform("highp mat4", "uModelViewProjection");
builder.addUniform("highp uint", "uPickID");
if (emitNormals) {
builder.addVarying("highp vec3", "vViewNormal");
builder.addUniform("highp mat3", "uViewNormalMatrix");
// Hover-highlighted segments opt out of AO by writing the zero-RGB sentinel to `vViewNormal`; see below.
builder.addUniform("highp float", "uHighlighted");
}
if (silhouetteRenderingEnabled) {
builder.addUniform("highp float", "uSilhouettePower");
}
Expand Down Expand Up @@ -395,13 +413,26 @@ float absCosAngle = abs(dot(normal, uLightDirection.xyz));
float lightingFactor = absCosAngle + uLightDirection.w;
vColor = vec4(lightingFactor * uColor.rgb, uColor.a);
`;
if (emitNormals) {
// Bypass `normal` (post-uNormalMatrix); `uViewNormalMatrix`
// already encodes the full model→view transform. Multiply by
// (1 - uHighlighted) so hovered segments emit the zero sentinel.
vertexMain += `
vViewNormal = (1.0 - uHighlighted) *
normalize(uViewNormalMatrix * (normalMultiplier * origNormal));
`;
}
if (silhouetteRenderingEnabled) {
vertexMain += `
vColor *= pow(1.0 - absCosAngle, uSilhouettePower);
`;
}
builder.setVertexMain(vertexMain);
builder.setFragmentMain("emit(vColor, uPickID);");
builder.setFragmentMain(
emitNormals
? "emit(vColor, uPickID, vViewNormal);"
: "emit(vColor, uPickID);",
);
},
});
}
Expand Down Expand Up @@ -516,6 +547,15 @@ export class MeshLayer extends PerspectiveViewRenderLayer<ThreeDimensionalRender
++presentChunks;
if (renderContext.emitColor) {
meshShaderManager.setColor(gl, shader, color!);
if (renderContext.emitNormals) {
const isHighlighted =
displayState.hoverHighlight.value &&
displayState.segmentSelectionState.isSelected(objectId);
gl.uniform1f(
shader.uniform("uHighlighted"),
isHighlighted ? 1.0 : 0.0,
);
}
}
if (renderContext.emitPickID) {
meshShaderManager.setPickID(gl, shader, pickIndex!);
Expand Down Expand Up @@ -896,6 +936,15 @@ export class MultiscaleMeshLayer extends PerspectiveViewRenderLayer<ThreeDimensi
}
if (renderContext.emitColor) {
meshShaderManager.setColor(gl, shader, color!);
if (renderContext.emitNormals) {
const isHighlighted =
displayState.hoverHighlight.value &&
displayState.segmentSelectionState.isSelected(objectId);
gl.uniform1f(
shader.uniform("uHighlighted"),
isHighlighted ? 1.0 : 0.0,
);
}
}
if (renderContext.emitPickID) {
meshShaderManager.setPickID(gl, shader, pickIndex!);
Expand Down
Loading
Loading