Skip to content

Commit

Permalink
feat: Multi component using volumes
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bruyeret committed Oct 4, 2024
1 parent 27890ef commit 4c9103a
Show file tree
Hide file tree
Showing 17 changed files with 2,440 additions and 2,666 deletions.
2 changes: 1 addition & 1 deletion Examples/Rendering/QuadView/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,12 @@ function createVolumeView(renderer, source) {
.reduce((a, b) => a + b, 0)
);
mapper.setSampleDistance(sampleDistance / 2.5);
mapper.setVolumeShadowSamplingDistFactor(5.0);

// Set volume properties
const volProp = vtkVolumeProperty.newInstance();
volProp.setComputeNormalFromOpacity(false);
volProp.setGlobalIlluminationReach(0.0);
volProp.setVolumeShadowSamplingDistFactor(5.0);
volProp.setVolumetricScatteringBlending(0.5);
volProp.setInterpolationTypeToLinear();
volProp.setScalarOpacityUnitDistance(
Expand Down
45 changes: 21 additions & 24 deletions Examples/Volume/VolumeMapperLightAndShadow/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import '@kitware/vtk.js/favicon';

import '@kitware/vtk.js/Rendering/Profiles/Volume';
import '@kitware/vtk.js/Rendering/Profiles/Geometry';
import macro from '@kitware/vtk.js/macros';
import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction';
Expand All @@ -14,6 +13,7 @@ import HttpDataAccessHelper from '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpD
import vtkVolumeController from '@kitware/vtk.js/Interaction/UI/VolumeController';
import vtkBoundingBox from '@kitware/vtk.js/Common/DataModel/BoundingBox';
import vtkFPSMonitor from '@kitware/vtk.js/Interaction/UI/FPSMonitor';
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';

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

const progressCallback = (progressEvent) => {
if (progressEvent.lengthComputable) {
const percent = Math.floor(
(100 * progressEvent.loaded) / progressEvent.total
);
progressContainer.innerHTML = `Loading ${percent}%`;
} else {
progressContainer.innerHTML = macro.formatBytesToProperUnit(
progressEvent.loaded
);
}
};

// ----------------------------------------------------------------------------
// Main function to set up and render volume
// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -94,11 +81,23 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
const source = vtiReader.getOutputData(0);

const actor = vtkVolume.newInstance();
const actorProperty = actor.getProperty();
const actorProperty = actor.getProperty(0);
const mapper = vtkVolumeMapper.newInstance();

actor.setMapper(mapper);
mapper.setInputData(source);
mapper.addInputData(source);

for (let i = 0; i < 0; ++i) {
const otherImageData = vtkImageData.newInstance();
otherImageData.setPointData(source.getPointData());
otherImageData.setDimensions(...source.getDimensions());
otherImageData.setSpacing(...source.getSpacing());
otherImageData.setOrigin(...source.getOrigin());
otherImageData.setDirection(...source.getDirection());
otherImageData.setOrigin(...[120 * (i + 1), 0, 0]);
mapper.addInputData(otherImageData);
actor.setProperty(actorProperty, 1 + i);
}

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

// Add transfer function
const lookupTable = vtkColorTransferFunction.newInstance();
Expand All @@ -135,7 +135,6 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
// Set actor properties
actorProperty.setComputeNormalFromOpacity(false);
actorProperty.setGlobalIlluminationReach(0.0);
actorProperty.setVolumeShadowSamplingDistFactor(5.0);
actorProperty.setVolumetricScatteringBlending(0.0);
actorProperty.setInterpolationTypeToLinear();
actorProperty.setScalarOpacityUnitDistance(
Expand All @@ -161,7 +160,6 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
actorProperty.setSpecularPower(0.0);
actorProperty.setUseLabelOutline(false);
actorProperty.setLabelOutlineThickness(2);
renderer.addActor(actor);

// Control UI for sample distance, transfer function, and shadow on/off
const controllerWidget = vtkVolumeController.newInstance({
Expand Down Expand Up @@ -201,7 +199,7 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
}
function updateSD(e) {
const sd = Number(e.target.value);
actorProperty.setVolumeShadowSamplingDistFactor(sd);
mapper.setVolumeShadowSamplingDistFactor(sd);
renderWindow.render();
}
function updateAT(e) {
Expand Down Expand Up @@ -257,6 +255,9 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
renderer.addActor(actorSphere);
}

// Add the volume actor here to avoid compiling the shader twice
renderer.addActor(actor);

// Camera and first render
renderer.resetCamera();
renderWindow.render();
Expand All @@ -279,11 +280,7 @@ function createVolumeShadowViewer(rootContainer, fileContents) {
// Read volume and render
// ----------------------------------------------------------------------------
HttpDataAccessHelper.fetchBinary(
'https://data.kitware.com/api/v1/item/59de9dc98d777f31ac641dc1/download',
{
progressCallback,
}
`${__BASE_PATH__}/data/volume/head-binary.vti`
).then((binary) => {
myContainer.removeChild(progressContainer);
createVolumeShadowViewer(myContainer, binary);
});
2 changes: 1 addition & 1 deletion Examples/Volume/WebXRChestCTBlendedCVR/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ HttpDataAccessHelper.fetchBinary(fileURL).then((fileContents) => {
actor.getProperty().setSpecular(0.0);
actor.getProperty().setGlobalIlluminationReach(0.1);
actor.getProperty().setVolumetricScatteringBlending(0.5);
actor.getProperty().setVolumeShadowSamplingDistFactor(1.0);
mapper.setVolumeShadowSamplingDistFactor(1.0);
mapper.setAutoAdjustSampleDistances(false);

// Set up rendering
Expand Down
2 changes: 1 addition & 1 deletion Examples/Volume/WebXRHeadFullVolumeCVR/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ HttpDataAccessHelper.fetchBinary(fileURL).then((fileContents) => {
actor.getProperty().setSpecular(0.0);
actor.getProperty().setGlobalIlluminationReach(1.0);
actor.getProperty().setVolumetricScatteringBlending(1.0);
actor.getProperty().setVolumeShadowSamplingDistFactor(1.0);
mapper.setVolumeShadowSamplingDistFactor(1.0);
mapper.setAutoAdjustSampleDistances(false);

// Set up rendering
Expand Down
2 changes: 1 addition & 1 deletion Examples/Volume/WebXRHeadGradientCVR/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ HttpDataAccessHelper.fetchBinary(fileURL).then((fileContents) => {
actor.getProperty().setShade(true);
actor.getProperty().setGlobalIlluminationReach(0.0);
actor.getProperty().setVolumetricScatteringBlending(0.0);
actor.getProperty().setVolumeShadowSamplingDistFactor(1.0);
mapper.setVolumeShadowSamplingDistFactor(1.0);
mapper.setAutoAdjustSampleDistances(false);

// Set up rendering
Expand Down
20 changes: 13 additions & 7 deletions Sources/Rendering/Core/VolumeMapper/Constants.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// Don't use the constants from this file
export declare enum BlendMode {
COMPOSITE_BLEND = 0,
MAXIMUM_INTENSITY_BLEND = 1,
MINIMUM_INTENSITY_BLEND = 2,
AVERAGE_INTENSITY_BLEND = 3,
ADDITIVE_INTENSITY_BLEND = 4,
RADON_TRANSFORM_BLEND = 5,
LABELMAP_EDGE_PROJECTION_BLEND = 6,
}

// Prefer constants from volume property:
export {
default,
BlendMode,
FilterMode,
} from '../VolumeProperty/Constants';
declare const _default: {
BlendMode: typeof BlendMode;
};
export default _default;
24 changes: 11 additions & 13 deletions Sources/Rendering/Core/VolumeMapper/Constants.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
// Don't use the constants from this file

// Prefer constants from volume property:
import Constants, {
BlendMode as OriginalBlendMode,
FilterMode as OriginalFilterMode,
} from 'vtk.js/Sources/Rendering/Core/VolumeProperty/Constants';

export const BlendMode = OriginalBlendMode;

export const FilterMode = OriginalFilterMode;

export default Constants;
export const BlendMode = {
COMPOSITE_BLEND: 0,
MAXIMUM_INTENSITY_BLEND: 1,
MINIMUM_INTENSITY_BLEND: 2,
AVERAGE_INTENSITY_BLEND: 3,
ADDITIVE_INTENSITY_BLEND: 4,
RADON_TRANSFORM_BLEND: 5,
LABELMAP_EDGE_PROJECTION_BLEND: 6,
};

export default { BlendMode };
12 changes: 2 additions & 10 deletions Sources/Rendering/Core/VolumeMapper/example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,6 @@ if (light.getPositional()) {
renderer.addActor(lca);
}

{
const optionElem = document.createElement('option');
optionElem.label = 'Default';
optionElem.value = '';
presetSelectElem.appendChild(optionElem);
presetSelectElem.value = optionElem.value;
}

Object.keys(ColorMixPreset).forEach((key) => {
if (key === 'CUSTOM') {
// Don't enable custom mode
Expand All @@ -167,7 +159,7 @@ Object.keys(ColorMixPreset).forEach((key) => {
});

const setColorMixPreset = (presetKey) => {
const preset = presetKey ? ColorMixPreset[presetKey] : null;
const preset = ColorMixPreset[presetKey];
actor.getProperty().setColorMixPreset(preset);
presetSelectElem.value = presetKey;
};
Expand Down Expand Up @@ -195,7 +187,7 @@ updateForceNearestElem(1);
volumeSelectElem.addEventListener('change', () => {
const { comp, data } = volumeOptions[volumeSelectElem.value];
if (comp === 1) {
setColorMixPreset('');
setColorMixPreset('DEFAULT');
presetSelectElem.style.display = 'none';
} else {
presetSelectElem.style.display = 'block';
Expand Down
13 changes: 13 additions & 0 deletions Sources/Rendering/Core/VolumeMapper/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export interface vtkVolumeMapper extends vtkAbstractMapper3D {
*/
getSampleDistance(): number;

/**
* Get the multipler for volume shadow sampling distance
* @default 5.0
*/
getVolumeShadowSamplingDistFactor(): number;

/**
* Sampling distance in the XY image dimensions.
* Default value of 1 meaning 1 ray cast per pixel. If set to 0.5, 4 rays will be cast per pixel.
Expand Down Expand Up @@ -113,6 +119,13 @@ export interface vtkVolumeMapper extends vtkAbstractMapper3D {
*/
setSampleDistance(sampleDistance: number): boolean;

/**
* Set the multipler for volume shadow sampling distance. This function is only effective when volumeScatterBlendCoef is greater than 0.
* For VSSampleDistanceFactor >= 1.0, volume shadow sampling distance = VSSampleDistanceFactor * SampleDistance.
* @param VSSampleDistanceFactor
*/
setVolumeShadowSamplingDistFactor(VSSampleDistanceFactor: number): void;

/**
*
* @param imageSampleDistance
Expand Down
17 changes: 8 additions & 9 deletions Sources/Rendering/Core/VolumeMapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ const methodNamesMovedToVolumeProperties = [
'getLAOKernelSize',
'getLocalAmbientOcclusion',
'getPreferSizeOverAccuracy',
'getVolumeShadowSamplingDistFactor',
'getVolumetricScatteringBlending',
'setAnisotropy',
'setAverageIPScalarRange',
Expand All @@ -61,7 +60,6 @@ const methodNamesMovedToVolumeProperties = [
'setLAOKernelSize',
'setLocalAmbientOcclusion',
'setPreferSizeOverAccuracy',
'setVolumeShadowSamplingDistFactor',
'setVolumetricScatteringBlending',
];

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

const superClass = { ...publicAPI };

publicAPI.getBounds = () => {
model.bounds = [...vtkBoundingBox.INIT_BOUNDS];
if (!model.static) {
publicAPI.update();
}
for (let inputIndex = 0; inputIndex < model.numberOfInputs; inputIndex++) {
const input = publicAPI.getInputData(inputIndex);
if (input) {
vtkBoundingBox.addBounds(model.bounds, input.getBounds());
}
}
model.bounds = [...publicAPI.getInputData().getBounds()];
return model.bounds;
};

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

publicAPI.setVolumeShadowSamplingDistFactor = (vsdf) =>
superClass.setVolumeShadowSamplingDistFactor(vsdf >= 1.0 ? vsdf : 1.0);

// Instead of a "undefined is not a function" error, give more context and advice for these widely used methods
methodNamesMovedToVolumeProperties.forEach((removedMethodName) => {
const removedMethod = () => {
Expand Down Expand Up @@ -149,6 +146,7 @@ const DEFAULT_VALUES = {
initialInteractionScale: 1.0,
interactionSampleDistanceFactor: 1.0,
blendMode: BlendMode.COMPOSITE_BLEND,
volumeShadowSamplingDistFactor: 5.0,
};

// ----------------------------------------------------------------------------
Expand All @@ -166,6 +164,7 @@ export function extend(publicAPI, model, initialValues = {}) {
'initialInteractionScale',
'interactionSampleDistanceFactor',
'blendMode',
'volumeShadowSamplingDistFactor',
]);

macro.event(publicAPI, model, 'lightingActivated');
Expand Down
19 changes: 11 additions & 8 deletions Sources/Rendering/Core/VolumeMapper/test/testColorMix.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,20 @@ test('Test Volume Rendering: custom shader code', async (t) => {
originalValue: '//VTK::CustomColorMix',
replaceFirst: false,
replacementValue: `
if (pwfValue1 > 0.5) {
float opacity1 = getOpacityFromTexture(tValue[1], 1, volume.transferFunctionsSampleHeight[1]);
if (opacity1 > 0.5) {
return vec4(0.0, 1.0, 1.0, 0.1);
} else {
mat4 normalMat = computeMat4Normal(posIS, tValue, tstep);
float opacity0 = pwfValue0;
#ifdef vtkGradientOpacityOn
float gof0 = computeGradientOpacityFactor(normalMat[0].a, goscale0, goshift0, gomin0, gomax0);
opacity0 *= gof0;
vec3 posIS = VCtoIS(posVC);
mat4 normalMat = computeMat4Normal(posIS, tValue);
float opacity0 = getOpacityFromTexture(tValue[0], 0, volume.transferFunctionsSampleHeight[0]);
vec3 color0 = getColorFromTexture(tValue[0], 0, volume.transferFunctionsSampleHeight[0]);
#if defined(EnabledGradientOpacity)
float gradientOpacity = computeGradientOpacityFactor(normal0.a, 0);
opacity0 *= gradientOpacity;
#endif
tColor0 = applyAllLightning(tColor0, opacity0, posIS, normalMat[0]);
return vec4(tColor0, opacity0);
color0 = applyAllLightning(color0, opacity0, posVC, normalMat[0]);
return vec4(color0, opacity0);
}
`,
replaceAll: false,
Expand Down
17 changes: 4 additions & 13 deletions Sources/Rendering/Core/VolumeProperty/Constants.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ export declare enum OpacityMode {
}

export declare enum ColorMixPreset {
// Add a `//VTK::CustomColorMix` tag to the Fragment shader
// See usage in file `testColorMix` and in function `setColorMixPreset`
CUSTOM = 0,
DEFAULT = 0,

// Two components preset
// Out color: sum of colors weighted by opacity
Expand All @@ -23,16 +21,10 @@ export declare enum ColorMixPreset {
// Out color: color of the first component, colorized by second component with an intensity that is the second component's opacity
// Out opacity: opacity of the first component
COLORIZE = 2,
}

export declare enum BlendMode {
COMPOSITE_BLEND = 0,
MAXIMUM_INTENSITY_BLEND = 1,
MINIMUM_INTENSITY_BLEND = 2,
AVERAGE_INTENSITY_BLEND = 3,
ADDITIVE_INTENSITY_BLEND = 4,
RADON_TRANSFORM_BLEND = 5,
LABELMAP_EDGE_PROJECTION_BLEND = 6,
// Add a `//VTK::CustomColorMix` tag to the Fragment shader
// See usage in file `testColorMix` and in function `setColorMixPreset`
CUSTOM = 3,
}

export declare enum FilterMode {
Expand All @@ -45,7 +37,6 @@ declare const _default: {
InterpolationType: typeof InterpolationType;
OpacityMode: typeof OpacityMode;
ColorMixPreset: typeof ColorMixPreset;
BlendMode: typeof BlendMode;
FilterMode: typeof FilterMode;
};
export default _default;
Loading

0 comments on commit 4c9103a

Please sign in to comment.