Skip to content

Screen Space Reflection - SSR

A reflection effect implementation based on screen space, which simulates the visual effect where a smooth object surface can reflect the images of surrounding objects in real time. The advantage of this reflection effect is real-time rendering: when an object moves, the object in the reflected image also moves; it can reflect accurately from each pixel. The disadvantage is that it cannot reflect the back of an object, and objects outside the screen range cannot be reflected onto other objects either.

ts
//Initialize the engine
let engine = await Engine3D.init();

engine.setting.render.postProcessing.ssr.fadeEdgeRatio = 0.2;
engine.setting.render.postProcessing.ssr.rayMarchRatio = 0.5;
engine.setting.render.postProcessing.ssr.fadeDistanceMin = 600;
engine.setting.render.postProcessing.ssr.fadeDistanceMax = 2000;
engine.setting.render.postProcessing.ssr.roughnessThreshold = 0.5;
engine.setting.render.postProcessing.ssr.powDotRN = 0.2;

// Add SSRPost
let postProcessing = this.scene.addComponent(PostProcessingComponent);
postProcessing.addPost(SSRPost);

//Start rendering
let view = new View3D();
view.scene = this.scene;
view.camera = this.camera;
engine.startRenderView(view);

engine.setting.render.postProcessing.ssr configuration parameters.

ParameterTypeDescription
fadeEdgeRationumberGradient rate
rayMarchRationumberRay marching rate
fadeDistanceMinnumberMinimum fade distance
fadeDistanceMaxnumberMaximum fade distance
roughnessThresholdnumberRoughness threshold
powDotRNnumberThe pow parameter of the dot product of normal and reflection

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

<
ts
import { DirectLight, Engine3D, View3D, LitMaterial, HoverCameraController, KelvinUtil, MeshRenderer, Object3D, PlaneGeometry, Scene3D, SphereGeometry, SSRPost, Time, CameraUtil, PostProcessingComponent, BloomPost, AtmosphericComponent } from '@orillusion/core';
import * as dat from 'dat.gui';

class Sample_SSR {
    lightObj: Object3D;
    scene: Scene3D;
    mats: any[];
    engine: Engine3D;

    constructor() {}

    async run() {
        this.engine = await Engine3D.init({
            canvasConfig: {
                devicePixelRatio: 1
            },
            renderLoop: () => this.loop(),
            setting: {
                shadow: {
                    enable: true,
                    shadowSize: 2048,
                    shadowBound: 200,
                    shadowBias: 0.05
                }
            }
        });

        this.scene = new Scene3D();
        this.scene.addComponent(AtmosphericComponent).sunY = 0.6;

        let mainCamera = CameraUtil.createCamera3DObject(this.scene, 'camera');
        mainCamera.perspective(60, this.engine.aspect, 1, 2000.0);
        let ctrl = mainCamera.object3D.addComponent(HoverCameraController);
        ctrl.setCamera(-75, -20, 40);
        await this.initScene(this.scene);

        let view = new View3D();
        view.scene = this.scene;
        view.camera = mainCamera;
        this.engine.startRenderView(view);

        let postProcessing = this.scene.addComponent(PostProcessingComponent);
        postProcessing.addPost(SSRPost);
        postProcessing.addPost(BloomPost);
    }

    async initScene(scene: Scene3D) {
        /******** light *******/
        {
            this.lightObj = new Object3D();
            this.lightObj.rotationX = 15;
            this.lightObj.rotationY = 110;
            this.lightObj.rotationZ = 0;
            let lc = this.lightObj.addComponent(DirectLight);
            lc.lightColor = KelvinUtil.color_temperature_to_rgb(5355);
            lc.castShadow = true;
            lc.intensity = 10;
            scene.addChild(this.lightObj);
        }

        // load test model
        let minimalObj = await this.engine.res.loadGltf('https://cdn.orillusion.com/PBR/ToyCar/ToyCar.gltf');
        minimalObj.scaleX = minimalObj.scaleY = minimalObj.scaleZ = 1000;
        minimalObj.y = -1.1
        scene.addChild(minimalObj);

        minimalObj.forChild((obj: Object3D) => {
            let mr = obj.getComponent(MeshRenderer)
            if (mr && mr.material) {
                if (mr.material.name == 'ToyCar') {
                    let mat = mr.material as LitMaterial;
                    mat.metallic = 0.9;
                    mat.roughness = 0.1;
                    mat.clearcoatFactor = 0.5;
                }
            }
        })

        await this.createPlane(scene);
        return true;
    }

    private sphere: Object3D;

    private async createPlane(scene: Scene3D) {
        const GUIHelp = new dat.GUI();
        {
            let floorMaterial = new LitMaterial();
            floorMaterial.roughness = 0.1;
            floorMaterial.metallic = 1;

            let planeGeometry = new PlaneGeometry(200, 200);
            let floor: Object3D = new Object3D();
            let mr = floor.addComponent(MeshRenderer);
            mr.material = floorMaterial;
            mr.geometry = planeGeometry;
            scene.addChild(floor);

            let f = GUIHelp.addFolder('floor')
            f.add(floorMaterial, 'roughness', 0.01, 1, 0.01);
            f.add(floorMaterial, 'metallic', 0.01, 1, 0.01);
            f.open()
        }

        {
            let mat = new LitMaterial();
            mat.roughness = 0.1;
            mat.metallic = 0.9;
    
            let sphereGeometry = new SphereGeometry(10, 50, 50);
            let obj: Object3D = new Object3D();
            let mr = obj.addComponent(MeshRenderer);
            mr.material = mat;
            mr.geometry = sphereGeometry;
            obj.x = 30;
            obj.y = 10;
            scene.addChild(obj);
            this.sphere = obj;

            let f = GUIHelp.addFolder('Sphere')
            f.add(mat, 'roughness', 0.01, 1, 0.01);
            f.add(mat, 'metallic', 0.01, 1, 0.01);
            f.open()
        }

        {
            let sphereGeometry = new SphereGeometry(2, 50, 50);
            for (let i = 0; i < 10; i += 2) {
                for (let j = 0; j < 10; j += 2) {
                    let rmMaterial = new LitMaterial();
                    rmMaterial.roughness = j / 10;
                    rmMaterial.metallic = i / 10;

                    let obj: Object3D = new Object3D();
                    let mr = obj.addComponent(MeshRenderer);
                    mr.material = rmMaterial;
                    mr.geometry = sphereGeometry;

                    obj.y = j * 5 + 10;
                    obj.x = 50;
                    obj.z = i * 5 - 25;
                    scene.addChild(obj);
                }
            }
        }
    }

    private loop(): void {
        if (this.sphere) {
            this.sphere.x = Math.sin(Time.time * 0.0001) * 30;
            this.sphere.z = Math.cos(Time.time * 0.0001) * 30;
        }
    }
}

new Sample_SSR().run();

Released under the MIT License