Vår første shader

Vår første shader

Thu 31 December 2009

Denne artikkelen vil forklare kort hva vi trenger for å få en glsl shader til å fungere. Vi vil ikke gå igjennom oppsett av OpenGL og miljø rundt. Vi bruker Java og LWJGL som språk, men syntaksen er tilnærmet lik for C++ og andre OpenGL bindinger.

En shader er et program som kjøres på en prosessor. Vi kan tenke på det som en modul til hovedprogrammet vårt.

  1. Last shaderkode inn i en ByteBuffer. D.v.s les shaderkoden fra tekstfil og lagre den.
  2. Opprett et tomt shaderobjekt.
  3. Koble shaderkoden til et tomt shaderobjekt.
  4. Kompiler shaderobjektet.
  5. Lag et shaderprogram. Dette er ofte et par bestående av vertex og fragment shader.
  6. Koble de kompilerte shaderobjektene til programmet.
  7. Link programmet.
  8. Valider shaderprogram.
  9. Bind shaderprogrammet.
  10. Skriv ut eventuell logginformasjon.

Det var en kort liste over det vi skal gå igjennom i denne artikkelen.

Et enkelt shadereksempel

Før vi begynner anbefaler vi deg å laste ned et shaderpar å prøve med. Du kan eventuelt bruke paret med diffuseshadere som er under. Klipp ut og lagre som plain-text i henholdsvis "diffuse.vert" og "diffuse.frag".

Diffuse Vertexshader Diffuse Fragmentshader
void main()
{
vec3 normal, lightDir;
vec4 diffuse, ambient, globalAmbient;
float NdotL;

normal = normalize(gl_NormalMatrix * gl_Normal);
lightDir = normalize(vec3(gl_LightSource[0].position));
NdotL = max(dot(normal, lightDir), 0.0);
diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;

/* Compute the ambient and globalAmbient terms */
ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
globalAmbient = gl_LightModel.ambient * gl_FrontMaterial.ambient;

gl_FrontColor = NdotL * diffuse + globalAmbient + ambient;

gl_Position = ftransform();
}
void main()
{
gl_FragColor = gl_Color;
}

Koden som binder det hele sammen

1: Last shaderkode inn i bytebuffer. Merk at vi bruker samme navnet på shaderene, men endrer .extension til å reflektere om det er vertex eller fragment shader det er snakk om. På denne måten trenger vi kun å endre en streng for å laste inn et nytt shaderpar.

{code class="brush:bash"}

//Shadersource filename. Note that name is same, but extension is different.
String shaderName = "diffuse";
String vertexFilename = "" + shaderName + ".vert";
String fragmentFilename = "" + shaderName + ".frag";

{code class="brush:bash"}

//Create empty buffers. One for each shadertype.
ByteBuffer vertBuffer = ShaderUtilities.getProgramCode(vertexFilename);
ByteBuffer fragBuffer = ShaderUtilities.getProgramCode(fragmentFilename);

2: Vi oppretter tomme shaderobjekt som vi skal binde shaderene våre til. Dette er akkurat som teksturID'er som vi kjenner fra fixedfunction pipeline på den måten at vi bruker heltalls objektID for manipulering og referering.

{code class="brush:bash"}

//Create empty shader objects. One for each shadertype
int vertexShader = ARBShaderObjects.glCreateShaderObjectARB(ARBVertexShader.GL_VERTEX_SHADER_ARB);
int fragmentShader = ARBShaderObjects.glCreateShaderObjectARB(ARBFragmentShader.GL_FRAGMENT_SHADER_ARB);

3: Koble shaderkilden til shaderobjektene vi nettopp opprettet.

{code class="brush:bash"}

//Attach shader source to the shader objects.
ARBShaderObjects.glShaderSourceARB(vertexShader, vertBuffer);
ARBShaderObjects.glShaderSourceARB(fragmentShader, fragBuffer);

4: Kompiler hele stasen.

{code class="brush:bash"}

//Compile the shader objects.
ARBShaderObjects.glCompileShaderARB(vertexShader);
ARBShaderObjects.glCompileShaderARB(fragmentShader);

5: Vi lager et program som er kjørbart på prosessorene. Dette består ofte av et shaderpar, men det trenger ikke være par. Dersom en utelukker f.eks vertexshader vil fixedfunction ta over og utføre de operasjonen, mens fragmentprosessor vil kjøre fragmentshaderen, og vice versa om en utelukker fragmentshader.

{code class="brush:bash"}

//Create a program that can run on the processors.
int programObject = ARBShaderObjects.glCreateProgramObjectARB();

6: Koble den kompilerte shaderkoden til programmet vårt.

{code class="brush:bash"}

//Attach the compiled shader objects to the program.
ARBShaderObjects.glAttachObjectARB(programObject, vertexShader);
ARBShaderObjects.glAttachObjectARB(programObject, fragmentShader);

7: Link programmet. Under linkingen vil uniformer, attributer o.l. bli behandlet.

{code class="brush:bash"}

//Link the program.
ARBShaderObjects.glLinkProgramARB(programObject);

8: Valider programmet. Dette er et godt tips, hvertfall så lenge vi er i utviklingsfasen. Dette steget kan fjernes når en vet at alt fungerer som det skal.

{code class="brush:bash"}

//Validate the program.
ARBShaderObjects.glValidateProgramARB(programObject);

9: Bruk programmet. Det vil si at vi binder det til pipelinen. Alt som blir rendret heretter vil bruke programmet vårt. Når vi er ferdig med programmet kan vi binde et nytt program, eller binde '0' for å gå tilbake til fixedfunction pipeline.

{code class="brush:bash"}

//Use the program. That is, bind it to the pipeline.
ARBShaderObjects.glUseProgramObjectARB(programObject);

10: Bruk denne for å skrive ut logginformasjon. Dette kan gjøres hvor som helst i trinnene over så lenge du har en gyldig objektID.

{code class="brush:bash"}

//Print out loginformation if any.
ShaderUtilities.printLogInfo(vertexShader);
ShaderUtilities.printLogInfo(fragmentShader);

Inspirasjon

Alle bilder er hentet fra "3Dshaders.com"

{gallery}custom_articles/opengl/glsl create/galleri{/gallery}

Tagged as : opengl cuda