Blurred reflections

I’ve implemented a solution that is better for performance and easier to use. Just a simple ENVMAP image, generated by a CubeCamera in real time or an cubemap texture, and then projected using box projected (aka parallax-corrected) cubemap. It only misses the faded reflection, but in my case, I just painted the fade directly into the texture from cubemap environment.

In case it helps, you can see it in action here (inside the apartment): http://realestate-neotix.vercel.app/
You can see the video here: Threejs Parallax Corrected Cube Map Environment Mapping - YouTube

I hope that helps anyone who searches for it and ends in this discussion here. I always get back here when searching for “blurry reflections threejs” :slight_smile:

1 Like

Amazing work, well done. Any possible way to get hold of the code for my project?
Would love to write this off my todo list :slight_smile:

1 Like

Hello everybody.

Yannic from twitter (https://twitter.com/0beqz) was able to convert it to Vanilla :star_struck:. It is working like a charm and it is very easy to use it. It suports the blur with depth based fade.

It is awesome. A great achievement and I’m sharing it here in case someone searches for it and end up here.

This is the first result I got with this: https://twitter.com/Andersonmancini/status/1499514798433869829?t=XubsW0VLmxS848HTcXFd-w&s=19

4 Likes

WOW! I had no idea this was so easy nowadays.
And I’m already using react-three. I should have been coming here much more often!

1 Like

Holy awesomeness Batman, wow pow aaaaaaaamazing

1 Like

always a good idea to check GitHub - pmndrs/drei: 🥉 useful helpers for react-three-fiber for updates. useful things get added on the regular :slight_smile:

1 Like

Gentlemen.
I have been looking for this exact thing. I have forked the “vanilla js” gist shared above, which is still using es6-style import declarations, and converted it to es5 here:

The reflection works, but for some reason the blur does not, and I think I am passing the params correctly. I noticed when the blur settings are turned on, I get this error in the browser:

[.WebGL-0x1dea00373800] GL_INVALID_FRAMEBUFFER_OPERATION: Draw framebuffer is incomplete

Also, this example requires building the es5 version of this postprocessing library:

I can share that code here soon.

I am running r129 of threejs (es5 flavor), so perhaps my problem with the blur is that version is perhaps incompatible by age. If anyone has some insights, do share. I will be debugging this and hopefully figuring it out over the next few days, because this is a high-value feature for my client.

1 Like

that file you have linked has blur pass commented out at line 98, maybe thats why :sweat_smile:

Line 98 is BlurPass, which I drafted from the newer drei tsx file to replace KawaseBlurPass, which is not commented out in that file. I had it commented out, because I was trying to switch between these two classes to see if that would make it work. So this is not the problem.

I actually got the blur to show. The problem was that I had my blur size numbers too small, so I wasn’t seeing the blur (Higher blur size == less blur, lower == more blur, which is a bit counter-intuitive).

However, now I am noticing a problem once depthScale is turned on, and I can even see the same problem in 0beqz’s example:

With depthScale off and blur off, you can see the reflection correctly like so:

Then, when you add blur, it also appears correct:

But by the time you add “depthScale”, the mix between the two is such that the unblurred reflection renders all the material white:

You can see in the above photo the part reflecting closest to the object is almost pure white.

I keep fiddling with the settings and I can’t seem to solve this. The examples in DREI don’t seem to have this problem, even the shader code appears identical to me. It is as if the depth part is not blending correctly with the blur.

1 Like

I think you need to play with these two parameters:
depthScale
depthToBlurRatioBias

In my scenes using mesh reflector, I had to add them to the GUI so I can tweak them visually to find the correct values for my scenes. And they change completely based on model scale, scene scale, etc.

In this demo you can see the mesh reflector being used into the floor. Open the GUI reflector folder and you can see the parameters I’m using in this particular scene. Play with them and see how it affects the reflector.

I was able to get very nice sharp reflection near the walls and very blurry reflection as distance grows. Those two parameters were crucial to achieve that ;).

