Have a realistic metallic rendering with three.js

28 Views Asked by At

On three.js, I have difficulty finding the best solution to render a metallic material. I tried several methods including these: https://youtu.be/aJun0Q0CG_A?feature=shared and also the one used here: https://github.com/mrdoob/three.js/blob/master/examples/webgl_materials_physical_clearcoat.html.

However, I find that the metallic appearance is not very realistic.

Perhaps the error comes from the positioning and intensity of the light points that I use or from some parameters to adjust.

Here is how my code is configured:

import * as THREE from 'three';
import { OrbitControls } from './jsm/controls/OrbitControls.js';
import { FlakesTexture } from './jsm/textures/FlakesTexture.js';
import { RGBELoader } from './jsm/loaders/RGBELoader.js';

let scene, camera, renderer, controls;
let pointLight, pointLight2, pointLight3;
let numCubesX = 20;
let numCubesY = 10;
let cubeSize=1; 
let wallWidth = numCubesX; // Largeur du mur (axe X)
let wallHeight = 5; // Hauteur du mur (axe Y)
const cubes = [];
let roomPlane;
let colorfloor;
let weight;
let pondfloorrarity=0.2;
let pondcubesrarity=0.4;
let pondpaletterarity=0.4;
let resol = 4;


function init() {
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 10000);    
    camera.position.set(numCubesX / 2, numCubesY / 1.5, 15);
    camera.outputEncoding = THREE.LinearToneMapping;
    
    renderer = new THREE.WebGLRenderer({ antialias: true },{ alpha: true });
    renderer.shadowMap.enabled = true;
    renderer.physicallyCorrectLights = true;
    renderer.toneMapping=THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure=0.5;
    renderer.outputColorSpace=THREE.LinearSRGBColorSpace;
    renderer.setPixelRatio(window.devicePixelRatio); 
    renderer.setSize(window.innerWidth, window.innerHeight);
    //document.body.appendChild(renderer.domElement);
    controls = new OrbitControls(camera, renderer.domElement);

    pointLight = new THREE.PointLight(0xffffff, 9000, 100);
    pointLight.castShadow = true; 
    pointLight.position.set(0-60, numCubesY/2, 40); 
    scene.add(pointLight);

    pointLight2 = new THREE.PointLight(0xffffff, 9000, 100);
    pointLight2.castShadow = true; 
    pointLight2.position.set(numCubesX+60, numCubesY/2, 40); 
    scene.add(pointLight2);

    pointLight3 = new THREE.PointLight(0xffffff, 7000, 100);
    pointLight3.castShadow = true; 
    pointLight3.position.set(numCubesX/2, numCubesY*1.5, 3); 
    scene.add(pointLight3);

    pointLight.shadow.mapSize.width = 4096; // Nouvelle résolution de l'ombre
    pointLight.shadow.mapSize.height = 4096; // Nouvelle résolution de l'ombre
    pointLight2.shadow.mapSize.width = 4096; // Nouvelle résolution de l'ombre
    pointLight2.shadow.mapSize.height = 4096; // Nouvelle résolution de l'ombre
    pointLight3.shadow.mapSize.width = 4096; // Nouvelle résolution de l'ombre
    pointLight3.shadow.mapSize.height = 4096; // Nouvelle résolution de l'ombre
}

// Créer les cubes
function createCubes() {
    function hexToCSSColor(hex) {
        let r = (hex >> 16) & 255;
        let g = (hex >> 8) & 255;
        let b = hex & 255;
        return `rgb(${r}, ${g}, ${b})`;
    }
    
    let palettes = [
        [0xd4af37, 0xe5e4e2, 0xcd7f32],
        [0xabcdef, 0x123456, 0x789abc],
    ].map(palette => palette.map(hexToCSSColor));
    
    // Définir les poids pour chaque palette
    let weights = [
        [1, 3, 6], 
        [1, 3, 6], 
    ];

    let paletteWeights = [3, 1];


    let colorWeights = {}; // Ajoutez cette ligne pour stocker les poids des couleurs
    let cubeCounts = {}; // Ajoutez cette ligne pour stocker le nombre de cubes de chaque couleur
    let floorRarity = 0; // Ajoutez cette ligne pour stocker la rareté du sol

    // Créer un tableau pondéré pour chaque palette
    let weightedPalettes = palettes.map((palette, i) => {
        let weightedPalette = [];
        palette.forEach((color, j) => {
            weight = weights[i][j];
            colorWeights[color] = weight; // Stocker le poids de la couleur
            for (let k = 0; k < weight; k++) {
                weightedPalette.push(color);
            }
        });
        return weightedPalette;
    });

        // Créer un tableau pondéré pour les indices de palette
        let weightedPaletteIndices = [];
        paletteWeights.forEach((weight, i) => {
            for (let j = 0; j < weight; j++) {
                weightedPaletteIndices.push(i);
            }
        });
    

    // Choisir un indice de palette pondéré aléatoirement
    let weightedPaletteIndex = weightedPaletteIndices[Math.floor(Math.random() * weightedPaletteIndices.length)];
    let weightedPalette = weightedPalettes[weightedPaletteIndex];
    let paletteRarity = paletteWeights[weightedPaletteIndex]; // Ajoutez cette ligne pour calculer la rareté de la palette

    let envmaploader=new THREE.PMREMGenerator(renderer);
    //new RGBELoader().setPath('./').load('venice_sunset_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('kloofendal_43d_clear_puresky_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('rooitou_park_4k.hdr', function (hdrmap){        
    //new RGBELoader().setPath('./').load('mealie_road_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('quarry_01_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('blinds_4k.hdr', function (hdrmap){
    new RGBELoader().setPath('./').load('autumn_crossing_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('aloe_farm_shade_house_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('industrial_workshop_foundry_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('satara_night_no_lamps_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('trekker_monument_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('pine_attic_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('stadium_01_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('mpumalanga_veld_puresky_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('kiara_1_dawn_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('pink_sunrise_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('harties_4k.hdr', function (hdrmap){
    //new RGBELoader().setPath('./').load('floral_tent_4k.hdr', function (hdrmap){

        let envmap=envmaploader.fromCubemap(hdrmap);
        let texture = new THREE.CanvasTexture(new FlakesTexture());
        texture.wrapS = THREE.RepeatWrapping;
        texture.wrapT = THREE.RepeatWrapping;
        texture.repeat.x=200;
        texture.repeat.y=50;
        colorfloor = weightedPalette[Math.floor(Math.random() * weightedPalette.length)];
        floorRarity = colorWeights[colorfloor]; // Calculez la rareté du sol
    
        const floormaterial = {
            clearcoat: 1,
            clearcoatRoughness: 0,
            metalness: 0.9,
            roughness: 0,
            color: colorfloor,
            normalMap: texture,
            normalScale: new THREE.Vector2(0.01, 0.01),
            envMap: envmap.texture,
        }
        let floor_material = new THREE.MeshPhysicalMaterial(floormaterial); 
        const roomPlaneGeometry = new THREE.PlaneGeometry(wallWidth, 5*wallHeight);
        roomPlane = new THREE.Mesh(roomPlaneGeometry, floor_material);
        roomPlane.rotation.x = -Math.PI / 2; // Pour le placer horizontalement
        roomPlane.position.set(numCubesX / 2, -cubeSize/2, 2.5*wallHeight); // Positionnez le plan au sol derrière la caméra
        roomPlane.receiveShadow = true;
        roomPlane.castShadow = true;     
        scene.add(roomPlane);
    
    for (let i = 0; i < numCubesX; i++) {
        for (let j = 0; j < numCubesY; j++) {
        const cubeHeight = Math.random() * 2; // Longueur Z aléatoire pour l'effet de relief
        let color = weightedPalette[Math.floor(Math.random() * weightedPalette.length)];
        cubeCounts[color] = (cubeCounts[color] || 0) + 1; // Incrémentez le nombre de cubes de cette couleur

        const cubeMaterial = {
            clearcoat: 1,
            clearcoatRoughness: 0,
            metalness: 0.9,
            roughness: 0,
            color: color,
            normalMap: texture,
            normalScale: new THREE.Vector2(0.01, 0.01),
            envMap: envmap.texture,
        }
               
        let cubeGeometry = new THREE.BoxGeometry(0.9, 0.9, cubeHeight);
        let material = new THREE.MeshPhysicalMaterial(cubeMaterial); 
        let cube = new THREE.Mesh(cubeGeometry, material);
        cube.position.set(i, j, cubeHeight / 2);
        cube.receiveShadow = true;
        cube.castShadow = true;     
        scene.add(cube);
        cubes.push(cube); // Ajoutez le cube au tableau
            }
        }
            // Calculez la rareté totale des cubes
    let cubeRarity = 0;
    for (let color in cubeCounts) {
        cubeRarity += cubeCounts[color] * colorWeights[color];
    }

    // Calculez la rareté de la scène
    let sceneRarity = (pondcubesrarity*cubeRarity) * (pondfloorrarity*floorRarity)* (pondpaletterarity*paletteRarity);

    // Affichez la rareté de la scène
    console.log('Rareté des cubes : ' + cubeRarity);
    console.log('Rareté de la palette : ' + paletteRarity);
    console.log('Rareté du sol : ' + floorRarity);
    console.log('Rareté de la scène : ' + sceneRarity);

    })
}

let animationId;

function animate() {
    animationId=requestAnimationFrame(animate);
    camera.lookAt(new THREE.Vector3(numCubesX / 2, numCubesY / 2,0));
    renderer.render(scene, camera);
    //composer.render();  // Remplacez 'renderer.render(scene, camera);' par ceci

}

function captureImage() {
    // Sauvegarder la taille d'origine
    let originalSize = new THREE.Vector2();
    renderer.getSize(originalSize);

    // Définir la taille du rendu à une valeur plus élevée
    let highResSize = originalSize.clone().multiplyScalar(resol); // 2 fois la taille d'origine
    renderer.setSize(highResSize.x, highResSize.y, true);
    renderer.domElement.style.width = `${highResSize.x}px`;
    renderer.domElement.style.height = `${highResSize.y}px`;

    // Attendre que la scène soit rendue à la nouvelle résolution
    requestAnimationFrame(() => {
        // Capturer une image de la scène
        let dataURL = renderer.domElement.toDataURL('image/png');

        // Créer une nouvelle image et l'ajouter au document
        let img = new Image();
        img.style.width = `${originalSize.x}px`;
        img.style.height = `${originalSize.y}px`;
        img.src = dataURL;
        document.body.appendChild(img);

        // Réinitialiser la taille du rendu à sa valeur d'origine
        renderer.setSize(originalSize.x, originalSize.y, true);
        renderer.domElement.style.width = `${originalSize.x}px`;
        renderer.domElement.style.height = `${originalSize.y}px`;

        cancelAnimationFrame(animationId);
    });
}

init();
createCubes();
animate();
// Attendre une seconde, puis capturer une image de la scène
setTimeout(captureImage, 1000);

Don't pay attention to the //, I've tried so many solutions that my code can turn out to be a bit messy.

Does anyone have a solution to get the best possible results with three.js?

Here's a photo of what I currently get when I use an hdr image with RGBELoader...enter image description here

I would like to have a rendering so that we can believe that it is gold, silver, or other precious material.

0

There are 0 best solutions below