Hey, I spent like three days with this issue, I think there is a bug on ShaderMaterial (v0.172.0) but hopefully there is some tweak to fix it.
The issue: inconsistent face orientation on transparent double sided shader material on a glb model. It works fine for front/back side, also using MeshStandardMaterial.
What I expect: this is how it looks with FrontSide, no inconsistency on face orientation.
Vertex normals and face normals are ok (tested on Rhino3d, Blender and online glb viewers). Also checked vertex winding order, whick are ok.
The code: this is a minimal code to see the issue. I can’t get rid of double sided (well, I could duplicate the mesh an make a material for the front and other for the back, but performance is a big issue for my purpouse). The shader code is also a minimal case to represent the problem, tried playing with gl_FrontFacing and I can’t discard a side since expected material is a diamond with refraction and reflection. I tried different models with the same problem, I also tried to change everything on the mesh creation on Rhino/Grasshopper before exporting to glb. I also tried every suggestion from ChatGPT without any success.
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 22, 10);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.1;
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);
const vertexShader = `
varying vec3 vNormal;
void main() {
vNormal = normalMatrix * normal;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
varying vec3 vNormal;
void main() {
gl_FragColor = vec4(vec3(0.0), 0.5);
}
`;
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
transparent: true,
side: THREE.DoubleSide,
});
const loader = new GLTFLoader();
loader.load(
'/model.glb',
(gltf) => {
const model = gltf.scene;
model.position.set(0, -5, 0);
scene.add(model);
model.traverse((child) => {
if (child.isMesh) {
child.material = material;
//child.material = new THREE.MeshStandardMaterial({transparent: true, opacity: 0.5, side: 2}); // this works fine so I guess it's not a model issue.
}
});
},
undefined,
(error) => {
console.error('An error occurred while loading the GLB model:', error);
}
);
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
The model:
model.glb (667.9 KB)
The project:
shadermatbug.zip (206.8 KB)
Please help!