Z-fighing for flip book

I am trying to recreate this react flip https://r3f-animated-book-slider-final.vercel.app/ in threejs, it is going well but I have som z-fighing that is not happening in react and I have no clue how to fix it …

The math is perfect but visually is a mess.

This is how I set the z so it is perfect, I can’t offset it it must be exactly like this so that the math is right, if I offset it it will not look as a book anymore…

mesh.position.z = (-i * this.pageDepth - this.curPage * this.pageDepth);

I can’t share the code on glitch so I paste the code here hoping for some help.

addPages() { // Raycaster this.raycaster = new FWDFB_THREE.Raycaster();
    // Create a new group for the book and add it to the scene.
    this.bookGroup = new FWDFB_THREE.Group();
    this.bookGroup.name = "bookGroup";
    this.scene.add(this.bookGroup);
  
    // Arrays to store page meshes
    this.pagesAR = [];
    this.curPage = 0;
    // Assume texturesAR has pairs of textures (front then back for each page)
    this.totalPages = Math.floor(this.texturesAR.length / 2);
    const pageWidth = this.data.pageWidth;
    const pageHeight = this.data.pageHeight;
    this.pageDepth = this.data.pageDepth; // e.g. 0.003 (a very thin value)
    const pageSegments = 30;
    const segmentWidth = pageWidth / pageSegments;
  
    // Create the book page geometry – a thin box – and translate it so its left edge is at x=0.
    const geometry = new FWDFB_THREE.BoxGeometry(
      pageWidth,
      pageHeight,
      this.pageDepth,
      pageSegments,
      2
    );
    geometry.translate(pageWidth / 2, 0, 0);
  
    // Prepare skinning attributes (the same for every page).
    const positionAttr = geometry.attributes.position;
    const vertex = new FWDFB_THREE.Vector3();
    const skinIndexes = [];
    const skinWeights = [];
    for (let i = 0; i < positionAttr.count; i++) {
      vertex.fromBufferAttribute(positionAttr, i);
      const x = vertex.x;
      let rawIndex = Math.floor(x / segmentWidth);
      rawIndex = Math.min(rawIndex, pageSegments - 2); // clamp so we don’t exceed available bones
      const skinIndex = rawIndex;
      let skinWeight = (x % segmentWidth) / segmentWidth;
      skinIndexes.push(skinIndex, skinIndex + 1, 0, 0);
      skinWeights.push(1 - skinWeight, skinWeight, 0, 0);
    }
    geometry.setAttribute('skinIndex', new FWDFB_THREE.Uint16BufferAttribute(skinIndexes, 4));
    geometry.setAttribute('skinWeight', new FWDFB_THREE.Float32BufferAttribute(skinWeights, 4));
  
    // Define some colors.
    const whiteColor = "#fff";
    const emissiveColor = "#ffffff";
  
    // Create base materials for the non-textured faces.
    const pageMaterials = [
      new FWDFB_THREE.MeshStandardMaterial({ color: whiteColor }),
      new FWDFB_THREE.MeshStandardMaterial({ color: whiteColor }),
      new FWDFB_THREE.MeshStandardMaterial({ color: whiteColor }),
      new FWDFB_THREE.MeshStandardMaterial({ color: whiteColor }),
    ];
  
    // Loop over pages (each page uses a pair of textures: front and back).
    for (let i = 0; i < this.totalPages; i++) {
      // Get front and back textures.
      const frontTexture = this.texturesAR[2 * i].texture;
      const backTexture = this.texturesAR[2 * i + 1].texture;
  
      // Create the front face material.
      const frontMaterial = new FWDFB_THREE.MeshStandardMaterial({
        color: whiteColor,
        map: frontTexture,
        ...(i === 0 ? { roughnessMap: this.roughnessTexture } : { roughness: 0.1 }),
        emissive: emissiveColor,
        emissiveIntensity: 0,
        depthTest: true,
      });
   
  
      // Create the back face material.
      const backMaterial = new FWDFB_THREE.MeshStandardMaterial({
        color: whiteColor,
        map: backTexture,
        ...(i === this.totalPages - 1 ? { roughnessMap: this.roughnessTexture } : { roughness: 0.1 }),
        emissive: emissiveColor,
        emissiveIntensity: 0,
        depthTest: true,
      
      });
    
  
      // Combine base materials with our front and back materials.
      const materials = [
        ...pageMaterials,
        frontMaterial,
        backMaterial,
      ];
  
      // Create bones for the skinned mesh (one per segment).
      const bones = [];
      for (let j = 0; j < pageSegments; j++) {
        const bone = new FWDFB_THREE.Bone();
        bones.push(bone);
        bone.position.x = (j === 0) ? 0 : segmentWidth;
        if (j > 0) bones[j - 1].add(bone);
      }
      const skeleton = new FWDFB_THREE.Skeleton(bones);
  
      // Create the skinned mesh.
      const mesh = new FWDFB_THREE.SkinnedMesh(geometry, materials);
      mesh.castShadow = true;
      mesh.receiveShadow = true;
      mesh.frustumCulled = false;
  
      // Position pages using your desired formula.
      // Here we use:
      mesh.position.z = (-i * this.pageDepth - this.curPage * this.pageDepth);
     
      // Add the root bone and bind the skeleton.
      mesh.add(skeleton.bones[0]);
      mesh.bind(skeleton);
  
      // Store and add the mesh.
      this.pagesAR.push({
        mesh,
        front: this.texturesAR[2 * i],
        back: this.texturesAR[2 * i + 1],
      });
      this.bookGroup.add(mesh);
    }
  }

After hours of typing ChatGPT that almost made me throw my monitor out the window he just spit this line logarithmicDepthBuffer: true, ot of nowhere and it fixes things…

1 Like

I notice your camera’s clip range is very wide (0.0001 to 1000). Since the book is always about 5 units away from the camera, you’re throwing away a lot of the depth buffer’s precision, which you need to distinguish between the pages.

You can just tighten up the clip range… this seems to fix the z-fighting:
this.camera = new PerspectiveCamera(70, this.width / this.height, 0.5, 10);

The range of depth in your scene is very small and totally predictable. Using log depth is obviously working, it’s just overkill and an “oddball” setup here. I don’t think there’d be important performance penalties, but it invites “weirdness” as you continue to work on the scene. (in other words, gives me the heebeejeebees)

I’d use the default (“hyperbolic”) depth buffer values, and just set an appropriate clip range for your camera until you have a reason to do otherwise.

Note that every “order of magnitude” you cover with your camera clip has a depth precision cost. So the resolution of a 0.1/10 clip is about 1000 times finer than 0.0001/10, at the expense of rendering a narrower range.

6 Likes

Thank you for the tip!