Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Comments

Fix major rendering bug of semi-transparent blocks#55

Open
KokeCacao wants to merge 1 commit intomisode:mainfrom
KokeCacao:tmp
Open

Fix major rendering bug of semi-transparent blocks#55
KokeCacao wants to merge 1 commit intomisode:mainfrom
KokeCacao:tmp

Conversation

@KokeCacao
Copy link

Related Issue: #54

Related PR:

Purpose: Mainly, this PR fixes issues when rendering multiple semi-transparent blocks stacked together.

How it's done:

  • For transparent chunks, we need to sort all the meshes within it
  • Since rendering is also chunk by chunk, we also need to sort all transparent chunks
  • We can prove mathematically that only these two sortings are needed to ensure the correct depth buffer as long as fragments don't exceed block/chunk boundary

Specifically:

  • getMeshes() is removed and replaced with getTransparentMeshes(cameraPos: vec3) and getNonTransparentMeshes()
  • drawColoredStructure and drawStructure should change accordingly, and non-transparent chunks should be rendered first.

The result was tested on various chunk sizes and view angles. Feel free to reorganize my code.

@misode
Copy link
Owner

misode commented Apr 12, 2025

I'm hesitant to merge this PR, both as I don't fully understand what is going on, and rebuilding the meshes every rendering frame doesn't seem like a super good idea. Translucency sorting is complicated and it's not my goal to have Sodium-level accuracy. Though of course if there are quick wins without impacting performance too much, I'm still interested in that.

@KokeCacao
Copy link
Author

I'm hesitant to merge this PR, both as I don't fully understand what is going on, and rebuilding the meshes every rendering frame doesn't seem like a super good idea. Translucency sorting is complicated and it's not my goal to have Sodium-level accuracy. Though of course if there are quick wins without impacting performance too much, I'm still interested in that.

The current algorithm only sorts transparent portions (which is small). It is currently done in $O(n\log(n))$ but could be accelerated to $O(n)$ due to its tensor structure. But I think your concern is mostly on rebuilding. The rebuilding can be avoided by caching the non-transparent portion (caching the final rebuild result after splitting for the non-transparent portion) to avoid rebuilding for each frame. We have to rebuild the transparent portion, though, for correctness. Do you think caching is the optimal approach? If so, I can implement this.

@KokeCacao
Copy link
Author

Edit: To people who care, in addition to commits above, drawMesh should be fix to the following to (1) update buffer when no split is performed (2) reduce sorting cost.

  protected drawMesh(mesh: Mesh, options: { pos?: boolean, color?: boolean, texture?: boolean, normal?: boolean, blockPos?: boolean, sort?: boolean }) {
    // If the mesh is too large, split it into smaller meshes
    const meshes = mesh.split()

    for (const m of meshes) {
      // If the mesh is intended for transparent rendering, sort the quads.
      if (mesh.quadVertices() > 0 && options.sort) {
        const cameraPos = this.extractCameraPositionFromView()
        mesh.quads.sort((a, b) => {
          const centerA = Renderer.computeQuadCenter(a)
          const centerB = Renderer.computeQuadCenter(b)
          const distA = vec3.distance(cameraPos, centerA)
          const distB = vec3.distance(cameraPos, centerB)
          return distB - distA // Sort in descending order (farthest first)
        })
        mesh.setDirty({
          quads: true,
        })
      }
    }

    // We rebuild mesh only right before we render to avoid multiple rebuild
    // Mesh will keep tracking whether itself is dirty or not to avoid unnecessary rebuild as well
    meshes.forEach(m => m.rebuild(this.gl, {
      pos: options.pos,
      color: options.color,
      texture: options.texture,
      normal: options.normal,
      blockPos: options.blockPos,
    }))

    for (const m of meshes) {
      this.drawMeshInner(m, options)
    }
  }

Some litematic I used for testing (for future reference):
honey.litematic.zip
honey_line.litematic.zip
sample_0864210_2_X2.litematic.zip

@jacobsjo
Copy link
Contributor

jacobsjo commented Jul 2, 2025

I've done some profiling of this PR when used in my jigsaw previewer. When rendering a trial chamber, this increases render time per frame from <3ms to around 75ms.

Old
image

With this PR
image

About 60% of that comes from the inefficient use of extractCameraPositionFromView, the rest from the rebuilding of the meshes.

The extractCameraPositionFromView part could probably be solved but then we're still at a frame time of >30ms. So that is still a 10x slowdown.

@KokeCacao
Copy link
Author

KokeCacao commented Jul 2, 2025

I've done some profiling of this PR when used in my jigsaw previewer. When rendering a trial chamber, this increases render time per frame from <3ms to around 75ms.

Thanks for the effort. What I am mostly concerned about timing is the model loading time and memory. With medium-sized Litematica (300x300 area), model loading might take minutes if not exploding my 16GB memory, but will render under a few seconds when loaded.

I understand that this project's goal is more for interactive UI and my goal is offline rendering, so I probably should maintain a separate branch in my own repo targeted for accurate offline rendering.

Below graph shows a (y-axis-log-scale) distribution of litematica sizes commonly found on internet.
image

@KokeCacao
Copy link
Author

I will maintain this feature on my own repo for offline rendering.

@KokeCacao KokeCacao closed this Jul 7, 2025
@KokeCacao
Copy link
Author

@jacobsjo Thanks for the profiling. Didn't realize extractCameraPositionFromView is so costly on your end.

image

I have also improved loading speed of the schematics and rendering becomes the new overhead. The sorting now (with some improvements on my own repo), now costs nearly nothing. It is very implementable. I believe the main overhead comes from the mapping function in mapping Quad and Vert to the buffer. We should not use these objects to begin with. I'll try to make something work.

@KokeCacao KokeCacao reopened this Jul 10, 2025
@KokeCacao
Copy link
Author

Okay. Added a small improvement to rebuildBufferV. A render previously took (max: 5724 ms, min: 2169 ms) now only takes (max: 927 ms, min: 246 ms). Total sum rendering time changed from 21236 ms to 2697 ms. 7.9x improvement.

Images below are made using the same structure as above.
image
image

@jacobsjo
Copy link
Contributor

@KokeCacao Just to note: you didn't add any commits to match your latest comments.

@KokeCacao
Copy link
Author

@KokeCacao Just to note: you didn't add any commits to match your latest comments.

Yes. I will do it when I get time. Although my local version is up-to-date with all misode/deepslate commits, they have some refactoring and are messy to merge. (I assume people want as few changes as possible, and separate each fix as an independent merge.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants