How to generate 1024px cube-maps for plane reflections

Introduction

Comparison Reflection Quality! Full size image

This description shows how to implement high quality reflections on planes ('flat mesh') like mirrors. Comparing to organic and curved shapes where the environment reflects properly, a plane only reflects a very small part of the environment-image.

As the cube maps used for the environment reflection (ibl-tool for generating .env files) supports max. 512px per cube face, we end up having a reflection with poor quality.

Following we will explain how to manually set up a cube-map with 1024px per face and assign it as 'custom-reflection' for the correspondent mesh (mirror-plane).

Curved vs. Mirrored Reflection! Full size image

Please keep in mind to only use this technique when a 'mirror'-reflection is needed. For high quality backgrounds without 'mirror-reflections' refer to using the ENVIRONMENT_BACKGROUND specification (photoDome) as described in the environment section.

Preparing the Cube-Map - 1024px per cube side

The first step is to manually generate a new cube-map in jpg-format. As the cube-map used for lighting (256/512px .env) still remains in the scene, only a 8-bit image (jpg) for the 'custom-reflection' is needed (1024px each side).

  1. Download the corresponding hdr file (e.g. polyhaven.com) as 4k image and flip (mirror) it horizontally before saving a jpg-file
    • notice: differences when converting hdr-file to jpg-format (32bit to 8bit) (image)
  2. Convert the jpg-texture using 'Equirectangular to Cubemap' in 360-Toolkit (six seperate files)
  3. Rename the files following the specification (filename _??) - each file should have 1024px x 1024px
    • top.png = filename_py .png
    • bottom.png = filename_nz .png
    • front.png = filename_ny .png
    • back.png = filename_pz.png
    • left.png = filename_nx .png
    • right.png = filename_px .png
  4. Save the textures as .jpg for smaller file-sizes

Curved vs. Mirrored Reflection! Full size image

Using the new Cube-Map as Custom Reflection-Texture

Having the new cube-map texture prepared (1k per face), we can set up the 'custom-reflection' with higher resolution for the material of the correspondent mesh (plane/mirror). All other materials and reflections in the scene remain untouched, the initially assigned texture (256/512 env-file) in the scene parameters environment will be used for lighting (IBL) and reflection/refraction.

Create a CubeTexture (all 6x files with 'filename_??' are loaded automatically)

const reflFiles = [
filename_px.png,
filename_py.png,
filename_pz.png,
filename_nx.png,
filename_ny.png,
filename_nz.png,
];
const customReflection = new CubeTexture('', scene, null, null, reflFiles);

Assign 'customReflection' as 'reflectionTexture' (material of the plane/mirror)

// use an exisitng or new pbr-material (here: creating a new one)
const mirrorReflection = new PBRMaterial('mirrorReflection', scene);
mirrorReflection.reflectionTexture = customReflection;
mirrorMesh.material = mirrorReflection;

Rotating the 'customReflection'

// for rotating the reflection it's needed to use the 'ReflectionTextureMatrix'
const envRotation = 90;
const matrixRotCustom = Matrix.RotationY(Tools.ToRadians(-envRotation));
customReflection.setReflectionTextureMatrix(matrixRotCustom);

Using the new Cube-Map as Background-Image

By default when using a background-image a photo-dome is created using a 4k-jpg texture (equirectangular). To avoid loading an additional file into the scene, also the new cube-map can be used as skybox, instead of the equirectangular image within the photo-dome (about 2MB less in the scene).

To use the new cube-map as background-image we are going to create a skybox - note that in the viewer-specs only the parameter environment (env-file: lighting and scene reflection/refraction) and not environment.background is being used, as well as environment.usedefault must be set to false.

Scene Parameters - Spec (scene.environment only - lighting & reflection/refraction incl. environment rotation)

scene: {
parameters: {
'environment': 'url',
'environment.usedefault': false,
'environment.rotation: envRotation' // see above
};
};

Scene Spec (skybox for background re-using 'new' cube-texture)

// using 'customReflection' defined above:
const customSkybox = scene.createDefaultSkybox(customReflection, true, 10000);
customSkybox.isVisible = true;

Rotating Background, environmentReflection and CustomReflection (one value for all)

customSkybox.rotation = new BABYLON.Vector3(0, BABYLON.Tools.ToRadians(envRotation), 0);

Final Result! Full size image

Full Code Example

export function createSpec(): StructureJson {
// @ts-ignore
return {
scene: {
parameters: {
'environment': 'church_square_2k_512.env',
'environment.usedefault': false,
'environment.rotation': envRotation,
},
// ...
},
// ...
};
}
// ...
function runDemo(scene: Scene) {
// ENVIRONMENT & SKYBOX
const churchEnv = 'church_square_2k_512.env';
const churchCustReflFiles = [
filename_px.png,
filename_py.png,
filename_pz.png,
filename_nx.png,
filename_ny.png,
filename_nz.png,
];
// JPG-Skybox: Custom Reflection 'Mirror' (1024px CubeMap) -> inverted before 360Kit !!!
const customReflection = new CubeTexture('', scene, null, null, churchCustReflFiles);
const customSkybox = scene.createDefaultSkybox(customReflection, true, 10000);
if (!customSkybox) return;
customSkybox.name = 'customSkybox';
customSkybox.isVisible = true;
// ENV-Skybox: IBL + Reflection General (256px CubeMap)
const hdrTexture = new CubeTexture(churchEnv, scene);
scene.environmentTexture = hdrTexture;
// Applying Rotation - Background Image (both Skyboxes)
const envRotation = 90;
customSkybox.rotation = new Vector3(0, Tools.ToRadians(envRotation), 0);
// Applying Rotation on Default Reflection
const matrixRotDefault = Matrix.RotationY(Tools.ToRadians(-envRotation));
hdrTexture.setReflectionTextureMatrix(matrixRotDefault);
// Adjustment CustomReflection (adapt to skybox rotation)
const matrixRotCustom = Matrix.RotationY(Tools.ToRadians(-envRotation));
customReflection.setReflectionTextureMatrix(matrixRotCustom);
// MESHES & MATERIALS
// sphereMetallic
const sphereMet = MeshBuilder.CreateSphere('sphereMetallic', { segments: 32, diameter: 1 }, scene);
sphereMet.position = new Vector3(0, 0, -1);
const sphereMetMtl = new PBRMaterial('metallicMtl', scene);
sphereMetMtl.enableSpecularAntiAliasing = true;
sphereMetMtl.metallic = 1.0;
sphereMetMtl.roughness = 0.0;
sphereMet.material = sphereMetMtl;
// sphereIBL (lightModel)
const sphere = MeshBuilder.CreateSphere('sphere', { segments: 32, diameter: 0.5 }, scene);
const sphereMtl = new PBRMaterial('sphereMtl', scene);
sphereMtl.metallic = 0;
sphereMtl.roughness = 1;
sphere.material = sphereMtl;
// planeReflection (mirror - customReflection)
const planeMet = MeshBuilder.CreatePlane('planeMetallic', { height: 2, width: 1 });
planeMet.rotation = new Vector3(0, Tools.ToRadians(270), 0);
planeMet.position = new Vector3(0, 0, 1);
const mirrorReflection = new PBRMaterial('mirrorReflection', scene);
planeMet.material = mirrorReflection;
mirrorReflection.reflectionTexture = customReflection;
}

Generated using TypeDoc