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
51 changes: 51 additions & 0 deletions packages/core/examples/volume_rendering/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,57 @@ volumeFolder
.add(volumeLayer, "earlyTerminationAlpha", 0.8, 1.0, 0.01)
.name("Early termination threshold");

const volumeMaxBounds = { x: 82.55, y: 82.26, z: 8.5 };
const boundsControls = {
minX: 0,
minY: 0,
minZ: 0,
maxX: volumeMaxBounds.x,
maxY: volumeMaxBounds.y,
maxZ: volumeMaxBounds.z,
};

function updateVolumeBounds() {
const min = vec3.fromValues(
boundsControls.minX,
boundsControls.minY,
boundsControls.minZ
);
const max = vec3.fromValues(
boundsControls.maxX,
boundsControls.maxY,
boundsControls.maxZ
);
volumeLayer.setClipBounds(min, max);
}

const boundsFolder = volumeFolder.addFolder("Clip Bounds");
boundsFolder
.add(boundsControls, "minX", 0, volumeMaxBounds.x, 0.01)
.name("Min X")
.onChange(updateVolumeBounds);
boundsFolder
.add(boundsControls, "minY", 0, volumeMaxBounds.y, 0.01)
.name("Min Y")
.onChange(updateVolumeBounds);
boundsFolder
.add(boundsControls, "minZ", 0, volumeMaxBounds.z, 0.01)
.name("Min Z")
.onChange(updateVolumeBounds);

boundsFolder
.add(boundsControls, "maxX", 0, volumeMaxBounds.x, 0.01)
.name("Max X")
.onChange(updateVolumeBounds);
boundsFolder
.add(boundsControls, "maxY", 0, volumeMaxBounds.y, 0.01)
.name("Max Y")
.onChange(updateVolumeBounds);
boundsFolder
.add(boundsControls, "maxZ", 0, volumeMaxBounds.z, 0.01)
.name("Max Z")
.onChange(updateVolumeBounds);

