Het uiterlijk der dingenIn de vorige aflevering ben ik in vogelvlucht langs de belangrijkste hoofdzaken gemanoeuvreerd.In deze cursus hebben we tot nu toe wel wat gepraat, een hoop dingen op eigen gelegenheid bekeken, maar nog niks zelf gemaakt. Daar gaan we wat aan doen. We maken een testopstelling om met eigen ogen te zien wat er allemaal met de oppervlakte van virtuele voorwerpen te doen is. Een oppervlakte moet natuurlijk een kleur hebben want anders valt er niets te zien. Maar er zijn meer eigenschappen aan een oppervlak te onderscheiden dan een kleur. In de eerste plaats hoeft een kleur niet egaal te zijn: marmer en hout zijn voor de hand liggende voorbeelden. In de tweede plaats hoeft het oppervlak niet mathematisch plat te zijn. Bubbeltjes in allerlei vormen en maten kunnen voorkomen, denk maar aan een perzik en een citroen. Dit laatste visuele voorbeeld brengt een derde eigenschap aan het licht: hoe glimmend is het oppervlak? Een citroen heeft een glimmend waslaagje. Ook gevernist hout en, in mindere mate, gepolijst marmer glimmen de wereld toe. Eigenschappen van voorwerpen verbinden we ook wel aan het materiaal. Je hebt metalen voorwerpen zoals glimmend chroom of geslagen roodkoper met hun typische 'metalen' uiterlijk. Dan is er de categorie van steenachtigen zoals beton, graniet en marmer en ook is het zinvol om de doorzichtige voorwerpen apart te zetten. Bij deze laatste denken we aan glazen bollen of lenzen, heldere of gekleurde gelatinepuddingen, waterijs enz. die iets met lichtbreking doen en/of licht doorlaten, verstrooien e.d. Gewoon alle voorwerpen eventjes een verfje geven zondere toeters en bellen is heel eenvoudig en zo is het berekenen ook: het plaatje is vlug klaar. Maar heb je met veel moeite een lens gemaakt van doorzichtig marmer met rode aders, dan is dat ook voor Povray een hele toer om door te rekenen: geduld dient bewaard te worden. Maar dat had je al want anders was je nooit aan die ingewikkelde precieze constructie begonnen, nietwaar? Testopstelling We gaan niet al te veel vormgeven. Met wat basale vormen moeten de kenmerkende verschillen tussen oppervlakten zichtbaar gemaakt worden. Ik stel me het volgende voor: links in beeld ligt een doos met een paar happen eruit. Dan kan je zien dat 'solid finite primitives' ook een inhoud hebben. Ook kan je de verschillen door de invalshoek van het licht beoordelen. Het is ook handig om een hol oppervlak te hebben voor het creëren van spiegelende effecten. Verder ligt er op de voorgrond een bol voorwerp, zeg om het simpel te houden, een bol met een cylindervormige holte. Dat is leuk voor het zien van de effecten van lichtbreking. Tot slot is een geleidelijk verlopend oppervlak handig: een kegel is in dit opzicht inzetbaar. Constructie We beginnen ons de goede gewoonte eigen te maken om alles eerst in het nulpunt op te bouwen en daarna met rotatie gevolgd door translatie op de juiste plaats te zetten. Tegelijk gaan we ons wennen aan het zo veel mogelijk declareren van dingen. Zeker in een testopstelling is dat handig, maar ook zonder dat is het aan te bevelen. Aangezien we ons met kleur en oppervlaktestructuur gaan bezig houden doen we een 'include' van twee files die veel kant en klare zaken bevatten. Ik zou zeggen, zoek ze maar eens op in de folder geheten 'include' en bekijk in een editor wat er allemaal in zit. Listing 1 #include "colors.inc" #include "textures.inc" #declare box0 = difference { box { <0, 0, 0>, <4, 1, 2> } cylinder { <0.2, -0.1, -0.2>, <0.2, 1.1, -0.2>, 1} } #declare prisma = intersection { plane {-x, 0 rotate <0, 60, 0>} plane { x, 1 rotate <0, -60, 0>} plane {-z, 0} plane { y, 1.1} plane {-y, -0.1} } #declare box1 = difference { object {box0 } object {prisma translate <3,0,-0.3>} } #declare bolhol = difference { // Sphere object sphere {< 0, 0, 0>, 0.5} cylinder {<0,-0.1, 0>,<0,1.1,0>,0.3} } // het uiterlijk der dingen // en soms ook het innerlijk #declare bolkleur = texture { pigment { Green } finish { ambient 0.5 } } #declare kegelkleur = texture { finish { ambient 0.5 } pigment { Red } } #declare dooskleur = texture { finish { ambient 0.5 } pigment { Blue } } cone { <0, 0, 0>, 0.7 <0, 1.5, 0>, 0 translate <3, 0, 3> texture { kegelkleur } } //bol plaatsen object { bolhol translate <2, 0.5, 2 > texture { bolkleur} } //doos plaatsen object { box1 rotate <0, -50, 0> translate <0, 0, 3> texture { dooskleur } } camera { location <2, 2, -3> direction <0, 0, 2> up <0, 1, 0> right <1.33, 0, 0> look_at <2, 1, 3> } light_source { <8, 4, 3> color White } // grondvlak plane { <0, 1, 0>, 0 pigment {checker color Red color Blue} } background { color SkyBlue } In listing 1 is te zien hoe we aan onze doos beginnen te bouwen. De grootte (l x b x h) is 4 bij 2 bij 1. Voor het gemak zetten we de linker onderhoek in de oorsprong. Vervolgens gaan we met een cylinder een ronde hap uit die hoek nemen. We doen dat met een 'difference' waarbij in de beschrijving dus het tweede voorwerp wordt afgetrokken zullen we maar zeggen van het eerste (de doos). De volgende stap is een driehoekige hap uit de doos te nemen; een soort taartpunt. Ik heb gekozen voor een prisma. Maar ja, ik kon die nergens vinden in de verzameling van voorgekookte vormen, dus ben ik maar gaan spieken bij anderen. Zo vond ik wat voorbeelden van veelvlakken. Aan de hand daarvan heb ik de definitie van het prisma gemaakt. Ik zal het verduidelijken. Vlakgebruik Een vlak heeft een voorkant en een achterkant of een bovenkant en een onderkant. Een vlak is oneindig groot maar je kan je wiskundig indenken dat het toch nog sluitend gebogen is. Je kan dan spreken van een binnenkant en een buitenkant. Dat is wat we in Povray ook doen. Uit onze schooltijd kunnen we ons allemaal wel herinneren dat er drie punten nodig waren om een vlak vast te leggen. Vandaar dat 3-potige tafetjes nooit wankelen maar dit ter zijde. Bij eenzelfde vlak kan je een heleboel groepjes van 3 punten verzinnen die ook in het vlak liggen. Er is dus emplooi voor een genormaliseerde definitie van de positie van een vlak. En, hoe kan het ook anders, dat doen we met een wiskundige 'normaal'. Die normaal kan je zien als een fictieve pijl die loodrecht staat op het vlak. Wil je nu een vlak hebben dat horizontaal loopt op een hoogte van 1 dan beschrijf je die als: plane { <0, 1, 0>, 1 } De normaal (de pijl, weet je wel) wijst in de positieve richting van de y-as. Er bestaat een afkorting voor: de naam 'y' in Povray. Om nu een prisma af te grenzen moet je dus zorgen dat de buitenkanten van de vlakken goed liggen. Doe je dat verkeerd dan krijg je geen gesloten voorwerp. Dus plane {y,1} is het bovenvlak, plane {-y,0} is het ondervlak en plane {-z,0} is de ene zijkant. Er moeten dan nog twee vlakken gezet worden die op de juiste manier gedraaid moeten worden. Ook hier weer: even nadenken over waar de buitenkant zit en hoe de draairichting moet zijn. Pak in gedachte de y-as met de linkerhand beet en met duim naar boven in de positieve richting. De rest van je vingers kromt zich in de positieve draairichting. Het eerste vermelde vlak keert zijn buitenkant naar links (negatieve x-richting) en krijgt een positieve draai om de y-as. Het tweede vlak heeft zijn buitenkant in de positieve x-richting wijzen en krijgt ook een draai om de y-as maar nu in tegengestelde richting. De opdracht 'intersection' knoopt de vlakken zo bij elkaar dat je een prisma overhoudt. In de declaratie van box1 zien we het verschoven prisma een hap nemen uit box0. Hier zien we ook hoe we vooraf gedefinieerde voorwerpen gebruiken. We zeggen 'object {naam}'. Let erop dat ik tijdens het samenstellen de objecten die een hap uit de doos nemen, net een beetje groter (lopend van -0.1 naar 1.1) heb gemaakt dan de hoogte van de doos (0.0 naar 1.0). Dat is gedaan, omdat er anders rare effecten kunnen optreden. Tijdens berekeningen wordt afgerond (of liever gezegd met eindige nauwkeurigheid gewerkt) en dan kan het voorkomen dat punten op een 'gezamenlijk' oppervlak de ene keer aan de ene en de andere keer aan de andere deelnemer wordt toegerekend. En dat geeft ongewenste resultaten. Nog 2 objecten Om zaken als glimlichten, lichtbreking door transparante vormen e.d. te bekijken is een bol een handig ding. Om ook het effect van holle ruimten te bestuderen is er een holle cylinder in de bol gezet op de bekende manier: met een 'difference'. Nu hebben we met de simpele kegel meegeteld drie objecten die we gaan gebruiken om te experimenteren met oppervlaktestructuren (textures). Met een #declare kunnen we veel meer dingen doen dan objecten beschrijven; eigenlijk kunnen we van alles declareren. Maar bedenk wel dat onze Povray declaratie een ander ding is dan een declaratie in de computertaal C. Daar is een declaratie een soort makro die tekst-expansie doet. Met een declaratie geef je in C als het ware een verkorte schrijfwijze op. Dat is in Povray echt niet het geval! Hier zijn declaraties echt om het met een mooi woord te zeggen 'object-georiënteerd'. Zie maar in de volgende declaraties waarin we het oppervlak van elke figuur apart vastleggen. Wat je declareert krijgt een type mee. Dat is hier 'texture'. Ga je de naam later gebruiken dan moet je weer 'texture' opgeven want anders komt er protest. Je wordt dus bewaakt, zodat een verkeerd gebruik niet kan. Als het ware ligt het 'type' vast. De enige uitzondering is bij het declareren van virtuele voorwerpen. Daar gebruik je 'object': terwijl box0 bijvoorbeeld als een difference is geconstrueerd spreek je bij gebruik toch over 'object {box0}. Wat nadenken zal de onderliggende logica wel begrijpelijk maken, maar nadenken hoeft niet: gewoon het recept opvolgen is een acceptabele handeling. Textures In deze listing heb ik het minimaal nodige opgenomen voor de texture van de 3 vormen. Ze hebben allemaal een egaal verfje in een simpele kleur en bovendien hebben ze redelijk veel 'ambient'. Dit zal ik uitleggen. In de normale wereld komt licht van overal vandaan: we spreken over diffuus licht. Als de zon schijnt dan is het niet totaal duister in de schaduw. Helaas is dit lichteffect niet met raytracing te verwezelijken. We nemen onze toevlucht tot een truc. Met 'ambient' zorgen we dat elk oppervlak is het ware toch licht uitstraalt. Geven we niks op dan is er een default actief: ambient 0.1 en dat is weinig. We kunnen onze defaults ook veranderen met b.v. #default texture { finish { ambient 0.5}} wat ons drie apart van elkaar staande regels tekst bespaart. Veel makkelijker is het verspreide licht te behandelen in Povray versie 3. Daar bestaat een global_settings { ambient_light 0.5} waardoor het effect schijnbaar is losgekoppeld van de voorwerpen en als een lichtbron wordt behandeld. Posities Ik heb de camera en de voorwerpen zo geplaatst dat alles in beeld is. Ook valt een stukje van de kegel achter het bolletje. Natuurlijk is iedereen vrij om alles anders neer te zetten maar ik zou om niet in verwarring te geraken alleen maar de camera verzetten. Vaak zal je details willen zien en dan moet je er wel als het ware met je neus bovenop gaan zitten. Handig is het ook om op schaal een plattegrond te hebben want elke keer dat je de camera verkeerd plaatst ben je een hoop rekentijd kwijt aan een onbruikbaar beeld. De scenebeschrijving van listing 1 is in afbeelding 1 te zien. Helaas in z/w, wat het niet al te duidelijk maakt. Experimenteren We kunnen nu gaan proberen hoe diverse textures er uit zien. Bedenk wel dat wat we ervan zien afhangt van de belichting. Laat ik elk geval, wat je ook doet met de belichting, de kleur wit zijn. Desnoods wat minder fel want Grey50 mag ook best; het is maar om het principe aan te geven. Eenvoudige oppervlakten worden in zijn algemeenheid beschreven als texture { TEXTURE_IDENTIFIER pigment {..}. normal {..}. finish {..}. halo {..}. TRANSFORMATIONS } Ik weet dat dit niet volledig is want in Povray versie 3 komen er heel wat meer mogelijkheden bij. Maar laten we het eenvoudig houden en eerst even naar het verfje gaan kijken. Ik vertaal 'pigment' met 'verfje' maar dat is onjuist. Een verfje zit aan de buitenkant en waar het hier om gaat is dat een pigment door en door is. Het zit ook binnenin voorwerpen. Dat blijkt b.v. bij de happen die uit onze doos zijn genomen. Die hebben ook kleur. Maar een egaal pigment is nogal saai en doods. Dat vond ik in eerste instantie ook van ons grondvlak en die heb ik als // Floor plane plane { <0, 1, 0>, 0 pigment {checker color Red color Blue} } opgegeven wat een goed perspectief effect geeft. Je ziet trouwens ook dat ik een beetje gesmokkeld heb. De 'texture {...' is er niet. Dat kan omdat Povray tamelijk tolerant is bij het weglaten van evidente dingen. Maar je ziet: ik gebruik hier een texture identifier, nl. checker, waardoor ik een vierkant geblokt vlak krijg met scherpe overgangen tussen de twee gebruikte kleuren. Er zijn nog een paar anderen: brick b.v. ook met twee kleuren en hexagon met drie kleuren. Maar vergis je niet in deze patroontjes: ze zijn niet plat maar 'gevuld'. Zo bestaat checker uit een oneindige ruimte die gevuld is met kubussen die afwisselend een ander pigment hebben. Hier zien we weer een slordigheidje van Povray: in mijn voorbeeld geef ik 'colors' op terwijl ik eigenlijk 'pigment { color...' bedoel. Laat ik mijn vlak door die ruimte lopen dan krijgt het oppervlak het pigment dat heerst in de snijpunten met die ruimte. Voor een horizontaal vlak worden dat dus vierkantjes. Hoe ziet het oppervlak van een bol eruit, als ik die in de 'checkers-ruimte' zou plaatsen? Wil je een horizontaal vlak met parallellogrammen dan moet het vlak dus het ruimtelijk patroon onder een hoek gaan snijden. Daartoe kan je het patroon zelf gaan verdraaien met rotate < ax, by, cz> in elke gewenste richting. We kunnen het grondvlak ook van een metselwerkje voorzien met pigment { brick pigment { Jade } , pigment { Black_Marble } } Je ziet dat je voor de vulling een pigment op geeft en niet een kleur. Ook dat is mogelijk. Wat betreft de afmetingen: steen met metselwerk is < 8, 3, 4.5 > en het cement heeft een dikte van 0.5. Om bruikbaar te worden moet er wat geschaald worden (met scale <0.01, 0.01, 0.01 > om alles 100 keer kleiner te maken) anders hebben we in onze afbeelding waanzinnig grote metselstenen. De eerst opgegeven kleur wordt voor het cement gebruikt en de tweede voor de steen zelf. Natuurlijk hoeft het vullen van de ruimte niet op zo'n simpele manier te gebeuren. Er is heel wat over afgedacht en veel formules zijn ingebruik. Trouwens, de algemene mogelijkheden van een pigment zijn: pigment { PIGMENT_IDENTIFIER PATTERN_TYPE PIGMENT_MODIFIERS... } Ook hier weer zijn een aantal veel gebruikte zaken voorgedefineerd voor ons. Zo bestaat er 'agaat' en 'bozo' en een heleboel hout- en steensoorten. Veel kant en klare steensoorten zijn te vinden in de file stones.inc. Stel nu dat onze doos van massief hout is en in zijn lengterichting uit een stam is gezaagd. Op de kopse kant moet dan een concentrisch ringenpatroon (jaarringen) te zien zijn en op de andere zijden moeten nerven zichtbaar zijn. Nu zal de cursist vast wel begrijpen waarom ik happen uit de doos heb genomen. Kleurovergangen Vaak wil je de ruimte niet verdeeld hebben in scherp afgegrensde stukjes. Regelmatige kleurverlopen in 3 dimensies krijg je met b.v. pigment { gradient x color_map { [0.1 color Red] [0.2 color Yellow] [0.4 color Yellow] [0.6 color Blue] [0.6 color Green] [0.8 color Cyan] [0.9 color Brown] } } } het type van het patroon dat we hebben gekozen heet 'gradient'. Die functie evalueert in de positieve richting van de x-as van 0.0 naar 1.0 en de kleur wordt dan als volgt bepaald: van 0.0 tot 0.1 is alles rood, van 0.1 tot 0.2 verloopt de kleur naar geel, tusssen 0.2 en 0.4 blijft de kleur hetzelfde (geel), de kleur verloopt verder van 0.4 naar 0.6 naar blauw toe en zodra we over 0.6 heen komen, verspringt de kleur plotseling naar groen die dan geleidelijk aan overgaat naar cyaan op 0.8. Het verloop gaat vervolgens naar bruin en blijft van 0.9 tot 1.0 dezelfde kleur. Eigenlijk is het veel beter om de 'color_map' gewoon te vergeten. Ook de kop boven dit stukje is foute boel. Het moet worden: Pigment patronen We moeten de verwarring tussen 'kleur in licht' en 'kleur van voorwerpen' zien te vermijden. Licht bestaat uit een aantal kleuren maar voorwerpen bevatten een pigment. Voortaan doen we dus dingen zoals: pigment { gradient x pigment_map { [0.3 wood scale 0.2] [0.3 Jade] [0.6 Jade] [0.9 marble turbulence 1] } } Je ziet, zo kan je veel meer maar er is niks op tegen om gewoon eigenwijs 'color Brown' en zo te gebruiken. Helaas was men met versie 2 van Povray nog niet zover gevorderd in het onderscheidend vermogen van het verstand, dus daar is pigment_map nog onbekend. Glimmer en glitter Een oppervlak heeft heel wat meer nodig dan alleen maar een verdeling van pigmenten in patronen, verlopen enz. Maar raytracing staat juist bekend, omdat er zulke mooie realistische glimlichtjes, weerkaatsingen enz. mogelijk zijn. Hoe voegen we glimmer aan het oppervlak toe? De simpelste is in de vorm: texture { pigment { color Yellow } finish { phong 1 } } Het Engelse woord 'finish' betekent hier niet eindpunt maar glans. En wat is de phong van een oppervlak? Die bestaat niet want het was een meneer Phong die de rekenmethode bedacht heeft. Probeer het maar eens uit met de bol. Staan lichtbron en camera goed dan kan je een 'highlight' zien in de kleur van de lichtbron (en die is wit). Tegelijk lijkt de bol ook wat gladder. De sterkte van het glimlicht kan tussen 0.0 en 1.0 liggen. Er zijn ook andere manieren om blinkende gladheid en glans aan oppervlakten te geven. Daar kom ik later nog wel op terug. Hobbeligheid Het Engelse woord hiervoor is 'bumpiness'. Je begrijpt wel dat als je bobbels op het oppervlak wil aanbrengen, dat je dan heel wat wiskundige formules moet gebruiken en dat Povray heel wat af te rekenen heeft. Soms kom je daar niet onderuit als je b.v. een wateroppervlak met golvingen e.d. wil. Maar in de meeste gevallen voldoet een simpele methode die ons oog gewoon voor de gek houdt. Dit is de normal { bumps 0.1 scale 0.05 } die je aan het texture toevoegt. Het eerste getal geeft de diepte van de hobbels. In ons geval 0.1 wat redelijk is op een bol met een radius van 0.5. De schaling geeft de bobbels een breedte van 0.05. Je begrijp wel dat er met de 'normaal' (zie een eindje terug in dit artikel) gestoeid gaat worden. Zet je hersens in wiskunde-mode en denk dat je een raakvlak aan een punt op het oppervlak maakt. Dat vlak heeft een 'normaal' in het raakpunt. Die staat als het ware ook 'loodrecht' op het gekromde vlak. Ga nu met de richting van die normaal knoeien, wanneer je gaat berekenen hoe het licht in het raakpunt moet worden. Doe dat veranderen van de normaal volgens een passende formule en je krijgt een bobbeleffect. Gereserveerde woorden adaptive height_field rgbf agate hexagon right agate_turb iff ripples all image_map rotate alpha include roughness ambient interpolate scale area_light intersection sky background inverse smooth bicubic_patch ior smooth_triangle blob jitter specular blue lambda sphere bounded_by leopard spotlight box light_source spotted bozo location sturm brilliance looks_like texture bumps look_at tga bump_map mandel threshold bump_size map_type tightness camera marble tile2 checker material_map tiles clipped_by max_intersections torus clock max_trace_level translate color merge triangle color_map metallic turbulence colour normal type colour_map no_shadow union component object up composite octaves use_color cone omega use_colour crand once use_index cubic onion u_steps cylinder open version declare phase v_steps default phong water_level dents phong_size waves difference pigment wood diffuse plane wrinkles direction point_at x disc poly y distance pot z dump quadric fallloff quartic filter quick_color finish quick_colour flatness radial fog radius frequency raw gif red gradient reflection granite refraction green rgb Volgende keer De redactie vindt dit epistel nu wel lang genoeg. Het is weer mijn oude probleem: ik wil meer schrijven maar de ruimte in dit blad is voor mij beperkt. De volgende keer zal ik verder ingaan op het gebruik van nog meer complexe textures want we zijn er nog lang niet over uitgepraat. Ik zal verder een rondvraag doen bij de cursisten (m/v) om te zien hoe noodzakelijk het is om in deze cursus Povray versie 2 te gebruiken. Mijn eigen studie van de nieuwe mogelijkheden van Povray versie 3 maken mij steeds enthousiaster: soms loopt het kwijl mij in de bek. Mocht blijken dat de meeste cursisten een harddisk hebben dan is de noodzaak om de oude versie te gebruiken niet meer zo dringend aanwezig. In dit verband wil ik nog wel eventjes een tip geven aan mensen die 4 Mb intern geheugen aan boord hebben maar die geen harddisk hebben of willen gebruiken: maak een RAM-disk aan! Dan kan je tijdens nachtenlange rekenklussen de harddisk (en de monitor) afzetten. Even ter verduidelijking: als je een harddisk die af staat, aan de ST hebt hangen dan kan je natuurlijk niet lezen/schrijven naar de harddisk maar, omdat de DMA-poort toch nog belast blijft, wil het gebruik van de floppydrive ook niet goed gaan (bij vele ST's). Maar lezen/schrijven naar een interne RAM-disk blijft goed functioneren! Een heel belangrijke reden om versie 3 niet voor deze cursus te gebruiken is: de povray.gtp past niet op een gewone dubbelzijdige DD-floppydisk. Maar er bestaat al heel lang een comprimeerder DCSquish geheten die programma's kan comprimeren als uitvoerbare files. Tijdens het activeren pakt het programma zich dan in het geheugen uit. Op die manier is povray.gtp van ruim 880 Kb terug te brengen tot 444 Kb. En dan is alleen werken met een floppydisk geen onmogelijkheid. Ik heb niet getest hoeveel geheugen er tijdens dat uitpakken wordt gebruikt dus de methode kan nog tegenvallen als je maar 1 Mb aan intern geheugen hebt! Piet Vogelaar Copyright © Rein Bakhuizen van den Brink Last updated on 1 september 1999 |