prev up next

Implementierung und Probleme

Bei der Implementierung sind dann des öfteren Diskusssionen um Punkte aus Heckers Artikel entbrannt, diese Punkte werden wir hier auch mit ansprechen. Wir sind erst einmal sehr einfach gestartet und haben uns einen bereits fertigen Renderer aus der Vorlesung genommen, der bereits Clipping und Backfaceculling beherrscht, und nur die renderFace() - Methode durch unseren Code ersetzt und dafür auch noch ein paar neue kleine Methoden eingefügt. Als Texture2D kam die Klasse von Ansgar und Ewgeni zum Einsatz.


Die Projektion.
Die erste Diskussion entstand dann um die Frage 'Von welchem System projeziert Hecker ins DC', oder, da er ja andersherum arbeitet, wohin projeziert er. Zuerst gingen alle vom WC aus, aber das passte nicht wirklich gut. Nach einiger Diskussion sind wir jetzt der Ansicht, dass es eine Zwischenstufe zwischen dem VRC und dem NPC sein muss, da: 1. Der PRP im Ursprung liegt, 2. Der Betrachter in z-Richtung schaut, es also nur positive z-Werte gibt. Diese Konstellation hat man, nachdem das Koordinatensystem transformiert wurde, um alles vom VRC ins NPC zu bringen. In unserer Implementierung haben wir uns diese Koordinaten simuliert, indem wir die x- und y-Werte aus dem VRC genommen haben und die z-Werte aus dem VRC um -PRP translatiert, an der xy-Ebene gespiegelt und dann durch die FPD geteilt haben, um fuer die Frontplane einen z-Wert von 1 zu bekommen. So haben wir Heckers Projektionsumgebung simuliert.



    float z0, z1, z2;  // 1/(z-zprp)-komponente aus dem vrc
    Point4f p = new Point4f();

    float zprp;                // z-koordinate vom prp im vrc

    view.cam.WC2VRC.multiply(view.cam.prp, p);
    zprp = p.z;

    view.cam.WC2VRC.multiply(v0.wc, p); // 1/z fuer p0 und v0
    z0 = -1/((p.z-zprp)/view.cam.fpd);

    view.cam.WC2VRC.multiply(v1.wc, p); // 1/z fuer p1 und v1
    z1 = -1/((p.z-zprp)/view.cam.fpd);

    view.cam.WC2VRC.multiply(v2.wc, p); // 1/z fuer p2 und v2
    z2 = -1/((p.z-zprp)/view.cam.fpd);



Die Gradienten.
Zuerst haben wir probiert, die Gradienten fuer die Interpolation von u/z, v/z und 1/z ueber eine eigene Methode mit einigen if-Bedingungen zu bestimmen. Da diese jedoch mit der Zeit immer länger wurde, begannen wir zu überlegen, ob die Formeln von Hecker fuer die Gradienten allgemein fuer jedes Dreieck auf dem Bildschirm gelten, auch für die Sonderfälle mit waagerechten und senkrechten Kanten. Nach einigem Nachrechnen fuer verschiedene Dreiecke kamen wir zu dem Schluss, dass dieses wohl so sein muesse. Darauf deutet auch Heckers Code hin, wo er diese Gradienten auch für jedes Polygon mit der gleichen Formel berechnet.



    float xu, xv, yu, yv, xz, yz;   // u/z, v/z und 1/z in Abhaengigkeit 
                                    // von x und y 
    float durchx, durchy;  // dx und dy

    durchx = 1/((p1.x-p2.x)*(p0.y-p2.y)-(p0.x-p2.x)*(p1.y-p2.y));
    durchy = 1/((p0.x-p2.x)*(p1.y-p2.y)-(p1.x-p2.x)*(p0.y-p2.y));

    xu = ((v1.tc.u*z1-v2.tc.u*z2)*(p0.y-p2.y)
           -(v0.tc.u*z0-v2.tc.u*z2)*(p1.y-p2.y))*durchx;
    xv = ((v1.tc.v*z1-v2.tc.v*z2)*(p0.y-p2.y)
           -(v0.tc.v*z0-v2.tc.v*z2)*(p1.y-p2.y))*durchx;
    xz = ((z1-z2)*(p0.y-p2.y)-(z0-z2)*(p1.y-p2.y))*durchx;

    yu = ((v1.tc.u*z1-v2.tc.u*z2)*(p0.x-p2.x)
           -(v0.tc.u*z0-v2.tc.u*z2)*(p1.x-p2.x))*durchy;
    yv = ((v1.tc.v*z1-v2.tc.v*z2)*(p0.x-p2.x)
           -(v0.tc.v*z0-v2.tc.v*z2)*(p1.x-p2.x))*durchy;
    yz = ((z1-z2)*(p0.x-p2.x)-(z0-z2)*(p1.x-p2.x))*durchy;



Fill Convention und Ceiling-Funktion.
Dann ging es um die Fill Convention und die Ceiling-Funktion. Hier stellte sich vor allem die Frage: wie am besten und schnellsten implementieren? Am Ende waren wir uns dann einig, dass die jetzige Version um einiges schneller ist, als die mit Modulo und als die der Java-API.



  private int ceil(float f) {

  if(f >= 0)
    if(((int)f - f) == 0)
      return ((int)f);
    else
      return ((int)f+1);
  else
    return (int)f;
  }



Desweiteren mussten wir uns dann um die Fill Convention kümmern, d.h. welches Pixel gehört zu welchem Polygon, so dass sich Polygone nicht ueberlappen, aber auch keine Lücken zwischen ihnen entstehen. Wenn man also bei einem Polygon beginnt mit der Scanline durchzurennen, so hat man Fließkommawerte, die Pixel aber haben Integerwerte. Man muss also eine Regel festlegen, welche Pixel man für diese Fließkommawerte setzt. Hecker benutzt in seinem Mapper eine Top-Left-Policy. Er gibt jedem Pixel einen Mittelpunkt und eine Ausdehnung von 0.5 in jede Richtung. Dabei setzt er jedes Pixel, welches sich mit seinem Zentrum innerhalb des Polygons befindet, dazu alle, die mit diesem auf der linken oder oberen Kante liegen. Die Pixel, die auf der Rechten und unteren Kante liegen, gehören nicht zu diesem Polygon, sondern zum nächsten. So werden Risse zwischen und Überlappungen von Polygonen vermieden.





Prestepping.
Die gleichen Überlegungen muss man auch für die Texturkoordinaten machen: Welches Pixel der Textur nehme ich? Dort wird dann der Begriff des Prestepping benutzt. Das bedeutet, wenn man von der Polygonkante auf seiner Scanline zum ersten Pixel wandert, müssen sich dementsprechend auch die u- und v-Koordinaten ändern, auch fuer den Schritt in y-Richtung auf die erste Scanline muss dies geschehen. Es werden also hierbei in einem Prestep die u/z-, v/z- und 1/z-Werte auf die aktuellen x-, y- und z-Koordinaten angepasst. Damit soll auch sichergestellt werden, dass die Textur während z. B. der Drehung einer Kamera drumherum nicht flackert.


Probleme sind hauptsächlich im Bereich Fill Convention und Prestepping aufgetreten, insbesondere in letzterem. Diese Probleme wurden auch bis jetzt nicht alle gelöst, und somit zeigt der Mapper schwarze Kanten am Rand.


Dann haben wir noch einmal versucht, den Code von Hecker auf Java zu portieren, aber auch hier ist uns vermutlich irgendwo ein Fehler unterlaufen, denn auch dieser Mapper arbeitet noch nicht korrekt.


Diesen Problemm haben wir beseitigen, dem, das wir den Code an der Stellen:

   if(bottomCompare > middleCompare) {
      middleIsLeft = 0;
      pLeft  = topToBottom;
      pRight = topToMiddle;
    }
und
if(width > 0 && width < cgc.WIDTH) {
      while(width-- > 0) {
        float z = 1/oneOverZ;
        int u = (int)(uOverZ*z);
        int v = (int)(vOverZ*z);
korregiert haben.
Damit haben wir perspektivisch korrektes Texture Mapping erreicht.



prev up next