Hlavní navigace

Použití MoviePy společně s knihovnou Vapory a raytracerem POV-Ray

Pavel Tišnovský

Dnes spojíme dvě témata, kterým jsme se věnovali v předchozích týdnech. Jednalo se o knihovnu MoviePy určenou pro manipulaci a vytváření video souborů a o knihovnu Vapory, která umožňuje deklarovat 3D scény pro raytracer POV-Ray.

Doba čtení: 33 minut

Obsah

1. Přidání elementů, které Vapory ve svém výchozím nastavení nepodporuje

2. Mapy barev a jejich použití s některými typy procedurálních textur

3. Testovací scéna napsaná přímo pro POV-Ray, která obsahuje torus

4. Shodná testovací scéna napsaná v Pythonu s využitím Vapory a nové třídy Torus

5. Zjednodušený zápis mapy barev – scéna popsaná v jazyku POV-Ray

6. Zjednodušený zápis mapy barev – scéna popsaná v jazyku Python s využitím Vapory

7. Využití implicitních ploch (blobs, metaballs) v POV-Rayi

8. Rozšíření možností CSG pomocí implicitních ploch

9. Konstrukce implicitních ploch s využitím kostry

10. Vykreslení implicitních ploch POV-Rayem

11. Vykreslení stejné scény s využitím knihovny Vapory

12. Idiomatický kód – použití n-tic namísto seznamů

13. Využití možností knihovny MoviePy pro jednodušší vytvoření animace

14. Úprava příkladu z předchozího článku: animace změny CSG tělesa a textury na toru

15. Příprava na animaci změn parametrů implicitních ploch

16. Shodná testovací scéna napsaná v Pythonu s využitím Vapory

17. Naprogramování animace – tvorba jednotlivých snímků později spojených do videa

18. Naprogramování animace – použití MoviePy

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Přidání elementů, které Vapory ve svém výchozím nastavení nepodporuje

V dnešním článku tematicky navážeme na čtyři předchozí články, v nichž jsme se zabývali jak popisem možností nabízených knihovnou MoviePy určené pro zpracování video souborů (včetně tvorby souborů nových), tak i knihovny Vapory, která slouží pro přípravu 3D scén určených pro vykreslení raytracerem POV-Ray:

  1. Programová tvorba a nelineární editace videa s využitím knihovny MoviePy
    https://www.root.cz/clanky/programova-tvorba-a-nelinearni-editace-videa-s-vyuzitim-knihovny-moviepy/
  2. Použití MoviePy společně s Matplotlibem pro tvorbu animovaných grafů
    https://www.root.cz/clanky/pouziti-moviepy-spolecne-s-matplotlibem-pro-tvorbu-animovanych-grafu/
  3. Použití MoviePy společně Matplotlibem pro tvorbu animovaných grafů (dokončení)
    https://www.root.cz/clanky/pouziti-moviepy-spolecne-matplotlibem-pro-tvorbu-animovanych-grafu-dokonceni/
  4. Projekt Vapory: kombinace možností Pythonu a POV-Raye
    https://www.root.cz/clanky/projekt-vapory-kombinace-moznosti-pythonu-a-pov-raye/
povray4901

Obrázek 1: Scéna pojmenovaná „Reach for the Stars“, která byla POV-Rayem vykreslena na Mezinárodní kosmické stanici (jedná se o variantu se zmenšeným rozlišením oproti originálu).

Obě zmíněné knihovny, tj. jak MoviePy, tak i Vapory, jsou určeny pro použití v programovacím jazyku Python, takže se přímo nabízí otázka, jestli je možné jejich možnosti nějakým vhodným způsobem zkombinovat. Ve skutečnosti to možné je, a to navíc relativně jednoduše. Nejdříve si však ukážeme další možnosti nabízené knihovnou Vapory, zejména použití takzvaných map barev (color map) a taktéž rozšíření této knihovny o další 3D entity (což je, jak uvidíme dále, až překvapivě snadné). Ve druhé třetině článku se zmíníme o použití implicitních ploch a v závěrečné části se pak budeme věnovat již slíbené přímé tvorbě video souborů přímo z Vapory.

povray1607

Obrázek 2: Procedurální textury dřeva: od smrku přes dub až po mahagon. Právě v procedurálních texturách jsou použity mapy barev popsané v navazujících kapitolách.

Pokud nějaká 3D entita není ve Vapory podporována, je ji možné do knihovny přidat a to velmi snadno. Postačuje vytvořit novou třídu se stejným jménem, jako má příslušná entita, a tuto třídu odvodit od POVRayElement. Třída může být prázdná (vše se pouze zdědí), takže se vlastně jedná o dvouřádkovou deklaraci:

class Torus(POVRayElement):
    """Torus()"""

Nic dalšího není zapotřebí provádět!

2. Mapy barev a jejich použití s některými typy procedurálních textur

U mnoha procedurálních textur, jejichž ukázky budou použity v dalších demonstračních příkladech, je nutné nějakým způsobem specifikovat způsob použití barev, protože se jedná o textury zadané nějakou funkcí, která pro každý bod v 3D prostoru vrátí reálné číslo v rozsahu 0–1 (to je typické chování naprosté většiny procedurálních textur v POV-Rayi). Toto číslo je nutné nějakým způsobem převést na barvu. V případě diskrétních hodnot (uložených například v rastrových obrázcích typu BMP, GIF či PNG) se pro tento účel využívají barvové palety (color palettes), v POV-Rayi je pro převod reálných čísel na barvu použito takzvaných barvových map (color maps). Zadání barvových map v jazyku POV-Ray je poměrně jednoduché – specifikuje se vždy reálná hodnota a barva platná pro tuto hodnotu.

povray1605

Obrázek 3: Testovací „panáci“ pokrytí jednoduchými procedurálními texturami (zleva doprava): lineárním gradientním přechodem, radiálním gradientním přechodem, sférickým gradientním přechodem a lineárním přechodem otočeným o 90°.

Mezi dvojicí reálných hodnot, které by měly být zadány v neklesajícím pořadí, je pak barva dopočítána pomocí lineární (interpolační) funkce. Je dokonce dovoleno vytvořit „skok“, a to tak, že se zadá ta stejná reálná hodnota s rozdílnou barvou. Pokud se budeme k této hodnotě přibližovat zespodu (od nižších hodnot), použije se první barva (první řádek ve specifikaci), při přibližování shora pak barva druhá. Jednoduchá mapa barev může vypadat následovně. Povšimněte si skoku mezi bílou a černou barvou na hodnotě 0,4:

// mapa barev
color_map {
    [0.0  color rgb <0.8, 0.2, 0.2>]   // hodnota 0 je dolní mezí, pro kterou lze specifikovat barvu
    [0.2  color rgb <0.4, 0.2, 0.2>]
    [0.4  color White]                 // skok
    [0.4  color Black]
    [0.6  color rgb <0.2, 0.6, 0.6>]
    [1.0  color rgb <0.8, 0.2, 0.2>]   // hodnota 1 je naopak horní mezí
}

Alternativně je možné na jeden řádek v barvové mapě zapsat obě barvy v krajních bodech rozsahu, což je možná čitelnější (a navíc nepatrně kratší):

// mapa barev
color_map {   // dvě barvy, které se na vzorku střídají
    [0.0 0.4  color red 0.36 green 0.20 blue 0.09  color red 0.36 green 0.20 blue 0.09 ]
    [0.4 1.01 color red 0.858824 green 0.576471 blue 0.439216 color red 0.858824 green 0.576471 blue 0.439216]
}
fractals57_1

Obrázek 4: Trojrozměrný model vytvořený pomocí programu Lparser a vykreslený POV-Rayem.

V knihovně Vapory se mapa barev zapisuje takovým způsobem, že se do konstruktoru třídy ColorMap předá libovolné množství vektorů nebo n-tic se specifikací jednotlivých barev v prostoru 0,1 až 1,0:

ColorMap([0.0, 'color', [0.8, 0.2, 0.2]],
         [0.2, 'color', [0.4, 0.2, 0.2]],
         [0.4, 'color', 'White'],
         [0.4, 'color', 'Black'],
         [0.6, 'color', [0.2, 0.6, 0.6]],
         [1.0, 'color', [0.8, 0.2, 0.2]]),
fractals57_4

Obrázek 5: Vykreslený model L-systému Fern (kapradina) složený z trojúhelníků (non-realistic rendering).

3. Testovací scéna napsaná přímo pro POV-Ray, která obsahuje torus

Podívejme se nyní na první demonstrační příklad zapsaný přímo v jazyku POV-Raye. V tomto příkladu jsou použity oba dva prvky, s nimiž jsme se seznámili v předchozích kapitolách – torus (tedy nově přidaný element, který původně knihovna Vapory nepodporovala) a barvové mapy. Aby byla situace ještě komplikovanější, jsou u toru specifikovány dvě textury, přičemž horní textura je v některých místech průsvitná, takže jí v těchto oblastech prosvítá textura spodní:

// objekt ve scéně: torus (anuloid)
torus {
    7.0, 4.0                             // geometrické informace
                                         // (poloměry celého toroidu a "trubky")
 
    // spodní (podkladová) textura se základním vzorkem
    texture {
        pigment {                        // definice vzorku textury
           bozo                          // typ vzorku
           color_map {                   // dvě barvy, které se na vzorku střídají
            [0.0 0.4  color red 0.36 green 0.20 blue 0.09  color red 0.36 green 0.20 blue 0.09 ]
            [0.4 1.01 color red 0.858824 green 0.576471 blue 0.439216 color red 0.858824 green 0.576471 blue 0.439216]
            }
            scale <4, 0.15, 0.15>        // změna měřítka (velikosti) namapovaného vzorku
            rotate 45*y
        }
    }
 
    // horní textura, která přidává jemnější vzorek
    texture {
        ...
        ...
        ...
    }
}

Obrázek 6: Výsledek renderingu dnešní první demonstrační scény v POV-Rayi.

Úplný zdrojový kód dnešní první demonstrační scény (v celkovém pořadí však již pátém příkladu) je možné nalézt na této adrese):

// ------------------------------------------------------------
// Jednoduchá scéna s uzavřeným objektem, plochou, dvojicí světel
// a jednou kamerou (pozorovatelem)
//
// rendering lze spustit příkazem:
//     povray +W800 +H600 +B +FN +D +Iscene5.pov +Oscene5.png
// (pro náhled postačí zadat povray scene5.pov)
//
// Založeno na souboru původně vytvořeném Danem Farmerem (leden 2002)
// ------------------------------------------------------------
 
#version 3.5;
 
// globální nastavení parametrů scény
global_settings {
    assumed_gamma 2.2
}
 
// načtení všech potřebných externích souborů
#include "colors.inc"
 
// nastavení kamery (pozorovatele)
camera {
    location  <0, 20, -15>               // pozice kamery
    look_at   <0, -2,   0>               // bod, na který kamera směřuje
}
 
// první (silnější) světelný zdroj s bílou barvou
light_source {
    <-50, 100, -80>                      // pozice světelného zdroje
    color rgb 1                          // barva světla (všech tří složek)
}
 
// druhý (slabší) světelný zdroj
light_source {
    <250, 25, -100>                      // pozice světelného zdroje
    color red 0.85 green 0.53 blue 0.10  // barva světla
}
 
// objekt ve scéně: torus (anuloid)
torus {
    7.0, 4.0                             // geometrické informace
                                         // (poloměry celého toroidu a "trubky")
 
    // spodní (podkladová) textura se základním vzorkem
    texture {
        pigment {                        // definice vzorku textury
           bozo                          // typ vzorku
           color_map {                   // dvě barvy, které se na vzorku střídají
            [0.0 0.4  color red 0.36 green 0.20 blue 0.09  color red 0.36 green 0.20 blue 0.09 ]
            [0.4 1.01 color red 0.858824 green 0.576471 blue 0.439216 color red 0.858824 green 0.576471 blue 0.439216]
            }
            scale <4, 0.15, 0.15>        // změna měřítka (velikosti) namapovaného vzorku
            rotate 45*y
        }
    }
 
    // horní textura, která přidává jemnější vzorek
    texture {
        finish {                         // vlastnosti materiálu
            phong 1                      // intenzita a
            phong_size 100               // velikost odlesků
            brilliance 3                 // míra změny odlesků s úhlem dopadu světelných paprsků
            ambient 0.2                  // ambientní složka (pro simulaci všesměrového světla)
            diffuse 0.8                  // difúzní složka (pro simulaci směrového světla)
        }
        pigment {                        // definice vzorku textury
            wood                         // typ vzorku
            turbulence 0.025
            color_map {                  // čtyři barvy, které se ve vzorku střídají
                [0.00 0.15 color red 0.42 green 0.26 blue 0.15 color red 0.85 green 0.53 blue 0.10 ]
                [0.15 0.40 color red 0.85 green 0.53 blue 0.10 color rgbf 1 ]
                [0.40 0.80 color rgbf 1  color red 0.85 green 0.53 blue 0.10 ]
                [0.80 1.01 color red 0.85 green 0.53 blue 0.10 color red 0.42 green 0.26 blue 0.15 ]
            }
            scale <3.5, 1, 1>            // změna měřítka a natočení vzorku
            translate -50*y
            rotate 1.5*z
        }
    }
}
 
plane {                                  // rovina tvořící pozadí scény
    <0, 1, 0>, -6                        // posun a orientace roviny
    texture {                            // textura - vlastnosti povrchu
        pigment {                        // vlastní vzorek textury
            checker color Gray color White*0.9
        }
        finish {                         // optické vlastnosti materiálu
            reflection 0.10
        }
        scale 4
    }
}
 
 
 
// ------------------------------------------------------------
// finito
// ------------------------------------------------------------ 

4. Shodná testovací scéna napsaná v Pythonu s využitím Vapory a nové třídy Torus

Nyní si pojďme předchozí scénu přepsat do Vapory. Ve skutečnosti to není nic složitého, jen nesmíme na začátku zapomenout na deklaraci nové třídy Torus, která bude odvozena od třídy POVRayElement:

class Torus(POVRayElement):
    """Torus()"""

Úplný zdrojový kód tohoto příkladu naleznete na adrese https://github.com/tisnik/vapory-examples/blob/master/scene5.py:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from vapory import *
 
 
class Torus(POVRayElement):
    """Torus()"""
 
 
y = [0, 1, 0]
z = [0, 0, 1]
 
camera = Camera('location', [0, 20, -15],
                'look_at',  [0, -2, 0])
 
# dva světelné zdroje
 
# první (silnější) světelný zdroj s bílou barvou
light1 = LightSource([-50, 100, -80],
                     'color', [1.0, 1.0, 1.0])
 
# druhý (slabší) světelný zdroj
light2 = LightSource([250, 25, -100],
                     'color', [0.85, 0.53, 0.10])
 
 
# objekt ve scéně: torus (anuloid)
torus = Torus(7.0, 4.0,
              # spodní (podkladová) textura se základním vzorkem
              Texture(
                  Pigment('bozo',
                          ColorMap([0.0, 0.4, 'color', [0.36, 0.20, 0.09],
                                              'color', [0.36, 0.20, 0.09]],
                                   [0.4, 1.01, 'color', [0.85, 0.57, 0.44],
                                               'color', [0.85, 0.57, 0.44]]),
                          'scale', [4, 0.15, 0.15],
                          'rotate', '45*y')),
              # horní textura, která přidává jemnější vzorek
              Texture(
                  Pigment('wood',
                          'turbulence', 0.025,
                          # čtyři barvy, které se ve vzorku střídají
                          ColorMap([0.00, 0.15, 'color', [0.42, 0.26, 0.15],
                                                'color', [0.85, 0.53, 0.10]],
                                   [0.15, 0.40, 'color', [0.85, 0.53, 0.10],
                                                'color', [1.00, 1.00, 1.00, 1.00]],
                                   [0.40, 0.80, 'color', [1.00, 1.00, 1.00, 1.00],
                                                'color', [0.85, 0.53, 0.10]],
                                   [0.80, 1.01, 'color', [0.85, 0.53, 0.10],
                                                'color', [0.42, 0.26, 0.15]]),
                          'scale', [3.5, 1, 1],
                          'translate', [0, -50, 0],
                          'rotate', [0, 0, 1.5]
                          ),
                  Finish('phong', 1,
                         'phong_size', 100,
                         'brilliance', 3,
                         'ambient', 0.2,
                         'diffuse', 0.8)))
 
# druhý objekt - nekonečná rovina
plane = Plane(y,
              -6,
              Texture(
                  Pigment('checker',
                          'color', 'Gray',
                          'color', 'White*0.9'),
                  Finish('reflection', 0.10),
                  'scale', 4))
 
scene = Scene(camera, objects=[light1, light2, plane, torus],
              included=['colors.inc'],
              global_settings=['assumed_gamma 2.2'])
 
scene.render('scene5_vapory.png', width=400, height=300)

5. Zjednodušený zápis mapy barev – scéna popsaná v jazyku POV-Ray

V předchozím příkladu se pro deklaraci barvové mapy používal tento zápis, v němž se v každém vektoru specifikovaly dvě barvy pro dvě hodnoty z intervalu 0,0 až 1,0:

color_map {                   // dvě barvy, které se na vzorku střídají
    [0.0 0.4  color red 0.36 green 0.20 blue 0.09  color red 0.36 green 0.20 blue 0.09 ]
    [0.4 1.01 color red 0.858824 green 0.576471 blue 0.439216 color red 0.858824 green 0.576471 blue 0.439216]
}

Samozřejmě můžeme použít i alternativní (delší) zápis, který by však měl vést k totožnému obrázku:

color_map {                   // dvě barvy, které se na vzorku střídají
    [0.0 color red 0.36 green 0.20 blue 0.09]
    [0.4 color red 0.36 green 0.20 blue 0.09]
    [0.4 color red 0.858824 green 0.576471 blue 0.439216]
    [1.01 color red 0.858824 green 0.576471 blue 0.439216]
}

Obrázek 7: Tento obrázek je (v každém pixelu) totožný s obrázkem číslo 6, i když barvová mapa byla deklarována odlišným způsobem.

Pro jistotu se podívejme na úplný zdrojový kód upravené scény, který je možné nalézt na této adrese):

// ------------------------------------------------------------
// Jednoduchá scéna s uzavřeným objektem, plochou, dvojicí světel
// a jednou kamerou (pozorovatelem)
//
// rendering lze spustit příkazem:
//     povray +W800 +H600 +B +FN +D +Iscene6.pov +Oscene6.png
// (pro náhled postačí zadat povray scene6.pov)
//
// Založeno na souboru původně vytvořeném Danem Farmerem (leden 2002)
// ------------------------------------------------------------
 
#version 3.5;
 
// globální nastavení parametrů scény
global_settings {
    assumed_gamma 2.2
}
 
// načtení všech potřebných externích souborů
#include "colors.inc"
 
// nastavení kamery (pozorovatele)
camera {
    location  <0, 20, -15>               // pozice kamery
    look_at   <0, -2,   0>               // bod, na který kamera směřuje
}
 
// první (silnější) světelný zdroj s bílou barvou
light_source {
    <-50, 100, -80>                      // pozice světelného zdroje
    color rgb 1                          // barva světla (všech tří složek)
}
 
// druhý (slabší) světelný zdroj
light_source {
    <250, 25, -100>                      // pozice světelného zdroje
    color red 0.85 green 0.53 blue 0.10  // barva světla
}
 
// objekt ve scéně: torus (anuloid)
torus {
    7.0, 4.0                             // geometrické informace
                                         // (poloměry celého toroidu a "trubky")
 
    // spodní (podkladová) textura se základním vzorkem
    texture {
        pigment {                        // definice vzorku textury
           bozo                          // typ vzorku
           color_map {                   // dvě barvy, které se na vzorku střídají
            [0.0 color red 0.36 green 0.20 blue 0.09]
            [0.4 color red 0.36 green 0.20 blue 0.09]
            [0.4 color red 0.858824 green 0.576471 blue 0.439216]
            [1.01 color red 0.858824 green 0.576471 blue 0.439216]
            }
            scale <4, 0.15, 0.15>        // změna měřítka (velikosti) namapovaného vzorku
            rotate 45*y
        }
    }
 
    // horní textura, která přidává jemnější vzorek
    texture {
        finish {                         // vlastnosti materiálu
            phong 1                      // intenzita a
            phong_size 100               // velikost odlesků
            brilliance 3                 // míra změny odlesků s úhlem dopadu světelných paprsků
            ambient 0.2                  // ambientní složka (pro simulaci všesměrového světla)
            diffuse 0.8                  // difúzní složka (pro simulaci směrového světla)
        }
        pigment {                        // definice vzorku textury
            wood                         // typ vzorku
            turbulence 0.025
            color_map {                  // čtyři barvy, které se ve vzorku střídají
                [0.00 color red 0.42 green 0.26 blue 0.15]
                [0.15 color red 0.85 green 0.53 blue 0.10]
                [0.40 color rgbf 1]
                [0.80 color red 0.85 green 0.53 blue 0.10]
                [1.01 color red 0.42 green 0.26 blue 0.15]
            }
            scale <3.5, 1, 1>            // změna měřítka a natočení vzorku
            translate -50*y
            rotate 1.5*z
        }
    }
}
 
plane {                                  // rovina tvořící pozadí scény
    <0, 1, 0>, -6                        // posun a orientace roviny
    texture {                            // textura - vlastnosti povrchu
        pigment {                        // vlastní vzorek textury
            checker color Gray color White*0.9
        }
        finish {                         // optické vlastnosti materiálu
            reflection 0.10
        }
        scale 4
    }
}
 
 
 
// ------------------------------------------------------------
// finito
// ------------------------------------------------------------ 

6. Zjednodušený zápis mapy barev – scéna popsaná v jazyku Python s využitím Vapory

Přepis předchozího příkladu do Pythonu s použitím knihovny Vapory dopadne následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from vapory import *
 
 
class Torus(POVRayElement):
    """Torus()"""
 
 
y = [0, 1, 0]
z = [0, 0, 1]
 
camera = Camera('location', [0, 20, -15],
                'look_at',  [0, -2, 0])
 
# dva světelné zdroje
 
# první (silnější) světelný zdroj s bílou barvou
light1 = LightSource([-50, 100, -80],
                     'color', [1.0, 1.0, 1.0])
 
# druhý (slabší) světelný zdroj
light2 = LightSource([250, 25, -100],
                     'color', [0.85, 0.53, 0.10])
 
 
# objekt ve scéně: torus (anuloid)
torus = Torus(7.0, 4.0,
              # spodní (podkladová) textura se základním vzorkem
              Texture(
                  Pigment('bozo',
                          ColorMap([0.00, 'color', [0.36, 0.20, 0.09]],
                                   [0.40, 'color', [0.36, 0.20, 0.09]],
                                   [0.40, 'color', [0.85, 0.57, 0.44]],
                                   [1.01, 'color', [0.85, 0.57, 0.44]]),
                          'scale', [4, 0.15, 0.15],
                          'rotate', '45*y')),
              # horní textura, která přidává jemnější vzorek
              Texture(
                  Pigment('wood',
                          'turbulence', 0.025,
                          # čtyři barvy, které se ve vzorku střídají
                          ColorMap([0.00, 'color', [0.42, 0.26, 0.15]],
                                   [0.15, 'color', [0.85, 0.53, 0.10]],
                                   [0.40, 'color', [1.00, 1.00, 1.00, 1.00]],
                                   [0.80, 'color', [0.85, 0.53, 0.10]],
                                   [1.01, 'color', [0.42, 0.26, 0.15]]),
                          'scale', [3.5, 1, 1],
                          'translate', [0, -50, 0],
                          'rotate', [0, 0, 1.5]
                          ),
                  Finish('phong', 1,
                         'phong_size', 100,
                         'brilliance', 3,
                         'ambient', 0.2,
                         'diffuse', 0.8)))
 
# druhý objekt - nekonečná rovina
plane = Plane(y,
              -6,
              Texture(
                  Pigment('checker',
                          'color', 'Gray',
                          'color', 'White*0.9'),
                  Finish('reflection', 0.10),
                  'scale', 4))
 
scene = Scene(camera, objects=[light1, light2, plane, torus],
              included=['colors.inc'],
              global_settings=['assumed_gamma 2.2'])
 
scene.render('scene6_vapory.png', width=400, height=300)

7. Využití implicitních ploch (blobs, metaballs) v POV-Rayi

Raytracer POV-Ray podporuje, podobně jako některé další modelovací a renderovací nástroje (například Blender), modelování složitých těles s využitím takzvaných implicitních ploch. Jedná se o velmi zajímavou techniku, která byla převzata z jiného (dnes již nevyvíjeného) raytraceru nazvaného Polyray Alexandra Enzmanna, ve kterém byly implicitní plochy použity v mnohem obecnějším pojetí než v POV-Rayi. Při popisu tělesa s využitím implicitních ploch se těleso nepopisuje hranicí (jak je tomu například u klasických polygonálních či parametrických reprezentací) ani vyčíslením obsazeného objemu v prostoru, ale množinou implicitních funkcí spolu se specifikací jejich vzájemné kombinace.

povray0601

Obrázek 8: Implicitní plocha vytvořená pomocí tří prvků kostry.

Jen ve stručnosti si řekněme teorii, na níž je vykreslování implicitních ploch založeno. Základem je implicitní funkce, která je určena rovnicí, která každému bodu v prostoru přiřazuje určitou hodnotu. Tuto hodnotu můžeme považovat například za hustotu tělesa či intenzitu síly v daném místě prostoru. V rovnici popisující implicitní plochu vystupují implicitní funkce, jejichž hodnota je závislá na poloze bodu (v Euklidovském trojrozměrném prostoru E3 jde o běžně používané souřadnice x, y a z). Vlastní vykreslení spočívá ve volbě hranice (threshold) zmíněné hustoty/síly, pomocí které je určena izoplocha (isosurface), která je s využitím analytických či numerických metod vykreslena. Díky funkci „hustoty“ je navíc zřejmé, která část prostoru leží uvnitř tělesa a která vně.

povray0602

Obrázek 9: Model vytvořený především z (jediné) implicitní plochy v dnes již historickém modelovacím programu. Vytvoření modelu bylo otázkou doslova několika minut, protože modelovací možnosti implicitních ploch jsou poměrně vysoké.

8. Rozšíření možností CSG pomocí implicitních ploch

Implicitní plochy lze považovat také za určitou alternativu či rozšíření ke konstruktivní geometrii těles (CSG), o které jsme se zmínili již minule. Z tohoto důvodu se také modelování s využitím implicitních ploch někdy označuje jako Implicit Solid Modelling neboli ISM. Výhodou ISM oproti CSG je poměrně jednoduchý výpočet celkového tělesa, které vzniká sloučením základních primitiv. Zatímco v CSG reprezentaci se musí (často složitě traverzací n-árního stromu) počítat průsečíky jednotlivých základních těles, v ISM reprezentaci se pozice průsečíků nemusí vyčíslit, protože se celkový tvar tělesa vyjadřuje pomocí jednoduché směšovací funkce (blending function). Nelze tak sice dosáhnout numericky naprosto přesných výsledků, ale v mnoha praktických aplikacích je kvalita a přesnost vizuálního výsledku více než dostačující.

povray0603

Obrázek 10: Složitější model, jehož ústřední část tvoří implicitní plochy.

9. Konstrukce implicitních ploch s využitím kostry

Implicitní plocha se konstruuje pomocí takzvané kostry (skeleton). Kostra se skládá z jednodušších částí nazývaných prvky kostry, kde každý prvek odpovídá jedné implicitní funkci. Prvky kostry jsou ve své nejjednodušší podobě vyjádřeny svým středem a intenzitou, kterou působí na své okolí. Tato intenzita je největší ve středu prvku kostry a se vzrůstající vzdáleností od tohoto středu se snižuje.

fractals57_6

Obrázek 11: Vykreslený model L-systému Airhorse složený z více objektů s bloby.

V původní definici funkce pro vyjádření intenzity byla intenzita v každém bodě prostoru kladná (a tedy nenulová), protože pro popis intenzity se používala exponenciální funkce. Pro urychlení výpočtů se zavádí jistá zjednodušení, která zaručí, že od určité vzdálenosti od středu prvku kostry bude intenzita nulová. Definici implicitní plochy lze rozšířit tak, že k prvku kostry povolíme přiřazovat zápornou intenzitu, což nám dovolí jednoduše modelovat i děravé objekty nebo objekty vzniklé odebíráním hmoty od základního tvaru (což POV-Ray umožňuje).

blobs1

Obrázek 12: Ukázka vzájemného prolínání dvou bodových prvků kostry při tvorbě izoplochy. V dolní části animace jsou zobrazeny dva bodové prvky kostry spolu s poloměrem dosahu jejich sil. Při vzájemném přibližování dochází ke sčítání sil a „slévání“ obou tvarů. V horní části je zobrazen průběh sil v řezu procházejícím přes oba bodové prvky kostry se silou=0,7, přičemž je na grafu zvýrazněna prahová hodnota threshold=0,5.

Pro algoritmy pracující s implicitními plochami je důležité vyjádřit hranici tělesa. Proto je definována globální proměnná T (označení T je odvozeno od slova threshold – hranice hodnot, práh), která určuje mez, kde se nachází hranice (obálka) implicitní plochy. Je-li intenzita v určitém bodě v prostoru větší než zadaná mez, leží bod uvnitř tělesa, je-li naopak intenzita menší než mez, leží bod vně tělesa. Body, jejichž intenzita je rovna proměnné T, leží přesně na povrchu implicitní plochy – tvoří izoplochu. Pro osamocené body, které tvoří prvky kostry, nabývá obálka implicitní plochy tvar kružnice v ploše a koule v prostoru, pro úsečky má obálka tvar válce s polokulovitými základnami atd.

blobs2

Obrázek 13: Ukázka vzájemného prolínání dvou bodových prvků kostry při zvýšení sil obou bodových prvků kostry na hodnotu 1,0.

10. Vykreslení implicitních ploch POV-Rayem

Jak jsme si již řekli v předchozích kapitolách, podporuje POV-Ray přímou deklaraci implicitních ploch. Pro tento účel se používá 3D entita nazvaná blob, která může obsahovat prakticky libovolné množství prvků kostry, přičemž každý prvek kostry se zapisuje slovem component, za nímž následuje specifikace poloměru, síly a umístění prvku v 3D prostoru. Nesmíme zapomenout ani na deklaraci hraniční hodnoty (threshold), která je pochopitelně pro všechny prvky kostry konstantní:

blob {
    threshold 0.6                        // hraniční hodnota
    component 1.0, 1.0, < 0.750,  0, 0>  // prvek kostry: síla, poloměr, souřadnice v prostoru
    component 1.0, 1.0, <-0.375,  0.64952, 0> // druhý prvek kostry
    component 1.0, 1.0, <-0.375, -0.64952, 0> // třetí prvek kostry
    ...
    ...
    ...
}

Výsledek může po doplnění kamery, osvětlení a nezbytných textur vypadat následovně:

Obrázek 14: Výsledek tohoto demonstračního příkladu vypočtený POV-Rayem.

Opět se podívejme na úplný zdrojový kód této scény, který je možné nalézt na této adrese):

// ------------------------------------------------------------
// Jednoduchá scéna s jednou implicitní plochou vymodelovanou
// pomocí tří prvků kostry.
//
// rendering lze spustit příkazem:
//     povray +W800 +H600 +B +FN +D +Iscene7.pov +Oscene7.png
// (pro náhled postačí zadat povray scene7.pov)
// ------------------------------------------------------------
 
#version 3.5;
 
#include "colors.inc"
 
global_settings {
    assumed_gamma 2.2
}
 
camera {
   location <0, 0, -2.5>                 // umístění kamery
   up       y                            // a nahoru
   right    4/3 * x                      // vektor směřující doprava
   look_at  <0, 0, 0>                    // bod, na který se kamera zaměřila
}
 
light_source {                           // první světelný zdroj
    <2, 10, -10>                         // pozice světelného zdroje
    color red 0.7 green 0.7 blue 0.7     // barva světelného zdroje
}
 
light_source {                           // druhý světelný zdroj
    <0, 0, -10000>                       // pozice světelného zdroje
    color red 0.7 green 0.7 blue 0.7     // barva světelného zdroje
    shadowless
}
 
plane {                                  // rovina tvořící pozadí scény
    <0, 0, 1>, 2                         // posun a orientace roviny
    // hollow on
    pigment {                            // procedurální textura
        agate
        agate_turb 0.3
    }
    finish {                             // optické vlastnosti materiálu povrchu
        ambient 0.1
        diffuse 0.4
    }
}
 
blob {
    threshold 0.6                        // hraniční hodnota
    component 1.0, 1.0, < 0.750,  0, 0>  // prvek kostry: síla, poloměr, souřadnice v prostoru
    component 1.0, 1.0, <-0.375,  0.64952, 0> // druhý prvek kostry
    component 1.0, 1.0, <-0.375, -0.64952, 0> // třetí prvek kostry
 
    texture {
        pigment {
            color red 0 green 0 blue 1   // barva materiálu
        }
        finish {                         // optické vlastnosti materiálu povrchu
            ambient  0.2
            diffuse  0.4
            specular 0.6
            phong    0.6
            phong_size 3
            reflection 0
        }
    }
}
 
// ------------------------------------------------------------
// finito
// ------------------------------------------------------------ 

11. Vykreslení stejné scény s využitím knihovny Vapory

Předchozí scénu popsanou v jazyku POV-Raye samozřejmě můžeme přepsat do Vapory a to relativně snadno. Nejdříve musíme vytvořit novou třídu nazvanou Blob, která bude představovat jakoukoli implicitní plochu:

class Blob(POVRayElement):
    """Blob()"""

Následně, a to bez nutnosti jakýchkoliv dalších úprav, je již možné vytvořit novou implicitní plochu s libovolným počtem prvků kostry:

# první objekt - blob/metaball
blob = Blob('threshold', 0.6,
            'component', 1.0, 1.0, [0.750, 0.000, 0.000],
            'component', 1.0, 1.0, [-.375, 0.64952, 0.000],
            'component', 1.0, 1.0, [-.375, -0.64952, 0.000],
            ...
            ...
            ...

Opět se podívejme na úplný zdrojový kód tohoto demonstračního příkladu, který v případě potřeby naleznete na adrese https://github.com/tisnik/vapory-examples/blob/master/scene7.py:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from vapory import *
 
 
class Blob(POVRayElement):
    """Blob()"""
 
 
y = [0, 1, 0]
z = [0, 0, 1]
 
camera = Camera('location', [ 0.0, 0.0, -2.5],
                'up',       [ 0.0, 1.0, 0.0],
                'right',    [ 4/3, 0.0, 0.0],
                'look_at',  [ 0.0, 0.0, 0.0])
 
# dva světelné zdroje
light1 = LightSource([2, 10, -10],
                     'color', [0.7, 0.7, 0.7])
 
light2 = LightSource([0, 0, -10000],
                     'color', [0.7, 0.7, 0.7],
                     'shadowless')
 
# první objekt - blob/metaball
blob = Blob('threshold', 0.6,
            'component', 1.0, 1.0, [0.750, 0.000, 0.000],
            'component', 1.0, 1.0, [-.375, 0.64952, 0.000],
            'component', 1.0, 1.0, [-.375, -0.64952, 0.000],
            Texture(
                Pigment('color', [0, 0, 1]),
                Finish('ambient', 0.2,
                       'diffuse', 0.4,
                       'specular', 0.6,
                       'phong', 0.6,
                       'phong_size', 3,
                       'reflection', 0)))
 
# druhý objekt - nekonečná rovina
plane = Plane(z,
              2,
              'hollow', 'on',
              Texture(
                  Pigment('agate',
                          'agate_turb', 0.3),
                  Finish('ambient', 0.1,
                         'diffuse', 0.4)))
 
scene = Scene(camera, objects=[light1, light2, plane, blob],
              included=['colors.inc'],
              global_settings=['assumed_gamma 2.2'])
 
scene.render('scene7_vapory.png', width=400, height=300)

12. Idiomatický kód – použití n-tic namísto seznamů

Ve všech předchozích příkladech naprogramovaných v Pythonu s využitím knihovny Vapory jsme pro popis vektorů (světelných složek, souřadnic v 3D prostoru, vektorů kamery atd.) používali datovou strukturu seznam (list). Vzhledem k tomu, že se v naprosté většině případů jedná o neměnné (immutable) datové struktury, bude lepší provést přepis a namísto seznamů na všech místech použít n-tice (tuple). Upravený příklad bude vypadat následovně (jeho funkcionalita se v tomto případě nijak nezmění):

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from vapory import *
 
 
class Blob(POVRayElement):
    """Blob()"""
 
 
y = (0, 1, 0)
z = (0, 0, 1)
 
camera = Camera('location', ( 0.0, 0.0, -2.5),
                'up',       ( 0.0, 1.0, 0.0),
                'right',    ( 4/3, 0.0, 0.0),
                'look_at',  ( 0.0, 0.0, 0.0))
 
# dva světelné zdroje
light1 = LightSource((2, 10, -10),
                     'color', (0.7, 0.7, 0.7))
 
light2 = LightSource((0, 0, -10000),
                     'color', (0.7, 0.7, 0.7),
                     'shadowless')
 
# první objekt - blob/metaball
blob = Blob('threshold', 0.6,
            'component', 1.0, 1.0, (0.750, 0.000, 0.000),
            'component', 1.0, 1.0, (-.375, 0.64952, 0.000),
            'component', 1.0, 1.0, (-.375, -0.64952, 0.000),
            Texture(
                Pigment('color', (0, 0, 1)),
                Finish('ambient', 0.2,
                       'diffuse', 0.4,
                       'specular', 0.6,
                       'phong', 0.6,
                       'phong_size', 3,
                       'reflection', 0)))
 
# druhý objekt - nekonečná rovina
plane = Plane(z,
              2,
              'hollow', 'on',
              Texture(
                  Pigment('agate',
                          'agate_turb', 0.3),
                  Finish('ambient', 0.1,
                         'diffuse', 0.4)))
 
scene = Scene(camera, objects=(light1, light2, plane, blob),
              included=('colors.inc',),
              global_settings=('assumed_gamma 2.2',))
 
scene.render('scene7_vapory_B.png', width=400, height=300)

13. Využití možností knihovny MoviePy pro jednodušší vytvoření animace

Poslední zajímavou vlastností projektu Vapory je podpora pro přímé vytváření video souborů. Již minule jsme si ukázali, že nám pochopitelně nic nebrání vytvářet (renderovat) jednotlivé snímky, ty postupně ukládat do formátu PNG či TGA a následně z těchto snímků vytvořit výsledné video například s využitím externího nástroje ffmpeg. Připomeňme si, že celý postup vypadal zhruba takto – scénu nově vytváříme pro každý snímek (frame) a ukládáme ji do očíslovaného souboru, aby ffmpeg dovedl jednotlivé snímky korektně zařadit:

def construct_scene(t):
    ...
    ... příprava objektů pro scénu
    ...
    return Scene(camera,
                 objects=[light1, light2, light3, csg_object, plane],
                 included=["colors.inc", "stones.inc", "woods.inc"],
                 global_settings=["assumed_gamma 2.2"])
 
FRAMES = 100
 
# vykreslení všech snímků
for frame in range(0, FRAMES):
    t = frame / float(FRAMES)
    scene = construct_scene(t)
    filename = "frame_{:03d}.png".format(frame)
    scene.render(filename, width=400, height=300)

Ve skutečnosti však máme ještě jednu alternativu, a to přímo zkombinovat možnosti projektu Vapory s knihovnou MoviePy. Nejprve musíme provést korektní import všech potřebných modulů:

from vapory import *
from moviepy.editor import VideoClip

Následně budeme postupovat naprosto stejným způsobem, jako při tvorbě jakéhokoli jiného videa v MoviePy – připravíme si callback funkci nazvanou make_frame, která bude automaticky volána při renderingu videa a bude se jí předávat čas snímku (reálné číslo):

# parametry animace
DURATION = 10
FPS = 20
 
 
def make_frame(t):
    scene = construct_scene(t / DURATION)
    return scene.render(width=400, height=300, antialiasing=0.001)
 
 
animation = VideoClip(make_frame, duration=DURATION)
animation.write_videofile('scene4.ogv', fps=FPS, progress_bar=True, bitrate="800000")

Povšimněte si, že funkce make_frame vrací objekt představující snímek PO vykreslení. Interně se jedná o datovou strukturu typu ndarray, a to z toho důvodu, že jsme metodě Scene.render() nepředali jméno výstupního souboru. A jak již víme z úvodního článku, zpracovává MoviePy snímky reprezentované právě polem typu ndarray:

class Scene:
    """ A scene contains Items and can be written to a file.
 
    def render(self, outfile=None, height=None, width=None,
                     quality=None, antialiasing=None, remove_temp=True,
                     auto_camera_angle = True  ):
 
        """ Renders the scene to a PNG, a numpy array, or the IPython Notebook.
 
        Parameters
        ------------
 
        outfile
          Name of the output:
          - "myfile.png" to output a PNG file
          - None to output a numpy array (if numpy is installed).
          - 'ipython' (and call this function last in an IPython Notebook)
 
        height
          height in pixels
 
        width
          width in pixels
 
        """

14. Úprava příkladu z předchozího článku: animace změny CSG tělesa a textury na toru

Minule jsme si ukazovali jednoduchou animaci založenou na postupné změně pozice těles, která se vzájemně kombinují s využitím CSG (Constructive Solid Geometry). Animace byla vytvořena z jednotlivých snímků, které musely být nejdříve uloženy na disk a teprve poté se spustil ffmpeg, aby je zpracoval a vytvořil z nich soubor s videem (použití klasické pipeline v tomto případě není možné, protože snímky do kodeku obecně vstupují v náhodném pořadí). Pokud ovšem zkombinujeme možnosti nabízené knihovnami Vapory a MoviePy, celý kód s deklarací scény se do jisté míry zjednoduší.

Výsledné video:

https://tisnik.github.io/moviepy-videos/video11.htm

Zdrojový kód upraveného příkladu vypadá následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from vapory import *
from moviepy.editor import VideoClip
 
 
y = [0, 1, 0]
z = [0, 0, 1]
 
camera = Camera('location', [1.65, 5.5,-5.0],
                'up',       [ 0.0, 1.0, 0.0],
                'right',    [ 4/3, 0.0, 0.0],
                'look_at',  [ 0.0, 0.5,-1.0])
 
# tři světelné zdroje
light1 = LightSource([-30, 11, 20],
                     'color', 'White')
 
light2 = LightSource([31, 12, -20],
                     'color', 'White')
 
light3 = LightSource([32, 11, -20],
                     'color', 'LightGray')
 
VEL = 1.45  # velikost krychle
 
box = Box([-VEL, -VEL, -VEL],
          [VEL, VEL, VEL],
          Texture(
              'T_Wood23',
              Finish('phong', 1,
                     'phong_size', 300,
                     'reflection', 0.15)))
 
# druhý objekt - nekonečná rovina
plane = Plane(y,
              -1.5,
              Texture(
                  'T_Stone1',
                  Pigment('octaves', 3,
                          'rotate', [i * 90 for i in z]),
                  Finish('reflection', 0.10)))
 
def construct_scene(t):
    sphere = Sphere([0, 3.3 - 7.0*t, 0],
                    1.8,
                    Texture(
                        'T_Wood24',
                        Finish('phong', 1,
                               'phong_size', 300,
                               'reflection', 0.15)))
 
    csg_object = Difference(box, sphere)
 
    return Scene(camera,
                 objects=[light1, light2, light3, csg_object, plane],
                 included=["colors.inc", "stones.inc", "woods.inc"],
                 global_settings=["assumed_gamma 2.2"])
 
 
# parametry animace
DURATION = 10
FPS = 20
 
 
def make_frame(t):
    scene = construct_scene(t / DURATION)
    return scene.render(width=400, height=300, antialiasing=0.001)
 
 
animation = VideoClip(make_frame, duration=DURATION)
animation.write_videofile('scene4.ogv', fps=FPS, progress_bar=True, bitrate="800000")

Další video…

https://tisnik.github.io/moviepy-videos/video12.htm

vzniklo z této scény:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from vapory import *
from moviepy.editor import VideoClip
 
 
class Torus(POVRayElement):
    """Torus()"""
 
 
y = [0, 1, 0]
z = [0, 0, 1]
 
camera = Camera('location', [0, 20, -15],
                'look_at',  [0, -2, 0])
 
# dva světelné zdroje
 
# první (silnější) světelný zdroj s bílou barvou
light1 = LightSource([-50, 100, -80],
                     'color', [1.0, 1.0, 1.0])
 
# druhý (slabší) světelný zdroj
light2 = LightSource([250, 25, -100],
                     'color', [0.85, 0.53, 0.10])
 
 
# objekt - nekonečná rovina
plane = Plane(y,
              -6,
              Texture(
                  Pigment('checker',
                          'color', 'Gray',
                          'color', 'White*0.9'),
                  Finish('reflection', 0.10),
                  'scale', 4))
 
 
# objekt ve scéně: torus (anuloid)
def create_torus(t):
    return Torus(7.0, 4.0,
                 # spodní (podkladová) textura se základním vzorkem
                 Texture(
                     Pigment('bozo',
                             ColorMap([0.0, 0.4, 'color', [0.36, 0.20, 0.09],
                                                 'color', [0.36, 0.20, 0.09]],
                                      [0.4, 1.01, 'color', [0.85, 0.57, 0.44],
                                                  'color', [0.85, 0.57, 0.44]]),
                             'scale', [4, 0.15, 0.15],
                             'rotate', [0, 45, 0])),
                 # horní textura, která přidává jemnější vzorek
                 Texture(
                     Pigment('wood',
                             'turbulence', 0.025,
                             # čtyři barvy, které se ve vzorku střídají
                             ColorMap([0.00, 0.15, 'color', [0.42, 0.26, 0.15],
                                                   'color', [0.85, 0.53, 0.10]],
                                      [0.15, 0.40, 'color', [0.85, 0.53, 0.10],
                                                   'color', [1.00, 1.00, 1.00, 1.00]],
                                      [0.40, 0.80, 'color', [1.00, 1.00, 1.00, 1.00],
                                                   'color', [0.85, 0.53, 0.10]],
                                      [0.80, 1.01, 'color', [0.85, 0.53, 0.10],
                                                   'color', [0.42, 0.26, 0.15]]),
                             'scale', [3.5, 1, 1],
                             'translate', [0, -50, 0],
                             'rotate', [0, 0, 10.0 * t]
                             ),
                     Finish('phong', 1,
                            'phong_size', 100,
                            'brilliance', 3,
                            'ambient', 0.2,
                            'diffuse', 0.8)))
 
 
def construct_scene(t):
    torus = create_torus(t)
 
    return Scene(camera, objects=[light1, light2, plane, torus],
                 included=['colors.inc'],
                 global_settings=['assumed_gamma 2.2'])
 
 
# parametry animace
DURATION = 10
FPS = 20
 
 
def make_frame(t):
    scene = construct_scene(t / DURATION)
    return scene.render(width=400, height=300, antialiasing=0.001)
 
 
animation = VideoClip(make_frame, duration=DURATION)
animation.write_videofile('scene5.ogv', fps=FPS, progress_bar=True, bitrate="800000")

15. Příprava na animaci změn parametrů implicitních ploch

Nyní když víme, jakým způsobem lze velmi snadno generovat video soubory přímo z Vapory, si můžeme ukázat další příklad, v němž se budou postupně měnit parametry výše popsaných implicitních ploch (blobs, metaballs).

Výsledkem by mělo být toto video:

https://tisnik.github.io/moviepy-videos/video13.htm

Nejprve si ukažme statickou podobu této scény:

// ------------------------------------------------------------
// Jednoduchá scéna s několika implicitními plochami (blobs),
// které se liší pouze hodnotou threshold.
//
// rendering lze spustit příkazem:
//     povray +W800 +H600 +B100 +FN +D +Iscene8.pov +Oscene8.png
// (pro náhled postačí zadat povray scene8.pov)
// ------------------------------------------------------------
 
#version 3.5;
 
#include "colors.inc"
 
global_settings {
    assumed_gamma 2.2
}
 
camera {
   orthographic                          // vypnutí perspektivy
   location <0, 0, -1>                   // umístění kamery
   right    5*4/3 * x                    // vektor směřující doprava
   up       y*5                          // a nahoru
   look_at  <0, 0, 0>                    // bod, na který se kamera zaměřila
 
}
 
light_source {                           // první světelný zdroj
    <2, 10, -10>                         // pozice světelného zdroje
    color red 0.7 green 0.7 blue 0.7     // barva světelného zdroje
}
 
light_source {                           // druhý světelný zdroj
    <0, 0, -10000>                       // pozice světelného zdroje
    color red 0.7 green 0.7 blue 0.7     // barva světelného zdroje
    shadowless
}
 
plane {                                  // rovina tvořící pozadí scény
    <0, 0, 1>, 2                         // posun a orientace roviny
    hollow on
    pigment {                            // procedurální textura
        agate
        agate_turb 0.9
    }
    finish {                             // optické vlastnosti materiálu povrchu
        ambient 0.1
        diffuse 0.4
    }
}
 
#declare Tex =
texture {
    pigment {
        color red 0.6 green 0.8 blue 1
    }
    finish {
        ambient 0.2 diffuse 0.4 phong 0.5 phong_size 5
    }
}
 
blob {
    threshold 0.4                        // hraniční hodnota
    component 1.0, 1.0, < 0, -0.6, 0>    // prvek kostry: síla, poloměr, souřadnice v prostoru
    component 1.0, 1.0, < 0,  0.6, 0>    // druhý prvek kostry
    texture {Tex}
    translate <-2.0, -1.2, 0>
}
 
blob {
    threshold 0.5                        // hraniční hodnota
    component 1.0, 1.0, < 0, -0.6, 0>    // prvek kostry: síla, poloměr, souřadnice v prostoru
    component 1.0, 1.0, < 0,  0.6, 0>    // druhý prvek kostry
    texture {Tex}
    translate <-0.65, -1.2, 0>
}
 
blob {
    threshold 0.6                        // hraniční hodnota
    component 1.0, 1.0, < 0, -0.6, 0>    // prvek kostry: síla, poloměr, souřadnice v prostoru
    component 1.0, 1.0, < 0,  0.6, 0>    // druhý prvek kostry
    texture {Tex}
    translate < 0.65, -1.2, 0>
}
 
blob {
    threshold 0.7                        // hraniční hodnota
    component 1.0, 1.0, < 0, -0.6, 0>    // prvek kostry: síla, poloměr, souřadnice v prostoru
    component 1.0, 1.0, < 0,  0.6, 0>    // druhý prvek kostry
    texture {Tex}
    translate < 2.0, -1.2, 0>
}
 
blob {
    threshold 0.75                       // hraniční hodnota
    component 1.0, 1.0, < 0, -0.6, 0>    // prvek kostry: síla, poloměr, souřadnice v prostoru
    component 1.0, 1.0, < 0,  0.6, 0>    // druhý prvek kostry
    texture {Tex}
    translate <-2.0, 1.2, 0>
}
 
blob {
    threshold 0.8                        // hraniční hodnota
    component 1.0, 1.0, < 0, -0.6, 0>    // prvek kostry: síla, poloměr, souřadnice v prostoru
    component 1.0, 1.0, < 0,  0.6, 0>    // druhý prvek kostry
    texture {Tex}
    translate <-0.65, 1.2, 0>
}
 
blob {
    threshold 0.82                       // hraniční hodnota
    component 1.0, 1.0, < 0, -0.6, 0>    // prvek kostry: síla, poloměr, souřadnice v prostoru
    component 1.0, 1.0, < 0,  0.6, 0>    // druhý prvek kostry
    texture {Tex}
    translate < 0.65, 1.2, 0>
}
 
blob {
    threshold 0.9                        // hraniční hodnota
    component 1.0, 1.0, < 0, -0.6, 0>    // prvek kostry: síla, poloměr, souřadnice v prostoru
    component 1.0, 1.0, < 0,  0.6, 0>    // druhý prvek kostry
    texture {Tex}
    translate < 2.0, 1.2, 0>
}
 
// ------------------------------------------------------------
// finito
// ------------------------------------------------------------

Obrázek 15: Výsledek renderingu předchozího příkladu.

16. Shodná testovací scéna napsaná v Pythonu s využitím Vapory

Přepis do Vapory je snadný a nečekají zde na nás žádné problémy. Jen si dovolím poukázat na imperativní způsob tvorby metaballs, který je kratší a vlastně i obecnější, než ruční specifikace jednotlivých prvků kostry tak, jak jsme to viděli v případě zdrojového kódu napsaného přímo v jazyku POV-Raye:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from vapory import *
 
 
class Blob(POVRayElement):
    """Blob()"""
 
 
y = (0, 1, 0)
z = (0, 0, 1)
 
camera = Camera('orthographic',
                'location', (0, 0, -1),
                'right', '5*4/3*x',
                'up', 'y*5',
                'look_at', (0, 0, 0))
 
# dva světelné zdroje
light1 = LightSource((2, 10, -10),
                     'color', (0.7, 0.7, 0.7))
 
light2 = LightSource((0, 0, -10000),
                     'color', (0.7, 0.7, 0.7),
                     'shadowless')
 
 
# objekt - nekonečná rovina
plane = Plane(z,
              2,
              'hollow', 'on',
              Texture(
                  Pigment('agate',
                          'agate_turb', 0.9),
                  Finish('ambient', 0.1,
                         'diffuse', 0.4)))
 
def new_blob(threshold, dx, dy):
    return Blob('threshold', threshold,
                'component', 1.0, 1.0, (0, -0.6, 0),
                'component', 1.0, 1.0, (0,  0.6, 0),
                Texture(
                      Pigment('color', (0.6, 0.8, 1.0)),
                      Finish('ambient', 0.2,
                             'diffuse', 0.4,
                             'phong', 0.5,
                             'phong_size', 5)),
                'translate', (dx, dy, 0))
 
 
objects=[light1, light2, plane]
objects.append(new_blob(0.40, -2.00, -1.2))
objects.append(new_blob(0.50, -0.65, -1.2))
objects.append(new_blob(0.60,  0.65, -1.2))
objects.append(new_blob(0.70,  2.00, -1.2))
objects.append(new_blob(0.75, -2.00, 1.2))
objects.append(new_blob(0.80, -0.65, 1.2))
objects.append(new_blob(0.82,  0.65, 1.2))
objects.append(new_blob(0.90,  2.00, 1.2))
 
scene = Scene(camera, objects=objects,
              included=('colors.inc',),
              global_settings=('assumed_gamma 2.2',))
 
scene.render('scene8_vapory.png', width=400, height=300, auto_camera_angle=False)

17. Naprogramování animace – tvorba jednotlivých snímků později spojených do videa

Celý postup pro vytvoření animace z jednotlivých snímků již známe, takže jen krátce:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from vapory import *
from math import *
 
 
class Blob(POVRayElement):
    """Blob()"""
 
 
y = (0, 1, 0)
z = (0, 0, 1)
 
camera = Camera('orthographic',
                'location', (0, 0, -1),
                'right', '5*4/3*x',
                'up', 'y*5',
                'look_at', (0, 0, 0))
 
# dva světelné zdroje
light1 = LightSource((2, 10, -10),
                     'color', (0.7, 0.7, 0.7))
 
light2 = LightSource((0, 0, -10000),
                     'color', (0.7, 0.7, 0.7),
                     'shadowless')
 
 
# objekt - nekonečná rovina
plane = Plane(z,
              2,
              'hollow', 'on',
              Texture(
                  Pigment('agate',
                          'agate_turb', 0.9),
                  Finish('ambient', 0.1,
                         'diffuse', 0.4)))
 
 
def new_blob(threshold, dx, dy, t):
    delta = 0.3 * sin(2*pi*t)
    return Blob('threshold', threshold,
                'component', 1.0, 1.0, (0, -0.5 - delta, 0),
                'component', 1.0, 1.0, (0,  0.5 + delta, 0),
                Texture(
                      Pigment('color', (0.6, 0.8, 1.0)),
                      Finish('ambient', 0.2,
                             'diffuse', 0.4,
                             'phong', 0.5,
                             'phong_size', 5)),
                'translate', (dx, dy, 0))
 
 
def construct_scene(t):
    objects = [light1, light2, plane]
    objects.append(new_blob(0.40, -2.00, -1.2, t))
    objects.append(new_blob(0.50, -0.65, -1.2, t))
    objects.append(new_blob(0.60,  0.65, -1.2, t))
    objects.append(new_blob(0.70,  2.00, -1.2, t))
    objects.append(new_blob(0.75, -2.00, 1.2, t))
    objects.append(new_blob(0.80, -0.65, 1.2, t))
    objects.append(new_blob(0.82,  0.65, 1.2, t))
    objects.append(new_blob(0.90,  2.00, 1.2, t))
 
    return Scene(camera, objects=objects,
                 included=('colors.inc',),
                 global_settings=('assumed_gamma 2.2',))
 
 
FRAMES = 200
 
for frame in range(0, FRAMES):
    t = frame / float(FRAMES)
    scene = construct_scene(t)
    filename = "frame_{:03d}.png".format(frame)
    scene.render(filename, width=400, height=300, auto_camera_angle=False)

18. Naprogramování animace – použití MoviePy

A na samotný závěr vytvoření videa s využitím knihovny MoviePy:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from vapory import *
from moviepy.editor import VideoClip
from math import *
 
 
class Blob(POVRayElement):
    """Blob()"""
 
 
y = (0, 1, 0)
z = (0, 0, 1)
 
camera = Camera('orthographic',
                'location', (0, 0, -1),
                'right', '5*4/3*x',
                'up', 'y*5',
                'look_at', (0, 0, 0))
 
# dva světelné zdroje
light1 = LightSource((2, 10, -10),
                     'color', (0.7, 0.7, 0.7))
 
light2 = LightSource((0, 0, -10000),
                     'color', (0.7, 0.7, 0.7),
                     'shadowless')
 
 
# objekt - nekonečná rovina
plane = Plane(z,
              2,
              'hollow', 'on',
              Texture(
                  Pigment('agate',
                          'agate_turb', 0.9),
                  Finish('ambient', 0.1,
                         'diffuse', 0.4)))
 
 
def new_blob(threshold, dx, dy, t):
    delta = 0.3 * sin(2*pi*t)
    return Blob('threshold', threshold,
                'component', 1.0, 1.0, (0, -0.5 - delta, 0),
                'component', 1.0, 1.0, (0,  0.5 + delta, 0),
                Texture(
                      Pigment('color', (0.6, 0.8, 1.0)),
                      Finish('ambient', 0.2,
                             'diffuse', 0.4,
                             'phong', 0.5,
                             'phong_size', 5)),
                'translate', (dx, dy, 0))
 
 
def construct_scene(t):
    objects = [light1, light2, plane]
    objects.append(new_blob(0.40, -2.00, -1.2, t))
    objects.append(new_blob(0.50, -0.65, -1.2, t))
    objects.append(new_blob(0.60,  0.65, -1.2, t))
    objects.append(new_blob(0.70,  2.00, -1.2, t))
    objects.append(new_blob(0.75, -2.00, 1.2, t))
    objects.append(new_blob(0.80, -0.65, 1.2, t))
    objects.append(new_blob(0.82,  0.65, 1.2, t))
    objects.append(new_blob(0.90,  2.00, 1.2, t))
 
    return Scene(camera, objects=objects,
                 included=('colors.inc',),
                 global_settings=('assumed_gamma 2.2',))
 
 
# parametry pro animaci
DURATION = 15
FPS = 20
 
 
def make_frame(t):
    scene = construct_scene(t / DURATION)
    return scene.render(width=400, height=300, antialiasing=0.001, auto_camera_angle=False)
 
 
animation = VideoClip(make_frame, duration=DURATION)
animation.write_videofile('scene8.ogv', fps=FPS, progress_bar=True, bitrate="800000")

18. Repositář s demonstračními příklady

Zdrojové kódy všech dnes popsaných demonstračních příkladů určených pro Python 3 byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/vapory-examples (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, stále doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

Následuje tabulka s projekty vytvořenými přímo v jazyku POV-Ray (pro jejich vykreslení tedy postačuje pouze instalace POV-Raye):

Výsledná videa:

  1. https://tisnik.github.io/moviepy-videos/video11.htm
  2. https://tisnik.github.io/moviepy-videos/video12.htm
  3. https://tisnik.github.io/moviepy-videos/video13.htm

20. Odkazy na Internetu

  1. MoviePy 0.2.3.3 na PyPi
    https://pypi.org/project/moviepy/
  2. MoviePy na GitHubu
    https://github.com/Zulko/moviepy
  3. MoviePy – dokumentace
    http://zulko.github.io/moviepy/
  4. MoviePy – galerie
    http://zulko.github.io/mo­viepy/gallery.html
  5. Data Animations With Python and MoviePy
    https://zulko.github.io/blog/2014/11/29/da­ta-animations-with-python-and-moviepy/
  6. Porovnání formátů Ogg Theora a H.264
    https://www.root.cz/zpravic­ky/porovnani-formatu-ogg-theora-a-h-264/
  7. Případ GIF
    https://www.root.cz/clanky/pripad-gif/
  8. Pravda a mýty o GIFu
    https://www.root.cz/clanky/pravda-a-myty-o-gifu/
  9. Anatomie grafického formátu GIF
    https://www.root.cz/clanky/anatomie-grafickeho-formatu-gif/
  10. GIF: animace a konkurence
    https://www.root.cz/clanky/gif-animace-a-konkurence/
  11. Two python modules : MoviePy and images2gif – part 001
    http://free-tutorials.org/two-python-modules-moviepy-and-images2gif-part-001/
  12. images2gif
    https://pypi.org/project/images2gif/
  13. Making GIFs from video files with Python
    https://www.devbattles.com/en/san­d/post-345-Making+GIFs+From+Video+Fi­les+With+Python
  14. GIF89a specification
    https://www.w3.org/Graphics/GIF/spec-gif89a.txt
  15. MPEG-4 Part 14
    https://en.wikipedia.org/wiki/MPEG-4_Part14
  16. Theora video compression
    https://www.theora.org/
  17. Theora
    https://en.wikipedia.org/wiki/Theora
  18. NumPy
    http://www.numpy.org/
  19. numpy 1.14.2 (on PyPi)
    https://pypi.org/project/numpy/
  20. Integrovaná vývojová prostředí ve Fedoře: praktické použití IPython Notebooku a knihovny Numpy
    https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-prakticke-pouziti-ipython-notebooku-a-knihovny-numpy/
  21. Integrovaná vývojová prostředí ve Fedoře: praktické použití IPython Notebooku a knihovny Numpy (2.část)
    https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-prakticke-pouziti-ipython-notebooku-a-knihovny-numpy-2-cast/
  22. Non-linear editing system
    https://en.wikipedia.org/wiki/Non-linear_editing_system
  23. Lorenzův atraktor
    http://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-iii/#k03
  24. Popis barvových map modulu matplotlib.cm
    https://gist.github.com/en­dolith/2719900#id7
  25. Ukázky (palety) barvových map modulu matplotlib.cm
    http://matplotlib.org/exam­ples/color/colormaps_refe­rence.html
  26. Lorenz system
    https://en.wikipedia.org/wi­ki/Lorenz_system
  27. Customising contour plots in matplotlib
    https://philbull.wordpres­s.com/2012/12/27/customising-contour-plots-in-matplotlib/
  28. Graphics with Matplotlib
    http://kestrel.nmt.edu/~ra­ymond/software/python_notes/pa­per004.html
  29. Systémy lineárních rovnic
    http://www.matematika.cz/systemy-linearnich-rovnic
  30. NumPy Home Page
    http://www.numpy.org/
  31. NumPy v1.10 Manual
    http://docs.scipy.org/doc/num­py/index.html
  32. NumPy (Wikipedia)
    https://en.wikipedia.org/wiki/NumPy
  33. Matplotlib Home Page
    http://matplotlib.org/
  34. vapory na GitHubu
    https://github.com/Zulko/vapory
  35. Seriál na Rootu: Vykreslujeme 3D scény s POV-Ray
    https://www.root.cz/seria­ly/vykreslujeme-3d-sceny-s-pov-ray/
  36. Animace v POV-Rayi
    https://www.root.cz/clanky/animace-v-pov-rayi/
  37. Tvorba pokročilejších animací v POV-Rayi
    https://www.root.cz/clanky/tvorba-pokrocilejsich-animaci-v-pov-rayi/
  38. The POV-Ray Cyclopedia:
    http://www.spiritone.com/~en­glish/cyclopedia/index.html
  39. POV-Ray New Ring:
    http://webring.povray.org/
  40. Animations with POV-Ray:
    http://www.f-lohmueller.de/pov_tut/ani­mate/pov_anie.htm
  41. The POV-Ray Objects Collection:
    http://objects.povworld.org/
  42. POV-Ray Texture Library 4.0:
    http://texlib.povray.org/
  43. Galerie modelů vytvořených v Lparseru:
    http://home.wanadoo.nl/lau­rens.lapre/lparser2.html
  44. Charlie Chernohorsky :-) L-systémy ve FractIntu:
    http://fractint.oblivion.cz/
  45. POV-Ray Hall of Fame,
    http://hof.povray.org/
  46. matplotlib (Wikipedia)
    https://en.wikipedia.org/wi­ki/Matplotlib
  47. The cell magics in IPython
    http://nbviewer.jupyter.or­g/github/ipython/ipython/blob/1­.x/examples/notebooks/Cell%20Ma­gics.ipynb
  48. Taylorův polynom
    https://algoritmy.net/arti­cle/1576/Tayloruv-polynom
  49. Taylor series
    https://en.wikipedia.org/wi­ki/Taylor_series
  50. Taylor Series Approximation to Cosine
    https://www.cut-the-knot.org/Curriculum/Calcu­lus/TaylorSeries.shtml
  51. Fourier series
    https://en.wikipedia.org/wi­ki/Fourier_series
  52. mpmath
    http://mpmath.org/
  53. Gallery of mathematical functions
    http://mpmath.org/gallery/
  54. 3D visualization of complex functions with matplotlib
    http://fredrikj.net/blog/2009/08/3d-visualization-of-complex-functions-with-matplotlib/
  55. Animating the Lorenz System in 3D
    https://jakevdp.github.io/blog/2013/02/16/a­nimating-the-lorentz-system-in-3d/
  56. Lorenz example
    http://docs.enthought.com/ma­yavi/mayavi/auto/example_lo­renz.html
  57. Color Graphs of Complex Functions
    http://fs2.american.edu/lcro­ne/www/ComplexPlot.html
  58. Tekno Frannansa,
    http://www.zazzle.com/tekf
  59. Internet Raytracing Competition,
    http://www.irtc.org/
  60. POVRay Short Code Contest – Round 3,
    http://local.wasp.uwa.edu­.au/~pbourke/exhibition/scc3/fi­nal/
  61. SCC4: POVRay Short Code Contest,
    http://local.wasp.uwa.edu­.au/~pbourke/exhibition/scc4/fi­nal/
  62. SCC5: POVRay Short Code Contest #5 – The animation round!,
    http://local.wasp.uwa.edu­.au/~pbourke/exhibition/scc5/fi­nal.html
  63. POV-Ray posters,
    http://www.povray.org/posters/
  64. Parametric Constructive Solid Geometry
    http://c-csg.com/
Našli jste v článku chybu?