three.js

Clipping human face in 3D world

Inspired by GANTZ from Japanese animation.

Background

In my previous post, I mention to create stencil seal on the object. In that, invisible planes crop the object off. You can see the example in this site quickly as I referred to the code. This means something inside the content come out by slashing it. While learning Three.js from the example, I figured out that the resulting image looked like Japanese animation called GANTZ. You can see my inspired vision in the following official tweet.

In the above scene, a guy’s body is being cut off and it is gradually fading out. The astonishing effect got me a significant impact. Therefore I determined to demonstrate such an effect using Three.js.

Load Human 3D model

First of all, I loaded a 3D model and textures that are in the examples in Three.js repository. Thanks to the repository, I can get a realistic human model.

var textureLoader = new THREE.TextureLoader();
var loader = new GLTFLoader();

loader.load('https://threejs.org/examples/models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
    mesh = gltf.scene.children[0];

    var material = new THREE.MeshStandardMaterial({
        specular: 0x111111,
        map: textureLoader.load('https://threejs.org/examples/models/gltf/LeePerrySmith/Map-COL.jpg'),
        specularMap: textureLoader.load('https://threejs.org/examples/models/gltf/LeePerrySmith/Map-SPEC.jpg'),
        normalMap: textureLoader.load('https://threejs.org/examples/models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg'),
        clippingPlanes: planes,
        clipShadows: true,
    });

    // add the color
    var clippedColorFront = new THREE.Mesh(mesh.geometry, material);	
    clippedColorFront.castShadow = true;
    clippedColorFront.renderOrder = 6;
    object.add(clippedColorFront);
    mesh.scale.set(10, 10, 10);        
})
Load a human model

Prepare Clipping planes

After the 3D model has loaded, I created invisible planes so that they can clip the 3D model overlapped in. The meat of the creation is in Material. That procedure to create is like the following a couple code:

// Set up clip plane rendering    
planeObjects = [];    
var planeGeom = new THREE.PlaneBufferGeometry(100, 100);    
for (var i = 0; i < 3; i++) {
    var poGroup = new THREE.Group();
    var plane = planes[i];     
    var stencilGroup = createPlaneStencilGroup(mesh.geometry, plane, i + 1);
    // plane is clipped by the other clipping planes     
    var planeMat =      new THREE.MeshStandardMaterial({
        color: 0xE91E63,       
        clippingPlanes: planes.filter(p => p !== plane), 
        stencilWrite: true,       
        stencilRef: 0,       
        stencilFunc: THREE.NotEqualStencilFunc,       
        stencilFail: THREE.ReplaceStencilOp,       
        stencilZFail: THREE.ReplaceStencilOp,       
        stencilZPass: THREE.ReplaceStencilOp,
    });     
    var po = new THREE.Mesh(planeGeom, planeMat);    
    po.onAfterRender = function (renderer) {
        renderer.clearStencil();
    };
    po.renderOrder = i + 1.1;
    object.add(stencilGroup);
    poGroup.add(po);    
    planeObjects.push(po);   
    scene.add(poGroup);
}

function createPlaneStencilGroup(geometry, plane, renderOrder) {

    var group = new THREE.Group();
    var baseMat = new THREE.MeshBasicMaterial();
    baseMat.depthWrite = false;
    baseMat.depthTest = false;
    baseMat.colorWrite = false;
    baseMat.stencilWrite = true;
    baseMat.stencilFunc = THREE.AlwaysStencilFunc;

    // back faces
    var mat0 = baseMat.clone();
    mat0.side = THREE.BackSide;
    mat0.clippingPlanes = [plane];
    mat0.stencilFail = THREE.IncrementWrapStencilOp;
    mat0.stencilZFail = THREE.IncrementWrapStencilOp;
    mat0.stencilZPass = THREE.IncrementWrapStencilOp;

    var mesh0 = new THREE.Mesh(geometry, mat0);
    mesh0.renderOrder = renderOrder;
    group.add(mesh0);

    // front faces
    var mat1 = baseMat.clone();
    mat1.side = THREE.FrontSide;
    mat1.clippingPlanes = [plane];
    mat1.stencilFail = THREE.DecrementWrapStencilOp;
    mat1.stencilZFail = THREE.DecrementWrapStencilOp;
    mat1.stencilZPass = THREE.DecrementWrapStencilOp;

    var mesh1 = new THREE.Mesh(geometry, mat1);
    mesh1.renderOrder = renderOrder;

    group.add(mesh1);

    return group;

}

To meet requisition for clipping object, I have to turn on the property, localClippingEnabled supported by WEBGLRenderer otherwise clipping would fail.

renderer.localClippingEnabled = true;

You can see how the invisible planes cut the 3D human model off.

Conclusion

As the invisible planes is likewise composed of Material, a shader material would be fine, too. Using them, the cutting face can colour more cool effects such as GANTZ animation.

https://codepen.io/otsumitsu/pen/YzyQXqR