Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrong z order when rendering material with alpha mode "BLEND" #28157

Closed
am05mhz opened this issue Apr 18, 2024 · 7 comments
Closed

wrong z order when rendering material with alpha mode "BLEND" #28157

am05mhz opened this issue Apr 18, 2024 · 7 comments

Comments

@am05mhz
Copy link

am05mhz commented Apr 18, 2024

Description

as you can see in the attached image, the tree behind is rendered on top of the tree in front of it. this behavior is also reflected on https://gltf-viewer.donmccurdy.com/ and https://sandbox.babylonjs.com/ but its rendered correctly on https://sketchfab.com/3d-models/forest-house-52429e4ef7bf4deda1309364a2cda86f
Screenshot 2024-04-19 011445

Reproduction steps

  1. download https://sketchfab.com/3d-models/forest-house-52429e4ef7bf4deda1309364a2cda86f glb file
  2. load it to https://gltf-viewer.donmccurdy.com/ or https://sandbox.babylonjs.com/ or your own project
  3. on babylon, you can play with the TreeLeaf material and change its alpha mode to alpha test, it will render correctly but with no alpha blending

Code

code is in Vue 3

<script setup lang="js">
import { onMounted, onBeforeUnmount, ref, computed } from 'vue'
import * as THREE from 'three'
import Stats from 'stats.js'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'

var scene, camera, renderer, house, shiba, controls, effect
var cx, cy, cz
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
const ratio = computed(() => width.value / height.value)

const stats = new Stats()
const canvas = ref(null)

const init = () => {
  scene = new THREE.Scene()
  camera = new THREE.PerspectiveCamera(45, ratio.value, 0.125, 1000)
  cx = -0.4
  cy = 0.4
  cz = 0.6
  camera.position.set(cx, cy, cz)

  renderer = new THREE.WebGLRenderer({ antialias: true })
  renderer.setClearColor('#e5e5e5')
  renderer.setSize(width.value, height.value)

  effect = new EffectComposer(renderer)
  const renderPass = new RenderPass(scene, camera)
  effect.addPass(renderPass)
  
  controls = new OrbitControls(camera, renderer.domElement)
  scene.add(new THREE.GridHelper(5, 50, 0xcccccc, 0xdddddd))
  
  canvas.value.append(renderer.domElement)
  canvas.value.append(stats.dom)
  
  const loader = new GLTFLoader()
  loader.load('models/forest-house.gltf', async (gltf) => {
    house = gltf.scene
    await renderer.compileAsync(house, camera, scene)
    house.scale.set(3, 3, 3)
    scene.add(house)
  }, (xhr) => {
    console.log('house', (xhr.loaded / xhr.total * 100) + '% loaded')
  }, function (error) {
    console.error(error)
  })
  loader.load('models/shiba-small.gltf', async (gltf) => {
    shiba = gltf.scene
    await renderer.compileAsync(shiba, camera, scene)
    shiba.position.set(-0.19, 0.0235, 0)
    shiba.rotation.y = 0.2
    shiba.scale.set(.1, .1, .1)
    scene.add(shiba)
  }, (xhr) => {
    console.log('shiba', (xhr.loaded / xhr.total * 100) + '% loaded')
  }, function (error) {
    console.error(error)
  })
  
  window.addEventListener('resize', resize)

  render()
}

const resize = (ev) => {
  width.value = window.innerWidth
  height.value = window.innerHeight
  camera.aspect = ratio.value
  camera.updateProjectionMatrix()

  if (renderer){
    renderer.setSize(width.value, height.value)
  }
}

const animate = () => {
  stats.update()
}

const render = () => {
  requestAnimationFrame(render)
  if (effect){
    effect.render()
  } else if (renderer){
    renderer.render(scene, camera)
  }
  controls.update()
  animate()
}

onMounted(() => {
  init()
})

onBeforeUnmount(() => {
  renderer.forceContextLoss();
  renderer.context = null;
  renderer.domElement = null;
  renderer = null;
})
</script>

<template>
  <div class="threejs" ref="canvas"></div>
</template>

Live example

live example can just load the file on step 1, to the preview tool on step 2

Screenshots

Screenshot 2024-04-19 011445

Version

^0.162.0

Device

Desktop

Browser

Chrome

OS

Windows

@donmccurdy
Copy link
Collaborator

donmccurdy commented Apr 19, 2024

Realtime 3D engines typically can sort objects - not triangles or pixels - making this category of problem in alpha blending a known limitation. Entire scenes should not use alpha blending without special care for the sort order, or other techniques like OIT or alpha hashing.

@RemusMar
Copy link
Contributor

@am05mhz
To get much better results you can use this trick:

		loader.load( 'meshes/forest_house.glb', function ( gltf ) {
			gltf.scene.traverse(function (child) {
				if (child.isMesh) {
					if (child.material.transparent === true) {
						child.material.depthWrite = true;
						child.material.alphaTest = 0.5;
					}
				}
			});

Here is the result: https://necromanthus.com/Test/html5/forest_house.html

@donmccurdy

Realtime 3D engines typically can sort objects - not triangles or pixels - making this category of problem in alpha blending a known limitation.

Don, there is an uninspired (I don't want to say wrong) setting inside of the GLTF Loader:
materialParams.depthWrite = false;
That will generate an alpha sorting mess in 99% of the scenarios.
You should study this sample as well: https://necromanthus.com/Test/html5/head.html
Click on the stage to see the 3 cases.
cheers

@Mugen87
Copy link
Collaborator

Mugen87 commented Apr 19, 2024

We actually use an updated version of the asset in https://threejs.org/examples/webgl_loader_gltf_avif

@Mugen87
Copy link
Collaborator

Mugen87 commented Apr 19, 2024

Closing. Considering this as a modeling issue.

@RemusMar
Copy link
Contributor

Considering this as a modeling issue.

It's not Michael.
See my above result with that (not very well designed) sample.

@am05mhz
Copy link
Author

am05mhz commented Apr 19, 2024

@am05mhz To get much better results you can use this trick:

		loader.load( 'meshes/forest_house.glb', function ( gltf ) {
			gltf.scene.traverse(function (child) {
				if (child.isMesh) {
					if (child.material.transparent === true) {
						child.material.depthWrite = true;
						child.material.alphaTest = 0.5;
					}
				}
			});

Here is the result: https://necromanthus.com/Test/html5/forest_house.html

@donmccurdy

Realtime 3D engines typically can sort objects - not triangles or pixels - making this category of problem in alpha blending a known limitation.

Don, there is an uninspired (I don't want to say wrong) setting inside of the GLTF Loader: materialParams.depthWrite = false; That will generate an alpha sorting mess in 99% of the scenarios. You should study this sample as well: https://necromanthus.com/Test/html5/head.html Click on the stage to see the 3 cases. cheers

thanks, this solves my problem

@RemusMar
Copy link
Contributor

@Mugen87

We actually use an updated version of the asset in https://threejs.org/examples/webgl_loader_gltf_avif

I get exactly that result (in fact mine is a bit better) using the initial asset and this runtime change:

			gltf.scene.traverse(function (child) {
				if (child.isMesh) {
					if (child.material.name === "TreeLeafs") {
						child.material.side = THREE.DoubleSide;
						child.material.depthWrite = true;
						child.material.depthTest = true;
						child.material.alphaTest = 0.4;
						child.material.transparent = false;
					}
				}
			});

https://necromanthus.com/Test/html5/forest_house.html
And you'll find a similar modified material in the "fixed" asset.
So Michael, it was a hack, not a real fix.
cheers

@Mugen87 Mugen87 added this to the r164 milestone Apr 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants