Object stretches between front and backface after volume rendering (raycasting)

Hello everyone I am trying to make volume rendering raycasting from a series of Dicom slices but I faced a problem that my object is stretched between the front and back faces as follows:
image

Here is where I created the scene:

export class volumeRender{
    constructor(canvas){
        this.engine = new Engine(canvas, true);
        this.scene = this.CreateScene();

        this.engine.runRenderLoop(()=>{
            this.scene.render();
        })
    }

    CreateCamera(){
        var camera = new ArcRotateCamera("Camera", Math.PI / 4, Math.PI / 4, 4, Vector3.Zero(), this.scene);
        // Positions the camera overwriting alpha, beta, radius
        // camera.setPosition(new Vector3(2, 2, 20));  
        camera.speed = 0.3
        camera.attachControl();
        return camera
    }

    CreateScene() {
        const scene = new Scene(this.engine);
        
        this.camera = this.CreateCamera();

        const hemiLight = new HemisphericLight("hemiLight", new Vector3(0,1,0), scene);
        hemiLight.intensity = 0.5;

        this.backFace = MeshBuilder.CreateBox("backSide", {size: 1, sideOrientation: Mesh.BACKSIDE}, scene); 
        this.backFace.material = new ShaderMaterial("backFaceShader", scene, "./backFace",
		{
			attributes: ["position"],
			uniforms: [ "view", "projection"],
		});
        //this will allow the box to only render to the rendertarget
        this.backFace.layerMask = 0x10000000; 
    



        this.frontFace = MeshBuilder.CreateBox("frontSide", {size: 1, sideOrientation: Mesh.FRONTSIDE}, scene); 
        this.frontFaceMaterial = new ShaderMaterial("frontFaceShader", scene, "./frontFace",
        {
            needAlphaBlending: true, 
            needAlphaTesting: true,
            attributes: ["position"],
            uniforms: ["world", "view", "projection", "steps", "alphaCorrection"],
            samplers: [ "tex", "textureSampler", "transferFunction"],
        });

        var renderTarget = new RenderTargetTexture(
            'render to texture', // name 
            512, // texture size
            scene // the scene
        );
        scene.customRenderTargets.push(renderTarget); 
        renderTarget.renderList.push(this.backFace);


        var transFuncArray = []
        for (let i=0;i<=255;i++){
            transFuncArray.push(i)
            transFuncArray.push(0)
            transFuncArray.push(0)
            transFuncArray.push(i)
        }
        var transferTexture = new RawTexture(new Uint8Array(transFuncArray), 256,1, Engine.TEXTUREFORMAT_RGBA, scene)

        this.frontFaceMaterial.setFloat('steps', 256.0);
        this.frontFaceMaterial.setFloat('alphaCorrection', 2);	
        this.frontFaceMaterial.setTexture("tex", renderTarget);
        this.frontFaceMaterial.setTexture("transferFunction", transferTexture);

        
        return scene 
    }
    
    
    updateTexture(dataArray){
        var tempArray = []
        dataArray.forEach(slice => {
            slice.forEach(element=>{
                tempArray.push(element)
            })
        });
        var newTex = new RawTexture3D(new Uint8Array(tempArray), 512,512, 234, Engine.TEXTUREFORMAT_LUMINANCE, this.scene)
        this.frontFaceMaterial.setTexture("textureSampler", newTex);
        this.frontFace.material = this.frontFaceMaterial;
    }
}

and here are my shaders:
Backface vertex shader:

`varying vec3 worldSpaceCoords;
attribute vec3 position;

uniform mat4 view;
uniform mat4 projection;

void main()
{
    //Set the world space coordinates of the back faces vertices as output.
    worldSpaceCoords = position + vec3(0.5, 0.5, 0.5);
    gl_Position = projection * view * vec4( position, 1.0 );
}`

Backface fragment shader:

varying vec3 worldSpaceCoords;

void main()
{
    //The fragment's world space coordinates as fragment output.
    gl_FragColor = vec4( worldSpaceCoords.x , worldSpaceCoords.y, worldSpaceCoords.z, 1 );
}

Frontface vertex shader:

precision highp float;

varying vec3 worldSpaceCoords;
varying vec4 projectedCoords;

uniform mat4 projection, view, world;

attribute vec3 position;

void main()
{
    worldSpaceCoords = (world * vec4(position + vec3(0.5, 0.5,0.5), 1.0 )).xyz;
    gl_Position = projection *  view * vec4( position, 1.0 );
    projectedCoords =  gl_Position;
}

Frontface fragment shader:

precision highp float;
precision highp sampler3D;
 
// varying vec3 vUV;
// varying vec3 positionAttrib;
varying vec4 projectedCoords;
varying vec3 worldSpaceCoords;

uniform sampler2D tex;
uniform sampler2D transferFunction;
uniform sampler3D textureSampler;

uniform float steps;
uniform float alphaCorrection;

const int MAX_STEPS = 887;

void main(void) {
    vec2 texc = vec2(
        ((projectedCoords.x / projectedCoords.w) + 1.0 ) / 2.0,
        ((projectedCoords.y / projectedCoords.w) + 1.0 ) / 2.0);
    vec3 backPos = texture2D(tex, texc).xyz;
    vec3 frontPos = worldSpaceCoords;

    //Using NearestFilter for rtTexture mostly eliminates bad backPos values at the edges
    //of the cube, but there may still be no valid backPos value for the current fragment.
    if ((backPos.x == 0.0) && (backPos.y == 0.0))
    {
        gl_FragColor = vec4(0.0);
        return;
    }

    //The direction from the front position to back position.
    vec3 dir = backPos - frontPos;

    float rayLength = length(dir);

    //Calculate how long to increment in each step.
    float delta = 1.0 / steps;

    //The increment in each direction for each step.
    vec3 deltaDirection = normalize(dir) * delta;
    float deltaDirectionLength = length(deltaDirection);

    //Start the ray casting from the front position.
    vec3 currentPosition = frontPos;

    //The color accumulator.
    vec4 accumulatedColor = vec4(0.0);

    //The alpha value accumulated so far.
    float accumulatedAlpha = 0.0;

    //How long has the ray travelled so far.
    float accumulatedLength = 0.0;

    //If we have twice as many samples, we only need ~1/2 the alpha per sample.
    //Scaling by 256/10 just happens to give a good value for the alphaCorrection slider.
    float alphaScaleFactor = 25.6 * delta;

    vec4 colorSample;
    vec4 voxelIntensity;
    float alphaSample;

    //Perform the ray marching iterations
    for(int i = 0; i < MAX_STEPS; i++)
    {
        //Get the voxel intensity value from the 3D texture.
        voxelIntensity = texture(textureSampler, currentPosition);
        colorSample = texture2D(transferFunction, vec2(voxelIntensity.r, 1.0));

        //Allow the alpha correction customization.
        alphaSample = colorSample.a * alphaCorrection;

        //Applying this effect to both the color and alpha accumulation results in more realistic transparency.
        alphaSample *= (1.0 - accumulatedAlpha);

        //Scaling alpha by the number of steps makes the final color invariant to the step size.
        alphaSample *= alphaScaleFactor;

        //Perform the composition.
        accumulatedColor += colorSample * alphaSample;

        //Store the alpha accumulated so far.
        accumulatedAlpha += alphaSample;

        //Advance the ray.
        currentPosition += deltaDirection;
        accumulatedLength += deltaDirectionLength;

        //If the length traversed is more than the ray length, or if the alpha accumulated reaches 1.0 then exit.
        if(accumulatedLength >= rayLength || accumulatedAlpha >= 1.0 )
            break;
    }

    gl_FragColor  = accumulatedColor;
}

Are you able to provide a repro in the Playground (https://playground.babylonjs.com/)?

Iā€™m not sure we will be able to help only with extracts of the code.

2 Likes