Post-Processing

V této části se podíváme na post-processing. Jedná se o přidávání různých efektů na vyrenderovaný obrázek. Zde se můžete podívat na pár příkladů, co se dá pomocí post-processingu udělat:

Jak post-processing funguje

Pokud nepoužíváme žádné post-processing efekty, tak ve Three.js renderujeme scénu přímo na canvas. Když ale používáme efekty, tak nejdříve renderujeme do render targetu. Můžeme si to představit tak, jako bychom vyrenderovali scénu a výsledný obrázek umístili na texturu. Tuto texturu potom můžeme aplikovat na nějakou plochu pokrývající celý výhled kamery a provést renderování znovu. Můžeme tak na obrázek aplikovat různé speciální efekty (pomocí fragment shaderu pro plochu, na které je obrázek aplikován).

Three.js nám nabízí pár tříd, které nám post-processing umožňují provádět. Funguje to tak, že vytvoříme EffectComposer a přidáme do něj několik Pass objektů. Poté zavoláme metodu render a scéna se nám vyrenderuje namísto do canvasu do render targetu a aplikují se všechny specifikované passy (nevím jak to nazvat v češtině). Každý pass může být nějakým post-processing efektem jako je třeba přidání viněty, úprava sytosti barev, rozmazání, a tak podobně.

Passy přidáváme do EffectComposeru pomocí metody addPass v pořadí, v jakém chceme aby se na obrázek aplikovali. Je dobré vědět jak EffectComposer postupuje, když passy aplikuje. Vytváří si na to dva render targety, protože pokud máme více passů, tak musíme z jednoho render targetu data číst a do druhého psát. Nemůžeme z jednoho render targetu číst a zároveň do něj psát. Scéna se tedy vyrenderuje do jednoho render targetu a pokud máme jen jeden pass, tak se poté vyrenderuje i s aplikovaným efektem přímo na canvas. Pokud ale máme třeba dva passy, tak se nejdříve vyrenderuje do prvního render targetu, poté do druhého render targetu a až poté na canvas. Pokud jich máme ještě více, tak se obrázek opakovaně renderuje z jednoho render targetu do druhého až se nakonec vyrenderuje na canvas. Na následujícím obrázku je to znázorněno.

post-processing renderování

Důvod proč jsem vám tu vysvětloval jak EffectComposer uvnitř funguje je ten, aby jste pochopili, že pro každý pass musí proběhnout renderování. Pokud tedy máme například 5 passů, tak musí proběhnout 6 renderování. Může to mít vliv na výkon. Měli bychom se snažit efekty sjednotit do jednoho passu, pokud to jde.

Startovní kód

Abychom si mohli přidání post-processing efektů vyzkoušet, tak je tu pro vás připravený startovní kód. Vytvořte si tedy pomocí startovního kódu z části o Webpacku nový projekt a do JavaScript souboru si zkopírujte kód z následující ukázky. Tento kód vytváří scénu, které nastavuje environment mapu jako pozadí a přidává do ní 3D model lavice. Environment mapu si můžete stáhnout zde a 3D model zde. Umístěte si je do složky static.

import './style.css';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

// vytvoření scény
const scene = new THREE.Scene();

// vytvoření kamery
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 3;
scene.add(camera);

// přidání DirectionalLight světla
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.9);
directionalLight.position.set(0.5, 1.5, 0.3);
scene.add(directionalLight);


// vytvoření Cube Texture Loaderu
const cubeTextureLoader = new THREE.CubeTextureLoader();
// načtení environment mapy
const environmentMapTexture = cubeTextureLoader.load([
    './static/px.png',
    './static/nx.png',
    './static/py.png',
    './static/ny.png',
    './static/pz.png',
    './static/nz.png',
]);
// nastavení defaultní environment mapy
// pro všechny PBR materiály ve scéně
scene.environment = environmentMapTexture;
// nastavení pozadí scény
scene.background = environmentMapTexture;


