Clipping Plane API is not applied properly

I am trying to do clipping with the help of Clipping Plane API used in this example Clipping Plane.

I made the changes in the Code, but it’s not working. I am sure I am missing something somewhere both in shaders and in init(). Trying both local and global but none of them are working.

I am not sure whether to make changes or add something so that my clipping starts happening.

Here is the full code. I am using code by http://www.lebarba.com/

function init() {
			//Parameters that can be modified.
			guiControls = new function() {
				this.model = 'foot';
				this.steps = 256.0;
				this.alphaCorrection = 1.0;
				this.color1 = "#00FA58";
				this.stepPos1 = 0.1;
				this.color2 = "#CC6600";
				this.stepPos2 = 0.7;
				this.color3 = "#F2F200";
				this.stepPos3 = 1.0;
			};
			container = document.getElementById( 'container' );
			camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.01, 3000.0 );
			camera.position.set(0,1.3,3);
			controls = new THREE.OrbitControls( camera, container );
			controls.center.set( 0.0, 0.0, 0.0 );
			//Load the 2D texture containing the Z slices.

			cubeTextures['foot'] = THREE.ImageUtils.loadTexture('foot.raw.png');
			//Don't let it generate mipmaps to save memory and apply linear filtering to prevent use of LOD.
			
			cubeTextures['foot'].generateMipmaps = false;
			cubeTextures['foot'].minFilter = THREE.LinearFilter;
			cubeTextures['foot'].magFilter = THREE.LinearFilter;
			var transferTexture = updateTransferFunction();
			var screenSize = new THREE.Vector2( window.innerWidth, window.innerHeight );
			//Use NearestFilter to eliminate interpolation.  At the cube edges, interpolated world coordinates
			//will produce bogus ray directions in the fragment shader, and thus extraneous colors.
			rtTexture = new THREE.WebGLRenderTarget( screenSize.x, screenSize.y,
													{ 	minFilter: THREE.NearestFilter,
														magFilter: THREE.NearestFilter,
														wrapS:  THREE.ClampToEdgeWrapping,
														wrapT:  THREE.ClampToEdgeWrapping,
														format: THREE.RGBFormat,
														type: THREE.FloatType,
														generateMipmaps: false} );

    // ***** Clipping planes: *****
		var localPlane = new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0.8 ),
			globalPlane = new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 0.1 );


			var materialFirstPass = new THREE.ShaderMaterial( {
				vertexShader: document.getElementById( 'vertexShaderFirstPass' ).textContent,
				fragmentShader: document.getElementById( 'fragmentShaderFirstPass' ).textContent,
				side: THREE.BackSide,
      // ***** Clipping setup (material): *****
        clippingPlanes: [ localPlane ],
        clipShadows: true
			} );
			materialSecondPass = new THREE.ShaderMaterial( {
				vertexShader: document.getElementById( 'vertexShaderSecondPass' ).textContent,
				fragmentShader: document.getElementById( 'fragmentShaderSecondPass' ).textContent,
				side: THREE.FrontSide,
				// ***** Clipping setup (material): *****/
        clippingPlanes: [ localPlane ],
        clipShadows: true,

				uniforms: {	tex:  { type: "t", value: rtTexture },
							cubeTex:  { type: "t", value: cubeTextures['bonsai'] },
							transferTex:  { type: "t", value: transferTexture },
							steps : {type: "1f" , value: guiControls.steps },
							alphaCorrection : {type: "1f" , value: guiControls.alphaCorrection }}
			 });
			sceneFirstPass = new THREE.Scene();
			sceneSecondPass = new THREE.Scene();
			var boxGeometry = new THREE.BoxGeometry(1.0, 1.0, 1.0);
			boxGeometry.doubleSided = true;
			var meshFirstPass = new THREE.Mesh( boxGeometry, materialFirstPass );
			var meshSecondPass = new THREE.Mesh( boxGeometry, materialSecondPass );
			sceneFirstPass.add( meshFirstPass );
			sceneSecondPass.add( meshSecondPass );

			// ***** Clipping setup (renderer): *****
			var globalPlanes = [ globalPlane ],
				Empty = Object.freeze( [] );

			renderer = new THREE.WebGLRenderer();

			renderer.clippingPlanes = Empty; // GUI sets it to globalPlanes
			renderer.localClippingEnabled = true;


			container.appendChild( renderer.domElement );




			stats = new Stats();
			stats.domElement.style.position = 'absolute';
			stats.domElement.style.top = '0px';
			container.appendChild( stats.domElement );




			var gui = new dat.GUI();
			var modelSelected = gui.add(guiControls, 'model', [ 'bonsai', 'foot', 'teapot' ] );
			gui.add(guiControls, 'steps', 0.0, 512.0);
			gui.add(guiControls, 'alphaCorrection', 0.01, 5.0).step(0.01);
			modelSelected.onChange(function(value) { materialSecondPass.uniforms.cubeTex.value =  cubeTextures[value]; } );
			//Setup transfer function steps.
			var step1Folder = gui.addFolder('Step 1');
			var controllerColor1 = step1Folder.addColor(guiControls, 'color1');
			var controllerStepPos1 = step1Folder.add(guiControls, 'stepPos1', 0.0, 1.0);
			controllerColor1.onChange(updateTextures);
			controllerStepPos1.onChange(updateTextures);
			var step2Folder = gui.addFolder('Step 2');
			var controllerColor2 = step2Folder.addColor(guiControls, 'color2');
			var controllerStepPos2 = step2Folder.add(guiControls, 'stepPos2', 0.0, 1.0);
			controllerColor2.onChange(updateTextures);
			controllerStepPos2.onChange(updateTextures);
			var step3Folder = gui.addFolder('Step 3');
			var controllerColor3 = step3Folder.addColor(guiControls, 'color3');
			var controllerStepPos3 = step3Folder.add(guiControls, 'stepPos3', 0.0, 1.0);
			controllerColor3.onChange(updateTextures);
			controllerStepPos3.onChange(updateTextures);
			step1Folder.open();
			step2Folder.open();
			step3Folder.open();

    var folderLocal = gui.addFolder( "Local Clipping" ),
				propsLocal = {
					get 'Enabled'() { return renderer.localClippingEnabled; },
					set 'Enabled'( v ) { renderer.localClippingEnabled = v; },

					get 'Plane'() { return localPlane.constant; },
					set 'Plane'( v ) { localPlane.constant = v }
				},
				folderGlobal = gui.addFolder( "Global Clipping" ),
				propsGlobal = {
					get 'Enabled'() { return renderer.clippingPlanes !== Empty; },
					set 'Enabled'( v  ) {
							renderer.clippingPlanes = v ? globalPlanes : Empty; },
					get 'Plane'() { return globalPlane.constant; },
					set 'Plane'( v ) { globalPlane.constant = v; }
				};
			folderLocal.add( propsLocal, 'Enabled' );
			folderLocal.add( propsLocal, 'Plane', 0.3, 1.25 );
			folderGlobal.add( propsGlobal, 'Enabled' );
			folderGlobal.add( propsGlobal, 'Plane', -0.4, 3 );


			onWindowResize();
			window.addEventListener( 'resize', onWindowResize, false );
		}
		function updateTextures(value)
		{
			materialSecondPass.uniforms.transferTex.value = updateTransferFunction();
		}
		function updateTransferFunction()
		{
			var canvas = document.createElement('canvas');
			canvas.height = 20;
			canvas.width = 256;
			var ctx = canvas.getContext('2d');
			var grd = ctx.createLinearGradient(0, 0, canvas.width -1 , canvas.height - 1);
			grd.addColorStop(guiControls.stepPos1, guiControls.color1);
			grd.addColorStop(guiControls.stepPos2, guiControls.color2);
			grd.addColorStop(guiControls.stepPos3, guiControls.color3);
			ctx.fillStyle = grd;
			ctx.fillRect(0,0,canvas.width -1 ,canvas.height -1 );
			var img = document.getElementById("transferFunctionImg");
			img.src = canvas.toDataURL();
			img.style.width = "256 px";
			img.style.height = "128 px";
			transferTexture =  new THREE.Texture(canvas);
			transferTexture.wrapS = transferTexture.wrapT =  THREE.ClampToEdgeWrapping;
			transferTexture.needsUpdate = true;
			return transferTexture;
		}
		function onWindowResize( event ) {
			camera.aspect = window.innerWidth / window.innerHeight;
			camera.updateProjectionMatrix();
			renderer.setSize( window.innerWidth, window.innerHeight );
		}
		function animate() {
			requestAnimationFrame( animate );
			render();
			stats.update();
		}
		function render() {
			var delta = clock.getDelta();


			//Render first pass and store the world space coords of the back face fragments into the texture.
			renderer.render( sceneFirstPass, camera, rtTexture, true );
			//Render the second pass and perform the volume rendering.
			renderer.render( sceneSecondPass, camera );
			materialSecondPass.uniforms.steps.value = guiControls.steps;
			materialSecondPass.uniforms.alphaCorrection.value = guiControls.alphaCorrection;
		}

