[WebGL/Dart] Fragmente in Linien

Cromon

Erfahrenes Mitglied
Hallo zusammen

Ja, es handelt sich um ein Webprojekt. Ja, die Sprache ist Dart und nicht C++, aber OpenGL ist und bleibt für mich ein Kerngebiet von C++ und die ganzen Funktionen aus dart:web_gl werden 1:1 in native Calls umgewandelt, also von daher ist es nur eine Abstraktionsebene über C++ :p.

Also zuerst hier mal ein Screenshot (man betrachte die blau markierten Teile):
ss (2014-02-05 at 12.42.17).jpg

Wie man sehen kann finden da gewisse unschöne Übergänge auf gewissen Linien statt. Die Linien werden im Pixelshader generiert und zwar auf Basis der Position im Viewport. Ich übermittle die Weltkoordinaten des Mittelpunktes des Viewports sowie die gewünschten Abstände zwischen den Linien an den Shader. Zudem erhält er die Info in welche Richtung der Viewport orientiert ist in Form von zwei normalisierten Vektoren, einer zeigt in die Richtung die im Viewport nach oben zeigt, der andere diejenige, die im Viewport nach rechts zeigt.

Für das Rendering wird eine orthographische Projektion verwendet deren w/h identisch sind mit der Grösse des jeweiligen Renderbuffers des Viewports. Abschliessend werden die 4 Texturen der Renderbuffer wiederum in den identischen Grössen ebenfalls orthographisch (w/h entsprechend client-w/client-h des Canvas) auf den Backbuffer gezeichnet.

Der entsprechende Shadercode des Pixelshaders sieht folgendermassen aus:
C:
precision mediump float;

uniform vec2 viewport; // w/h des Viewports in Weltkoordinaten
uniform vec3 rightVector; // Richtung in die die rechte Seite des Viewports geht in Weltkoordinaten
uniform vec3 upVector; // Analog für die Aufwärtsrichtung
uniform vec3 viewportCenter; // Weltkoordinaten des Mittelpunktes des Viewports
uniform vec2 thresholds; // Beinhält die Grösse in Weltkoordinaten die einem Balken der Dicke von 2px entsprechen
uniform float modValue; // Frequenz der Koordinatenlinien

varying vec3 devicePosition; // xyz in device coordinates ([-1, 1])

void main() {
  // die dritte Komponente parallel zur Kamerarichtung ist nicht relevant, die Koordinatenfläche wird in dieser Richtung als unendlich fern betrachtet
  vec3 pixelPos = -(viewport.x / 2.0) * devicePosition.x * rightVector + (viewport.y / 2.0) * devicePosition.y * upVector + viewportCenter;
  
  // Länge der Komponente in Richtung des Rechtsvektors -> Entspricht zum Beispiel bei frontaler Ansicht der x-Koordinate der Weltposition des Pixels. Generell einfach wie weit rechts (oder links) das Pixel in Weltkoordinaten vom Zentrum des Viewports entfernt ist
  float lenx = length(dot(pixelPos, rightVector));
  // idem
  float leny = length(dot(pixelPos, upVector));
  
  float remx = mod(lenx, modValue);
  float remy = mod(leny, modValue);
  
  // step(0.1, step(a, b) + step(c, d)) => if(a < b || c < d) facx = 1.0; else facx = 0.0
  // Beispiel: facx = 1 falls remx < 0.1 oder remx > 0.4 (angenommen modValue = 0.5, thresholds.x = 0.1)
  float facx = step(0.1, step(remx, thresholds.x) + step(modValue - thresholds.x, remx));
  float facy = step(0.1, step(remy, thresholds.y) + step(modValue - thresholds.y, remy));
  
  float step_len_x = step(abs(lenx), thresholds.x); // step_len_x = 1 -> x-koordinate ist maximal thresholds.x vom 0-punkt entfernt
  float step_len_y = step(abs(leny), thresholds.y); // analog
  float fatLine = step(0.1, step_len_x + step_len_y); // dicke grüne/blaue/rote linie wenn eines von beiden erfüllt
  
  vec4 colorRight = abs(rightVector.x) * vec4(1.0, 0.0, 0.0, 1.0) + abs(rightVector.y) * vec4(0.0, 1.0, 0.0, 1.0) + abs(rightVector.z) * vec4(0.0, 0.0, 1.0, 1.0);
  vec4 colorUp = abs(upVector.x) * vec4(1.0, 0.0, 0.0, 1.0) + abs(upVector.y) * vec4(0.0, 1.0, 0.0, 1.0) + abs(upVector.z) * vec4(0.0, 0.0, 1.0, 1.0);
  
  vec4 fatLineColor = colorRight * step_len_y + colorUp * step_len_x;
  
  float cfac = step(0.1, facx + facy);
  vec4 colorDefault = (cfac * vec4(0.0, 0.0, 0.0, 0.7) + (1.0 - cfac) * vec4(0.0, 0.0, 0.0, 0.0));
  vec4 colorFat = (cfac * vec4(0.0, 0.0, 0.0, 1.0) + (1.0 - cfac) * vec4(0.0, 0.0, 0.0, 0.0));
  
  gl_FragColor = fatLine * fatLineColor + (1.0 - fatLine) * colorDefault;
}

Soweit sollte eigentlich alles im Shader erklärt sein.

Vielleicht noch etwas Code. Vertices + Indices:
C++:
    mVBuffer.setDataTyped(new Float32List.fromList([ -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0 ])); // x, y, z, x, y, z, ...
    mIBuffer.setDataTyped(new Uint32List.fromList([ 0, 1, 2, 0, 2, 3 ]));

Erstellung der Geometrie (aus meiner Engine):
C++:
    mGeometry..setVertexCount(4)
             ..setTriangleCount(2)
             ..setProgram(ProgramCollection.instance.get("CoordGrid"))
             ..setStride(12)
             ..addNewElement(VertexSemantic.Position, 0, 3)
             ..setVertexBuffer(mVBuffer)
             ..setIndexBuffer(mIBuffer)
             ..finalize();

Berechnung des Thresholds für die 2px-Grenze (in mModulus (ja, suboptimale Bennung)):
C++:
    var curZoom = (mCamera as OrthoCamera).zoom;
    num perPixelX = (curZoom * aspect * 2) / mWidth.toDouble();
    num perPixelY = (curZoom * 2) / mHeight.toDouble();
    
    mModulus.x = perPixelX;
    mModulus.y = perPixelY;

Berechnung der Frequenz der Linien anhand des Zooms (mFrequency):
C++:
  void onScroll(num value) {
    if(!(mCamera is OrthoCamera)) {
      // TODO: Perspectivic zoom
      return;
    }
    
    OrthoCamera camera = mCamera as OrthoCamera;
    var zoom = camera.zoom;
    zoom += value;
    if(zoom < 1.0) {
      zoom = 1.0;
    }
      
    camera.zoom = zoom;
    
    var curZoom = camera.zoom;
    num perPixelX = (curZoom * aspect * 2) / mWidth.toDouble();
    num perPixelY = (curZoom * 2) / mHeight.toDouble();
    
    var modPct = curZoom.floor() * 0.1;
    var diff = (modPct / mFrequency).abs();
    if(diff >= 1.5 || diff <= 0.75) {
      mFrequency = modPct;
    }
    
    mModulus.x = perPixelX;
    mModulus.y = perPixelY;
  }

Joa, das sollten eigentlich alle relevanten Kerngrössen sein. Bei weiteren benötigten gerne einfach schreiben.

Viele Grüsse und gute Nacht
Cromon
 
Hallo Matthias

Ich habe mal im Zustand des 'falschseins' die Werte, die an den Shader übergeben wurden ausgegeben. Es handelt sich dabei um die des Viewports unten links:
6MvRv.jpg

Werde mal schauen, ob ich das da reproduzieren kann.

/Edit:
Falls jemand auch testen möchte:
http://www.shadertoy.com/view/Xd23z3

Viele Grüsse
Cromon
 
Zuletzt bearbeitet:
Hallo Cromon,

die Übergänge in den Gitterlinien kann ich hier nicht reproduzieren. Ich vermute aber, dass rightVector und upVector nicht exakt 0.0 und 1.0 enthalten, sondern leicht davon abweichende Werte.

Dass die Gitterlinien nicht immer exakt 2 Pixel dick sind, ist nachvollziehbar. Es kommt halt darauf an, wie das Gitter gerade gegenüber dem Pixelgitter liegt.

Du kannst den Shader übrigens etwas vereinfachen, wenn du die einzelnen Variablen lenx, leny, remx, remy etc. jeweils paarweise in einen vec2 zusammenfasst.

Grüße
Matthias
 
Hallo Matthias

Mit einigen Anpassungen konnte ich die Effekte allgemein verringern. Die unterbrochenen Linien konnte ich verhindern indem ich anstelle der device coordinates aus dem vertex shader mir diese (wie im Beispiel auf shadertoy) aus gl_FragCoord selber berechne. Indem ich dann gl_FragCoord auf 0/0 übertrage und durch iResolution - 1 teile kann ich die Ungenauigkeiten im Pixelgitter (also wenn die beiden nicht sauber überlappen) minimieren. Zudem mit einer minimalen Vergrösserung des thresholds auf 3 Pixel fällt der Effekt auch allgemein viel weniger ins Gewicht. Nach kleinen anderen Anpassungen bin ich jetzt mit dem Ergebnis ganz zufrieden:
6N781.jpg

Mit dem dazu passenden Shader:
C:
precision mediump float;

uniform vec2 viewport;
uniform vec3 rightVector;
uniform vec3 upVector;
uniform vec3 viewportCenter;
uniform vec2 thresholds;
uniform float modValue;
uniform vec2 iResolution;

void main() {
  vec2 vp = viewport;
  vp.x = viewport.y * iResolution.x / iResolution.y;
  vec2 grid = (vec2(gl_FragCoord.xy) - 0.5) / (iResolution - 1.0);
  vec2 devicePosition = grid * 2.0 - 1.0;
  
  vec3 pixelPos = -(vp.x / 2.0) * devicePosition.x * rightVector + (vp.y / 2.0) * devicePosition.y * upVector + viewportCenter;
  
  float lenx = dot(pixelPos, rightVector);
  float leny = dot(pixelPos, upVector);
  
  float remx = abs(mod(lenx, modValue));
  float remy = abs(mod(leny, modValue));
  
  float facx = step(0.1, step(remx, thresholds.x));
  float facy = step(0.1, step(remy, thresholds.y));
  
  float step_len_x = step(abs(lenx), thresholds.x);
  float step_len_y = step(abs(leny), thresholds.y);
  float fatLine = step(0.1, step_len_x + step_len_y);
  
  vec4 colorRight = abs(rightVector.x) * vec4(1.0, 0.0, 0.0, 1.0) + abs(rightVector.y) * vec4(0.0, 1.0, 0.0, 1.0) + abs(rightVector.z) * vec4(0.0, 0.0, 1.0, 1.0);
  vec4 colorUp = abs(upVector.x) * vec4(1.0, 0.0, 0.0, 1.0) + abs(upVector.y) * vec4(0.0, 1.0, 0.0, 1.0) + abs(upVector.z) * vec4(0.0, 0.0, 1.0, 1.0);
  
  vec4 fatLineColor = colorRight * step_len_y + colorUp * step_len_x;
  
  float cfac = step(0.1, facx + facy);
  vec4 colorDefault = (cfac * vec4(0.0, 0.0, 0.0, 0.7) + (1.0 - cfac) * vec4(1.0, 1.0, 1.0, 0.0));
  
	gl_FragColor = fatLine * fatLineColor + (1.0 - fatLine) * colorDefault;
}

Viele Grüsse
Cromon
 

Neue Beiträge

Zurück