Sisältö Tietokonegrafiikka / perusteet Tik-111.300/301 4 ov / 2 ov Suora ja ympyrä Antialiasointi Fill-algoritmit Point-in-polygon Luento 2: Tulostusprimitiivien toteutus Lauri Savioja 09/03 Primitiivien toteutus / 1 Primitiivien toteutus / 2 GRAAFISTEN PRIMITIIVIEN TOTEUTUS HUOM! Oletuksena on XY-koordinaatisto Piirtäminen rasteriruudulle Alkeisoperaationa: SetPixel ( x, y, color ) Suorien viivojen piirtäminen Malli piirturikynällä piirtämisestä Kynä on aina jossakin (CP = Current Position) Inkrementaaliset alkeisoperaatiot: PenUp / PenDown CP' = CP + (±1, ±1) -> 8 eri askelta Pisteet piirrettävissä missä järjestyksessä hyvänsä Koordinaatit suhteellisina määritellyn rasterin origoon nähden Primitiivien toteutus / 3 Primitiivien toteutus / 4 Laskennallisia vaatimuksia Nopea, eli kokonaislukuja yhteen- ja vähennyslaskuja shiftejä Hitaita asioita kertolaskut reaaliluvut kirjastofunktiot, esim. trigonometriset operaatiot Suora Suoran viivan vaatimukset Tasapaksu (tasainen intensiteetti) Portaaton Täsmällinen alku ja loppu Intensiteetti riippumaton pituudesta ja kulmakertoimesta Primitiivien toteutus / 5 Primitiivien toteutus / 6 1
Algoritmi suoran piirtämiseksi Suoran yhtälö y=ax+b Alkupiste (x o,y o ), loppupiste (x 1,y 1 ) DDA (Digital Differential Analyzer) - algoritmi Periaate: pisteitä suoralla voidaan laskea esim. janan päätepisteiden (p 1, p 2 ) avulla esitetystä parametrimuodosta p(t) = p 1 + t *?p = p 1 + t * (p 2 - p 1 ) ; t [0,1] x(t) = x 1 + t *?X y(t) = y 1 + t *?Y = y 1 + m * (t *?X) ; m =?Y /?X eli antamalla t:lle sopivia arvoja ja pyöristämällä lasketut koordinaatit. Primitiivien toteutus / 7 Primitiivien toteutus / 8 DDA (jatkuu) symmetrinen DDA : iteroidaan t-parametria riittävän pienin askelin, jotta kaikki "janalla olevat" (so. lähellä olevat) pisteet tulevat käytyä läpi. yksinkertainen DDA : iteroidaan yksikköaskelin sitä koordinaattia, jonka siirtymä on suurempi, esim. x increment = ±1 ja y increment = m, kun m < 1. heikkouksia: jakolasku alussa liukuluvut, pyöristys joka pisteessä procedure dda (x1, y1, x2, y2 : integer); var dx, dy, steps, k : integer; x_increment, y_increment, x, y : real; dx := x2 - x1; dy := y2 - y1; if abs(dx) > abs(dy) then steps := abs(dx); else steps := abs(dy); x_increment := dx / steps; y_increment := dy / steps; x := x1; y := y1; set_pixel (round(x), round(y)); for k := 1 to steps do x := x + x_increment; y := y + y_increment; set_pixel (round(x), round(y)) end DDA (koodina) Primitiivien toteutus / 9 Primitiivien toteutus / 10 SIGGRAPH diat Bresenhamin algoritmi DDA:ssa turhaa laskentaa, sillä yksi koordinaatti (esim. x) muuttuu aina kokonaisaskelin toisestakin koordinaatista (y) tarvitsee havaita vain muutos, ts. milloin pyöristys antaa eri arvon kuin edellisessä pisteessä. Johdetaan tehokkaampi algoritmi: tarkkaillaan pyöristysvirhettä nousevalla suoralla (0 <= m <= 1 ) x-arvo etenee kokonaisaskelin: round(x) - x = 0 y:n virhe vaihtelee: d = round(y) - y (-0.5,+0.5) merkitään viimeksi piirrettyä pistettä ( x', y' ) = ( round(x), round(y) ) = ( x, y+d ) seuraava piste on ( x+1, y+m ), joka pyöristettynä on round(x+1) = x' + 1 round(y+m) = y', jos d+m < 0.5 = y' + 1, jos d+m >= 0.5 Primitiivien toteutus / 11 Primitiivien toteutus / 12 2
Bresenham (jatkuu) riittää siis tarkkailla lausekkeen s = d + m -0.5 etumerkkiä ja päivittää sitä askelittain: alussa d := 0, eli s0 := m -0.5 joka kierroksella lisätään s := s + m, ja vähennetään s := s - 1, jos s > 0 (koska y' kasvaa yhdellä). siirrytään kokonaislukuihin skaalaamalla s tekijällä 2*dx : m = dy / dx p = 2*dx*s = 2*dx*d + 2*dy - dx eli alussa p0 := 2*dy - dx joka kierroksella p := p + 2*dy ja lisäksi aina kun p > 0: p := p - 2*dx. procedure bres_line (x1, y1, x2, y2 : integer); Bresenham PASCAL-kielisenä: var dx, dy, x, y, x_end, p, const1,const2 : integer; dx := abs(x1 - x2); dy := abs(y1 - y2); p := 2 * dy - dx; const1 := 2 * dy; const2 := 2 * (dy - dx); if x1 > x2 then { if x1 > x2 } end else x := x2; y := y2; x_end := x1 { if x1 <= x2 } x := x1; y := y1; x_end := x2 set_pixel(x, y); while x < x_end do end x := x + 1; if p < 0 then p := p + const1 else set_pixel(x, y) y := y + 1; p := p + const2 Primitiivien toteutus / 13 Primitiivien toteutus / 14 Bresenham (jatkuu) algoritmin edut: ei lainkaan jakolaskua eikä liukulukuja kertolasku vain tekijällä 2 (= bin.luvun sivuttaissiirto) Huom. algoritmi johdettu nousevalle suoralle, dx>0, m < 1. Jos dx < 0, vaihdetaan päätepisteet (p 1, p 2 ) keskenään, tai päivitetään x-arvoa -1:n askelin laskevalle suoralle, -1 < m < 0, vaihdetaan y:n askeleeksi -1 Jos m > 1, vaihdetaan x:n ja y:n roolit keskenään. KÄYRÄT VIIVAT suorien ohella tarvitaan usein myös käyriä: 2 käyrät (ympyrän, ellipsin, parabelin kaaret) splinit (myöhemmin tällä kurssilla) perusprimitiivi POLYLINE tuottaa vain suora-osaisia murtoviivoja, yleistetty primitiivi GDP tarkoitettu mm. käyrien piirtämiseen GDP:n parametrien tulisi pääsääntöisesti olla geometrisia pisteitä, ei esim. polynomiyhtälön kertoimia periaatteessa mikä hyvänsä käyrä voidaan approksimoida murtoviivalla, mutta tehokkaampaa on yleensä tuottaa käyrän pisteet suoraan sen määritelmästä Primitiivien toteutus / 15 Primitiivien toteutus / 16 Käyrät viivat (jatkuu) DDA-algoritmi ja Bresenhamin muunnelma siitä ovat helposti muunnettavissa monille käyrille sopiviksi, mutta on varottava äärellisestä differenssiaskeleesta aiheutuvaa virhettä. Esim. paraabeli voidaan esittää differentiaalimuodossa: y = ax 2 + b <=> dy = 2ax * dx, y(0) = b. Täsmällisen käyrän tuottavassa algoritmissa on otettava huomioon myös termi dx 2 :?y(0) =?x 2?y(n) =?y(n-1) + 2ax *?x Paraabeli koodi x := 0; y := b; dx := 1; dy := dx * dx; dy_const := 2 * a * dx; while x < xmax do x := x + dx; y := y + dy; set_pixel (round(x), round(y)) dy := dy + dy_const; Huom! kun ohitetaan piste, jossa dx = dy, pitäisi x:n ja y:n rooleja muuttaa siten, että askelletaan y:tä yhdellä ja lasketaan dx sen mukaan. Primitiivien toteutus / 17 Primitiivien toteutus / 18 3
Ympyränpiirtoalgoritmi Bresenhamin ympyränpiirtoalgoritmi symmetrian ansiosta riittää tarkastella vain kahdeksasosaa koko ympyrästä; loput pisteet saadaan vaihtamalla x:n ja y:n rooleja ja etumerkkejä (olettaen, että keskipiste on origossa): -x,y x,y -y,x y,x -y,-x y,-x -x,-y x,-y piirtäminen alkaa pisteestä (0,r) ja päättyy, kun x=y. Primitiivien toteutus / 19 Primitiivien toteutus / 20 y exact yi yi-1 Ympyrä (jatkuu) Tarkastellaan askelta i, jossa ollaan viimeksi piirretty piste (x i, y i ), ja seuraavaksi on pääteltävä kumpi pisteistä, (x i +1, y i ) vai (x i +1, y i 1), on lähempänä todellista käyrän pistettä (x i +1, y exact ) : d1 d2 Ympyrä (jatkuu) ympyrän yhtälöstä y 2 = r 2 x 2 saadaan: y exact2 = r 2 (x i + 1) 2, jota voidaan verrata vaihtoehtoisiin pisteisiin: d 1 = y i2 y exact 2 = y i 2 r 2 + (x i + 1) 2, d 2 = y exact2 (y i - 1) 2 = r 2 (x i + 1) 2 (y i - 1) 2 p i = d 1 d 2 = 2(x i + 1) 2 + y i2 + (y i - 1) 2 2r 2 valitaan se piste, jonka etäisyys oikeasta on pienempi, eli jos parametri p i < 0, niin valitaan arvo y i, muuten arvo (y i - 1). xi xi+1 Primitiivien toteutus / 21 Primitiivien toteutus / 22 Ympyrä (jatkuu) Parametrin p arvo seuraavassa pisteessä (x i +1, y i+1 ) voidaan laskea iteratiivisesti edellisessä pisteessä lasketusta arvosta. Edellä laskettua lauseketta muokkaamalla saadaan: p i+1 = 2[(x i +1) + 1] 2 + y 2 i+1 + (y i+1-1) 2 2r 2 = p i + 4x i + 6 + 2(y i +12 - y i2 ) 2(y i+1 - y i ) = p i + 4x i + 6, jos y i+1 = y i = p i + 4x i + 6 4y i + 4, jos y i+1 = y i + 1 aloituspisteessä (0, r) lauseke on: p 1 = 3 2r. procedure bres_circle (x_center, y_center, radius : integer); var p, x, y : integer; procedure plot_circle_points; { 8 symmetric positions } set_pixel(x_center + x, y_center + y); set_pixel(x_center - x, y_center + y); set_pixel(x_center + x, y_center - y); set_pixel(x_center - x, y_center - y); set_pixel(x_center + y, y_center + x); set_pixel(x_center - y, y_center + x); set_pixel(x_center + y, y_center - x); set_pixel(x_center - y, y_center - x) Ympyräkoodi Primitiivien toteutus / 23 Primitiivien toteutus / 24 4
Ympyräkoodi (jatkuu) { bres_circle } x := 0; y := radius; p := 3-2 * radius; while x < y do plot_circle_points; if p < 0 then p := p + 4 * x + 6 else p := p + 4 * (x- y) + 10; y := y - 1; x := x + 1; end if x = y then plot_circle_points; Aliasoituminen l. vieraantumisongelma rasteriviivojen piirtämisessä piirtosuunta vaikuttaa viivan tiheyteen tiheys = 8 / (8 * 2) tiheys = 8 / 8 viivan leveys voi olla vain kokonaisluku xy-suunnissa vinon viivan reuna on porrasmainen (jagged) HUOM! Sama ongelma koskee myös muita graafisia kuvioita. Primitiivien toteutus / 25 Primitiivien toteutus / 26 Antialiasointi-diat Antialiasointi Antialiasointi = vieraantumisongelman ehkäiseminen tavallisin menetelmä on kunkin pikselin sävyttäminen sen mukaan, kuinka suurelta osin piirrettävä kuvio peittää sitä: Primitiivien toteutus / 27 Primitiivien toteutus / 28 Antialiasointi (jatkuu) viivan reunat sumenevat, mutta keskimäärin tasoittuvat mahdollista vain sävynäytöllä (ei mustavalkealla) erityislaitteilla mahdollista pikselien hienosäätö (pixel phasing): porraskohdissa olevia pikseleitä siirretään lähemmäs oikeaa viivaa pikselien suuruutta säädetään viivan kaltevuuden mukaan ALUEEN TÄYTTÄMINEN Kaksi perusperiaatetta: 1) konversio janoiksi pyyhkäisyjuoville 2) värin levittäminen rasterissa Primitiivien toteutus / 29 Primitiivien toteutus / 30 5
Juovakonversio etsitään kultakin pyyhkäisyjuovalta ne janat, jotka jäävät monikulmioalueen sisälle y1 y2 y3 y4 y5 y6 y7 y8 Juovakonversio (jatkuu) voidaan laskea joka juovalle (y=vakio) toisistaan riippumatta, missä kohdissa (x-arvo) juova leikkaa monikulmion reunan tehokkaampaa, jos hyödynnetään koherenssia (so. vierekkäisten juovien samankaltaisuutta): saman reunajanan perättäiset leikkauspisteet juovien kanssa voidaan laskea inkrementaalisesti, kuten janan rasteroinnissa (DDA, Bresenham) kutakin juovaa leikkaa vain osa janoista; likimain sama aktiivinen janajoukko leikkaa myös seuraavaa juovaa (samassa x- järjestyksessä) singulariteettiongelma: montako leikkausta lasketaan, jos pyyhkäisyjuova kulkee monikulmion nurkan kautta? ratkaistaan tarkastelemalla janojen suuntia... x = 1 2 3 4 5 6 87 9 10 Primitiivien toteutus / 31 Primitiivien toteutus / 32 Algoritmin runko TYPE points = ARRAY[1..max_points] OF integer; PROCEDURE fill_area_solid (count : integer; x, y : points); TYPE each_entry = RECORD y_top : integer; {larger y coordinate for line} x_int : real; {x that goes with larger y} delta_y : integer; {difference in y coordinates} x_change_per_scan: real {x change per unit change in y} ; list = ARRAY[0..max_points] OF each_entry; VAR sides : list; first_s, last_s, scan, bottomscan, x_int_count, r : integer; {fill_area_solid} sort_on_bigger_y(count, x, y, sides, bottomscan); first_s := 1; last_s := 1; {initialize pointers into sorted list} FOR scan := sides[1].y_top DOWNTO bottomscan DO update_first_and_last(sides, count, scan, first_s, last_s); process_x_intersections(sides, scan, first_s, last_s, x_int_count); draw_lines(sides, scan, x_int_count, first_s); update_sides_list(sides, first_s, last_s) ; {fill_area_solid} PROCEDURE sort_on_bigger_y (n : integer; x, y : points; VAR sides : list; VAR bottomscan : integer); VAR k, x1, y1, xpix, side_count : integer; FUNCTION next_y (k : integer) : integer; VAR i : integer; {returns next vertex y value which is not equal to y[k]} FOR i := k TO n DO IF y[i] <> y[k] THEN next_y := y[i]; ; Tietorakenteen pohjustus: PROCEDURE put_in_sides_list (VAR sides : list; entry, x1, y1, x2, y2, next_y : integer); VAR maxy : integer; x2_temp, x_change_temp : real; {make adjustments for problem vertices} x_change_temp := (x2 -x1) / (y2 -y1); x2_temp := x2; IF (y2 > y1) AND (y2 < next_y) THEN y2 := y2-1; x2_temp := x2_temp - x_change_temp ELSE IF (y2 < y1) AND (y2 > next_y) THEN y2 := y2 + 1; x2_temp := x2_temp + x_change_temp ; WHILE (entry > 1) AND (maxy > sides[entry -1].y_top) DO sides[entry] := sides[entry - 1]; entry := entry - 1 ; WITH sides[entry] DO {insert into sides list} y_top := maxy; delta_y := abs(y2 - y1) + 1; IF y1 > y2 THEN x_int := x1; maxy=y1; ELSE x_int := x2_temp; maxy:=y2 ; ; x_change_per_scan := x_change_temp {put_in_sides_list} samalla, kun järjestetään janat y -koordinaatin mukaan, ennaltaehkäistään singulariteettiongelmat lyhentämällä kuhunkin nurkkapisteeseen tulevaa janaa loppupäästään Primitiivien toteutus / 33 Primitiivien toteutus / 34 Tietorakenteen pohjustus (jatkuu) Aktiivisen janajoukon rajaaminen: F B Top scanline {sort_on_bigger_y} side_sount := 0; y1 := y[n]; x1 := x[n]; {initialize} bottomscan := y[n]; FOR k := 1 TO n DO IF y1 <> y[k] THEN {put non- horizontal edges in table} side_count := side_count + 1; {pass old point, current point, and y of next non-horizontal point} put_in_sides_list(sides, side_count, x1, y1, x[k], y[k], next_y(k)) ELSE {horizontal} FOR xpix := x1 TO x[k] DO setpixel (xpix, y1, fillcolor); IF y[k] < bottomscan THEN bottomscan := y[k]; y1 := y[k]; x1 := x[k] {save for next side} ; {sort_on_bigger_y} PROCEDURE update_first_and_last (sides : list; count, scan : integer; VAR first_s, last_s : integer); WHILE (sides[last_s + 1].y_top >= scan) AND (last_s < count) DO last_s := last_s + 1; WHILE sides[first_s].delta_y = 0 DO first_s := first_s + 1 ; E G A D C Scan line i AB BC EF FG GA CD DE Sorted Edge List Scan line i+1 first_s last_s Primitiivien toteutus / 35 Primitiivien toteutus / 36 6
VAR tmp : each_entry; tmp := s1; s1 := s2; s2 := tmp ; Leikkauspisteiden laskenta aktiivisille janoille: PROCEDURE process_x_intersections (sides : list; scan, first_s, last_s : integer; VAR x_int_count : integer); VAR k : integer; PROCEDURE swap(s1, s2 : each_entry); {swap reverses placement of two entries within the table sides} PROCEDURE sort_on_x (entry, first_s : integer); WHILE (entry > first_s) AND (sides[entry].x_int < sides[entry -1].x_int) DO swap(sides[entry], sides[entry - 1]); entry := entry - 1 ; {sort_on_x} {process_x_intersections} x_int_count := 0; FOR k := first_s TO last_s DO IF sides[k].delta_y > 0 THEN x_int_count := x_int_count + 1; sort_on_x(k, first_s) ; {process_x_intersections} Sisään jäävien juovan osien tulostaminen: PROCEDURE draw_lines (sides : list; scan, x_int_count, index : integer); VAR k, x, x1, x2 : integer; FOR k := 1 TO round(x_int_count / 2) DO ; {draw_lines} WHILE sides[index].delta_y = 0 DO index := index + 1; x1 := round(sides[index].x_int); index := index + 1; WHILE sides[index].delta_y = 0 DO index := index + 1; x2 := round(sides[index].x_int); index := index + 1; FOR x := x1 TO x2 DO set_pixel(x, scan, fill_color); index := index + 1 Primitiivien toteutus / 37 Primitiivien toteutus / 38 Aktiivisen janalistan päivitys: Päällekkäiset monikulmiot: prioriteettijärjestys PROCEDURE update_side_list (VAR sides : list); VAR k : integer; B B FOR k := first_s TO last_s DO WITH sides[k] DO IF delta_y > 0 THEN {determine next x_int, decrease delta_y} scan line delta_y := delta_y - 1; x_int := x_int - x_change_per_scan A C A C ; {update_side_list} päällekkäisyysjärjestys eri osissa pyyhkäisyjuovaa: A B C C C A B B A Yleinen koherenssiperiaate: geometristen objektien järjestäminen Y-suunnassa (pyyhkäisyjuovittain) X-suunnassa (kunkin juovan sisällä) P-suunnassa (prioriteetti eli "syvyys") saa aikaan sen, että joudutaan kulloinkin vertailemaan vain pienelläaktiivisella alueella olevia objekteja keskenään. Primitiivien toteutus / 39 Primitiivien toteutus / 40 Päällekkäisyys (jatkuu) Eri tekijöiden (XYP) käyttöjärjestys tuottaa eri algoritmeja, vaikuttaen tietorakenteeseen ohjelman tehokkuuteen / monimutkaisuuteen tulosten havainnollisuuteen (kasvaako kuva ylhäältä alas vai takaa eteenpäin) Näitä piilopinta-algoritmeja käsitellään kirjan luvussa 13 ja myöhemmällä luennolla. Rekursiivinen 4-naapurusto ja 8-naapurusto Reunojen määrittely? Mustetippa-algoritmi void boundaryfill(int x, int y, int fill, int boundary) { int current; } current = getpixel(x, y); if ((current!= boundary) && (current!= fill)) { setcolor(fill); setpixel(x, y); boundaryfill(x+1, y, fill, boundary); boundaryfill(x-1, y, fill, boundary); boundaryfill(x, y+1, fill, boundary); boundaryfill(x, y-1, fill, boundary); } Primitiivien toteutus / 41 Primitiivien toteutus / 42 7
Pisteen sijainti alueen suhteen Usein tarvitaan tietoa siitä, kuuluuko annettu piste mallin määrittämään alueeseen. Eri esitystavoille voidaan käyttää erilaisia menetelmiä: suoraan sijoittamalla koordinaatit epäyhtälöön esim. piste (2, 3) on 10-säteisen ympyrän sisällä: 2 2 2 + 3 < 2 10 vertaamalla avaruusjakoon ja luetteloon alueen soluista tutkimalla ovatko pisteen parametrit rajatuilla väleillä edellyttää parametrikuvauksen käänteismuunnosta 1 f : ( x, y, z) ( u, v) tutkimalla geometrisesti, onko piste reunan sisä- vai ulkopuolella Point-in-polygon Odd-even rule, scan-line Asetetaan ääretön puolisuora tutkittavast pisteestä lähtien ja lasketaan sen leikkauspisteet alueen reunaviivan (tai pinnan) kanssa. Koska äärettämän kaukana oleva puolisuoran pää on alueen ulkopuolella ja reunan ylitys merkitsee aina siirtymistä joko ulkoa sisään tai sisältä ulos, niin pariton määrä leikkauksia merkitsee pisteen olevan alueen sisällä n=1 n=3 n=4 Primitiivien toteutus / 43 Primitiivien toteutus / 44 Point-in-polygon Normaalivektorit ristituloilla, vektorien suunnat Ongelmatapaukset itseään leikkaavat tai sulkeutumattomat reunat Primitiivien toteutus / 45 8