// vytvoření GLTFLoaderu
const gltfLoader = new GLTFLoader();
// načtení 3D modelu
gltfLoader.load(
    "./static/Desk.glb",
    (gltf) => {
        // přidání načteného modelu do scény
        scene.add(gltf.scene);
    }
);


// vytvoření rendereru
const renderer = new THREE.WebGLRenderer({
    canvas: document.getElementById("WebGLCanvas")
});
// nastavení velikosti canvasu a pixel ratio
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

// přidání event listeneru pro změnu velikosti okna
window.addEventListener("resize", () => {
    // aktualizace poměru stran kamery
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    // změnění velikosti canvasu a pixel ratio
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});

// vytvoření OrbitControls ovládání
const controls = new OrbitControls(camera, renderer.domElement);
// zapnutí tlumení při posunutí
controls.enableDamping = true;

// tato funkce je volána každý frame
function tick() {
    // aktualizace OrbitControls ovládání
    controls.update();
    // vyrenderování scény na canvas
    renderer.render(scene, camera);
}

// nastavení animační smyčky
// - funkce tick se bude volat každý frame
renderer.setAnimationLoop(tick);

Protože canvas roztahujeme přes celou velikost okna prohlížeče, tak si ještě zkopírujte následující CSS styly a umístěte do CSS souboru. Zbavíme se tím defaultních marginů a paddingů.

*, *::before, *::after {
    padding: 0;
    margin: 0;
}

body {
    overflow: hidden;
}

Po spuštění aplikace uvidíte ve scéně načtený model a scéna bude mít nastavené pozadí na evnironment mapu.

Vytvoření Effect Composeru

Jak jsem již psal, tak přidávání post-processing efektů funguje tak, že vytvoříme EffectComposer, přidáme do něj Pass objekty a namísto rendereru jej použijeme k vyrenderování scény. EffectComposer není součástí proměnné THREE, kterou si do našeho JavaScript souboru importujeme, protože ne každá aplikace post-processing potřebuje. Musíme si jej tedy naimportovat zvlášť. Následující ukázka ukazuje, jak to můžeme udělat.

import './style.css';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';

/* ... */

Po naimportování si můžeme EffectComposer vytvořit. Jako parametr mu předáváme renderer, jelikož při aplikaci posledního passu se nerenderuje do render target, ale na canvas. Také můžeme předat nakonfigurovaný WebGLRenderTarget, který má EffectComposer použít, to ale uděláme až později. Stejně jako u rendereru, tak i u EffectComposeru musíme nastavovat rozměry a pixel ratio pomocí metod setSize a setPixelRatio. Následující ukázka ukazuje, jak můžeme v našem příkladu EffectComposer vytvořit a nastavit. Vytváříme jej hned po vytvoření rendereru a vidíte, že je to podobné.

/* ... */

// vytvoření rendereru
const renderer = new THREE.WebGLRenderer({
    canvas: document.getElementById("WebGLCanvas")
});
// nastavení velikosti canvasu a pixel ratio
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

// vytvoření EffectComposeru
const effectComposer = new EffectComposer(renderer);
// nastavení velikosti EffectComposeru
effectComposer.setSize(window.innerWidth, window.innerHeight);
// nastavení pixel ratio EffectComposeru
effectComposer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

/* ... */

Stejně jako renderer aktualizujeme při změně velikosti okna, tak stejným způsobem musíme při změně velikosti okna aktualizovat i EffectComposer.

/* ... */

// přidání event listeneru pro změnu velikosti okna
window.addEventListener("resize", () => {
    // aktualizace poměru stran kamery
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    // změnění velikosti canvasu a pixel ratio
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    // změnění velikosti a pixel ratio EffectComposeru
    effectComposer.setSize(window.innerWidth, window.innerHeight);
    effectComposer.setPixelRatio(Math.min(widnow.devicePixelRatio, 2));
});

/* ... */

Namísto rendereru budeme teď pro renderování používat EffectComposer. Proto musíme změnit kód v naší tick funkci a zavolat metodu render EffectComposeru. Scénu a kameru jí nepředáváme jako u metody render rendereru.

