Reducing moiré-effect on certain angles

While I was generating my meshes I ran into this visual issue:
image

I am pretty sure this is a moiré-effect but I cant find anything to get rid of it or at least reduce it.
Is there anything I can do to reduce this? It only happens under specific angles, but if possible I would like to get rid of it altogether.

Have you enable antialiasing (MSAA) like so?

const renderer = new THREE.WebGLRenderer( { antialias: true } );

yes I have, seemed like the first logical thing to do :smiley:

Then I suppose you also configured the pixel ratio? This can make a noticeable difference on certain devices with retina displays.

It might be helpful to see my renderer code, jsut in case I overlooked anything. As you can see I have an FXAA pass on my composer, and the renderer class is setting the pixel ratio at the start of the web page.

export class Renderer extends THREE.WebGLRenderer {
    constructor(threeCanvas: HTMLCanvasElement) {
        super({ canvas: threeCanvas });

        this.setPixelRatio(window.devicePixelRatio);
        this.setSize(threeCanvas.clientWidth, threeCanvas.clientHeight);
        this.setClearColor('#777777');
        this.shadowMap.enabled = true;
        this.shadowMap.type = THREE.PCFSoftShadowMap;
    }
}

export class Composer extends EffectComposer {
    outlinePass: OutlinePass;
    private renderPass: RenderPass;
    private FXAAPass: ShaderPass;

    constructor(scene: THREE.Scene, perspectiveCamera: THREE.PerspectiveCamera, renderer: Renderer) {
        super(renderer);

        const size = new THREE.Vector2(renderer.domElement.clientWidth, renderer.domElement.clientHeight);
        renderer.setSize(size.x, size.y);
        this.setSize(size.x, size.y);

        this.outlinePass = new OutlinePass(size, scene, perspectiveCamera);
        this.renderPass = new RenderPass(scene, perspectiveCamera);
        this.FXAAPass = new ShaderPass(FXAAShader);
        // tslint:disable-next-line: no-string-literal
        this.FXAAPass.uniforms['resolution'].value.set(1 / size.x, 1 / size.y);
        this.updateRaycaster(size);

        this.addPass(this.renderPass);
        this.addPass(this.outlinePass);
        this.addPass(this.FXAAPass);
    }

    updateRaycaster(size: THREE.Vector2) {
        this.outlinePass.resolution = size;
    }

    toggleAntiAliasing() {
        this.FXAAPass.enabled = !this.FXAAPass.enabled;
    }

    getSelectedObject() {
        return this.outlinePass.selectedObjects[0];
    }
}

Do I need to enable MSAA on the renderer itself as well if I am using the shaderpass that does the FXAA? Are these the same?

When using FXAA, it’s not necessary to enable MSAA.

In general, MSAA can’t be used in context for RTT since it only works when rendering to the default framebuffer (screen).

As far as I know, no matter what you do there are going to be certain situations where moiré occurs. This applies to all digital images, not just 3D. Your example with lots of tiny parallel straight lines is close to a worst case scenario.

I’ve found FXAA particularly bad at handling this.
Have you tried using the SMAA pass instead?

Or, if using WebGL2 is an option, use a multi-sampled render target:

https://threejs.org/examples/?q=ren#webgl2_multisampled_renderbuffers

If you do this, you don’t need the final FXAA pass.

2 Likes

The best option to get rid of this issue is to not use geometry for these kind of elements if possible or using the repeated texture representation / impostor on distance since regardless of MSAA if even available on the device it will run into this issue at some point.

2 Likes

@looeee I’ll need to check whether the platform we are targetting has support for WebGL2. For the SMAA, I will try it and see what the difference is.

@Fyrestar This might be difficult since the height is variable, I know how to make the planks generate in mesh, but for texture generation and manipulation at runtime I am not sure if I can do that (with three.js), sounds more like an OpenCV kind of thing.

Torben Van Assche,

Awesome name. There are 3 directions you can go, generally speaking:

  • Invest in better rendering pipeline with more sophisticated AA (MSAA and the like)
  • Adjust your scene/camera angle, either increase the slits sufficiently or bring the camera closer if possible to achieve the same effect. Or get rid of the slits all together.
  • Bake slitted geometry out into a texture, use decent resolution texture, enable filtering and mipmaps and let the hardware filtering+mips do the job of representing those slits at sub-pixel scale.

I suggest the last approach, as suggested by @Fyrestar. There are some things you can and can’t represent well in rasterized 3D, long thin geometry is pretty much up there at the top of the list when it comes to pathologically bad geometry to render. Try to avoid it.

3 Likes

This might be difficult since the height is variable, I know how to make the planks generate in mesh, but for texture generation and manipulation at runtime I am not sure if I can do that (with three.js), sounds more like an OpenCV kind of thing.

@Usnul Unless I do some runtime image processing this wont really work will it? Or should I tile my texture over the mesh (just a block than instead) if a buffergeometry is changed in size the texture should tile instead of stretching. Thats what I think my solution might be.

Note: This would probably also significantly impact performance, thats way less meshes (haven’t had performance issues, but at some point they might require me to do such things.)

Are you creating the horizontal lines using a mesh or a texture? From everyone’s responses, I assume the latter. But just wanted to make sure.

If texture, you might be able to reduce the effect by drawing some of the lines in the mesh. (Not all, since that would slow things down.)

You might also be able to break things up by applying separate textures to sections of the mesh rather than applying a single texture to the whole side of the mesh.

Or perhaps use a normal map to create texture? (Although that increases comp time and sometimes creates new problems.)

Phil

It actually are meshes. The choice was made to make more detail possible and some versatility for the frame. I am calculating the bounding box of the parent and drawing the elements as much as they can fit in between it.

I have been looking at doing it with a texture instead, but the procedurality of my project is causing me a bit of issues.

I don’t quite know what you mean by using a normal map. Wouldn’t an opacity map make more sense?

Sorry for the delayed response. Was visiting relatives and caught the flu.

If you are using meshes, there may be some answers in the forums dealing with modeling. That is an interesting pattern as it appears that the moire is appearing in areas where there are gaps in the mesh. Almost as if is is created by light reflections on top of the slats.

Would smoothing help? I assume that each slat is a single plane. If so, it might help to use 2 planes, including a small plane at the top which has a bit of a bevel (like Blender Guru did to smooth the top of his coffee cup).

Yes, I did mean a normal map (the kind with the purple background). But that was before I realized that the surface you have created is transparent. In that case, an opacity map does make more sense. Or you could interleave transparent planes between the slats or put a single transparent plane behind (or in front of) the mesh. You could vary the properties of the plane, to prevent reflections or to make it only mostly transparent.

These are just ideas and I am not sure if any will work. Unfortunately, when a program displays a series of lines that are closely parallel with each other, there is a tendency for a moire pattern to appear when the lines are tilted. But I am not 100% that is what is causing the problem here.