UV wrapping in ShaderMaterial (seam visible)

Hello everyone, I apologize if this question has been asked before, I’m not sure how to word it.

I am fiddling with custom fragment shaders in the Three.js editor, and when trying to apply transparency, I happen to see the bouncary of the UV space on a cylinder. Some images should make it clearer:

Capture d’écran du 2023-10-03 13-04-58

The first one is with transparency disabled ine the material’s properties, and depth writing enabled.

Capture d’écran du 2023-10-03 13-05-15

This next one is with transparency enabled and depth writing enabled.

Capture d’écran du 2023-10-03 13-05-26

Then transparency enabled and depth writing disabled.

Capture d’écran du 2023-10-03 13-05-34

And finally transparency disabled and depth writing disabled.

As you can see, only in the first picture we have a continuous color around the cylinder, for every other set of options, that seam is appearing. What could cause it?

Here’s the code of my fragment shader:

varying vec2 v_uv;

void main() {
	gl_FragColor = vec4( 0.0, 0.0,  1.0 - v_uv.y, 1.0 - v_uv.y);
}

With v_uv being just the uvattribute of the default vertex shader passed to the fragment shader.

P.S.: All of the pictures above use DoubleSide for face culling.

Alpha transparency doesn’t work well for double sided geometry since the order in which the triangles get drawn, is somewhat arbitrary… so sometimes the further faces get rendered before the front faces, and the transparency looks wrong.

You can fix this by using 2 separate materials… one for back faces and one for front faces. This causes the renderer to render the object in 2 calls… first for back faces then front faces…

Something like…

cylinderMesh.material = [
new Material( { side: THREE.BackSide } ),
new Material( { side: THREE.FrontSide } ) 
]

where Material is your shader…

Thank you for the hint, however, with this code, I can only see one face being rendered:

Capture d’écran du 2023-10-05 11-07-23

Is it because I use a THREE.CylinderGeometry for my mesh?

My code:

  load() {
    this.cylinder = new Mesh(
      new CylinderGeometry(6, 6, 8, 32, 1, true),
      [
        new ShaderMaterial({ side: FrontSide, transparent: true, vertexShader, fragmentShader, uniforms: { time: { value: 0 } } }),
        new ShaderMaterial({ side: BackSide, transparent: true, vertexShader, fragmentShader, uniforms: { time: { value: 0 } } })
      ]
    )
    this.cylinder.rotation.x = Math.PI / 4
    this.scene.add(this.cylinder)
  },

  update(dt) {
    this.cylinder.material.forEach(material => { material.uniforms.time.value += dt / 100 })
  },

this being a basic player I coded.

Try switching the order of the 2 materials? backside first, then frontside?
If that doesn’t work, then try adding “depthWrite:false” to the FrontSide one.

By switching the order of the materials, I realize only the first one gets drawn. The depthWrite parameter on the FrontSide didn’t change anything.

Are you sure this trick can be applied on asingle Mesh? I can make it work with a Group of two meshes, each one with its own material:

    const geometry = new CylinderGeometry(6, 6, 8, 32, 1, true)
    this.cylinder = new Group() 
    this.cylinder.add(new Mesh(
      geometry,
      new ShaderMaterial({ side: BackSide, transparent: true, vertexShader, fragmentShader, uniforms: { time: { value: 0 } } })
    ))
    this.cylinder.add(new Mesh(
      geometry,
      new ShaderMaterial({ side: FrontSide, transparent: true, vertexShader, fragmentShader, uniforms: { time: { value: 0 } } }),
    ))
    this.cylinder.rotation.x = Math.PI / 4
    this.scene.add(this.cylinder)

But that is probably not the most efficient way, is it?

Thank you for taking time to help me.

I think 2 meshes is a fine solution! :slight_smile:

1 Like