Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: C++: OpenGL shader esimerkki

aaämdee [27.10.2010 21:36:32]

#

Ajattelin lähettää tänne shader koodivinkin, kun en sellaista huomannut täällä olevan.

OpenGL sisältää tuen shadereille GLSL (GL shader language) kielen muodossa. Shadereilla saa kaikenlaisia efektejä aikaiseksi, esimerkiksi varjostuksia tai itsemääriteltyjä valaistuksia.

Shadereita on erilaisia. OpenGL tuntee seuraavat: vertex shader, fragment shader, sekä uudemmat versiot myös geometry shaderin.

Shadereilla korvataa OpenGL:n omat piirtomekanismit. Vertex shaderilla korvataan verteksien perspektiivilaskenta. Fragment shaderilla korvataan fragmentin (osapikseli) väriarvo. Geometry shaderilla voidaan luoda olemassa olevista vertekseistä uusia verteksejä.

Kun shaderi määritellään, poistuu sen korvaama piirtomekanismi kokonaan käytöstä. Esimerkiksi fragment shaderin korvattua on valaistukset ja värjäykset ohjelmoitava itse shaderiin.

Tässä koodivinkissä luodaan GLSL:llä ohjelmoidut vertex ja fragment -shaderit. Esimerkin vertex shader muokkaa verteksin z-koordinaattia luoden aaltoefektin, ja välittää tiettyjä tietoja fragment shaderille. Fragment shader taas luo yksinkertaisen plasmaefektin.

Vinkin shaderit eivät oikeastaan tee mitään hyödyllistä. Suurempi ilo on varmaankin vinkin koodi, jolla OpenGL konfiguroidaan käyttämään shadereita. Demossa käytetty plasmaefekti löytyy jo koodivinkeistä, joten sitä ei liene tarvitse sen enempää selitellä.

