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++
.
Also zuerst hier mal ein Screenshot (man betrachte die blau markierten Teile):

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:
Soweit sollte eigentlich alles im Shader erklärt sein.
Vielleicht noch etwas Code. Vertices + Indices:
Erstellung der Geometrie (aus meiner Engine):
Berechnung des Thresholds für die 2px-Grenze (in mModulus (ja, suboptimale Bennung)):
Berechnung der Frequenz der Linien anhand des Zooms (mFrequency):
Joa, das sollten eigentlich alle relevanten Kerngrössen sein. Bei weiteren benötigten gerne einfach schreiben.
Viele Grüsse und gute Nacht
Cromon
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++

Also zuerst hier mal ein Screenshot (man betrachte die blau markierten Teile):

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