• Hur man skriver ett 4KB demo i C++ Del 3: Content.

    Som jag nämnde i förra artikeln, så skulle denna handla om hur man skapar content på ett minnessnålt vis. En del problem uppstod för mig under förberedandet av koden till artikeln:

    Jag lovade i första artikeln att undvika assembler, men det gick helt enkelt inte...
    Saken är den att vissa floating point funktioner som t.ex. "abs", "cos", "sin", "sqrt" är funktioner som finns med i c-runtime biblioteket som vi kastade ut redan i artikel ett för att spara minne. Just dessa funktioner finns som enkla komandon på fpu'n, så jag ser inte anledningen till varför de skall ligga i ett bibliotek, men det gör de alltså.
    Enda sättet för mig att nå den funktionaliteten i ett 4K projekt är alltså att lägga till den funktionaliteten själv via assembler. För att förenkla/förtydliga koden så lade jag all assembler kod som inline assembler i en separat fil som jag helt enkelt kallade för "assembler.h".
    En sak som är värt att notera, är att i Visual studio, så kan man enbart skriva inline assembler kod för 32bit program, men det är inget problem för oss när vi ändå sätter minnessnålheten främst.

    En till sak som var tvunget att läggas in som assembler kod, var en sak som man lätt tar för givet: type casting från double till int. Även här blev jag lite överraskad, för fpu'n fixar ju lätt översättningar mellan floating point och integer, så varför kallas det då upp en c-runtime funktion för varje type cast från double till int? Jo, problemet är hur standardinställningen för avrundning är på fpu'n. Den är nämligen inställd på att alltid avrunda till närmaste heltal.
    Så, med standardinställningen för fpu'n, så skulle följande översättningar gälla: 1.23=1, 4.51=5, -0.23=0.
    Detta är inte så som man förväntar sig när man programmerar C, i C så skulle samma serie bli: 1.23=1, 4.51=4, -0.23=0. Så i c-runtime biblioteket finns det en funktion som tillfälligt ställer om fpu'n till att avrunda så som C skall ha det, för att sedan sätta tillbaka fpu'n till standardinställningen. En intressant detalj är att all denna omställning är mycket tidskrävande, så om ni vid något läge skulle behöva optimera prestanda i kod, se då till att undvika type casts från flyttal till integer. Men, pga. denna saknade funktionalitet, så fick jag även skriva en assembler funktion för att type casta från double till int. Och eftersom jag dessutom är intresserad av att alltid avrunda nedåt, även om talet är negativt, så gjorde jag funktionen så. Kan vara värt att notera.

    Ett annat problem jag fick, var att introducerandet av koden som är själva grunden till att generera content blev rätt stor. Mycket kod helt enkelt, så jag fick begränsa denna tutorial en del och skippa en hel del exempel på användandet av koden... Jag får helt enkelt visa det i en annan tutorial (ja, det blir med andra ord en till artikel).
    I denna artikeln så nöjer jag mig med att introducera själva koden och se till att den producerar ett fungerande resultat.

    Men, nog om alla problem, en snabb genomgång av koden som tillkommit i vår main.cpp:

    Två nya includes:
    Kod:
    #include "noise.h"
    #include "assembler.h"
    Vi talar om för OpenGL att vi vill ha texturering:
    Kod:
    glEnable(GL_TEXTURE_2D);
    Koden vi tidigare hade som ritade ut en triangel har justerats till att rita ut en texturerad fyrkant:
    Kod:
    glBegin(GL_QUADS);
    glColor3f(1.0f, 1.0f, 1.0f);
    glTexCoord2f(1.0f, 1.0f);
    glVertex2f(0.5f, 0.5f);
    glTexCoord2f(1.0f, 0.0f);
    glVertex2f(0.5f, -0.5f);
    glTexCoord2f(0.0f, 0.0f);
    glVertex2f(-0.5f, -0.5f);
    glTexCoord2f(0.0f, 1.0f);
    glVertex2f(-0.5f, 0.5f);
    glEnd();
    Vi kallar upp en rutin som genererar en textur:
    Kod:
    GenerateTexture(TEXTURE);
    En enum för textur handles:
    Kod:
    enum HANDLE_ENUM
    {
    	UNBIND=0,
    	TEXTURE
    };
    Och så koden som genererar en textur:
    Kod:
    void GenerateTexture(HANDLE_ENUM handle)
    {
        static unsigned char texelArray[256][256];
    
        for (int y = 0; y < 256; ++y)
        {
            for (int x = 0; x < 256; ++x)
            {
                texelArray[y][x] = (unsigned char)double2int((GetPerlinNoise(10.0, 2.0, 0.5, 6, (double)x / 256.0, (double)y / 256.0, 0.0, 26572, false) * 80.0) + 128.0);
            }
        }
    
        glBindTexture(GL_TEXTURE_2D, handle);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 256, 256, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texelArray);
    }
    De av er som arbetat med OpenGL innan, har sett en underlighet i min kod: Jag använder inte "glGenTextures" för att få tag på en handle till texturen.
    Faktum är att man inte behöver det. "glGenTextures" är bara en hjälprutin som ser till att man inte råkar använda en handel som redan är upptagen. Och eftersom alla handles startar från ett för en och samma procedur, så kan vi helt enkelt välja att skapa våra egna handles, vilket sparar minne.
    När man arbetar med texturer i OpenGL, så sker allting man gör emot den textur som för tillfället är bunden, vilket man gör med anropet: "glBindTexture".
    När man bundit sin textur handle, så kan man ladda upp sin grafik till den, och det gör man med anropet: "glTexImage2D". I detta fallet så har jag bara en gråskala i texturen, så för enkelhets skull, så laddar jag upp den som en sådan också, vilket jag specificerar med "GL_LUMINANCE".
    Anropen till "glTexParameteri" talar om för OpenGL hur grafiken i texturen skall filtreras när den ritas ut.

    Den dubbla loopen i texturgenereringen har som uppgift att för varje pixel i en textur av storleken 256*256, fylla i värden som kommer från funktionen: "GetPerlinNoise". Värdena från den funktionen kan vara positiva och negativa, och rör sig som regel mellan +-1.0, fast beroende på inparametrarna, så kan det bli större eller mindre range på värdena.
    Jag multiplicerar med 80 och plussar på med 128 för att få värdena inom det som ryms i en byte, vilket är det som jag baserat texturformatet på. Detta talar jag även om för OpenGL i "glTexImage2D" genom att specificera: "GL_UNSIGNED_BYTE".

    Så, vad gör denna "GetPerlinNoise" funktionen då?
    Rakt på sak, så levererar den "Perlin noise", vilket är en funktion i kategorin: "coherent noise", vilket kan översättas till Svensson språk som: "Brus med sammanhang". Och här kommer det coola: Massvis av det ni sett i spel och filmer gällande fraktalt genererat landskap, moln, texturer etc. är baserat på denna enkla funktion (ja, även minecraft http://notch.tumblr.com/post/3746989...eration-part-1). Nu har jag dock modiferat funktionen en del från orinalet, för att ta upp mindre minne etc. men det är ändå samma princip som den arbetar efter. Eftersom Perlin noise och Billowing noise är mycket lika varandra i sin funktion, så har jag lagt till en bool i funktionen där man kan välja vilken sort man vill ha, allting för att ge fler funktioner men att bara förbruka lite minne.
    Hela idén med coherent noise är att man utefter vissa angivna parametrar kan skapa ett mönster. Se det som en slumpgenerator där ett visst seed alltid återskapar samma ström med slumptal, men med coherent noise, så är alla närliggande slumptal lite släkt med varandra, de har påverkat varandra så att det finns ett sammanhang i strömmen. Med Perlin noise, så kan man dessutom fråga efter ett visst tal i strömmen utan att räkna ut alla tidigare tal. Dvs. man kan fråga efter bruset med hjälp av en kordinat, i vårt fall så anger vi det hela med hjälp av en tredimensionell xyz kordinat. Effekten av det är att i texturloopen i main ovan, så frågar vi efter alla Perlin noise värden mellan kordinaterna 0,0,0 och 1,1,0. Vi använder bara två dimensioner eftersom texturen ändå är tvådimensionell. Skulle vi få för oss att ändra på kordinaterna, så skulle vi kunna zooma in och ut ur den grafik som skapas, eller scrolla runt i den. Ändrar vi på någon av de andra parametrarna: "frequency", "lacunarity", "persistence", "octaveCount", "seed", så skulle vi få se en dramatisk förändring i själva mönstret, det är så man anger vilket mönster som skall skapas.

    Själva funktionen "GetPerlinNoise" med alla sina tillhörande rutiner finns i noise.cpp och noise.h, men jag kommer inte att detaljförklara de här. De här artiklarna tar upp de metoder man använder för att kunna göra 4K produktioner, och om ni är mer intresserade av coherent noise så rekomenderar jag er att tilla närmare på http://libnoise.sourceforge.net/ eller http://en.wikipedia.org/wiki/Perlin_noise

    Jag har inte orkat gå in för att minnesoptimera den nya koden särskillt mycket, så vi halkade upp till 1.5KB med de senaste tilläggen... Men det skall nog inte hindra oss från att få ihop något coolt till slut. Tanken är att använda dessa funktionerna och kanske några fler framtida tillägg till att skapa landskap och texturer som vi kan flyga omkring över. Låter väl coolt eller?
    Det nuvarande projektet för Visual studio 2010 finns att hämta här: minimal_part3.zip
    Glöm inte bort det jag tidigare berättat i Crinkler etc. om vi skall kompilera projektet till de små minnesmängderna jag uppnår.
    Om ni bygger och kör programmet så skall ni se en textur som ser ut så här:


    Några smakprov över hur jag själv använt coherent noise i mina egna experiment:

    Landskapet till Ballistious:
    http://www.youtube.com/watch?v=T5oknVzKHqY

    I ett experiment där jag ville skapa ett grottsystem, så genererade jag en kub med brus, för att sedan använda "Marching cubes" formeln till att bygga upp en triangel mesh:


    Nästa artikel jag skriver kommer att handla om linjär algebra på en komplett nybörjar nivå.
    Efter det så skall jag fortsätta på denna serien, och då blir det nog ett landskap, rudimentär ljusläggning och en enkel kamera.