Simulating Refraction in Three.js

Last time, I made a simple wine glass in Blender and rendered it using Three.js. It mostly demonstrated the transmission
property of the MeshPhysicalMaterial
. This time I’m gonna show you how to simulate the refractive effects of the MeshPhysicalMaterial
.
How refraction works in Three.js
It is actually much simpler than you think.
All you need for simulating refraction in Three.js is having these three properties set:
const material1 = new THREE.MeshPhysicalMaterial({
roughness: 0,
transmission: 1,
thickness: 2
});
First of all, setting transmission
to 1 makes it transmissive. Then, set roughness
to 0 or a very low value such that the material surface is smooth enough for us to clearly see through it. The final magic: giving your material a thickness
so that it has the effect of a magnifying glass. The higher the thickness, the higher the magnifying property of your material.
Try my codesandbox embed below and tweak the settings to see how it affects the outcome:
There is one caveat though. Although the effects look real enough, it is not actually how real-life refraction works!
In real-life scenarios, if the refracted scenery or object is far enough from the glass orb, you should actually see an upside-down refraction. But in Three.js, this upside-down effect cannot be achieved from setting thickness
in the PBR material.

Iterations with normalMap and clearcoatNormalMap
I also made this other example that has 4 different glass orbs to show how other map properties affect the final refractive outcome in different interesting ways.
Firstly, I loaded an equirectangular texture on the envMap
of all these orbs such that they have more realistic reflections.
For the first(top-left) orb, it has the most basic settings.
For the second(top-right) orb, I turned clearcoat
to max and set a clearcoatNormalMap
which is basically a normal map of scratched lines. You should be able to spot out some of those scratches at the brightest areas on the orb’s reflection. Clearcoat simulates the effect of another layer of reflective coating being applied on surfaces. Real-life examples are car paint, polished floor and wet surfaces. I also made it rougher and thicker so as to act as a clearer comparison to the first orb.
const material2 = new THREE.MeshPhysicalMaterial({
envMap: hdrEquirect,
roughness: 0.15,
clearcoat: 1,
clearcoatNormalMap: clearcoatNormal,
transmission: 1,
thickness: 4
});
For the third(bottom-left) orb, I applied a golf ball normal map for the normalMap
property, resulting in this beautiful glass golf ball. We apply normal maps on materials to instruct Three.js to calculate reflections/refractions on each pixel according to the normal values provided by the normal map. The result is that reflections/refractions will look as if the material surface is uneven and bumpy, but in fact the material surface is still smoothly rendered according to the geometry.
For the refractions on the golf ball pattern to be more evident, I have to add in a little roughness.
const material3 = new THREE.MeshPhysicalMaterial({
envMap: hdrEquirect,
normalMap: golfNormal,
roughness: 0.15,
transmission: 1,
thickness: 2
});

For the last orb, I applied the scratched clearcoatNormalMap
and a different normalMap
to contrast with the golf orb.
const material4 = new THREE.MeshPhysicalMaterial({
envMap: hdrEquirect,
normalMap: roughNormal,
clearcoat: 1,
clearcoatNormalMap: clearcoatNormal,
roughness: 0.15,
transmission: 1,
thickness: 2
});

And that’s it for this tutorial! Feel free to fork my codesandbox projects and mess around.
Shout out to https://tympanus.net/codrops/2021/10/27/creating-the-effect-of-transparent-glass-and-plastic-in-three-js/ which is a great tutorial and inspiration for me on this demo.