<script id="fragmentShaderFirstPass" type="x-shader/x-fragment">
		varying vec3 worldSpaceCoords;
		void main()
		{
			//The fragment's world space coordinates as fragment output.
			gl_FragColor = vec4( worldSpaceCoords.x , worldSpaceCoords.y, worldSpaceCoords.z, 1 );
		}
	</script>

	<script id="vertexShaderFirstPass" type="x-shader/x-vertex">
		varying vec3 worldSpaceCoords;
		void main()
		{
			//Set the world space coordinates of the back faces vertices as output.
			worldSpaceCoords = position + vec3(0.5, 0.5, 0.5); //move it from [-0.5;0.5] to [0,1]
			gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
		}
	</script>

	<script id="fragmentShaderSecondPass" type="x-shader/x-fragment">
		varying vec3 worldSpaceCoords;
		varying vec4 projectedCoords;
		uniform sampler2D tex, cubeTex, transferTex;
		uniform float steps;
		uniform float alphaCorrection;
		// The maximum distance through our rendering volume is sqrt(3).
		// The maximum number of steps we take to travel a distance of 1 is 512.
		// ceil( sqrt(3) * 512 ) = 887
		// This prevents the back of the image from getting cut off when steps=512 & viewing diagonally.
		const int MAX_STEPS = 887;
		//Acts like a texture3D using Z slices and trilinear filtering.
		vec4 sampleAs3DTexture( vec3 texCoord )
		{
			vec4 colorSlice1, colorSlice2;
			vec2 texCoordSlice1, texCoordSlice2;
			//The z coordinate determines which Z slice we have to look for.
			//Z slice number goes from 0 to 255.
			float zSliceNumber1 = floor(texCoord.z  * 255.0);
			//As we use trilinear we go the next Z slice.
			float zSliceNumber2 = min( zSliceNumber1 + 1.0, 255.0); //Clamp to 255
			//The Z slices are stored in a matrix of 16x16 of Z slices.
			//The original UV coordinates have to be rescaled by the tile numbers in each row and column.
			texCoord.xy /= 16.0;
			texCoordSlice1 = texCoordSlice2 = texCoord.xy;
			//Add an offset to the original UV coordinates depending on the row and column number.
			texCoordSlice1.x += (mod(zSliceNumber1, 16.0 ) / 16.0);
			texCoordSlice1.y += floor((255.0 - zSliceNumber1) / 16.0) / 16.0;
			texCoordSlice2.x += (mod(zSliceNumber2, 16.0 ) / 16.0);
			texCoordSlice2.y += floor((255.0 - zSliceNumber2) / 16.0) / 16.0;
			//Get the opacity value from the 2D texture.
			//Bilinear filtering is done at each texture2D by default.
			colorSlice1 = texture2D( cubeTex, texCoordSlice1 );
			colorSlice2 = texture2D( cubeTex, texCoordSlice2 );
			//Based on the opacity obtained earlier, get the RGB color in the transfer function texture.
			colorSlice1.rgb = texture2D( transferTex, vec2( colorSlice1.a, 1.0) ).rgb;
			colorSlice2.rgb = texture2D( transferTex, vec2( colorSlice2.a, 1.0) ).rgb;
			//How distant is zSlice1 to ZSlice2. Used to interpolate between one Z slice and the other.
			float zDifference = mod(texCoord.z * 255.0, 1.0);
			//Finally interpolate between the two intermediate colors of each Z slice.
			return mix(colorSlice1, colorSlice2, zDifference) ;
		}
		void main( void ) {
			//Transform the coordinates it from [-1;1] to [0;1]
			vec2 texc = vec2(((projectedCoords.x / projectedCoords.w) + 1.0 ) / 2.0,
							((projectedCoords.y / projectedCoords.w) + 1.0 ) / 2.0 );
			//The back position is the world space position stored in the texture.
			vec3 backPos = texture2D(tex, texc).xyz;
			//The front position is the world space position of the second render pass.
			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;
			float alphaSample;
			//Perform the ray marching iterations
			for(int i = 0; i < MAX_STEPS; i++)
			{
				//Get the voxel intensity value from the 3D texture.
				colorSample = sampleAs3DTexture( currentPosition );
				//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;
		}
	</script>

	<script id="vertexShaderSecondPass" type="x-shader/x-vertex">
		varying vec3 worldSpaceCoords;
		varying vec4 projectedCoords;
		void main()
		{
			worldSpaceCoords = (modelMatrix * vec4(position + vec3(0.5, 0.5,0.5), 1.0 )).xyz;
			gl_Position = projectionMatrix *  modelViewMatrix * vec4( position, 1.0 );
			projectedCoords =  projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
		}
	</script>

Your shaders don’t include the ShaderChunks needed for clipping. It’s not that obvious from the API, but clipping is actually defined in the shaders.

Thanks for pointing this out. Where exactly do I need to include the ShaderChunk? Can you please help me with this? Sorry I am new to this three js thing.

Just check how it is done in the included shaders:


The chunks:




1 Like