Refracting glass sphere, how to add some reflection

Hi there,
recently I created a panoramic viewer with a reflecting glass sphere in the centre:


As a next step I tried to make the sphere transparent and refracting instead of reflecting. Struggled for hours and finally found out that there was a simple solution:
Simply use THREE.EquirectangularRefractionMapping instead of THREE.EquirectangularReflectionMapping:

However there is one issue left: When looking at real glass spheres, e. g. here:

it’s obvious that my virtual version is not really realistic. On the real sphere there is some reflection on top.
Is there a way to simulate this effect too?
Best regards - Ulrich

Some comments regarding your code:

  • Setting toneMappingExposure without defining no tone mapping type has no effect.
  • For an unlit scene like yours, use MeshBasicMaterial and not an expensive PBR material like MeshPhysicalMaterial.
  • I’m not sure why but the refractions of your sphere are flipped. It should actually look like in this example.
  • There is no need to make the material double sided and transparent.
1 Like

Hi Mugen87, thanks for these hints, I modified my code accordingly.
Regarding the flipping: This is just the effect I intended to achieve. A real glass sphere does it the same way. Have a look at the photos on Pinterest.

My latest version:

Any ideas how to make the refracting version more realistic? I mean the faint bright light on top. I suspect I would have to add another sphere with a solid surface and some light. However I don’t know how to combine both.

One solution may be screen-space-reflection + screen-space-refraction.

There are already many implementations listed below, but I’m making my own implementation suit with latest three.js and can use with WebGL1, now just screen-space-reflection, intend to make screen-space-refraction next.





I don’t think SSR is appropriate for this use case.

1 Like

Hi gonnavis and thanks for this info. Seems to be a complex subject and I’m not shure if my skills are sufficient to dive into.

When I look at real photos of a glass sphere closely it seems to me that there is a combination of refraction and reflection: In the center the sphere is transparent while near to the border the background is reflected. Just like the surface of the virtual water: When the viewing angle is large, it’s transparent and when the angle is small the water is reflecting. Not shure if it’s possible to simulate this by use of three.js.

Best regards - Ulrich

This kind of physically correct rendering will be hard to achieve. I have not seen it before with any type of realtime 3D engine. You probably need raytracing for this.

Yes, shurely this is true. The water demos are looking fine, however a sphere is apparently more difficult to make as it’s not flat.

In contrast, shining light was fairly easy. The illuminated surface could be merged with the refraction or reflection but this was not what was in my mind and was visible on the photos of real spheres.

This is called Fresnel Effect, I implemented it using a very simple dot calculation ( commit ). Should not be physically correct, but I think the feeling is ok.

float fresnel=(dot(viewIncidenceDir,viewReflectDir)+1.)/2.;
op*=fresnel;

Demo, Demo2, Demo3



image

Hm, did I miss the point why you couldn’t use refraction envmap ?

Even more, if you combine what @Mugen87 and @gonnavis said (maybe except SSR) you should be able to create a realistic glass ball like in the pinterest you linked.

  1. You don’t need opacity / transparency at all, the ball you linked is 100% opaque refraction (the reflections you see on it are not transparency, it’s a reflection environment mapping, transparency will result in a flat reflection.)
  2. It should be quite simple to create a fragment shader that mixes refraction envmap with reflection envmap (same example, just toggle the switch on the right, it’s just 2 textures with different mapping).
  3. If you’d like edges to fade to some color opaque (ie. fresnel) you can also mix in fresnel shader.

In this example, https://sbcode.net/threejs/refract-reflect/
I create an inner and outer shine effect by creating 2 objects with different materials.
You can experiment with the gui sliders on the link above.

Three r122

    const material1 = new THREE.MeshPhysicalMaterial({
        metalness: 1.0,
        roughness: 0.05,
        refractionRatio: 0.1,
        envMap: cubeRenderTarget.texture,
        transparent: true,
        transmission: 1.0,
        side: THREE.BackSide
    });
    const material2 = new THREE.MeshPhysicalMaterial({
        metalness: 0,
        roughness: 0.2,
        envMap: envTexture,
        transparent: true,
        transmission: 1.0,
        side: THREE.FrontSide
    });

Are you sure about ball2 tho @seanwasere ?

This is how real refraction looks (notice the fish-eye effect):

This is how your refraction ball looks (with ball1 hidden, since it’s only reflection if I see correctly):

Screenshot 2020-11-01 at 10.11.15

Refraction gives a little different effect on the edges compared to just turning down the opacity.

Lol, I knew there was something fundamentally wrong with it,
What about now? This is the best I can do.

With lower opacity it looks like a bubble

With lower metalness it looks more glassy

4 Likes