const channelsFolder = gui.addFolder("Channels");
channelProps.forEach((config, index) => {
createChannelControls(channelsFolder, config, index);
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/core/renderable_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export abstract class RenderableObject extends Node {
public wireframeEnabled = false;
public wireframeColor = Color.WHITE;
public depthTest = true;
protected visible_ = true;
private readonly textures_: Texture[] = [];
private staleTextures_: Texture[] = [];
private readonly transform_ = new TrsTransform();
Expand All @@ -33,6 +34,10 @@ export abstract class RenderableObject extends Node {
return stale;
}

public get visible() {
return this.visible_;
}

public get geometry() {
return this.geometry_;
}
Expand Down
48 changes: 27 additions & 21 deletions packages/core/src/layers/volume_layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RenderablePool } from "../utilities/renderable_pool";
import { vec3 } from "gl-matrix";
import { sortFrontToBack } from "../math/sort_by_distance";
import { ChannelProps, ChannelsEnabled } from "../objects/textures/channel";
import { Box3 } from "@/math/box3";

export type VolumeLayerProps = {
source: ChunkSource;
Expand All @@ -32,6 +33,7 @@ export class VolumeLayer extends Layer implements ChannelsEnabled {
private sourcePolicy_: ImageSourcePolicy;
private chunkStoreView_?: ChunkStoreView;
private channelProps_?: ChannelProps[];
private clipBounds_?: Box3;

private lastLoadedTime_: number | undefined = undefined;
private lastNumRenderedChannelChunks_: number | undefined = undefined;
Expand Down Expand Up @@ -68,6 +70,28 @@ export class VolumeLayer extends Layer implements ChannelsEnabled {
}
}

public setClipBounds(min?: vec3, max?: vec3) {
if (min === undefined && max === undefined) {
this.clipBounds_ = undefined;
return;
}
const minBound = min
? vec3.clone(min)
: vec3.fromValues(-Infinity, -Infinity, -Infinity);
const maxBound = max
? vec3.clone(max)
: vec3.fromValues(Infinity, Infinity, Infinity);
if (!this.clipBounds_) {
this.clipBounds_ = new Box3(minBound, maxBound);
} else {
this.clipBounds_.min = minBound;
this.clipBounds_.max = maxBound;
}
for (const volume of this.currentVolumes_.values()) {
volume.clipToBounds(this.clipBounds_);
}
}

public setChannelProps(channelProps: ChannelProps[]) {
this.channelProps_ = channelProps;
for (const volume of this.currentVolumes_.values()) {
Expand Down Expand Up @@ -114,6 +138,7 @@ export class VolumeLayer extends Layer implements ChannelsEnabled {
const existing = this.currentVolumes_.get(key);
if (existing) {
for (const chunk of chunks) existing.updateVolumeWithChunk(chunk);
existing.updateWorldScaleAndBoundsFromChunk(chunks[0]);
return existing;
}

Expand All @@ -123,7 +148,7 @@ export class VolumeLayer extends Layer implements ChannelsEnabled {
this.volumeToPoolKey_.set(volume, poolKey);

for (const chunk of chunks) volume.updateVolumeWithChunk(chunk);
this.updateVolumeTransform(volume, chunks[0]);
volume.updateWorldScaleAndBoundsFromChunk(chunks[0]);
return volume;
}

Expand Down Expand Up @@ -168,6 +193,7 @@ export class VolumeLayer extends Layer implements ChannelsEnabled {

for (const [key, chunks] of groupedChunks) {
const volume = this.getOrCreateVolume(key, chunks);
if (this.clipBounds_) volume.clipToBounds(this.clipBounds_);
volume.wireframeEnabled = this.debugShowWireframes;
this.currentVolumes_.set(key, volume);
this.addObject(volume);
Expand All @@ -177,26 +203,6 @@ export class VolumeLayer extends Layer implements ChannelsEnabled {
if (this.state !== "ready") this.setState("ready");
}

private updateVolumeTransform(volume: VolumeRenderable, chunk: Chunk) {
const worldSize = {
x: chunk.shape.x * chunk.scale.x,
y: chunk.shape.y * chunk.scale.y,
z: chunk.shape.z * chunk.scale.z,
};
volume.transform.setScale([worldSize.x, worldSize.y, worldSize.z]);
vec3.set(volume.voxelScale, chunk.scale.x, chunk.scale.y, chunk.scale.z);
const originOffset = {
x: (chunk.shape.x * chunk.scale.x) / 2,
y: (chunk.shape.y * chunk.scale.y) / 2,
z: (chunk.shape.z * chunk.scale.z) / 2,
};
volume.transform.setTranslation([
chunk.offset.x + originOffset.x,
chunk.offset.y + originOffset.y,
chunk.offset.z + originOffset.z,
]);
}

private releaseAndRemoveVolumes(volumes: Iterable<VolumeRenderable>) {
for (const volume of volumes) {
volume.clearLoadedChannels();
Expand Down
95 changes: 95 additions & 0 deletions packages/core/src/objects/renderable/volume_renderable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ import {
import { Texture3D } from "../textures/texture_3d";
import { vec3 } from "gl-matrix";
import type { Chunk } from "../../data/chunk";
import { Box3 } from "@/math/box3";

export class VolumeRenderable extends RenderableObject {
public voxelScale: vec3 = vec3.fromValues(1, 1, 1);
private fullVolumeWorldBounds_: Box3 = new Box3();
private clippedVolumeUVWBounds_: Box3 = new Box3(
vec3.fromValues(0, 0, 0),
vec3.fromValues(1, 1, 1)
);

private channels_: Required<Channel>[];
private channelToTextureIndex_: Map<number, number> = new Map();
Expand Down Expand Up @@ -44,6 +50,85 @@ export class VolumeRenderable extends RenderableObject {
this.loadedChannels_.add(channelIndex);
}

public updateWorldScaleAndBoundsFromChunk(chunk: Chunk) {
vec3.set(this.voxelScale, chunk.scale.x, chunk.scale.y, chunk.scale.z);
this.fullVolumeWorldBounds_.min = vec3.fromValues(
chunk.offset.x,
chunk.offset.y,
chunk.offset.z
);
this.fullVolumeWorldBounds_.max = vec3.fromValues(
chunk.offset.x + chunk.shape.x * chunk.scale.x,
chunk.offset.y + chunk.shape.y * chunk.scale.y,
chunk.offset.z + chunk.shape.z * chunk.scale.z
);
this.transform.setScale(
vec3.subtract(
vec3.create(),
this.fullVolumeWorldBounds_.max,
this.fullVolumeWorldBounds_.min
)
);
this.transform.setTranslation(
vec3.scaleAndAdd(
vec3.create(),
this.fullVolumeWorldBounds_.min,
vec3.subtract(
vec3.create(),
this.fullVolumeWorldBounds_.max,
this.fullVolumeWorldBounds_.min
),
0.5
)
);
}

public clipToBounds(clipBounds: Box3) {
this.visible_ = Box3.intersects(clipBounds, this.fullVolumeWorldBounds_);
if (!this.visible_) return;

const clippedMin = vec3.max(
vec3.create(),
this.fullVolumeWorldBounds_.min,
clipBounds.min
);
const clippedMax = vec3.min(
vec3.create(),
this.fullVolumeWorldBounds_.max,
clipBounds.max
);
const proxySize = vec3.subtract(vec3.create(), clippedMax, clippedMin);
if (proxySize[0] <= 0 || proxySize[1] <= 0 || proxySize[2] <= 0) {
this.visible_ = false;
return;
}
const proxyCenter = vec3.scaleAndAdd(
vec3.create(),
clippedMin,
proxySize,
0.5
);
this.transform.setScale(proxySize);
this.transform.setTranslation(proxyCenter);

const volumeSize = vec3.subtract(
vec3.create(),
this.fullVolumeWorldBounds_.max,
this.fullVolumeWorldBounds_.min
);
this.clippedVolumeUVWBounds_.min = vec3.fromValues(
(clippedMin[0] - this.fullVolumeWorldBounds_.min[0]) / volumeSize[0],
(clippedMin[1] - this.fullVolumeWorldBounds_.min[1]) / volumeSize[1],
(clippedMin[2] - this.fullVolumeWorldBounds_.min[2]) / volumeSize[2]
);

this.clippedVolumeUVWBounds_.max = vec3.fromValues(
(clippedMax[0] - this.fullVolumeWorldBounds_.min[0]) / volumeSize[0],
(clippedMax[1] - this.fullVolumeWorldBounds_.min[1]) / volumeSize[1],
(clippedMax[2] - this.fullVolumeWorldBounds_.min[2]) / volumeSize[2]
);
}

private addChannelTexture(channelIndex: number, chunk: Chunk): void {
const texture = Texture3D.createWithChunk(chunk);
const textureIndex = this.textures.length;
Expand Down Expand Up @@ -129,6 +214,16 @@ export class VolumeRenderable extends RenderableObject {
this.voxelScale[1],
this.voxelScale[2],
],
BoxMinUVW: [
this.clippedVolumeUVWBounds_.min[0],
this.clippedVolumeUVWBounds_.min[1],
this.clippedVolumeUVWBounds_.min[2],
],
BoxMaxUVW: [
this.clippedVolumeUVWBounds_.max[0],
this.clippedVolumeUVWBounds_.max[1],
this.clippedVolumeUVWBounds_.max[2],
],
}
);
}
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/renderers/shaders/volume_frag.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ uniform vec4 Visible;
uniform vec4 ValueOffset;
uniform vec4 ValueScale;
uniform vec3 Color[4];
uniform vec3 BoxMinUVW;
uniform vec3 BoxMaxUVW;

vec2 findBoxIntersectionsAlongRay(vec3 rayOrigin, vec3 rayDir, vec3 boxMin, vec3 boxMax) {
vec3 reciprocalRayDir = 1.0 / rayDir;
Expand Down Expand Up @@ -87,6 +89,7 @@ void main() {
// The ray in model space goes from the camera to the point on the back face
vec3 RayDirModel = normalize(PositionModel - CameraPositionModel);

// Compute ray intersections in normalized model space of clipped proxy
vec2 rayIntersections = findBoxIntersectionsAlongRay(CameraPositionModel, RayDirModel, boundingboxMin, boundingboxMax);
float tEnter = rayIntersections.x;
float tExit = rayIntersections.y;
Expand All @@ -96,10 +99,12 @@ void main() {
return;
}

vec3 entryPoint = CameraPositionModel + RayDirModel * tEnter;
entryPoint = clamp(entryPoint + 0.5, 0.0, 1.0);
vec3 exitPoint = CameraPositionModel + RayDirModel * tExit;
exitPoint = clamp(exitPoint + 0.5, 0.0, 1.0);
vec3 entryPoint = CameraPositionModel + RayDirModel * tEnter + 0.5;
vec3 exitPoint = CameraPositionModel + RayDirModel * tExit + 0.5;

// Map from clipped proxy model space to texture UVW space
entryPoint = clamp(BoxMinUVW + entryPoint * (BoxMaxUVW - BoxMinUVW), 0.0, 1.0);
exitPoint = clamp(BoxMinUVW + exitPoint * (BoxMaxUVW - BoxMinUVW), 0.0, 1.0);

// Step 2 - calculate the number of samples based on the length of the ray
vec3 rayWithinModel = exitPoint - entryPoint;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/renderers/webgl_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export class WebGLRenderer extends Renderer {
this.textures_.disposeTexture(texture);
});

if (!object.programName) return;
if (!object.programName || !object.visible) return;
this.state_.setCullFaceMode(object.cullFaceMode);
this.state_.setDepthTesting(object.depthTest);
this.state_.setDepthMask(object.depthTest);
Expand Down
Loading