Skip to content

Commit 7dc38ac

Browse files
committed
feat: Multi component using volumes
Support multiple images as input Uniform styling of fragment shader Fix resource sharing core object for volume mapper scalars Many shader bugs are fixed, often linked to wrong coordinate system. Fix normal computation in shader. BREAKING CHANGE: the volume mapper fragment shader is very different. This can cause shader replacements to break. refactor: Use the right matrices in volume FS shader
1 parent 27890ef commit 7dc38ac

File tree

17 files changed

+2449
-2666
lines changed

17 files changed

+2449
-2666
lines changed

Examples/Rendering/QuadView/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,12 @@ function createVolumeView(renderer, source) {
158158
.reduce((a, b) => a + b, 0)
159159
);
160160
mapper.setSampleDistance(sampleDistance / 2.5);
161+
mapper.setVolumeShadowSamplingDistFactor(5.0);
161162

162163
// Set volume properties
163164
const volProp = vtkVolumeProperty.newInstance();
164165
volProp.setComputeNormalFromOpacity(false);
165166
volProp.setGlobalIlluminationReach(0.0);
166-
volProp.setVolumeShadowSamplingDistFactor(5.0);
167167
volProp.setVolumetricScatteringBlending(0.5);
168168
volProp.setInterpolationTypeToLinear();
169169
volProp.setScalarOpacityUnitDistance(

Examples/Volume/VolumeMapperLightAndShadow/index.js

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import '@kitware/vtk.js/favicon';
22

33
import '@kitware/vtk.js/Rendering/Profiles/Volume';
44
import '@kitware/vtk.js/Rendering/Profiles/Geometry';
5-
import macro from '@kitware/vtk.js/macros';
65
import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
76
import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
87
import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction';
@@ -14,6 +13,7 @@ import HttpDataAccessHelper from '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpD
1413
import vtkVolumeController from '@kitware/vtk.js/Interaction/UI/VolumeController';
1514
import vtkBoundingBox from '@kitware/vtk.js/Common/DataModel/BoundingBox';
1615
import vtkFPSMonitor from '@kitware/vtk.js/Interaction/UI/FPSMonitor';
16+
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
1717

1818
import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
1919
import vtkSphereSource from '@kitware/vtk.js/Filters/Sources/SphereSource';
@@ -31,19 +31,6 @@ const fpsMonitor = vtkFPSMonitor.newInstance();
3131
const progressContainer = document.createElement('div');
3232
myContainer.appendChild(progressContainer);
3333

34-
const progressCallback = (progressEvent) => {
35-
if (progressEvent.lengthComputable) {
36-
const percent = Math.floor(
37-
(100 * progressEvent.loaded) / progressEvent.total
38-
);
39-
progressContainer.innerHTML = `Loading ${percent}%`;
40-
} else {
41-
progressContainer.innerHTML = macro.formatBytesToProperUnit(
42-
progressEvent.loaded
43-
);
44-
}
45-
};
46-
4734
// ----------------------------------------------------------------------------
4835
// Main function to set up and render volume
4936
// ----------------------------------------------------------------------------
@@ -94,11 +81,23 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
9481
const source = vtiReader.getOutputData(0);
9582

9683
const actor = vtkVolume.newInstance();
97-
const actorProperty = actor.getProperty();
84+
const actorProperty = actor.getProperty(0);
9885
const mapper = vtkVolumeMapper.newInstance();
9986

10087
actor.setMapper(mapper);
101-
mapper.setInputData(source);
88+
mapper.addInputData(source);
89+
90+
for (let i = 0; i < 0; ++i) {
91+
const otherImageData = vtkImageData.newInstance();
92+
otherImageData.setPointData(source.getPointData());
93+
otherImageData.setDimensions(...source.getDimensions());
94+
otherImageData.setSpacing(...source.getSpacing());
95+
otherImageData.setOrigin(...source.getOrigin());
96+
otherImageData.setDirection(...source.getDirection());
97+
otherImageData.setOrigin(...[120 * (i + 1), 0, 0]);
98+
mapper.addInputData(otherImageData);
99+
actor.setProperty(actorProperty, 1 + i);
100+
}
102101

103102
// Add one positional light
104103
const bounds = actor.getBounds();
@@ -125,6 +124,7 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
125124
.reduce((a, b) => a + b, 0)
126125
);
127126
mapper.setSampleDistance(sampleDistance / 2.5);
127+
mapper.setVolumeShadowSamplingDistFactor(5.0);
128128