/* ... */

// tato funkce je volána každý frame
function tick() {
    // aktualizace OrbitControls ovládání
    controls.update();
    // vyrenderování scény přes EffectComposer
    effectComposer.render();
}

/* ... */

Pokud si aplikaci spustíte, tak zatím renderování fungovat nebude. Žádným způsobem jsme totiž ještě EffectComposeru neřekli, aby naši scénu renderoval.

Přidání post-processing efektů

Post-processing efekty přidáváme tak, že do EffectComposeru přidáváme Pass objekty. Každý pass může na vyrenderovaný obrázek aplikovat nějaký efekt. První pass, který potřebujeme, je RenderPass. Ten sice nepřidává na obrázek žádný efekt ale postará se o to, aby se scéna vyrenderovala do render targetu. Následující ukázka ukazuje, jak si jej můžeme naimportovat.

import './style.css';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';

/* ... */

Po naimportování si můžeme RenderPass vytvořit a přidat jej do EffectComposeru pomocí metody addPass. Při jeho vytváření jako parametr předáváme scénu, kterou chceme vyrenderovat, a kameru, přes kterou chceme renderování provést.

/* ... */

// vytvoření RenderPassu
const renderPass = new RenderPass(scene, camera);
// přidání RenderPassu do EffectComposeru
effectComposer.addPass(renderPass);

Pokud si teď aplikaci spustíte, tak by se vám scéna měla renderovat stejně jako před přidáním EffectComposeru.

Po přidání RenderPassu si můžeme do EffectComposeru přidat i jiné passy. Některé nám Three.js nabízí a můžeme si je vyzkoušet. Sice je nenajdete v dokumentaci, ale můžete se podívat do zdrojového kódu v GitHub repozitáři nebo ve složce node_modules. Najdete je ve složce examples/jsm/postprocessing.

První pass, který si vyzkoušíme aplikovat, je DotScreenPass. Ten na obrázek přidá takový zrnitý efekt. Následující ukázka ukazuje, jak jej můžeme naimportovat.

import './style.css';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
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 { DotScreenPass } from 'three/examples/jsm/postprocessing/DotScreenPass';

/* ... */

Po naimportování můžeme DotScreenPass vytvořit a přidat do EffectComposeru pomocí metody addPass.

/* ... */

// pass pro zrnitý efekt
const dotScreenPass = new DotScreenPass();
effectComposer.addPass(dotScreenPass);

Když si teď aplikaci spustíte, tak uvidíte, že se na vyrenderovaný obrázek efekt přidal.

Další pass, který bychom si mohli vyzkoušet, je třeba GlitchPass. Ten na obrazovku přidává takový efekt, jakoby byla obrazovka hacknutá nebo tak něco. Následující ukázka ukazuje, jak si jej můžeme naimportovat.

import './style.css';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
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 { DotScreenPass } from 'three/examples/jsm/postprocessing/DotScreenPass';
import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass';

/* ... */

GlitchPass můžeme vytvořit a použít úplně stejně jako DotScreenPass.

/* ... */

// pass pro Glitch efekt
const glitchPass = new GlitchPass();
effectComposer.addPass(glitchPass);

Po spuštění aplikace si můžete glitch efekt prohlédnout.

Pokud chceme nějaké passy deaktivovat, tak to můžeme udělat pomocí vlastnosti enabled, kterou nastavíme na hodnotu false. V našem příkladu si můžeme zkusit deaktivovat DotScreenPass, abychom mohli vidět jen efekt GlitchPassu.

/* ... */

// pass pro zrnitý efekt
const dotScreenPass = new DotScreenPass();
effectComposer.addPass(dotScreenPass);
// vypnutí passu pro zrnitý efekt
dotScreenPass.enabled = false;

// pass pro Glitch efekt
const glitchPass = new GlitchPass();
effectComposer.addPass(glitchPass);

Teď si můžete po spuštění aplikace prohlédnout jen samotný GlitchPass.

