Code: Select all
SCREEN 512,512
// Korkeuskartan valaistus
// koodaillut cce, kiitos misthemalle parista fiksauksesta
//
// Ohjelma laskee harmaasävyisen korkeuskartan jokaiselle pisteelle ns. "pinnan normaalin",
// joka tarkoittaa suuntavektoria kohtisuoraan ylös pinnasta sillä kohdalla karttaa.
// Itse pääloopissa sitten verrataan pisteen normaalin ja valosta pisteeseen osoittavan vektorin
// suuntia, ja annetaan pikselille väriarvo kulmien erotuksen mukaan.
//
// 3D-vektori voidaan esittää komponenttimuodossa (x, y, z).
// Kaksi vektoria voidaan summata seuraavasti:
// v1 + v2 = (v1_x + v2_x, v1_y + v2_y, v1_z + v2_z)
//
// Vähennyslasku toimii samalla tavalla laskemalla komponentti kerrallaan.
//
// Vektorin pituus lasketaan pythagoraan lauseella
// sqrt(x^2 + y^2 + z^2)
//
// Vektori voidaan ns. "normalisoida", jolloin sen pituudeksi tulee tasan 1 (katso ylempää)
// mutta sen suunta pysyy samana. Tämä onnistuu jakamalla vektorin jokainen komponentti kyseisen
// vektorin pituudella, eli näin
// v = vektori; vx, vy, ja vz ovat vektorin kolme komponenttia
//
// v_pituus = sqrt(vx^2 + vy^2 + vz^2)
// vx = vx / v_pituus
// vy = vy / v_pituus
// vz = vz / v_pituus
//
// Nyt vektorin v pituus on tasan yksi, tarkista vaikka laskemalla.
//
// Kun vektorin pituus on yksi, kutsutaan sitä nimellä "yksikkövektori" (engl. "unit vector")
// Kun puhutaan suuntavektorista, tarkoitetaan yksikkövektoria joka osoittaa ainoastaan jonkin suunnan.
// Vektorin suuntaa voi havainnollistaa piirtämällä viivan origosta pisteeseen (vx, vy, vz), jolloin
// vektorin suunta olisi viivan piirtosuunta.
//
// Suuntavektorien lisäksi on olemassa "paikkavektoreita", jotka yksinkertaisesti kertovat jonkin
// paikan kolmiulotteisessa avaruudessa. Esimerkiksi pelaajan paikkaa pelimaailmassa voisi kuvata
// ohjelman sisäisesti paikkavektorilla, eli siis kolmella arvolla pelaaja_x, pelaaja_y ja pelaaja_z,
// joista muodostuisi vektori (pelaaja_x, pelaaja_y, pelaaja_z).
//
// Kahden suuntavektorin välinen kulma voidaan laskea ns. "pistetulolla"
// Olkoon yksikkövektorit a = (ax, ay, az) ja b = (bx, by, bz), niin niiden pistetulo on
// pistetulo# = (ax*bx) + (ay*by) + (az*bz)
//
// Huomaa että pistetulon arvo on liukulukuarvo eikä vektori. Mikäli vektorit olivat yksikkövektoreita,
// pitäisi pistetulon arvo olla nyt välillä [-1.0, 1.0]. Mikäli se on 1.0, ovat vektorit samansuuntaiset,
// ja mikäli se on -1.0, osoittavat vektorit vastakkaisiin suuntiin. Hyödynnämme tätä valoisuuden laskemisessa,
// mikäli pinnan normaali osoittaa valoa kohti, tulee pistetulon arvoksi positiivinen arvo, ja väritämme
// kyseisen pikselin vaaleaksi. Mikäli arvo on negatiivinen, tummennamme pikseliä.
//
// Suuntavektorin muodostus:
//
// Pisteiden A = (Ax, Ay, Az) ja B = (Bx, By, Bz) välisen vektorin (vektori AB) voi selvittää laskemalla
// v = (Bx-Ax, By-Ay, Bz-Az)
//
// Jos tämän jälkeen vielä normalisoit vektorin (katso ylempää), niin vektori on suuntavektori, joka
// osoittaa pisteestä A pisteeseen B. Käytämme tätä kun laskemme mistä suunnasta valo paistaa kuhunkin
// kuvan pisteeseen.
// luodaan harmaasävyinen testikuva
img = MakeImage(255,255)
font = LoadFont("tahoma", 60)
SetFont font
reunat = 20
For u = reunat To 0 Step -1
cc = Max(0,255-255.0/Float(reunat)*u)
Color cc, cc, cc
For i = 0 To 128
ang# = i * (360/128.0)
Text 64-x + cos(ang)*u*1.0, 64-y + sin(ang)*u*1.0, "peisik"
Next i
Next u
CopyBox 0,0, 255, 255, 0, 0, SCREEN(), Image(img)
Const NORMAL_X = 0
Const NORMAL_Y = 1
Const NORMAL_Z = 2
Const HEIGHT = 3
Dim normals(ImageWidth(img), ImageHeight(img), 3) As Float
// tätä muuttamalla muuttuu korkeuskartan kovuus
depth# = 0.6
Lock(Image(img))
For y=1 To ImageWidth(img)-1
For x=1 To ImageHeight(img)-1
// Havainnollistava kuva, X = nykyinen pikseli (x,y); za1, za3, zb1 ja zb3 ovat sen ympäriltä luettavat pikselit
// luetaan siis korkeuskartan arvot ympäröivistä pikseleistä, ja muodostetaan niiden avulla suuntavektori
// zb1
//za1 X za3
// zb3
//
// luetaan punaisen kanavan väriarvot ympäröivistä pikseleistä, ja skaalataan se välille [0.0, 1.0]
za1# = (((GetPixel2(x-1, y, Image(img))) Shl 8) Shr 3*8)/255.0
'za2# = (((GetPixel2(x, y, Image(img))) Shl 8) Shr 3*8)/255.0
za3# = (((GetPixel2(x+1, y, Image(img))) Shl 8) Shr 3*8)/255.0
zb1# = (((GetPixel2(x, y-1, Image(img))) Shl 8) Shr 3*8)/255.0
'zb2# = (((GetPixel2(x, y, Image(img))) Shl 8) Shr 3*8)/255.0
zb3# = (((GetPixel2(x, y+1, Image(img))) Shl 8) Shr 3*8)/255.0
// vektori a (kolmiulotteinen vektori (xa, ya, za)
xa# = depth
ya# = 0.0
za# = za3 - za1
// vektori b
xb# = 0.0
yb# = depth
zb# = zb3 - zb1
// normalisoidaan vektorit a ja b (jaetaan jokainen komponentti pituudella)
la# = Sqrt(xa*xa + ya*ya + za*za) // pituus saadaan pythagoraan lauseella
xna# = xa/la
yna# = ya/la
zna# = za/la
lb# = Sqrt(xb*xb + yb*yb + zb*zb)
xnb# = xb/lb
ynb# = yb/lb
znb# = zb/lb
// lopullinen suuntavektori joka on pinnan normaali
// lopullinen normaali saadaan ns. "ristitulolla", jolloin kahdesta vaakatasossa makaavasta vektorista tulee
// yksi suoraan pystyyn osoittava.
// kuva: http://dl.dropbox.com/u/5753422/paste/ristitulo.png
// kaava johdettu tästä: https://fi.wikipedia.org/wiki/Vektori#Vektoritulo_eli_ristitulo_.28Cross_Product.29
// (i, j ja k vastaavat akseleita x, y ja z)
xc# = (yna*znb-zna*ynb)
yc# = (zna*xnb-xna*znb)
zc# = (xna*ynb-yna*xnb)
// ja normalisoidaan se vielä
lc# = Sqrt(xc*xc + yc*yc + zc*zc)
xnc# = xc/lc
ync# = yc/lc
znc# = zc/lc
// normalisoidun vektorin komponenttien arvot tulisivat olla välillä [-1.0, 1.0]
normals(x,y, NORMAL_X) = Max(-1.0, Min(1.0, xnc))
normals(x,y, NORMAL_Y) = Max(-1.0, Min(1.0, ync))
normals(x,y, NORMAL_Z) = Max(-1.0, Min(1.0, znc))*-1.0 // vaihdetaan normaalin z-komponentin merkki
// että normaali osoittaisi ylös pinnasta
normals(x,y, HEIGHT) = Max(-1.0,Min(1.0, xa*2.0 - 0.5))// tallennetaan korkeuskartan nykyinen arvo
Next x
Next y
Unlock(Image(img))
offset_x = 200
offset_y = 0
start=Timer()
Repeat
t=Timer()-start
// valon sijainti
lx# = Cos(t/64.0)*60 + 100
ly# = Sin(t/32.0)*60 + 100
lz# = 2.0 // tätä ei välttämättä tarvita (valon korkeus pinnasta)
DrawImage img, 0, 0
Lock()
For y=1 To ImageWidth(img)-1
For x=1 To ImageHeight(img)-1
// Lasketaan suuntavektori nykyisestä pisteestä valoon (miinustetaan komponentit toisistaan)
// mikäli valo tulee "kaukaa", voi vektori p olla kaikkialla vakio.
// Esimerkiksi auringonvalon voisi approksimoida näin.
px# = x-lx
py# = y-ly
pz# = normals(x, y, HEIGHT) - lz
pz# = 1.0
// Normalisoidaan suuntavektori p (jaetaan jokainen komponentti vektorin pituudella lp)
lp# = Sqrt(px*px + py*py + pz*pz)
pnx# = px/lp
pny# = py/lp
pnz# = pz/lp
// lasketaan valovektorin ja taulukkoon tallennetun pinnan normaalin pistetulo
// cosine -muuttujan arvot tulevat olemaan väliltä [-1.0, 1.0]
// pistetulo kertoo kahden vektorin välisen kulman kosinin suuruuden, jos se ON 1
// niin vektorit ovat samansuuntaiset, jos taas -1, ON kulmien erotus 180 astetta
cosine# = (normals(x,y, NORMAL_X)*pnx) + (normals(x,y, NORMAL_Y)*pny) + (normals(x,y, NORMAL_Z)*pnz)
// lasketaan hatusta heitetyllä kaavalla pikselin kirkkaus
c = (1-(1.0+cosine)*0.5)*255.0
// muutetaan väriarvo pikseliksi
pix = 255 Shl 24 + c Shl 16 + c Shl 8 + c
PutPixel2 offset_x + x, offset_y + y, pix
Next x
Next y
Unlock()
// piirretään liikkuva valolaatikko
Color 255, 255, 255
Box lx-2 + offset_x, ly-2 + offset_y, 5, 5, 1
SetWindow "FPS: " + FPS()
DrawScreen
Forever