Radionica OpenGl Shaders

U ponedeljak 17. Jula u Krovu održaću radionicu programiranja OpenGl shader-a.

“Šejderi” su programi koji se izvršavaju na grafičkoj jedinici (GPU) i omogućavaju efikasno kreiranje slike. Sve moderne igrice koriste šejdere kao osnovu renderovanja.

OpenGl Shading Language (GLSL) je jezik za programiranje šejdera, i najpopularniji je jezik za ovu vrstu programiranja. Sintaksa jezika liči na sintaksu C jezika (ali za razliku od C jezika nema pokazivače, niti bilo kakvo napredno upravljanje memorijom itd…). Svi koji imaju iskustva sa programiranjem (C, Python, JS, …) lako će se snaći u GLSL-u.

Karakteristično za programiranje šejdera je velika upotreba naprednijih matematičkih koncepata, ali u ovom predavanju će količina matematike biti svedena na apsolutni minimum. Ipak, dobro je da se poznaju neke osnove analitičke geometrije (ovaj video je sasvim dovoljan uvod).

Na ovoj radionici neće biti prikazano “tradicionalno” OpenGl programiranje (pošto se o tome može govoriti danima), već nešto mnogo jednostavnije (i po meni zanimljivije). Osim GLSL-a, nećemo pisati drugi jezik (npr. C++ koji se često koristi paralelno sa GLSL-om), niti ćemo pisati vertex i geometry šejdere. Pisaćemo samo fragment šejdere, i pokušaćemo da kroz fragment šejder implementiramo raymarching algoritam. Za programiranje šejdera koristićemo ovaj editor.

Neke zanimljive primere onoga što je moguće postići samo sa fragment šejderom, možete videti u ovoj galeriji. Naravno, mi ćemo napraviti nešto skromnije.

Radionica će trajati maksimalno dva sata. Sve što vam je potrebno je laptop sa pristupom za internet, i pretraživačem koji podržava gore linkovani šejder editor

Ako vidite poruku It does not appear your computer can support WebGL u gornjem desnom uglu, to znači da vaš pretraživač iz nekog razloga ne podržava WebGl (verovatno zbog drajvera). Probajte da otvorite stranicu stranicu sa drugim pretraživačem, npr. Firefox-om ako koristite Chrome. Svi moderni pretraživači bi trebalo da rade bez problema, ali se na Linux sistemima sa Nvidia grafikom ponekad dođe do problema.

Ako vas je prethodni opis zaplašio, ne brinite. Cilj radionice je da se pre svega zabavimo.

5 Likes

A jel ce biti kasnije, na nekoj drugoj radionici, reci o vertex i geometry sejderima ?

Nisam planirao o time da pričam. Te stvari zahtevaju rad sa OpenGl API-jem, što po meni nije baš zanimljivo.

1 Like

da ali bas to se trazi u gameing-IDEs. Nemas mozda u kombinaciji Unril ili Juniti (Unreal or Unity3d) za vertex i geometry sejderima neki tutorial za radionicu takvu ?

Nesto kao ovo :

(samo kao idea)

Pa dobro, najcesce u Decentrali nemamo ono sto industrija trazi nego sto je nama zanimljivo. Ali naravno nesto sto nekom nije, nekom drugom moze biti zanimljivo.

Aha, mislio sam da moze kao i fragmentni da bude jednostavno. Kontam da su vise za ubacivanje nekih modela itd. U svakom slucaju jako mi je drago da ce biit nesto ovaok vizualno pokazano :smiley:

Iskreno, ne petljam se sa ostalim šejderima (pisao sam ranije verteks šejdere). Razlog tome je što su fragment šejderi dovoljni za ono što mene interesuje.

Problem sa ovakvim predavanjem je što nameštanje OpenGl pipeline sa barem dva šejdera, zahteva berem 50 linija čistog boilerplate koda. Mislim da nikome nije to interesantno. U 50 linija fragment šejdera možeš čuda da napraviš. Evo nešto što ćemo mi napraviti na radionici (možda nećemo sve detalje da stignemo):

Kod
#ifdef GL_ES
precision highp float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

#define EPS 0.01

const vec3 LIGHT = vec3(1.0, 5.0, 0.0);

vec3 sphereCenter() {
    return vec3(0.0, pow(abs(sin(1.*u_time)), 0.7), 0.0);
}

vec3 sphereColor(vec3 pos) {
    vec3 center = sphereCenter();
    vec3 normal = normalize(center - pos);
    float albedo = smoothstep(0.2, 1.0, 1. - acos(dot(normal, normalize(pos - LIGHT))) / 3.141);
    return albedo*vec3(0.5,0.1,0.1);
}

float sphereSDF(vec3 pos) {
    return length(pos - sphereCenter()) - 1.0;
}

float floorSDF(vec3 pos) {
    return pos.y + 1.;
}

vec3 iterate2(vec3 origin, vec3 ray){
    vec3 color = vec3(0.1,1.0,0.1);
    float distance = 0.0;
    float d = 0.0;
    vec3 position = origin;
    for(float i = 0.0; i < 200.0; i++) {
        float d = sphereSDF(position);
        int object = 0;
        
        if(d <= EPS) { 
               return sphereColor(position);
        }
        
        if(d > 10.0) {
            break;
        }
        distance += d;
    	position = origin + distance*ray;
    }
    
    return vec3(ray.y/5.0 + 0.1, ray.y/5.0 + 0.2, ray.y/3.0 + 0.5);
}

vec3 floorColor(vec3 pos, vec3 ray) {
    vec3 color;
    if(mod(floor(pos.x + u_time/2.0) + floor(pos.z), 2.0) < 1.0) {
        color = vec3(0.7, 0.7, 0.7);
    } else {
        color =  vec3(0.277,0.272,0.300);
    }
    
    vec3 reflected = reflect(ray, vec3(0.,1.0,0.));
	vec3 reflectColor = pow(iterate2(pos, reflected), vec3(2.0));
    
    vec3 lightDirection = normalize(LIGHT - pos);
    
    float distance = 0.;
    float minDistance = 1000.;
    for (float i =0.0; i<100.0; i++) {
        distance = sphereSDF(pos);
        minDistance = min(distance, minDistance);

        pos += distance * lightDirection;
    }
    
    minDistance = smoothstep(0.0, 0.4, minDistance)/1. + 0.5;
    
    return 0.9*minDistance*color + 0.3*reflectColor;
}

vec3 iterate(vec3 origin, vec3 ray){
    vec3 color = vec3(0.1,1.0,0.1);
    float distance = 0.0;
    float d = 0.0;
    vec3 position = origin;
    for(float i = 0.0; i < 100.0; i++) {
        float a1 = sphereSDF(position);
    	float a2 = floorSDF(position);
        int object = 0;
        
    	if(a2 <= a1) {
    		object = 2;
            d = a2;
    	} else {
        	object = 1;
            d = a1;
    	}
        
        if(d <= EPS) { 
            if(object == 1) {
               return sphereColor(position);
            } else {
               return floorColor(position, ray);
            }
        }
        
        if(d >= 20.0){
            break;
        }
        
        distance += d;
    	position = origin + distance*ray;
    }
    
    return vec3(ray.y/5.0 + 0.3, ray.y/5.0 + 0.4, ray.y/3.0 + 0.6);
}

void main() {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;
    st.x *= u_resolution.x/u_resolution.y;
	st = 2.0 * st - vec2(1.0); 
    
    float t = u_time/10.0; 
    
    vec3 up = vec3(0., 1.0, 0.);
    vec3 pos = vec3(cos(t), 0.0, sin(t));
    vec3 side = cross(pos, up);
    
    gl_FragColor = vec4(0.,1.,0.,1.);
    
    vec3 ray = normalize(-1.0*pos + st.x*side + st.y*up);
    vec3 color = iterate(5.0*pos, ray);

    gl_FragColor = vec4(pow(color, vec3(0.7)), 1.0);
}


Navedeni kod možete da kopipejstujete u editor kojeg ćemo koristiti. Napraviti ovakvu neku scenu sa klasičnim pipline-om zahteva (barem) duplo više koda.

3 Likes

Pozdrav svim šejderolozima :vulcan_salute:

Ostaviću ovde par linkova:

  • An introduction to Shader Art Coding - uvodni klip u programiranje šejdera. Namenjen u potpunosti za početnike. Obrađuje se samo 2D grafika. Svima preporučujem da pogledaju ovo.
  • Ray Marching for Dummies! uvod u rejmaršing algoritam koji smo danas videli na predavanju. Takođe su zanimljivi i nastavci Ray Marching Simple Shapes i RayMarching: Basic Operators
  • Sajt Inigio Quilez-a, na kom možete naći mnogo informacija o programiranju šejdera. Sajt nije pogodan za potpune početnike. Takođe, Quilez je autor sajta Shadertoy na kom možete naći mnoge šejdere koje su drugi autori napravili. Sahder Toy koristi editor koji je sličan onom kom smo mi danas koristili, ali postoje neke male razlike (npr. u imenu nekih promenljivih).

Kao što smo pričali, možemo za mesec dana da održimo radionicu na sličnu temu. Do tada bih jako voleo da vidim vaše šejdere u ovoj temi.

1 Like

Evo još par linkova koje sam spominjao u sredu: