Files |  Tutorials |  Articles |  Links |  Home |  Team |  Forum |  Wiki |  Impressum

Aktuelle Zeit: Do Mär 28, 2024 14:29

Foren-Übersicht » Sonstiges » Projekte
Unbeantwortete Themen | Aktive Themen



Ein neues Thema erstellen Auf das Thema antworten  [ 1 Beitrag ] 
Autor Nachricht
 Betreff des Beitrags: Shader Präprozessorsprache
BeitragVerfasst: Fr Jul 20, 2012 13:43 
Offline
DGL Member
Benutzeravatar

Registriert: Di Dez 03, 2002 22:12
Beiträge: 2105
Wohnort: Vancouver, Canada
Programmiersprache: C++, Python
Hi,

in diesem Thema kam die Frage nach meiner Shader Präprozessorsprache auf:
viewtopic.php?f=2&p=91301

Daher möchte ich die hier kurz etwas näher erläutern.
Ich wusste nicht direkt in welches Unterforum ich es Posten soll.. denn ein "Projekt" in dem Sinne ist es nicht, es ist bereits fertig und wenn überhaupt nur ein kleiner Teil meiner Engine (welche dann das Projekt wäre). Falls jemand einen besseren Ort weiß - bitte verschieben :)


Seit ich mit Shadern programmiere habe ich mir immer neue Lösungen ausgedacht um das ganze möglichst übersichtlich und gut handhabbar zu halten - ich war nie ein Fan davon tausende Shader Dateien für jeden erdenklichen Fall der eintreten könnte zu haben. Daher wuchs über die Zeit ein System welches mir meine Shader sehr dynamisch zusammenbaut, je nach den Gegebenheiten.

So entstand meine Präprozessorsprache welche anfangs im Grunde nicht mehr machte als was mit #ifdef auch möglich wäre - nur in meinen Augen etwas übersichtlicher.

Mit der Zeit kamen dann immer neue Anforderungen, z.B. hat es mich enorm gestört das ich einen Shader für jeweils Forward und Deferred Rendering machen musste, obwohl diese in weiten teilen identisch waren. Oder, ich wollte gern die Möglichkeit haben meine Shader mit Nodes zusammen zu bauen. Ich wollte bei meinem Phong Shader nicht gezwungen sein als diffuse nur eine Textur angeben zu können - sondern auch die Freiheit haben eine fixe Farbe oder auch z.B. komplexere Sachen wie 3 ineinander geblendete Texturen etc.

So entstand dann mein neuestes Material System mit diesen Features:

- Shader werden dynamisch anhand der Anforderungen zusammen gebaut.
- Nur ein Shader für Forward/Deferred und jede andere erdenkliche Renderart erforderlich.
- Als uniform variablen sind beliebig komplexe Node Bäume umsetzbar.
- Weitestgehend Shadersprachen unabhängig. Das gleiche System ist auch für HLSL und CG verwendbar (und im prinzip sogar für RenderMan Shader).
- Sehr leicht zu erweitern um neue Materialien.


Aber nun ein Beispiel:

Code:
  1. ciVertexShader(phong)
  2. {
  3.   varying vec3 viewSpaceNormal;
  4.  
  5.   void main(void)
  6.   {
  7.     gl_Position = gl_Vertex;
  8.     ciPass(skinCluster) {
  9.       gl_Position = ciFunction[applySkinning](gl_Vertex);
  10.     }
  11.  
  12.     viewSpaceNormal = gl_NormalMatrix * gl_Normal;
  13.     ciHasInput(normalMap) {
  14.       ciFunction[normalMappingViewSpace](viewSpaceNormal);
  15.     }
  16.  
  17.     ciCompose();
  18.     gl_Position = gl_ModelViewProjectionMatrix * gl_Position;
  19.     gl_TexCoord[0] = gl_MultiTexCoord0;
  20.     gl_FrontColor = gl_Color;
  21.     gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;
  22.   }
  23. }
  24.  
  25. ciPixelShader(phong)
  26. {
  27.   ciFunctionDestination();
  28.   ciInputDestination();
  29.  
  30.   varying vec3 viewSpaceNormal;
  31.  
  32.   void main(void)
  33.   {
  34.     ciEvaluate(diffuse);
  35.     ciEvaluate(specular);
  36.     ciEvaluate(shininess);
  37.  
  38.     vec3 normal = viewSpaceNormal;
  39.     ciEvaluate(normalMap) {
  40.       normal = ciFunction[normalMapping](normal, normalMap);
  41.     }
  42.  
  43.     ciOutput["normal"] = normal;
  44.     ciOutput["diffuse"] = diffuse.rgb;
  45.     ciOutput["specular"] = specular;
  46.     ciOutput["shininess"] = 1.0 / shininess;
  47.     ciCompose();
  48.   }
  49. }
  50.  
  51. ciShader(phong)
  52. {
  53.   ciVertexShader(phong);
  54.   ciPixelShader(phong);
  55. }