129129
// Add transfer function
130130
const lookupTable = vtkColorTransferFunction.newInstance();
@@ -135,7 +135,6 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
135135
// Set actor properties
136136
actorProperty.setComputeNormalFromOpacity(false);
137137
actorProperty.setGlobalIlluminationReach(0.0);
138-
actorProperty.setVolumeShadowSamplingDistFactor(5.0);
139138
actorProperty.setVolumetricScatteringBlending(0.0);
140139
actorProperty.setInterpolationTypeToLinear();
141140
actorProperty.setScalarOpacityUnitDistance(
@@ -161,7 +160,6 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
161160
actorProperty.setSpecularPower(0.0);
162161
actorProperty.setUseLabelOutline(false);
163162
actorProperty.setLabelOutlineThickness(2);
164-
renderer.addActor(actor);
165163

166164
// Control UI for sample distance, transfer function, and shadow on/off
167165
const controllerWidget = vtkVolumeController.newInstance({
@@ -201,7 +199,7 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
201199
}
202200
function updateSD(e) {
203201
const sd = Number(e.target.value);
204-
actorProperty.setVolumeShadowSamplingDistFactor(sd);
202+
mapper.setVolumeShadowSamplingDistFactor(sd);
205203
renderWindow.render();
206204
}
207205
function updateAT(e) {
@@ -257,6 +255,9 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
257255
renderer.addActor(actorSphere);
258256
}
259257

258+
// Add the volume actor here to avoid compiling the shader twice
259+
renderer.addActor(actor);
260+
260261
// Camera and first render
261262
renderer.resetCamera();
262263
renderWindow.render();
@@ -279,11 +280,7 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
279280
// Read volume and render
280281
// ----------------------------------------------------------------------------
281282
HttpDataAccessHelper.fetchBinary(
282-
'https://data.kitware.com/api/v1/item/59de9dc98d777f31ac641dc1/download',
283-
{
284-
progressCallback,
285-
}
283+
`${__BASE_PATH__}/data/volume/head-binary.vti`
286284
).then((binary) => {
287-
myContainer.removeChild(progressContainer);
288285
createVolumeShadowViewer(myContainer, binary);
289286
});

Examples/Volume/WebXRChestCTBlendedCVR/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ HttpDataAccessHelper.fetchBinary(fileURL).then((fileContents) => {
109109
actor.getProperty().setSpecular(0.0);
110110
actor.getProperty().setGlobalIlluminationReach(0.1);
111111
actor.getProperty().setVolumetricScatteringBlending(0.5);
112-
actor.getProperty().setVolumeShadowSamplingDistFactor(1.0);
112+
mapper.setVolumeShadowSamplingDistFactor(1.0);
113113
mapper.setAutoAdjustSampleDistances(false);
114114

115115
// Set up rendering

Examples/Volume/WebXRHeadFullVolumeCVR/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ HttpDataAccessHelper.fetchBinary(fileURL).then((fileContents) => {
141141
actor.getProperty().setSpecular(0.0);
142142
actor.getProperty().setGlobalIlluminationReach(1.0);
143143
actor.getProperty().setVolumetricScatteringBlending(1.0);
144-
actor.getProperty().setVolumeShadowSamplingDistFactor(1.0);
144+
mapper.setVolumeShadowSamplingDistFactor(1.0);
145145
mapper.setAutoAdjustSampleDistances(false);
146146

147147
// Set up rendering

Examples/Volume/WebXRHeadGradientCVR/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ HttpDataAccessHelper.fetchBinary(fileURL).then((fileContents) => {
138138
actor.getProperty().setShade(true);
139139
actor.getProperty().setGlobalIlluminationReach(0.0);
140140
actor.getProperty().setVolumetricScatteringBlending(0.0);
141-
actor.getProperty().setVolumeShadowSamplingDistFactor(1.0);
141+
mapper.setVolumeShadowSamplingDistFactor(1.0);
142142
mapper.setAutoAdjustSampleDistances(false);
143143

144144
// Set up rendering
Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
// Don't use the constants from this file
1+
export declare enum BlendMode {
2+
COMPOSITE_BLEND = 0,
3+
MAXIMUM_INTENSITY_BLEND = 1,
4+
MINIMUM_INTENSITY_BLEND = 2,
5+
AVERAGE_INTENSITY_BLEND = 3,
6+
ADDITIVE_INTENSITY_BLEND = 4,
7+
RADON_TRANSFORM_BLEND = 5,
8+
LABELMAP_EDGE_PROJECTION_BLEND = 6,
9+
}
210

3-
// Prefer constants from volume property:
4-
export {
5-
default,
6-
BlendMode,
7-
FilterMode,
8-
} from '../VolumeProperty/Constants';
11+
declare const _default: {
12+
BlendMode: typeof BlendMode;
13+
};
14+
export default _default;
Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
// Don't use the constants from this file
2-
3-
// Prefer constants from volume property:
4-
import Constants, {
5-
BlendMode as OriginalBlendMode,
6-
FilterMode as OriginalFilterMode,
7-
} from 'vtk.js/Sources/Rendering/Core/VolumeProperty/Constants';
8-
9-
export const BlendMode = OriginalBlendMode;
10-
11-
export const FilterMode = OriginalFilterMode;
12-
13-
export default Constants;
1+
export const BlendMode = {
2+
COMPOSITE_BLEND: 0,
3+
MAXIMUM_INTENSITY_BLEND: 1,
4+
MINIMUM_INTENSITY_BLEND: 2,
5+
AVERAGE_INTENSITY_BLEND: 3,
6+
ADDITIVE_INTENSITY_BLEND: 4,
7+
RADON_TRANSFORM_BLEND: 5,
8+
LABELMAP_EDGE_PROJECTION_BLEND: 6,
9+
};
10+
11+
export default { BlendMode };

Sources/Rendering/Core/VolumeMapper/example/index.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,6 @@ if (light.getPositional()) {
145145
renderer.addActor(lca);
146146
}
147147

148-
{
149-
const optionElem = document.createElement('option');
150-
optionElem.label = 'Default';
151-
optionElem.value = '';
152-
presetSelectElem.appendChild(optionElem);
153-
presetSelectElem.value = optionElem.value;
154-
}
155-
156148
Object.keys(ColorMixPreset).forEach((key) => {
157149
if (key === 'CUSTOM') {
158150
// Don't enable custom mode
@@ -167,7 +159,7 @@ Object.keys(ColorMixPreset).forEach((key) => {
167159
});
168160

169161
const setColorMixPreset = (presetKey) => {
170-
const preset = presetKey ? ColorMixPreset[presetKey] : null;
162+
const preset = ColorMixPreset[presetKey];
171163
actor.getProperty().setColorMixPreset(preset);
172164
presetSelectElem.value = presetKey;
173165
};
@@ -195,7 +187,7 @@ updateForceNearestElem(1);
195187
volumeSelectElem.addEventListener('change', () => {
196188
const { comp, data } = volumeOptions[volumeSelectElem.value];
197189
if (comp === 1) {
198-
setColorMixPreset('');
190+
setColorMixPreset('DEFAULT');
199191
presetSelectElem.style.display = 'none';
200192
} else {
201193
presetSelectElem.style.display = 'block';

Sources/Rendering/Core/VolumeMapper/index.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ export interface vtkVolumeMapper extends vtkAbstractMapper3D {
4141
*/
4242
getSampleDistance(): number;
4343

44+
/**
45+
* Get the multipler for volume shadow sampling distance
46+
* @default 5.0
47+
*/
48+
getVolumeShadowSamplingDistFactor(): number;
49+
4450
/**
4551
* Sampling distance in the XY image dimensions.
4652
* Default value of 1 meaning 1 ray cast per pixel. If set to 0.5, 4 rays will be cast per pixel.
@@ -113,6 +119,13 @@ export interface vtkVolumeMapper extends vtkAbstractMapper3D {
113119
*/
114120
setSampleDistance(sampleDistance: number): boolean;
115121

122+
/**
123+
* Set the multipler for volume shadow sampling distance. This function is only effective when volumeScatterBlendCoef is greater than 0.
124+
* For VSSampleDistanceFactor >= 1.0, volume shadow sampling distance = VSSampleDistanceFactor * SampleDistance.
125+
* @param VSSampleDistanceFactor
126+
*/
127+
setVolumeShadowSamplingDistFactor(VSSampleDistanceFactor: number): void;
128+
116129
/**
117130
*
118131
* @param imageSampleDistance

Sources/Rendering/Core/VolumeMapper/index.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ const methodNamesMovedToVolumeProperties = [
4545
'getLAOKernelSize',
4646
'getLocalAmbientOcclusion',
4747
'getPreferSizeOverAccuracy',
48-
'getVolumeShadowSamplingDistFactor',
4948
'getVolumetricScatteringBlending',
5049
'setAnisotropy',
5150
'setAverageIPScalarRange',
@@ -61,7 +60,6 @@ const methodNamesMovedToVolumeProperties = [
6160
'setLAOKernelSize',
6261
'setLocalAmbientOcclusion',
6362
'setPreferSizeOverAccuracy',
64-
'setVolumeShadowSamplingDistFactor',
6563
'setVolumetricScatteringBlending',
6664
];
6765

@@ -81,17 +79,13 @@ function vtkVolumeMapper(publicAPI, model) {
8179
// Set our className
8280
model.classHierarchy.push('vtkVolumeMapper');
8381

82+
const superClass = { ...publicAPI };
83+
8484
publicAPI.getBounds = () => {
85-
model.bounds = [...vtkBoundingBox.INIT_BOUNDS];
8685
if (!model.static) {
8786
publicAPI.update();
8887
}
89-
for (let inputIndex = 0; inputIndex < model.numberOfInputs; inputIndex++) {
90-
const input = publicAPI.getInputData(inputIndex);
91-
if (input) {
92-
vtkBoundingBox.addBounds(model.bounds, input.getBounds());
93-
}
94-
}
88+
model.bounds = [...publicAPI.getInputData().getBounds()];
9589
return model.bounds;
9690
};
9791

@@ -122,6 +116,9 @@ function vtkVolumeMapper(publicAPI, model) {
122116
publicAPI.getBlendModeAsString = () =>
123117
macro.enumToString(BlendMode, model.blendMode);
124118

119+
publicAPI.setVolumeShadowSamplingDistFactor = (vsdf) =>
120+
superClass.setVolumeShadowSamplingDistFactor(vsdf >= 1.0 ? vsdf : 1.0);
121+
125122
// Instead of a "undefined is not a function" error, give more context and advice for these widely used methods
126123
methodNamesMovedToVolumeProperties.forEach((removedMethodName) => {
127124
const removedMethod = () => {
@@ -149,6 +146,7 @@ const DEFAULT_VALUES = {
149146
initialInteractionScale: 1.0,
150147
interactionSampleDistanceFactor: 1.0,
151148
blendMode: BlendMode.COMPOSITE_BLEND,
149+
volumeShadowSamplingDistFactor: 5.0,
152150
};
153151

154152
// ----------------------------------------------------------------------------
@@ -166,6 +164,7 @@ export function extend(publicAPI, model, initialValues = {}) {
166164
'initialInteractionScale',
167165
'interactionSampleDistanceFactor',
168166
'blendMode',
167+
'volumeShadowSamplingDistFactor',
169168
]);
170169

171170
macro.event(publicAPI, model, 'lightingActivated');

0 commit comments

Comments
 (0)