Kirjautuminen

Haku

Tehtävät

Koodit: C++: 2D Bumpmapperi (SDL)

Kirjoittaja: peki

Kirjoitettu: 25.10.2004 – 25.10.2004

Tagit: grafiikka, koodi näytille, vinkki

Suht alkeellinen SDL bumpmapperi. Koodissa on vielä optimoimisen varaa, kuten eräät varmasti huomaavat.. :)
Bumpmappauksen perusidea käy kuitenkin kommentoinnista ilmi suht selkeästi.

Koodi käyttää optimointikikkana SSE:tä kriittisimmän neliöjuuren laskemiseen. (taulukkohypyytystä voisi käyttää, mutta pidän tätä tapaa jotenkin "siistimpänä", sillä efekti on fysiikaltaan sillon lähempänä valon aitoa käyttäytymistä.) Kommentoi yli se kohta ja korvaa osoittamallani rivillä, jos koneesi ei tue SSE:tä.

Koodi olettaa, että ohjelman kansiosta löytyy tiedosto Bumpmap2.bmp, jonka koko on 640x480 pikseliä. Kuten koodista käy ilmi, kuvasta otetaan talteen vain ja ainoastaan punasävyt.

Valmis exe (SSE, sekä tavallinen versio) ja kokeilukuva löytyvät täältä.

Edit:
Olen päivittänyt koodin tukemaan tekstuurimappausta!
Suosittelen kokeilemaan, vaikka hitaampi nyt onkin.

Tarvitset kokoa 640x480 olevan 24 bittisen bittikartan nimeltä tex.bmp samaan hakemistoon ohjelman kanssa.

#include "SDL.h"
#include <math.h>

enum {
  SCREENWIDTH = 640,
  SCREENHEIGHT = 480,
  SCREENBPP = 32,
  SCREENFLAGS = SDL_HWSURFACE | SDL_DOUBLEBUF
};

#define max(a, b)((a) > (b)) ? (a) : (b)

// valon sijainti
float lz, lx, ly;

// bumpmappi
int bumpmap[SCREENWIDTH][SCREENHEIGHT];
int tex[SCREENWIDTH][SCREENHEIGHT][3];

float pz[SCREENWIDTH][SCREENHEIGHT];
float px[SCREENWIDTH][SCREENHEIGHT];
float py[SCREENWIDTH][SCREENHEIGHT];

// Palauttaa pikselin (x,y) arvon
Uint32 _fastcall getpixel(SDL_Surface *surface, int x, int y)
{
    int bpp = surface->format->BytesPerPixel;
    // p on osoitin pikseliin, jonka haluamme kopioida
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
    case 1:
        return *p;

    case 2:
        return *(Uint16 *)p;

    case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return p[0] << 16 | p[1] << 8 | p[2];
        else
            return p[0] | p[1] << 8 | p[2] << 16;

    case 4:
        return *(Uint32 *)p;

    default:
        return 0;       // ei pitäisi tapahtua.
    }
}