Jako další pass si můžeme třeba zkusit HalftonePass. Následujícím způsobem si jej naimportujeme.

import './style.css';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
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 { DotScreenPass } from 'three/examples/jsm/postprocessing/DotScreenPass';
import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass';
import { HalftonePass } from 'three/examples/jsm/postprocessing/HalftonePass';

/* ... */

Po naimportování můžeme HalftonePass použít. Aby jsme jej viděli bez GlitchPassu, tak si GlitchPass deaktivujeme.

/* ... */

// pass pro Glitch efekt
const glitchPass = new GlitchPass();
effectComposer.addPass(glitchPass);
// vypnutí passu pro glitch efekt
glitchPass.enabled = false;

// pass pro halftone efekt
const halftonePass = new HalftonePass();
effectComposer.addPass(halftonePass);

Po spuštění aplikace si můžete efekt, který HalftonePass aplikuje, prohlédnout.

Některé post-processing efekty nemají vlastní pass, ale dají se aplikovat pomocí ShaderPassu. Různé shader efekty můžete ve zdrojovém kódu najít ve složce examples/jsm/shaders. Mi si zkusíme aplikovat VignetteShader, který nám na obrazovku přidá vinětu. Následující ukázka ukazuje, jak si můžeme ShaderPass a VignetteShader naimportovat.

import './style.css';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
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 { DotScreenPass } from 'three/examples/jsm/postprocessing/DotScreenPass';
import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass';
import { HalftonePass } from 'three/examples/jsm/postprocessing/HalftonePass';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
import { VignetteShader } from 'three/examples/jsm/shaders/VignetteShader';

/* ... */

Následující ukázka ukazuje, jak můžeme vinětu, kterou VignetteShader provádí, pomocí ShaderPassu na EffectComposer aplikovat. V podstatě jen ShaderPassu při jeho vytváření předáme shader, který se má použít. Předchozí pass, který jsme si zkoušeli deaktivujeme.

/* ... */

// pass pro halftone efekt
const halftonePass = new HalftonePass();
effectComposer.addPass(halftonePass);
halftonePass.enabled = false;

// pass pro vinětu
const vignettePass = new ShaderPass(VignetteShader);
// takto můžeme nastavovat hodnoty uniforms,
// které se v shaderu použijí
vignettePass.uniforms.offset.value = 1.5;
effectComposer.addPass(vignettePass);

V ukázce vidíte, že po vytvoření passu měníme hodnotu uniform jménem offset, která se ve VignetteShaderu používá. Jaké uniforms shader používá se můžete podívat ve zdrojovém kódu. Zde je ukázka kódu pro VignetteShader.

/**
* Vignette shader
* based on PaintEffect postprocess from ro.me
* http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js
*/

const VignetteShader = {

    uniforms: {

        'tDiffuse': { value: null },
        'offset': { value: 1.0 },
        'darkness': { value: 1.0 }

    },

    vertexShader: /* glsl */`

        varying vec2 vUv;

        void main() {

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

        }`,

    fragmentShader: /* glsl */`

        uniform float offset;
        uniform float darkness;

        uniform sampler2D tDiffuse;

        varying vec2 vUv;

        void main() {

            // Eskil's vignette

            vec4 texel = texture2D( tDiffuse, vUv );
            vec2 uv = ( vUv - vec2( 0.5 ) ) * vec2( offset );
            gl_FragColor = vec4( mix( texel.rgb, vec3( 1.0 - darkness ), dot( uv, uv ) ), texel.a );

        }`

};

export { VignetteShader };

Po spuštění aplikace můžete kolem obrazovky vinětu vidět.

Vytváření vlastních efektů

I když nám Three.js nabízí různé post-processing efekty, tak nejsme limitováni jen na ně. Můžeme si klidně vytvořit vlastní post-processing efekty. Pokud jste si prohlédli zdrojový kód pro VignetteShader, který jsme používali, tak by vás možná mohlo napadnout, jak to udělat.

