-
Notifications
You must be signed in to change notification settings - Fork 10
convert raymarch to use 3d texture instead of atlas. #370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -5,24 +5,27 @@ import { | |||
| BufferAttribute, | ||||
| BufferGeometry, | ||||
| Color, | ||||
| Data3DTexture, | ||||
| DataTexture, | ||||
| DepthTexture, | ||||
| Group, | ||||
| LineBasicMaterial, | ||||
| LineSegments, | ||||
| LinearFilter, | ||||
| Material, | ||||
| Matrix4, | ||||
| Mesh, | ||||
| NearestFilter, | ||||
| OrthographicCamera, | ||||
| PerspectiveCamera, | ||||
| RGBAFormat, | ||||
| ShaderMaterial, | ||||
| Texture, | ||||
| Vector2, | ||||
| UnsignedByteType, | ||||
| Vector3, | ||||
| WebGLRenderer, | ||||
| } from "three"; | ||||
|
|
||||
| import FusedChannelData from "./FusedChannelData.js"; | ||||
| import { | ||||
| rayMarchingVertexShaderSrc, | ||||
| rayMarchingFragmentShaderSrc, | ||||
|
|
@@ -32,8 +35,10 @@ import { Volume } from "./index.js"; | |||
| import Channel from "./Channel.js"; | ||||
| import type { VolumeRenderImpl } from "./VolumeRenderImpl.js"; | ||||
|
|
||||
| import type { FuseChannel } from "./types.js"; | ||||
| import { FUSE_DISABLED_RGB_COLOR, type FuseChannel } from "./types.js"; | ||||
| import { VolumeRenderSettings, SettingsFlags } from "./VolumeRenderSettings.js"; | ||||
| import { LUT_ARRAY_LENGTH } from "./Lut.js"; | ||||
| import { VolumeDataUpdater } from "./utils/VolumeDataUpdater.js"; | ||||
|
|
||||
| const BOUNDING_BOX_DEFAULT_COLOR = new Color(0xffff00); | ||||
|
|
||||
|
|
@@ -47,9 +52,13 @@ export default class RayMarchedAtlasVolume implements VolumeRenderImpl { | |||
| private tickMarksMesh: LineSegments; | ||||
| private geometryTransformNode: Group; | ||||
| private uniforms: ReturnType<typeof rayMarchingShaderUniforms>; | ||||
| private channelData!: FusedChannelData; | ||||
| private emptyPositionTex: DataTexture; | ||||
|
|
||||
| private volumeTexture: Data3DTexture; | ||||
| private lutTexture: DataTexture; | ||||
| private viewChannels: number[]; // should have 4 or less elements | ||||
| private volumeDataUpdater: VolumeDataUpdater; | ||||
|
|
||||
| /** | ||||
| * Creates a new RayMarchedAtlasVolume. | ||||
| * @param volume The volume that this renderer should render data from. | ||||
|
|
@@ -58,6 +67,7 @@ export default class RayMarchedAtlasVolume implements VolumeRenderImpl { | |||
| */ | ||||
| constructor(volume: Volume, settings: VolumeRenderSettings = new VolumeRenderSettings(volume)) { | ||||
| this.volume = volume; | ||||
| this.viewChannels = [-1, -1, -1, -1]; | ||||
|
|
||||
| this.uniforms = rayMarchingShaderUniforms(); | ||||
| [this.geometry, this.geometryMesh] = this.createGeometry(this.uniforms); | ||||
|
|
@@ -80,6 +90,28 @@ export default class RayMarchedAtlasVolume implements VolumeRenderImpl { | |||
|
|
||||
| this.emptyPositionTex = new DataTexture(new Uint8Array(Array(16).fill(0)), 2, 2); | ||||
|
|
||||
| // create volume texture (Data3DTexture) | ||||
| const { x: sx, y: sy, z: sz } = volume.imageInfo.subregionSize; | ||||
| const data = new Uint8Array(sx * sy * sz * 4).fill(0); | ||||
| this.volumeTexture = new Data3DTexture(data, sx, sy, sz); | ||||
| this.volumeTexture.format = RGBAFormat; | ||||
| this.volumeTexture.type = UnsignedByteType; | ||||
| this.volumeTexture.minFilter = LinearFilter; | ||||
| this.volumeTexture.magFilter = LinearFilter; | ||||
| this.volumeTexture.generateMipmaps = false; | ||||
| this.volumeTexture.needsUpdate = true; | ||||
| this.volumeDataUpdater = new VolumeDataUpdater(); | ||||
|
|
||||
| // create LUT texture (256x4, each row is a channel's LUT) | ||||
| const lutData = new Uint8Array(LUT_ARRAY_LENGTH * 4).fill(255); | ||||
| this.lutTexture = new DataTexture(lutData, 256, 4, RGBAFormat, UnsignedByteType); | ||||
| this.lutTexture.minFilter = LinearFilter; | ||||
| this.lutTexture.magFilter = LinearFilter; | ||||
| this.lutTexture.needsUpdate = true; | ||||
|
|
||||
| this.setUniform("volumeTexture", this.volumeTexture); | ||||
| this.setUniform("gLutTexture", this.lutTexture); | ||||
|
|
||||
| this.settings = settings; | ||||
| this.updateSettings(settings, SettingsFlags.ALL); | ||||
| // TODO this is doing *more* redundant work! Fix? | ||||
|
|
@@ -97,21 +129,6 @@ export default class RayMarchedAtlasVolume implements VolumeRenderImpl { | |||
| this.boxHelper.box.set(fullRegionScale.clone().multiplyScalar(-0.5), fullRegionScale.clone().multiplyScalar(0.5)); | ||||
| this.tickMarksMesh.scale.copy(fullRegionScale); | ||||
| this.settings && this.updateSettings(this.settings, SettingsFlags.ROI); | ||||
|
|
||||
| // Set atlas dimension uniforms | ||||
| const { atlasTileDims, subregionSize } = this.volume.imageInfo; | ||||
| const atlasSize = new Vector2(subregionSize.x, subregionSize.y).multiply(atlasTileDims); | ||||
|
|
||||
| this.setUniform("ATLAS_DIMS", atlasTileDims); | ||||
|
|
||||
| this.setUniform("textureRes", atlasSize); | ||||
| this.setUniform("SLICES", this.volume.imageInfo.volumeSize.z); | ||||
|
|
||||
| // (re)create channel data | ||||
| if (!this.channelData || this.channelData.width !== atlasSize.x || this.channelData.height !== atlasSize.y) { | ||||
| this.channelData?.cleanup(); | ||||
| this.channelData = new FusedChannelData(atlasSize.x, atlasSize.y); | ||||
| } | ||||
| } | ||||
|
|
||||
| public viewpointMoved(): void { | ||||
|
|
@@ -193,18 +210,21 @@ export default class RayMarchedAtlasVolume implements VolumeRenderImpl { | |||
| } | ||||
|
|
||||
| if (dirtyFlags & SettingsFlags.SAMPLING) { | ||||
| this.setUniform("interpolationEnabled", this.settings.useInterpolation); | ||||
| this.volumeTexture.minFilter = this.volumeTexture.magFilter = this.settings.useInterpolation | ||||
| ? LinearFilter | ||||
| : NearestFilter; | ||||
| this.volumeTexture.needsUpdate = true; | ||||
| this.setUniform("iResolution", this.settings.resolution); | ||||
| } | ||||
|
|
||||
| if (dirtyFlags & SettingsFlags.MASK_ALPHA) { | ||||
| this.setUniform("maskAlpha", this.settings.maskChannelIndex < 0 ? 1.0 : this.settings.maskAlpha); | ||||
|
||||
| this.setUniform("maskAlpha", this.settings.maskChannelIndex < 0 ? 1.0 : this.settings.maskAlpha); |
Copilot
AI
Mar 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Raymarch now relies on Data3DTexture/sampler3D, which requires WebGL2. This renderer currently doesn’t guard against running under the WebGL1 fallback path (see ThreeJsPanel’s non-WebGL2 branch), so in WebGL1 this will fail shader compilation / rendering. Please add an explicit WebGL2 capability check (e.g., renderer.capabilities.isWebGL2) and either (a) fall back to the old atlas path or (b) disable raymarch with a clear error message when WebGL2 is unavailable.
Copilot
AI
Mar 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updateLuts() calls Channel.combineLuts() without providing the optional out buffer, which allocates a new Uint8Array every time LUTs are updated. This can create noticeable GC churn when users adjust colors/LUTs frequently. Consider reusing a preallocated Uint8Array per active slot (and passing it as out) so LUT updates are in-place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sampling interpolation is now controlled via
volumeTexture.minFilter/magFilter, but the shader uniforminterpolationEnabledis still declared and included in the uniforms object. Since it’s no longer used inraymarch.frag, consider removing the uniform from both the TS uniforms and GLSL to avoid dead/unreferenced state and reduce confusion.