Render artifacts with KHR_materials_transmission and high roughness

Hello Babylon experts!

I’m seeing some unpleasant rendering artifacts for transmissive materials (i.e. glTF files using the KHR_materials_transmission extension) and, at the same time, a high roughness value.

Non-transparent objects behind this material renders very blocky/pixelated. The issues almost disappears when I reduce the roughness value:

I think that Babylon is rendering transparent objects in two passes. One pass with only opaque materials and then another one with the transparent objects to allow you to “see what’s behind” the transparent materials – I guess roughness is implemented by blurring the result from the opaque render pass - it seems like the approach to blurring (sampling a higher mipmap level?) might be the cause of this.

This is all speculation on my part :slight_smile: of course — My question: Is this a bug or by-design, maybe due to performance considerations?

Hey this is exactly it :slight_smile: sampling higher mip only for performance considerations @MiiBond is actually adding a bit more to it like depth peeling but it would not sort the roughness part. I wonder if turning realTimeFiltering would help ? it would not be physically accurate but could remove some of the jagginess.

Thank you for the fast response and suggestion!

I didn’t have much success with the realtime filtering option, unfortunately.
I was comparing against the glTF sample viewer from Khronos, which does seem to produce more pleasing results for this particular case. Would it be appropriate to create a feature request to improve this in Babylon?

The main issue is we can not do a lot more without adding some pretty big extra costs :frowning: Maybe @Evgeni_Popov would have a pretty cool idea ? or we might need to gaussian blur instead of mip maps :slight_smile:

You could try to change the parameters of the render target we use to render the opaque objects:
https://playground.babylonjs.com/#SYQW69#891

Notably, lodGenerationOffset is the offset applied to the LOD used to lookup the texel in the RTT. The smaller value the less blurry because we sample the largest LODs (we sample the LOD (n+lodGenerationOffset), the LOD 0 being the picture with original sizes). The default value is -4, so you can try to lower this value (-5, -6), but the result will be less blurry.
Or you can try to raise the RTT size (1024 by default) to get better resolution.

Thanks for the suggestions. I will try to experiment with this.

I am curious though: would a small Gaussian filter (3x3 or 5x5, for example), sampling from an appropriate LOD level, be prohibitively expensive here?

It is 8 (kernel 3x3) / 24 (kernel 5x5) additional texture reads each time we have to sample the refraction texture…

So, it depends on the number of times this sampling must occur, but it could be noticeable if you have some big areas covered with the refraction texture.

You could also try to pre-blur the different LODs of the refraction texture, but this pre-blur must occur each frame (at least each time the refraction texture is regenerated, and it is currently at each frame, even if nothing is moving), so it will also take some time, which may be bigger than doing the 8/24 additional lookups in the shader if the area covered by the refraction texture is not too big.

So, I guess you would need to make the changes and see how it goes for you. The changes would need to be made in Shaders/ShadersInclude/pbrBlockSubSurface.fx around line 267. For example, assuming the refraction is coming from a 2D texture and that the texture is 256x256, you could do (for a 3x3 blur):

float tsize = 1.0/256.0;
environmentRefraction =
    sampleRefractionLod(refractionSampler, refractionCoords + vec2(0., 0.), requestedRefractionLOD) * 0.25 +
    sampleRefractionLod(refractionSampler, refractionCoords + vec2(0., -tsize), requestedRefractionLOD) * 0.125 +
    sampleRefractionLod(refractionSampler, refractionCoords + vec2(-tsize, 0.), requestedRefractionLOD) * 0.125 +
    sampleRefractionLod(refractionSampler, refractionCoords + vec2(0., tsize), requestedRefractionLOD) * 0.125 +
    sampleRefractionLod(refractionSampler, refractionCoords + vec2(tsize, 0.), requestedRefractionLOD) * 0.125 +
    sampleRefractionLod(refractionSampler, refractionCoords + vec2(-tsize, -tsize), requestedRefractionLOD) * 0.0625 +
    sampleRefractionLod(refractionSampler, refractionCoords + vec2(tsize, -tsize), requestedRefractionLOD) * 0.0625 +
    sampleRefractionLod(refractionSampler, refractionCoords + vec2(-tsize, tsize), requestedRefractionLOD) * 0.0625 +
    sampleRefractionLod(refractionSampler, refractionCoords + vec2(tsize, tsize), requestedRefractionLOD) * 0.0625;

Thanks a lot for the insights and code references. Very helpful!

Filament is pre blurring the Render Target with a gaussian and they seem to have great results without too much of a perf hit from what Romain told me. I guess we should try to support smthg like this in Babylon as a high quality low perf mode ???

Adding as a great reference filament/Renderer.cpp at main · google/filament · GitHub

1 Like

Pre-filtering seems like a good approach – I can create a feature request for this, perhaps?

1 Like