I’m also using it in my gallery scene and looks fantastic. Again, very sharp to the edges and very blurry into the distance. See it in action here (time: 0:17): Threejs Gallery Demo Update - YouTube

So, I hope this can help you. Please let me know if you need any adicional help, okay?

Best,
Anderson Mancini

2 Likes

Screenshot 2023-02-06 at 2.56.52 PM
It says options object is not defined. What should I do?

Screenshot 2023-02-06 at 3.12.04 PM
I was actually not passing the mess in the params. Sorry about the previous annoying question. Here’s another one. I’ve manually linked all the cdns using script tags, and MeshReflectorMaterial isn’t getting Postprocessing.js How can this be solved.
Pleas Help!!!

you need

import * as POSTPROCESSING from "postprocessing"

i don’t know about script tags and cdn’s, this is an npm package. it would be easier if you have a sane dev environment, this shouldn’t be much more than a “npm install postprocessing”. quickly skip through this: Three.js Journey — Local Server

1 Like

Which means I’ll now have to convert my whole project to a dev env in order to have these blurred reflections. :face_with_diagonal_mouth: :sweat:

just a suggestion. it will certainly help.

1 Like

Screenshot 2023-02-06 at 5.21.49 PM
Alright, I’ve moved my whole project to a dev env but now I’m stucked here.

import * as THREE from 'three';
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass";
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass";
import { LuminosityHighPassShader } from "three/examples/jsm/shaders/LuminosityHighPassShader.js";
import { CopyShader } from "three/examples/jsm/shaders/CopyShader.js";
import { Reflector} from "three/examples/jsm/objects/Reflector";
import {MeshReflectorMaterial} from '../../MeshReflectorMaterial';


const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
const renderer = new THREE.WebGLRenderer({
    antialias: true,


})

renderer.shadowMap.enabled = true
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputEncoding = THREE.sRGBEncoding;
// renderer.toneMapping = THREE.ACESFilmicToneMapping;
// renderer.toneMappingExposure = 1;
renderer.setClearColor(0xffffff, 1);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

const scene = new THREE.Scene();
const renderScene = new RenderPass( scene, camera );


// ========Enable Bloom========
const params = {
    exposure: 1,
    bloomStrength: 5,
    bloomThreshold: 0,
    bloomRadius: 0
  };
const bloomPass = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 1.5, 0.4, 0.85 );
bloomPass.threshold = params.bloomThreshold;
bloomPass.strength = params.bloomStrength;
bloomPass.radius = params.bloomRadius;

const bloomComposer = new EffectComposer( renderer );
bloomComposer.renderToScreen = false;
bloomComposer.addPass( renderScene );
bloomComposer.addPass( bloomPass );


const finalPass = new ShaderPass(
    new THREE.ShaderMaterial( {
      uniforms: {
        baseTexture: { value: null },
        bloomTexture: { value: bloomComposer.renderTarget2.texture }
      },
      vertexShader:`	varying vec2 vUv;

      void main() {

          vUv = uv;

          gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

      }
`,
      fragmentShader: `
      uniform sampler2D baseTexture;
      uniform sampler2D bloomTexture;

      varying vec2 vUv;

      void main() {

          gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );

      }`,
      defines: {}
    } ), "baseTexture"
  );
  finalPass.needsSwap = true;
  
  const finalComposer = new EffectComposer( renderer );
  finalComposer.addPass( renderScene );
  finalComposer.addPass( finalPass );
// ========Enable Bloom End========
// scene.background = new THREE.Color(0xffffff);



camera.position.set(0, 0, 8);


const orbit = new OrbitControls(camera, renderer.domElement);
// orbit.autoRotate = true;
// console.log(orbit.autoRotate);
// orbit.autoRotateSpeed = 0.5;
orbit.enableDamping = true;

// const pointLightShadow = new THREE.PointLight(0xffffff, 1);
// pointLightShadow.castShadow = true;
// pointLightShadow.shadow.mapSize.width = 4096;
// pointLightShadow.shadow.mapSize.height = 4096;
// pointLightShadow.position.set(30, 10, 10);
// scene.add(pointLightShadow);

// const pointLight = new THREE.PointLight(0xffffff, .9);
// pointLight.position.set(10, 0, 10);
// pointLight.castShadow = true;
// scene.add(pointLight);

// =====plane behind
// const bgTexture = new THREE.TextureLoader().load("./texture-bg.jpg");
const plane = new THREE.Mesh(new THREE.PlaneBufferGeometry(10, 10))
plane.position.y = -0.5
plane.rotation.x = -Math.PI / 2
scene.add(plane)



plane.material = new MeshReflectorMaterial(renderer, camera, scene, plane, {
    resolution: 1024,
    blur: [512, 128],
    mixBlur: 2.5,
    mixContrast: 1.5,
    mirror: 1
});





// const textureLoader = new THREE.TextureLoader();
// const normalMapTexture = textureLoader.load("./assets/normalMap.jpg");
// normalMapTexture.wrapS = THREE.RepeatWrapping;
// normalMapTexture.wrapT = THREE.RepeatWrapping;
// normalMapTexture.repeat.set(1, 1);

// const groundMirror = new THREE.Reflector( bgGeometry, {
//     clipBias: 0.001,
//     textureWidth: window.innerWidth * window.devicePixelRatio,
//     textureHeight: window.innerHeight * window.devicePixelRatio,
//     color: 0x777777,
//     blending: THREE.AdditiveBlending,
//     blur: 100,
//     resolution: 2048,
//     metalness: 0.5,
//     mixBlur: 1,
//     roughness: 1,
//     depthScale: 1.2,
//     minDepthThreshold0: 0.4,
//     maxDepthThreshold: 1.4
//     //     normalScale: new THREE.Vector2(1),
//     // normalMap: normalMapTexture,
//     // clearcoatNormalMap: normalMapTexture,
//     // clearcoatNormalScale: new THREE.Vector2(0.3)
    
   
// } );
// groundMirror.position.y = -0.01;
// groundMirror.rotateX( - Math.PI / 2 );
// groundMirror.receiveShadow = true
// scene.add( groundMirror );



const ambientLight = new THREE.AmbientLight(0xFFFFFF, 0.2);
scene.add(ambientLight);

const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 20, 20);
light.castShadow = true

scene.add(light);




// let envMap;
// let envmapLoader = new THREE.PMREMGenerator(renderer)

// const hdrEquirect = new THREE.RGBELoader().load(
//     "./empty_warehouse_01_4k.hdr",
//     () => {
//         hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
//         envMap = envmapLoader.fromCubemap(hdrEquirect)
//     }
// );






// ==========Adding 3D Model==========
let robot = null;
new THREE.GLTFLoader().load("./assets/ford_mustang_shelby_gt500/scene.gltf", (gltf) => {
    robot = gltf.scene;
    robot.position.set(0, 0, -2);
   robot.traverse(function (model) {
        if (model.isMesh) {
            model.castShadow = true;
        }
    });
    robot.getObjectByName("GT500Body_carpaint_0").material.color.setHex(0xffffff)
    scene.add(robot);
});








const clock = new THREE.Clock()
let lastElapsedTime = 0;

// animate
function animate(time) {
    orbit.update();

    const elapsedTime = clock.getElapsedTime()
    const deltaTime = elapsedTime - lastElapsedTime;
    lastElapsedTime = elapsedTime;
   
    bloomComposer.render();
    finalComposer.render();





    renderer.render(scene, camera)
    // camera.lookAt(scene.position);
}

renderer.setAnimationLoop(animate);



// resize code
window.addEventListener('resize', function () {
    const dpr = Math.min(pixelRatio, 2); // Cap DPR scaling to 2x

    // ====Enable Bloom Pass====
    // bloomPass.resolution.set(window.innerWidth, window.innerHeight);
    

    renderer.setPixelRatio(dpr);
    renderer.setSize(window.innerWidth, window.innerHeight);

    composer.setPixelRatio(dpr);
    composer.setSize(window.innerWidth, window.innerHeight);


    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight)


})

This is my whole three js code