Adding a decal to a GLTF model


Question about DecalGeometry. I am trying to use DecalGeometry to paste a mesh on to a GLTF/GLB model. I did somehow manage to get something until the DecalGeometry line, where whenever I run the code it throws off a Cannot read property ‘isGeometry’ of undefined error. It seems like the method isn’t recognizing the mesh (3D model)

What am I doing wrong? I have looked at other tutorials but I still can’t wrap my head around it.

Thank you for your time, I appreciate any insights!

    var mesh, renderer, scene, camera, controls;
    var mouse, raycaster, helper, decalMaterial;


    function init() {

        // renderer
        renderer = new THREE.WebGLRenderer();
        renderer.setSize( window.innerWidth, window.innerHeight );
        renderer.setPixelRatio( window.devicePixelRatio ); 
        document.body.appendChild( renderer.domElement );

        // scene
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xffffff);
        // camera
        camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
        camera.position.set( 20, 20, 20 );

        // controls
        controls = new THREE.OrbitControls( camera, renderer.domElement );
        // ambient
        scene.add( new THREE.AmbientLight( 0x222222 ) );
        // light
        var light = new THREE.DirectionalLight( 0xffffff, 1 );
        light.position.set( 20,20, 0 );
        scene.add( light );
        // axes
        // Displays axes; THREE.AxesHelper(length_of_axes)
        scene.add( new THREE.AxesHelper( 300 ) );

        let loader = new THREE.GLTFLoader();
          ( gltf ) => {
              var mesh = gltf.scene.children[0];
              mesh.rotation.x = (90/180) * Math.PI;
              mesh.rotation.y =  Math.PI;

            // decal related stuff 
        mouse = new THREE.Vector2(); // assign mouse as a 2d vector
        raycaster = new THREE.Raycaster(); 
        helper = new THREE.Object3D();
    decalMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff, depthWrite: false, polygonOffset: true, polygonOffsetFactor: - 4, } );
        document.addEventListener( 'click', onClick );
        function onClick( event ) {

          mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
          mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
          // whatever camera is pointing at, plus the position of mouse is going to define where beam is cast
          raycaster.setFromCamera( mouse, camera );
          var intersects = raycaster.intersectObjects( [mesh], true);
          helper = new THREE.Object3D();
    		  decalMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff, depthWrite: false, polygonOffset: true, polygonOffsetFactor: - 4, } );

          if ( intersects.length > 0 ) {
            var n = intersects[ 0 ].face.normal.clone();
            n.transformDirection( gltf.scene.matrixWorld ); // transform vector 3 to matrix
            n.add( intersects[ 0 ].point );
            helper.position.copy( intersects[ 0 ].point );
            helper.lookAt( n );
            var position = intersects[ 0 ].point;
            var size = new THREE.Vector3( 1, 1, 1 );
            var decalGeometry = new THREE.DecalGeometry( mesh, position, helper.rotation, size )

          ( xhr ) => {

    function animate() {
      requestAnimationFrame( animate );
      renderer.render( scene, camera );


Can you please verify if gltf.scene.children[0] is actually of type Mesh?

Besides, it seems you just create an instance of DecalGeometry but not the corresponding mesh object.

In general, you should always add the entire glTF scene (meaning scene.add( gltf.scene );) to your scene graph to ensure correct orientation and scaling.

1 Like

Just checked, it actually says that the type is Object3D. I thought I had to put in my GLB model inside the DecalGeometry method because that’s what the developer who made the paint splatter example was doing (

And I have also corrected the code so that it add the entire GLTF scene, though it does still give me the same error. And yes, I just created the instance, this was just to check to see if I didn’t get any errors. I’m trying to go step by step.

What is the correct way to achieve what I am trying to do? Or are there any examples similar to mine but simpler, because every gltf projects on github seems to be referring to the DecalGeometry example.

Ah okay, makes sense!

You definitely have to pass in an instance of Mesh to DecalGeometry. I suggest you query the mesh object by its name like so:

var mesh = gltf.scene.getObjectByName( 'nameOfMesh' );

Of course this assumes that the mesh has a name and you know it^^. Besides, you have to ensure that your asset consists of a single mesh object and not multiple ones. Otherwise it will be more complicated to use DecalGeometry.

Thank you for the advice!

So you’re saying that unless it is an Array, I should always set the mesh to scene with the specific object name to ensure I am selecting the correct mesh instance?

Also, I did change var mesh = gltf.scene.children[0] to var mesh = gltf.scene.getObjectByName( 'OSG_Scene' ); as 'OSG_Scene' was the value for the name key for gtlf.scene. However, it still throws me the same undefined ‘isGeometry’ error. I tried attaching the children key but no luck.

You need the name of the mesh, not the entire scene. Again, you can’t pass an instance of THREE.Scene, THREE.Object3D or THREE.Group to THREE.DecalGeometry because they don’t have a geometry property. You have to select the mesh object from your loaded glTF.

1 Like

Ah I see.

So when I traverse through the loaded scenes, I get more than one mesh object with different names pointing to different parts of the model. Which one do I select? You mentioned that I must:

But if I want to be able to paste decals on all sides of the model, am I supposed to only choose one given by the traverse method or put all the mesh objects into one big object?

Sorry for being inquisitive, just trying to wrap my head around it.

If the model consists of more than one mesh, it’s probably better to import the model in a DCC tool like Blender and merge all parts into a single object. You can also do this with three.js by using BufferGeometryUtils.mergeBufferGeometries() but it requires a bit more effort.

Thank you for the response! I took a look into the meshes using Blender, and found out that all of the meshes are collapsed under one root mesh. Do you mean to pack all of the meshes inside the root mesh to one? I feel like that’s what the root is doing.

You have to ensure that there is only a single mesh and not multiple ones.

Ah I see I got it. Thank you!