Yeah, you’re right, I probably messed up the sliders… :sweat_smile: (But mind - the only realistic value for opacity is 1.0. Even a bubble should refract edges afair, since it’s a curved surface. Transparency kinda breaks the effect of realistic surface.)

So yeah, it all got me intrigued, and I tried to recreate this image with every - @seanwasere, @gonnavis, and Fresnel - approach, and in the end this glass ball is as far as I managed to get without a custom shader:

(1) @seanwasere approach, ie. 2 spheres + 2 env is the easiest and looks quite realistic enough. In a super-simplified version it technically needs just 3-4 params for materials (just don’t use opacity, that’s what envMaps and cubemap cameras are for. :upside_down_face: )
(2) As little as it makes sense I tried the experimental SSR branch, since I have it locally - but because it’s post-processing, reflections affect the rendered lights and it just looked a bit awkward. :face_with_raised_eyebrow: (the white light-dots on glass surface disappeared etc.)
(3) Fresnel shader looks by far the best - but first, it works only with cube map textures, second, it doesn’t seem to be affected by scene lights which kills the purpose. :thinking:

But it’d still be best to do a custom shader, since none of these could do the focused light effect on the other side of the ball (at least dynamically, and it could be easily calculated from sun position and IOR) :

2 Likes

Many thanks for the numerous answers and information. Unfortunately, as I’m a novice to threejs, I was not able to understand all of this completely.
@seanwasere Your latest demo is looking very fine. I intented to download it and fiddle but when using the downloading function of my browser it didn’t download the modules being imported. I tried Opera, Chrome, Firefox, Edge.
Is there an easy way of downloading a complete page including javascript modules?
Best regards - Ulrich

@mjurczyk I tested your demo as well and after some fiddling it seems to me that I understand how it works. Many thanks for this contribution! There are two issues:

  • The reflection at the edge is a bit too faint
  • The reflection in the center is too strong when looking at a dark ground: The sphere reflects the blue sky then.

You mentioned a custom shader. Can this be implemented by javascript? If so I would do some research and try. If not, it would be too complex for me.

PS:

I found the answer: It has to be implemented in GLSL. Thus too complex for me.

That’s caused by your requirement for the reflection to be flipped - not much can be done about that, unless you’ll involve real raytracing Mugen mentioned earlier.

Gotchu, so first of all - there’s a bit of magic happening with these materials that make them look ok - that includes never touching transmission: .99. Otherwise you’ll render body colour and reflections of the external sphere.

To make the edge show more / less, you can add envMapIntensity and set it to anything from 0.0 to infinity (technically you’re only allowed to set it between 0.0 - 1.0, but rules are there to be broken. Setting intensity above 1.0 basically counters the fact that we set transmission to nearly 1.0):

// Add refraction (inner sphere)
mesh.add(new THREE.Mesh(
  new THREE.SphereBufferGeometry(2, 32, 32),
  new THREE.MeshStandardMaterial({
    envMap: createEnvMap('refraction'),
    metalness: 1.0,
    roughness: 0.1
  })
));
// Add reflection (half-transparent outer sphere)
mesh.add(new THREE.Mesh(
  new THREE.SphereBufferGeometry(2 + Number.MIN_VALUE, 32, 32),
  new THREE.MeshPhysicalMaterial({
    envMap: createEnvMap('reflection'),
    envMapIntensity: 2.0, // <- Changing this changes the fake fresnel effect
    roughness: 0.1,
    transmission: .99, // <- Changing this will reveal that it's just fake fresnel, so lets never do that
    transparent: true
  })
));

Screenshot 2020-11-01 at 16.36.58
Screenshot 2020-11-01 at 16.37.08
Screenshot 2020-11-01 at 16.37.25

If you’d change transmission or opacity (or going too high with envMapIntensity) on either sphere you’ll start seeing this reflections also in the middle of the sphere, and it’s not good:

Screenshot 2020-11-01 at 16.18.10

I wouldn’t judge it that fast. GLSL is very simple - it’s the math that can make shaders complex. Here’s an example of a shader that creates a real Fresnel effect:

FresnelShader.js

Ignoring the horrible code readability, the entire shader is like ~20 lines of code, out of which 75% is addition and multiplication of some texture values and vectors.

1 Like

Hi there,
sorry for replying late. In the meantime I fiddled a lot and at last ran into an issue I cannot fix myself. Gonna post this later …

@ mjurczyk

To make the edge show more / less, you can add envMapIntensity and set it to anything from 0.0 to infinity

This reads promising. I’m gonna take your code and give it a try.

An intermediate result I achieved is this:


Bare with me when viewing the code, shurely it might have be done more simple and better. However I learned a lot about threejs when coding it.

Best regards - Ulrich

2 Likes