Kirjastona ikkunan luomiselle ja syötteen lukemiseen käytetään SFML-kirjastoa. (http://sfml-dev.org)

Seuraavat kirjastot täytyy linkata: sfml-window, sfml-system, GLU ja GL

GNU:n C++ kääntäjällä kääntäminen onnistuu seuraavasti:
g++ -O2 -lsfml-window -lsfml-system -lGLU -o shader-demo main.cpp

Ai niin, vaatii toki toimiakseen tuen OpenGL shadereille näytönohjaimelta ja ajureilta. Ilman sitä tulee ruudulle vain valkoinen suorakaide...

Lisätietoa shader ohjelmoinnista esimerkiksi: (toimiva tutoriaali)
http://www.lighthouse3d.com/opengl/glsl/index.php?intro

main.cpp

//Koodia saa käyttää, mutta en vastaa seuraamuksista

#define GL_GLEXT_PROTOTYPES

#include <SFML/Window.hpp>
#include <iostream>
#include <fstream>

//Vakiot
const char *VertexShaderFile = "vertex.shader";
const char *FragmentShaderFile = "fragment.shader";
const int NumberOfTrianglesX = 100;
const int NumberOfTrianglesY = 1;
const float TriangleSizeX = 1.f;
const float TriangleSizeY = 100.f;

//Funktioiden prototyypit
void drawTriangles();
void initGL();
void initShaders();
void printShaderInfoLog(GLuint obj);
void printProgramInfoLog(GLuint obj);
char* readSource(const char *filename);

//Globaalit
GLuint shaderProgram;

//Main
int main(int argc, char **argv)
{
    //GLint loc;
    int mouseX, mouseY;
    int windowWidth, windowHeight;

    mouseX = 0;
    mouseY = 0;
    windowWidth = 800;
    windowHeight = 800;

    std::cout << argv[0] << std::endl;

    //Luodaan ikkuna, 4x antialiasointi
    sf::Window App(sf::VideoMode(windowWidth, windowHeight, 32),
                   "Shader demo",
                   sf::Style::Resize | sf::Style::Close,
                   sf::WindowSettings(24, 8, 4));

    //Ruudunpäivitys maksimissaan 60 kertaa sekunnissa
    App.SetFramerateLimit(60);

    //Luodaan kello-olio
    sf::Clock clock;

    //OpenGL alustukset
    initGL();
    //Shader alustukset
    initShaders();

    //Pyöritään silmukassa niin kauan kuin ohjelma on päällä
    while (App.IsOpened())
    {
        //käydään eventit läpi
        sf::Event Event;
        while (App.GetEvent(Event))
        {
            switch (Event.Type)
            {
            case sf::Event::Closed: //Ohjelman sammuttaminen
            case sf::Event::KeyPressed: //Mikä tahansa näppäimen painallus
                App.Close();
                break;
            case sf::Event::Resized: //Ikkunan koon muuttaminen
                windowWidth = Event.Size.Width;
                windowHeight = Event.Size.Height;
                glViewport(0, 0, Event.Size.Width, Event.Size.Height);
                break;
            case sf::Event::MouseMoved: //Hiiren liikutus
                mouseX = Event.MouseMove.X - windowWidth / 2;
                mouseY = Event.MouseMove.Y - windowHeight / 2;
                break;
            default:
                break;
            }
        }

        //Tyhjennetään ruutu tyhjennysvärillä
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        //Tehdään muunnokset OpenGL modelview matriisiin
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        //Koordinaatisto syvemmälle
        glTranslatef(0.f, 0.f, -200.f);
        //Pyöritetään hiiren sijainnin mukaan
        glRotatef(mouseY * .3f, 1.f, 0.f, 0.f);
        glRotatef(mouseX * .3f, 0.f, 1.f, 0.f);
        //Siirretään vielä koordinaatistoa
        glTranslatef(-50.f, -50.f, 0.f);

        //renderöinti
        drawTriangles();

        //Piirrä ruutu
        App.Display();
    }

    return EXIT_SUCCESS;
}

//Piirtää ruudulle kolmioista koostuvan suorakaiteen muotoisen alueen
//ja huolehtii shadereiden time muuttujasta
void drawTriangles()
{
    static float slide = 0.f;
    GLuint loc;

    slide += 0.03f;

    //Asetetaan kaikille shadereille näkyvä vakio time
    loc = glGetUniformLocation(shaderProgram,"time");
    glUniform1f(loc, slide);

    //Luodaan kolmiosysteemi
    for (int y=0; y<NumberOfTrianglesY; y++)
    {
        glBegin(GL_TRIANGLE_STRIP);
        for (int x=0; x<NumberOfTrianglesX; x++)
        {
            glVertex2f(x*TriangleSizeX, y*TriangleSizeY);
            glVertex2f(x*TriangleSizeX, y*TriangleSizeY+TriangleSizeY);
        }
        glEnd();
    }
}

void initGL()
{
    //Tyhjennysväri
    glClearColor(0.f, 0.f, 0.f, 0.f);

    //Tyhjennyssyvyys
    glClearDepth(1.f);

    //Syvyystarkistus päälle
    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);

    //Asetetaan OpenGL:n perspektiivimatriisi
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(40.f, 1.f, 1.f, 500.f);
}

void initShaders()
{
    GLuint vertexShader;
    GLuint fragmentShader;
    const GLchar *fragmentCode;
    const GLchar *vertexCode;

    //Luodaan shader objektit
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    vertexShader = glCreateShader(GL_VERTEX_SHADER);

    //Luetaan shader koodit tiedostoista ja asetetaan koodit objekteihin
    fragmentCode = readSource(FragmentShaderFile);
    vertexCode = readSource(VertexShaderFile);
    if (fragmentCode == NULL || vertexCode == NULL)
    {
        std::cout << "Shader tiedosto puuttuu!" << std::endl;
        exit(0);
    }
    glShaderSource(fragmentShader, 1, &fragmentCode, NULL);
    glShaderSource(vertexShader, 1, &vertexCode, NULL);
    delete [] fragmentCode;
    delete [] vertexCode;

    //Käännetään shaderit
    glCompileShader(fragmentShader);
    glCompileShader(vertexShader);

    //Tulostetaan käännöstiedot
    printShaderInfoLog(fragmentShader);
    printShaderInfoLog(vertexShader);

    //Luodaan ja linkataan shader ohjelma
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, fragmentShader);
    glAttachShader(shaderProgram, vertexShader);
    glLinkProgram(shaderProgram);

    //Tuolostetaan ohjelman linkkaustiedot
    printProgramInfoLog(shaderProgram);

    //Asetetaan ohjelma käyttöön
    glUseProgram(shaderProgram);
}

char* readSource(const char *filename)
{
    //Luodaan ifstream olio osoittamaan tiedoston loppuun
    std::ifstream f(filename, std::ios::in | std::ios::ate);
    int size;
    char *str;

    if (!f.is_open())
        return NULL;

    //Kutsutaan tellg tiedoston lopussa, saadaan tiedoston koko
    size = static_cast<int>(f.tellg());

    //varataan muisti ja luetaan tiedosto
    str = new char[size+1];
    str[size] = 0;
    f.seekg (0, std::ios::beg);
    f.read(str, size);

    f.close();

    return str;
}

//Tulostaa tiedot shaderin kääntämisestä
void printShaderInfoLog(GLuint obj)
{
    int size = 0;
    char *log;

    glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &size);

    if (size > 0)
    {
        log = new char[size];
        glGetShaderInfoLog(obj, size, NULL, log);
        std::cout << log << std::endl;
        delete [] log;
    }
}

//Tulostaa tiedot shader ohjelmasta
void printProgramInfoLog(GLuint obj)
{
    int size = 0;
    char *log;

    glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &size);

    if (size > 0)
    {
        log = new char[size];
        glGetProgramInfoLog(obj, size, NULL, log);
        std::cout << log << std::endl;
        delete [] log;
    }
}

fragment.shader

//Universaali aika, kaikille fragmenteille sama
uniform float time;

//location lasketaan automaattisesti interpoloimalla verteksien antamat arvot
varying vec2 location;

void main()
{
    float white;
    vec4 color;
    vec2 plasmaloc;

    plasmaloc.x = location.x + cos(time * 0.1) * 100;
    plasmaloc.y = location.y + sin(time * 0.1) * 70;

    plasmaloc /= 50.0;

    //Jonkinlainen plasmaefektilasku
    white = sin(plasmaloc.x) * sin(plasmaloc.y) * 2.5
          + sin(plasmaloc.x*(20 + 5*cos(plasmaloc.y*5))) * 1.5
          + sin(plasmaloc.y*(30 + 7*cos(plasmaloc.x*3))) * 1.5;

    white = abs(sin(white));

    //Määritellään fragmentin lopullinen väri
    color.x = (1 - white) * abs(sin(plasmaloc.y));
    color.y = white;
    color.z = 0.5 - 0.5 * white + 0.5;
    color.w = 1.0;

    //Kerrotaan vielä OpenGL:lle väri...
    gl_FragColor = color;
}

vertex.shader

//time on määriteltu main.cpp tiedostossa
//uniform tyyppi tarkoittaa, että se näkyy kaikille shadereille samana
uniform float time;

//varying muuttuja location välitetään verteksiltä fragmenteille
//fragmentin saama arvo on interpoloitu sen muodostaneiden verteksien
//arvoista
varying vec2 location;

void main()
{
    //Talletetaan vektoriin v verteksin sijainti
    vec4 v = vec4(gl_Vertex);

    //Muokataan verteksin z-koordinaattia
    v.z = v.z + sin(0.3*v.x + time) * 5;

    //Asetetaan verteksin x ja y -koordinaatit location muuttujaan
    //location vektori välittyy interpoloituna fragmenteille
    location.x = v.x;
    location.y = v.y;

    //Verteksin lopullinen sijainti saadaan kertomalla se
    //projektiomatriisin kanssa
    gl_Position = gl_ModelViewProjectionMatrix * v;
}

Makefile (kääntämiseen laiskureita varten)

shader-test: main.cpp
	g++ -O2 -lsfml-window -lsfml-system -lGLU -o shader-demo main.cpp

Nanoputki [03.04.2011 00:56:19]

#

Pohjatietoa shadereiden käytännön toteutuksesta suomeksi. Kiitän tästä.

leonarven [08.08.2011 16:12:49]

#

Näin assyjen innoittamana taas into opetella shadereiden käyttöä kasvaa luokattomaksi. Ja kun vielä Suomeksi tietoa löytyi niin pitää kiittää tonneittain! :)

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta