3D texture MIP depth face clipping when using a perspective camera

I am working on a web GUI using three.js for one of my light simulators. The GUI requires rendering a 3D volume (integers or float), so I adapted the 3D texture demo script and made a test page.

After some tweaking, I was able to render the 3D texture in MIP style, but the display appears to have some issue in correctly rendering the depth - some of the bounding box faces may disappear at certain camera angles, and make the render inaccurate.

The below screen capture shows the problem. The texture is a 40x20x30 volume with two z-layers: value 1.0 for 0<z<20 and value 2.0 for 20<z<30.

As you can see from the above screenshot, the MIP rendering is ok at the left view angle, but starts showing jagged boundaries (or missing bbx faces) at most other angles, shown on the right.

You can see it yourself by clicking on the below link


to input this volume, please click on the “JSON” tab, delete the existing text, and copy/paste the below JSON data (as you can see, the “Shapes” object defines a 3D volume using the JData ND array format, and is decoded to a numjs NdArray object). Once you replaced the JSON text, you can then click on the “Preview” tab to see the rendering.

  "Session": {
    "ID": "mcx",
    "Photons": 100000,
    "DoMismatch": true,
    "DoAutoThread": true,
    "DoSaveVolume": true,
    "DoPartialPath": true,
    "DoNormalize": true,
    "DoSaveRef": false,
    "DoSaveExit": false,
    "DoSaveSeed": false,
    "DoDCS": false,
    "DoSpecular": true,
    "DebugFlag": "",
    "SaveDataMask": "DP",
    "OutputFormat": "nii",
    "OutputType": "x",
    "RNGSeed": 1648335518
  "Forward": {
    "T0": 0,
    "T1": 5e-9,
    "Dt": 5e-9
  "Optode": {
    "Source": {
      "Type": "pencil",
      "Pos": [30,30,0],
      "Dir": [0,0,1],
      "Param1": [0,0,0,0],
      "Param2": [0,0,0,0]
    "Detector": []
  "Domain": {
    "OriginType": true,
    "Dim": [60,60,60],
    "VolumeFile": "",
    "Media": [
        "mua": 0,
        "mus": 0,
        "g": 1,
        "n": 1
        "mua": 0,
        "mus": 0,
        "g": 1,
        "n": 1

My script was largely adapted from the 3d texture demo. I tried to add transparent: true, opacity: 0.6, alphaTest: 0.5, depthWrite: false to the material setting, but it did not make any difference.

Because my data is the same float32 array as the demo script, but the demo script runs perfectly fine in the MIP mode. I am wondering if I overlooked something.

Your comment and pointers are highly appreciated! thanks

function render(){
  renderer.render( scene, camera );
function drawvolume(volume){
  const dtype={
  const dim=volume.shape;
  const buf=nj.array(volume.flatten().selection.data, 'float32');//had to cast to float32, otherwise, uint8 won't render

  const texture = new THREE.DataTexture3D( buf.selection.data, dim[2], dim[1], dim[0]);
  texture.format = THREE.RedFormat;
  texture.type = THREE.FloatType;
  texture.minFilter = texture.magFilter = THREE.LinearFilter;
  texture.unpackAlignment = 1;

  // Colormap textures
  const cmtextures = {
	  viridis: new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/cm_viridis.png', render ),
	  gray: new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/cm_gray.png', render )

  // Material
  const shader = VolumeRenderShader1;

  const uniforms = THREE.UniformsUtils.clone( shader.uniforms );

  uniforms[ "u_data" ].value = texture;
  uniforms[ "u_size" ].value.set( dim[2], dim[1], dim[0] );
  uniforms[ "u_clim" ].value.set( volume.min(), volume.max() );
  uniforms[ "u_renderstyle" ].value = 0;
  uniforms[ "u_renderthreshold" ].value =  0.2;
  uniforms[ "u_cmdata" ].value = cmtextures[ "viridis" ];

  const material = new THREE.ShaderMaterial( {
	  uniforms: uniforms,
	  vertexShader: shader.vertexShader,
	  fragmentShader: shader.fragmentShader,
	  side: THREE.BackSide // The volume shader uses the backface as its "reference point"
  } );

  // THREE.Mesh
  const geometry = new THREE.BoxGeometry(  dim[2], dim[1], dim[0] );
  geometry.translate(dim[2]*0.5 - 0.5, dim[1]*0.5 - 0.5, dim[0]*0.5 - 0.5 );

  const mesh = new THREE.Mesh( geometry, material );
  return mesh;


       let jd=new jdata(cfg.Shapes,{});
       let vol=jd.decode().data;
       boundingbox.add( drawvolume(vol.transpose()) );

An addition question: I am also having trouble to render a 3D integer array instead of a float array. I tried updating the texture setting to the following, but it shows a uniform block. I suspect that I also need to write a new shader?

  const texture = new THREE.DataTexture3D( volume.selection.data, dim[2], dim[1], dim[0]);
  texture.format = THREE.RedIntegerFormat;
  texture.type = dtype[volume.dtype];
  texture.minFilter = texture.magFilter = THREE.LinearFilter;
  texture.unpackAlignment = 1;

I found that the issue was caused by using a PerspectiveCamera instead of a OrthographicCamera. If I switch the camera to OrthographicCamera, everything is rendered ok.

However, I prefer to use a perspective camera - can anyone point me to a sample/documentation how to display a 3D mip texture on a perspective camera? I suspect that the vertex shader need to be updated.


just want bump this question, does anyone know how to configure a PerspectiveCamera to render a 3D texture without clipping the boundaries? again, everything works fine using an OrthographicCamera, but I would prefer a PerspectiveCamera for better rendering of the domain.