In diesem sehr einfach gehaltenen Phong Shader sehr ihr viele befehle die mit "ci" anfangen, diese gehören zu meiner eigenen Shadersprache.

Da in dem Material System alles in einer Datei stehen kann, werden die Shader mit ciVertexShader, ciPixelShader und ciGeometryShader definiert und benannt. Es gibt in dem Beispiel also einen Vertex Shader und einen Pixel Shader jeweils mit namen "phong".

Da man in der Regel nicht einen einzelnen VertexShader braucht, sondern immer eine kombination aus Vertex- und Pixel Shader (und ggf. noch GeometryShader) definiert man mit ciShader einen zusammen gehörigen Shader. So können sich z.B. mehrere Pixel Shader den gleichen Vertex Shader teilen.

In meinem Programm erzeuge ich einen Shader dann z.B. via:
Code:
  1. materialSystem.createShader("phong");



ciPass ist das gleiche wie ein #ifdef, sofern beim erstellen des Shaders im Material System ein Flag für den jeweiligen Pass gesetzt ist, wird dieser Code abschnitt beim erstellen des Shaders entweder ausgelassen oder berücksichtigt. Er unterstützt auch AND, OR und NOT im Parameter.


ciFunction erwartet in den eckigen Klammern einen Funktions Namen, welche an anderer Stelle definiert ist.

Code:
  1. ciFunction(applySkinning)
  2. {
  3.   attribute vec4 weights;
  4.   uniform mat4 joints[64];
  5.  
  6.   vec4 applySkinning(vec4 baseVertex)
  7.   {
  8.     vec4 result = vec4(0.0, 0.0, 0.0, 0.0);
  9.     for (int i = 0; i < 4; i++)
  10.       result += fract(weights[i]) * joints[int(weights[i])] * baseVertex;
  11.     return result;
  12.   }
  13. }


Der zugehörige Code wird im Shader code an die Stelle geschrieben an der ciFunctionDestination() steht, sollte dies fehlen kommt es einfach an den Anfang.


ciHasInput ist vergleichbar mit ciPass, allerdings doch etwas anders :P
Wie schon erwähnt können in dem Material System alle uniform Variablen beliebige Inputs haben, aber eben auch gar keinen..
Da eine Berechnung z.B. der Normalen fürs Normalmapping keinen Sinn macht wenn man keine NormalMap verwendet, kann man mit ciHasInput überprüfen ob an einem Input überhaupt was steckt.

Auf ciCompose komme ich später zu sprechen.


Damit wäre der Vertex Shader fertig, kommen wir zum Pixel Shader:

Die ersten beiden Zeilen ciFunctionDestination und ciInputDestination geben an, das an diese Stelle der code für später aufgerufene Funktionen und Variablen hin soll.

ciEvaluate sieht auf den ersten blick sehr unspektakulär aus, macht im Hintergrund aber die größte arbeit von allen. Es baut anhand der Nodes welche mit dem jeweiligen eingang verbunden sind den Shader code zusammen.

Dazu werden an andere Stelle Evaluatoren erstellt - Hier ein paar sehr simple:

Code:
  1. ciEvaluator(texRepeat, vec2)
  2. {
  3.   vec2(mod($texCoord.x, $uTo - $uFrom) + $uFrom, mod($texCoord.y, $vTo - $vFrom) + $vFrom)
  4. }
  5.  
  6. ciEvaluator(texture, vec3)
  7. {
  8.   texture2D($texture, $texCoord).rgb
  9. }
  10.  
  11. ciEvaluator(texture, vec2)
  12. {
  13.   texture2D($texture, $texCoord).rg
  14. }
  15.  
  16. ciEvaluator(texture, float)
  17. {
  18.   texture2D($texture, $texCoord).r
  19. }
  20.  
  21. ciEvaluator(texture)
  22. {
  23.   texture2D($texture, $texCoord)
  24. }
  25.  
  26. ciEvaluator(normalMap)
  27. {
  28.   (texture2D($normalMap, $texCoord).rgb * 2.0 - 1.0)
  29. }
  30.  
  31. ciEvaluator(multiply)
  32. {
  33.   ($inputA * $inputB)
  34. }
  35.  
  36. ciEvaluator(add)
  37. {
  38.   ($inputA + $inputB)
  39. }
  40.  
  41. ciEvaluator(subtract)
  42. {
  43.   ($inputA - $inputB)
  44. }
  45.  
  46. ciEvaluator(divide)
  47. {
  48.   ($inputA / $inputB)
  49. }
  50.  
  51. ciInternal(MultiTexCoord0) {
  52.   vec2(gl_TexCoord[0])
  53. }
  54.  
  55. ciInternal(MultiTexCoord1) {
  56.   vec2(gl_TexCoord[1])
  57. }
  58.  
  59. ciInternal(MultiTexCoord2) {
  60.   vec2(gl_TexCoord[2])
  61. }
  62.  
  63. ciInternal(MultiTexCoord3) {
  64.   vec2(gl_TexCoord[3])
  65. }


Wenn mein Diffuse nun z.B. aus zwei miteinander multiplizierten texturen bestehen soll, würde ich eine Multiply-Node erstellen und an diese meine zwei Texture-Nodes verbinden.
Die Texture Nodes haben zusätzlichz noch ihr UV-Set als Input, standardmäßig MultiTexCoord0, kann aber auch ein beliebig anderes sein.


ciEvaluate würde daraus dann folgenden Code machen:

Code:
  1. vec3 DIFFUSE = (texture2D(INPUT_TEXTURE_1, vec2(gl_TexCoord[0])).rgb * texture2D(INPUT_TEXTURE_2, vec2(gl_TexCoord[0])).rgb)


Zudem würde es für INPUT_TEXTURE_1 und INPUT_TEXTURE_2 die uniform Variablen anlegen.
Das ganze kann beliebig komplex werden und unterstützt neben variablen auch konstanten.

Im Programm würde obiges Beispiel so aussehen:

Code:
  1. CIPhongMaterial phong;
  2. phong.setDiffuse(new CIMEMultiply(new CIMETextureVec4(new CITexture("./textureA.png"), new CIMETextureVec4(new CITexture("./textureB.png")));


Oder, wenn ich ein anderes UV Set für Texture B brauche:
Code:
  1. phong.setDiffuse(new CIMEMultiply(new CIMETextureVec4(new CITexture("./textureA.png"), new CIMETextureVec4(new CITexture("./textureB.png", new CIMEInternalVec2("MultiTexCoord3"))));


Oder, wenn ich Textur A mit der Farbe rot multiplizieren wollte:
Code:
  1. phong.setDiffuse(new CIMEMultiply(new CIMETextureVec4(new CITexture("./textureA.png")), CIColor3f(1.0f, 0.0f, 0.0f)));


Die Eingänge sind alle sehr variabel gehalten wie ihr seht.


ciOutput[] ist das Herzstück um den Shader sowohl für Forward als auch Deferred Rendering zu nutzen. Alles was man an Outputs definiert kann man im ciCompose-Part benutzen, so sieht z.B. der Compose-Shader für einen Standard Deferred Renderer sehr simpel aus:

Code:
  1. ciComposer(MultiRenderTarget4)
  2. {
  3.   ciDefaultOutput[normal] = vec3(0.0, 1.0, 0.0);
  4.   ciDefaultOutput[diffuse] = vec3(1.0, 1.0, 1.0);
  5.   ciDefaultOutput[specular] = vec3(0.0, 0.0, 0.0);
  6.   ciDefaultOutput[shininess] = 0.0;
  7.   ciDefaultOutput[light] = vec3(0.0, 0.0, 0.0);
  8.  
  9.   gl_FragData[0] = vec4((normalize(ciOutput[normal]) + 1.0) * 0.5, 1.0);
  10.   gl_FragData[1] = vec4(ciOutput[diffuse], 1.0);
  11.   gl_FragData[2] = vec4(ciOutput[specular], ciOutput[shininess]);
  12.   gl_FragData[3] = vec4(ciOutput[light], 1.0);
  13. }


Wohingegen im Forward Composer die Outputs genutzt werden um direkt die Beleuchtung etc zu berechnen.

Um z.B. nur den diffuse Output sich anzeigen zu lassen, könnte man folgenden Composer nehmen:

Code:
  1. ciComposer(DiffuseOnly)
  2. {
  3.   ciDefaultOutput[diffuse] = vec3(1.0, 1.0, 1.0);
  4.   gl_FragColor = vec4(ciOutput[diffuse], 1.0);
  5. }



Zusätzlich gibt es noch ein paar andere Befehle, welche im Phong Shader oben keine Verwendung fanden:

ciInclude
Identisch zu einem #include unter C.
Das Material System möchte nur eine einzige Datei haben, um das ganze übersichtlich zu halten gibt es ciInclude.
Die Datei die ich meinem MaterialSystem geben sieht daher z.B. so aus:

Code:
  1. ciInclude("./functions.cisl");
  2. ciInclude("./evaluators.cisl");
  3.  
  4. ciInclude("./normalDebug.cisl");
  5.  
  6. ciInclude("./sky.cisl");
  7. ciInclude("./phong.cisl");
  8. ciInclude("./line.cisl");
  9. ciInclude("./flat.cisl");
  10. ciInclude("./lambert.cisl");
  11. ciInclude("./terrain.cisl");
  12. ciInclude("./ocean.cisl");
  13. //....
  14.  
  15. ciInclude("./lighting.cisl");
  16. ciInclude("./deferred.cisl");
  17. ciInclude("./forward.cisl");


ciAddInput
Sollte eine Funktion oder ein Evaluator zusätzliche Varying/Uniform/Attribute Variablen benötigen kann sie diese auf diese Weise dem Code hinzufügen an der dafür vorgesehenen Stelle.

ciType
Um unabhängig von GLSL zu sein definiert man hiermit wie die internen bezeichner sind und wie sie genutzt werden.

ci*Maker
Es gibt diverse "Maker", z.B. ciEvalMaker, welcher definiert wie die Syntax aussieht:
Code:
  1. ciEvalMaker() {
  2.   $type $name = $value;
  3. }


Das waren soweit die Wichtigsten - bisher bin ich damit noch an keine Grenzen gestoßen, aber selbst wenn das passieren sollte ist alles sehr einfach erweiterbar.

Als letzten Punkt noch kurz zum Debugging. In meinen Früheren versionen war das Debuggen immer sehr nervig. Denn wenn der compiler sagt in zeile 63 ist ein fehler - woher weiß ich dann bitte wo Zeile 63 ist?! Meine Lösung besteht in diesem Fall nicht darin die Zeilen Nummern umzurechnen, sondern ich baue den Shader so zusammen das er korrekte Zeilenumbrüche und Einrückungen hat. So kann ich mir im Fall eines fehlers einfach den Shadercode ausgeben lassen - da dieser dann sehr gut strukturiert ist fällt es sehr leicht die entsprechende Stelle in meinem Code zu finden.


Vieleicht hat ja der ein oder andere ein paar Inspirationen bekommen, würde mich freuen :)
Leider ist das ganze kein Tutorial. Auch mit fertigem Code kann ich nicht dienen - das ganze ist sehr auf meine Engine abgestimmt (mit umfangreicheren dingen wie CIColor, CITexture, CIBitmap etc.., aber auch simple dinge wie die String klasse etc).

Es sollte lediglich als inspiration dienen :)


Liebe Grüße,
Aya

PS: Falls es interessiert, das ganze ist in C++ geschrieben.


Nach oben
 Profil  
Mit Zitat antworten  
Beiträge der letzten Zeit anzeigen:  Sortiere nach  
Ein neues Thema erstellen Auf das Thema antworten  [ 1 Beitrag ] 
Foren-Übersicht » Sonstiges » Projekte


Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 23 Gäste


Du darfst keine neuen Themen in diesem Forum erstellen.
Du darfst keine Antworten zu Themen in diesem Forum erstellen.
Du darfst deine Beiträge in diesem Forum nicht ändern.
Du darfst deine Beiträge in diesem Forum nicht löschen.
Du darfst keine Dateianhänge in diesem Forum erstellen.

Suche nach:
Gehe zu:  
cron
  Powered by phpBB® Forum Software © phpBB Group
Deutsche Übersetzung durch phpBB.de
[ Time : 0.049s | 17 Queries | GZIP : On ]