Zkusíme si nějaký jednodušší post-processing efekt vytvořit. Dělá se to pomocí shaderů. Jak jsem již psal, tak post-processing funguje tak, že se vyrenderovaný obrázek aplikuje na plochu a znovu se vyrenderuje. Na tuto plochu tedy můžeme aplikovat nějaké shadery a obrázek tak upravit. Vytvoříme si objekt obsahující vlastnosti uniforms, vertexShader a fragmentShader. Vlastnost vertexShader bude obsahovat kód pro vertex shader, vlastnost fragmentShader bude obsahovat kód pro fragment shader a vlastnost uniforms bude obsahovat uniforms, které se v shaderech používají. Pomocí tohoto objektu poté vytvoříme ShaderPass a přidáme jej do EffectComposeru. Následující ukázka ukazuje objekt obsahující shadery, které pro náš efekt použijeme. Zkopírujte si jej do svého kódu.

// vytvoření vlastního efektu
const MyShader = {
    uniforms: {
        // tato uniform bude obsahovat obrázek
        // z minulého passu (sampler2D)
        // - EffectComposer jej automaticky nastaví
        tDiffuse: {
            value: null
        }
    },
    vertexShader: `
        varying vec2 vUv;

        void main() {
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
            // předání UV souřadnic do fragment shaderu pomocí varying
            vUv = uv;
        }
    `,
    fragmentShader: `
        // definování konstanty pro PI (glsl ji neobsahuje)
        #define M_PI 3.1415926535897932384626433832795

        // obrázek z minulého passu
        uniform sampler2D tDiffuse;

        varying vec2 vUv;

        void main() {
            gl_FragColor = texture2D(
                tDiffuse,
                vec2(
                    vUv.x - sin((vUv.x-0.5) * 2.0 * M_PI) * 0.09
                    , vUv.y - sin((vUv.y-0.5) * 2.0 * M_PI) * 0.09
                )
            );
        }
    `
}

Jak můžete v předchozí ukázce vidět, tak si deklarujeme a používáme uniform jménem tDiffuse. Této uniform EffectComposer automaticky nastaví obrázek z předchozího passu. Jedná se o sampler2D a můžeme z něj tedy ve fragment shaderu získávat barvu (správně se tomu říká texel) pomocí funkce texture2D. Ve vertex shaderu jen nastavujeme správnou pozici vertexů a předáváme UV souřadnice do fragment shaderu jako varying. Ve fragment shaderu modifikujeme UV souřadnice pomocí pár výpočtů s funkcí sinus a používáme je k získání barvy z obrázku. Trvalo mi celkem dlouho než jsem přišel na něco co fungovalo. Nemusíte vědět jak to funguje, stačí když víte že jen nějak modifikujeme UV souřadnice a získáváme podle nich barvu z obrázku.

Pomocí našeho objektu, který obsahuje shadery, můžeme vytvořit ShaderPass a přidat jej do EffectComposeru.

/* ... */

// vytvoření ShaderPassu s vlastním efektem
const myPass = new ShaderPass(MyShader);
// přidání passu do EffectComposeru
effectComposer.addPass(myPass);

Po spuštění aplikace si můžete náš vytvořený efekt prohlédnout.

Konfigurace render targetu

Když renderujeme scénu přes EffectComposer, tak ji renderujeme do render targetu. Pokud máme například pro náš renderer nastaven encoding na sRGB nebo zapnutý antialiasing, tak to nemá na renderování do render targetu vliv. Má to vliv jen na renderování na canvas. Proto si musíme render target nakonfigurovat, pokud chceme tyto vlastnosti mít nastavené i při renderování přes EffectComposer. Musíme si vytvořit instanci třídy WebGLRenderTarget, nakonfigurovat ji a předat do EffectComposeru jako druhý parametr při jeho vytváření. Následující ukázka ukazuje, jak to můžeme udělat.

/* ... */

// vytvoření render targetu
const renderTarget = new THREE.WebGLRenderTarget(
    // jako šířku a výšku je jedno co nastavíme, protože
    // používáme EffectComposer - nastaví si to sám
    1, 1,
    // konfigurace render targetu
    {
        encoding: THREE.sRGBEncoding
    }
)

// vytvoření EffectComposeru
const effectComposer = new EffectComposer(renderer, renderTarget);
/* ... */

Aby ale sRGB encoding fungovalo, tak je potřeba ještě jako poslední pass nastavit pass s GammaCorrectionShaderem, jak jsem se po hodině bádání proč to nefunguje dozvěděl na Stack Overflow. Následující ukázka ukazuje, jak si můžeme GammaCorrectionShader naimportovat.

import './style.css';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
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 { DotScreenPass } from 'three/examples/jsm/postprocessing/DotScreenPass';
import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass';
import { HalftonePass } from 'three/examples/jsm/postprocessing/HalftonePass';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
import { VignetteShader } from 'three/examples/jsm/shaders/VignetteShader';
import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader';

/* ... */

Po naimportování můžeme pass s GammaCorrectionShaderem použít.

/* ... */

// pass s GammaCorrectionShaderem
const gammaCorrectionPass = new ShaderPass(GammaCorrectionShader);
effectComposer.addPass(gammaCorrectionPass);

Po spuštění aplikace by jste měli vidět, že se sRGB encoding použilo.

Co se týká anti-aliasingu, tak máme u render targetu možnost použít MSAA (Multisample anti-aliasing). Při konfiguraci render targetu si můžeme nastavit kolik chceme samples pomocí možnosti samples. Vyšší číslo znamená vyšší kvalitu ale také větší vliv na výkon. Následující ukázka ukazuje, jak můžeme MSAA nastavit v našem příkladu. MSAA můžeme použít, jen když prohlížeč podporuje WebGL 2, takže to v kódu zjišťujeme pomocí vlastnosti capabilities na rendereru a podle toho MSAA zapínáme nebo ne. Také jej zapínáme jen pro zařízení, která mají menší pixel ratio jak 2, protože při větší hustotě pixelů uživatel vyrenderované schody neuvidí. Zvýšíme tím o něco výkon naší aplikace.

/* ... */

// zjištění jestli se má zapnout MSAA
let samplesCount = 0;
if (renderer.getPixelRatio() < 2 && renderer.capabilities.isWebGL2) {
    samplesCount = 2;
}

// vytvoření render targetu
const renderTarget = new THREE.WebGLRenderTarget(
    // jako šířku a výšku je jedno co nastavíme, protože
    // používáme EffectComposer - nastaví si to sám
    1, 1,
    // konfigurace render targetu
    {
        encoding: THREE.sRGBEncoding,
        samples: samplesCount
    }
);

/* ... */

Pokud si aplikaci spustíte, tak by jste neměli na hranách modelu vidět vyrenderované schody, pokud jste je předtím viděli. Váš prohlížeč ale musí podporovat WebGL 2.

Kromě toho, že můžeme zapnout multisample anti-aliasing na render targetu nastavením vlastnosti samples, tak nám Three.js také nabízí pro anti-aliasing pár passů. Můžete je třeba použít jako náhradu, pokud prohlížeč nepodporuje WebGL 2. Zde jsem je vypsal:

  • FXAA (FXAAShader) - výkonný anti-aliasing ale výsledek je jen tak dobrý a může být rozmazaný
  • SMAA (SMAAPass) - většinou je lepší než FXAA, ale je méně výkonný (nepleťte si to s MSAA)
  • SSAA (SSAARenderPass) - nejkvalitnější anti-aliasing, ale s nejhorší výkonností
  • TAA (TAARenderPass) - výkonný anti-aliasing, ale s limitovaným výsledkem

To je vše, co jsem vám chtěl o post-processingu sdělit. Nyní již víte, jak si můžete na vyrenderovaný obrázek přidat různé efekty pomocí EffectComposeru. Víte, že můžete použít efekty, které nám Three.js nabízí, nebo si vytvořit i své vlastní pomocí shaderů.