void DrawScene(SDL_Surface* surface, SDL_Event* event)
{
    // Lukitse pinta, jotta voimme käsitellä pikseleitä suoraan
    if ( SDL_MUSTLOCK(surface) ) {
        if ( SDL_LockSurface(surface) < 0 ) {
            fprintf(stderr, "Can't lock the screen: %s\n", SDL_GetError());
            return;
        }
    }
    lz = 30;
    // pointteri pikseleihin
    Uint8 *p = (Uint8 *)surface->pixels;

    // muuttujat etukäteen(hieman nopeampi näin)
    float Lx, Ly, Lz, f, clr;
    for(int y=1;y<SCREENHEIGHT-1;y++)
    {
        for(int x=1;x<SCREENWIDTH-1;x++)
        {
            // normaalidata talteen

            // valovektori
            Lx = lx-x;
            Ly = ly-y;
            Lz = lz;

            // SSE:tä käyttävä neliöjuuri
            f = Lx*Lx + Ly*Ly + Lz*Lz;
            __asm
            {
                movss xmm0, f
                rsqrtss xmm0, xmm0
                rcpss xmm0, xmm0
                movss f, xmm0
            }
            // Jos tietokoneesi ei tue SSE:tä, korvaa edelliset rivit tällä:
            //f = sqrtf(Lx*Lx + Ly*Ly + Lz*Lz);

            // normalisoidaan
            Lx /= f;
            Ly /= f;
            Lz /= f;

            // clr on normalisoidun valovektorin ja normaalivektorin pistekertoma, eli niiden välisen kulman kosini
            clr = Lx*px[x][y] + Ly*py[x][y] + Lz*pz[x][y];

            // phong kartoitus, eli  dot * diff + dot^2 * spec + ambient
            clr = max(clr*0.6+clr*clr*0.4, 0);

            // Blendaa yhteen phong ja taustakuva
            int clrr = (tex[x][y][0]*clr + clr*255) / 2;
            int clrg = (tex[x][y][1]*clr + clr*255) / 2;
            int clrb = (tex[x][y][2]*clr + clr*255) / 2;

            // Pikseli oikeaan kohtaan
            *(Uint32 *)(p + y * surface->pitch + x * 4) = (clrr << 16) | (clrg << 8) | clrb;
        }
    }
    // poistetaan lukitus
    if ( SDL_MUSTLOCK(surface) ) {
        SDL_UnlockSurface(surface);
    }
    //päivitä pinta
    SDL_Flip(surface);
}

int main(int argc, char* argv[])
{
    //alusta systeemit
    SDL_Init(SDL_INIT_VIDEO);
    //aseta exit
    atexit(SDL_Quit);
    //luo ikkuna
    SDL_Surface* pSurface = SDL_SetVideoMode ( SCREENWIDTH, SCREENHEIGHT, SCREENBPP, SCREENFLAGS );

    lz = ly = lz = 0;

    // ladataan kuvio
    SDL_Surface *bmpBump = SDL_LoadBMP("Bumpmap2.bmp");
    for (int x=0;x<SCREENWIDTH;x++)
        for (int y=0;y<SCREENHEIGHT;y++)
        {
            // punaväri
            bumpmap[x][y] = getpixel(bmpBump, x,y) >> 16;
        }
    // ei tavita enää
    SDL_FreeSurface(bmpBump);

    // Tekstuurin data talteen, jotta saadaan nopeampi linkki dataan
    SDL_Surface* texture = SDL_LoadBMP("tex.bmp");
    for (int x=0;x<SCREENWIDTH;x++)
        for (int y=0;y<SCREENHEIGHT;y++)
        {
            tex[x][y][0] = getpixel(texture, x,y) >> 16;
            tex[x][y][1] = (getpixel(texture, x,y) >> 8) & 0xff;
            tex[x][y][2] = getpixel(texture, x,y) & 0xff;
        }
    SDL_FreeSurface(texture);


    // precalcitaan kuvan normaalit
    float tx,ty;
    for (int x=-SCREENWIDTH/2;x<SCREENWIDTH/2;x++)
        for (int y=-SCREENHEIGHT/2;y<SCREENHEIGHT/2;y++)
        {
            tx=x/(SCREENWIDTH/2);
            ty=y/(SCREENHEIGHT/2);

            // lasketaan normaali
            float nx = bumpmap[x+SCREENWIDTH/2+1][y+SCREENHEIGHT/2] - bumpmap[x+SCREENWIDTH/2-1][y+SCREENHEIGHT/2];
            float ny = bumpmap[x+SCREENWIDTH/2][y+SCREENHEIGHT/2+1] - bumpmap[x+SCREENWIDTH/2][y+SCREENHEIGHT/2-1];
            float nz = max(1 - sqrtf(tx*tx + ty*ty), 0); // täytyy flipata z..

            // normalisoidaan
            float length = sqrtf(nx*nx + ny*ny + nz*nz);
            pz[x+SCREENWIDTH/2][y+SCREENHEIGHT/2] = nz / length;
            px[x+SCREENWIDTH/2][y+SCREENHEIGHT/2] = nx / length;
            py[x+SCREENWIDTH/2][y+SCREENHEIGHT/2] = ny / length;
        }

    // normi sdl-looppi
    SDL_Event event;
    for (;;)
    {
        if ( SDL_PollEvent ( &event ) )
        {
            if ( event.type == SDL_QUIT ) break;
            // liikutetaan valoa
            lx = event.motion.x;
            ly = event.motion.y;
        }
        DrawScene(pSurface, &event);
    }

    // vapautellaan
    SDL_FreeSurface(pSurface);

    return(0);
}

Kommentit

FooBat [26.10.2004 16:19:13]

#

Ihan hyvältähän tuo näyttää. Ei se kaikkein nopein bump map ole, mutta näyttäisi olevan jotakuinkin fysiikan mallien mukainen. Yleensähän bumpmappaus on toteutettu taulukoimalla valo taulukkoon ja sitten mapin perusteella haettu sieltä valoarvo. Tämä tietenkin rajoittaa valon vain tietylle alueella, mutta nopeus on sen verran suurempi, että 10 vuotta vanhemmallakin koneella pärjää. Vertailun vuoksi voi tutkia miten bump mappaus taulukoimalla onnistuu (appletti ja koodi):
http://users.interfriends.net/maurid/BumpMapping.htm

peki [26.10.2004 16:32:18]

#

Tuo appletti muuten näytti todella kauniilta. Pitäisi varmaan itsekin yrittää keksiä, miten tuo environment mappaus tehdään..

Gwaur [26.10.2004 16:39:43]

#

Tuossa FooBatin mainitsemassa hommelissa on kuitenkin paljon pienempi resoluutio, huomatkaa se :)

BlueByte [27.10.2004 16:45:55]

#

vaadin linux binääriä

peki [27.10.2004 18:04:58]

#

Hankalampi homma, kun en Linuxille mitään osaa tehdä. :P

Edit:

HUOM! Olen päivittänyt koodin tukemaan tekstuurimappausta!

FooBat [27.10.2004 18:41:24]

#

lainaus:

vaadin linux binääriä

Eiköhän ole parempi, että linux binääriä kaipaava kääntää sellaisen itse niin kuin kaikki muutkin ohjelmat. Ei tarvitse kuin asentaa sdl kirjasto ja sanoa pari valittua sanaa gcc:lle tai g++:lle.
Yksinkertainen ohje Makefile:n kanssa:
http://andrew.textux.com/tutorials/tut1/tutorial1.html
Tämä ohjelma käyttää matikkakirjastoa, joten joudut vielä lisäämään linkkaukseen -lm parametrin.

tejeez [27.10.2004 22:22:10]

#

Älä määrittele niitä muuttujia missä sattuu paikoissa ni tulee ihan kunnollista C:tä eikä mitään C++-paskaa. :)

x86-linuxbinääri: http://koodaa.mine.nu/a/kakkabmma

peki [27.10.2004 22:52:22]

#

Tarkoitukseni ei ole ohjelmoida C:tä, vaan C++:aa.
Kerro ensin, mitä käytännön hyötyä C++:n kannalta on määritellä muuttujat funktion alussa. Mielestäni se tekee vain koodista sekavaa ja rumaa.
Mitä tehdään enää vanhalla C:llä, kun voidaan käyttää uutta ja selkeämpää C++:aa?

Kun pystyt kertomaan vahvat perusteet sille, miksi pitäisi käyttää C:tä, siirryn siihen ilomielin. Perusteeksi ei tietenkään kelpaa niin usein kuullut perusteet: "Se nyt vaan on niin." tai "Guru tuolla ircissä sanoi niin." En ole koskaan suunnitellut koodaavani ohjelmia mikrosiruille, joten siihenkään en C:n syntaksia tarvitse. Olen tästä samasta asiasta kuullut jo niin monet valitukset ja aina saanut samoja epämääräisiä vastauksia. Olisiko jonkun aika valaista asiaa selkeästi?

Mielestäni olisi aika jo luopua C:n turhan jäykästä tavasta koodata ja siirtyä tehokkaampaan ja selkeämpään tapaan kirjoittaa ohjelmia.

Antti Laaksonen [27.10.2004 23:36:57]

#

Jos koodi menisi muuttujien määrittelykohtaa vaihtamalla C:stä, niin minusta yhteensopivuutta ei kannata ehdoin tahdoin tärvellä. Kyse on aika pienestä asiasta, joka ei vaikuta koodin nopeuteen eikä juuri selkeyteenkään. Tilanne olisi toinen, jos koodi olisi pullollaan C++:n ominaisuuksia, joita C:stä ei löydy. Käytännössä tällä asialla ei tietenkään ole mainittavaa merkitystä.

FooBat [28.10.2004 02:51:47]

#

// pari elintärkeää makroa
#define max(a, b)((a) > (b)) ? (a) : (b)
#define min(a, b)((a) < (b)) ? (a) : (b)

Taisi nyt sitten tulla noutaja, jos noi oli elintärkeitä. Kokeiles mitä 5*max(3,4) tai max(4,3)*2 palauttaa ;). Kannattaa korjata muihinkin ohjelmiisi, tuo on ilmiselvästi kopioitu jostakin muusta koodinpätkästäsi, kun et min-makroa missään käytä.

C:n ja C++:n eroista voin sanoa sen, että C-kääntäjät tuntuvat tuottavat tehokkaanpaa koodia kuin C++:n kääntäjät. Hyvä C-kääntäjä oikeailla parametreilla tuottaa paljon optimoidumpaa koodia kuin keskiverto assembler-koodari ikinä saa aikaiseksi, C++ ei puolestaan pahimmillaan pärjää edes javalle ;). Omien kokemuksieni mukaan C++ -binäärit ovat usein myös huomattavasti isompia kuin vastaavat C-binäärit.

peki [28.10.2004 13:26:41]

#

Tuo min jäi vahingossa aikaisemmista bumpmapperin versioista. Otanpa sen pois.

Jos koodia MS VC++:lla kääntää, kääntääkö se puhtaan C -koodin erillisellä kääntäjällä? Tarvitsisiko minun hankkia erillinen C-kääntäjä, jos haluan täysin puhdasta C:tä koodata?

Foobat: Ei ne nyt ihan elintärkeitä ole, mutta pistinpä nyt vähän "rennomman" kommentin vaihteeksi.. ;)

Edit: Makro-ongelmahan korjautuu, kun laittaa makron sulkeisiin aina, kun sitä käytetään. Ainakaan tässä koodissa se ei vaikuta mihinkään, koska makron antamalle tulokselle ei suoraan tehdä mitään. Täytyy nuo muutkin koodit tarkistaa..

thefox [28.10.2004 15:17:39]

#

peki: mutta on tietenkin suositeltavampaa laittaa ne sulut jo tuohon makroon :) VC++ kääntää C:n ihan C:nä jos tiedoston pääte on .c ja sitä ei erikseen määritellä kääntämään C++:aa.

FooBat [28.10.2004 17:51:42]

#

lainaus:

peki: mutta on tietenkin suositeltavampaa laittaa ne sulut jo tuohon makroon :)

Jep. Makrojen kanssa kannattaa muutenkin olla tarkkana. Itse aikoinaan etsin pitkään virhettä seuraavanlaisesta koodista.

#define X1 200
#define X2 300
#define LEVEYS 3*X1+2*X2
...
...
int n = LEVEYS / 2;

Muutaman päivän pään seinään hakkaus auttoi ainakin opettamaan jotain makroista :)

TVdata [11.12.2011 17:58:25]

#

Miksi tuossa on kaksi exeä?

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta