Since the early days when VR was nothing more than two tiny cathode tubes firing their β-rays directly at your eyes, I was a VR enthusiast. Well it wasn’t actually firing directly at your eyes. There was an also tiny screen involved that offered a resolution of no more than 320x240 for most devices.
But what was true for those unquestionable experimental first devices, is still true for today: The acceptance of these devices does not so much depend on the quality of the hardware, but on the availability of content that really makes a difference. If software and especially games do not deliver an experience that you just can’t have at a desktop, then VR still is nothing more than a high price gimmick. Today’s VR headsets already deliver a stunning resolution of above 1600x1600 pixels per eye, in which you can get perfectly lost in what you’re seeing - if what you are experiencing is something that a flat 2D screen (or even your high price 3D monitor that feels more or less like an aquarium) just can’t provide.
Ok, so much for my enthusiasm in this. Good VR content that entertains you for longer than 5 minutes per game is rare and I have a feeling as if the big content makers are waiting for almost everybody to have a VR headset at home to really start working for these devices - totally ignoring the fact, that almost no one will buy a headset if there isn’t anything to do with.
We really need to get started in creating VR content. So i finally stared to try rendering VR content in blender.
While 360° pictures are nice to look at, especially if you want to have the feeling of really standing in some interesting place and being able to look around freely, 360° vision is not that great if you want to visualize something like a 3D model. You’d always have to have something to look at behind you for the rare occurrence, that the user fully turns around. Providing a real 360° vision in 3D is even harder because you can’t just take like four 3D shots of some environment and stitch that together. You’ll get weird 3D stitching artifacts that will break your immersion. This is at least true for real videos that are shot with real lenses. There are ways to create a correct 360° 3D images and videos in blenderscycles renderer, but I’m not going to cover that here for the one reason that motivated me to create VR180 content with blendereevee:
The problem with rendering video content for VR is, that you don’t prepare exactly what the user will see, instead you’ll need to prepare everything that the user may be looking at. The VR180 video format puts some constrains on the users freedom to move his head, that have proven to be acceptable.
You’ll still have the immersion from being able to direct you vision by slightly moving you head, but you’re forced to stay within the 180° that are in front of the camera.
This enables the content creators to really focus on some specific setting on the one hand, on the other hand creators can now really record such scenes with a physical camera, without having to apply some weird hacks to remove themselves and everything else behind the camera from the scene.
Especially you can record such a scene with just two physical or virtual lenses. The fact that you can’t really turn your head 180° while watching such a video quite nicely masks the errors that you’ll get in your 3D projection because the two lenses are always pointing to the front.
This is also true for your eyes. If you could somehow look 90° to the left without turning your head for example, your eyes would line up with the left eye being in front of the right eye, providing no 3D vision at all.
The angle you’re free to move your head is effectively 180° minus the field of vision of your headset. This will be something around 60° for most devices.
So much for the basics - let’s get back to blender
The reason why we probably don’t want to use cycles is that the resolution we’re going to need is laughably high. 4k is already the lowest resolution you’ll probably want to use, because it needs to hold all the 180° of vision per eye. Projecting both eyes into one frame effectively divides that resolution into halves for one direction of your choice, which will typically be horizontal. So we’d chop our field of vision out of the already halved resolution and then reproject it for the eyes.
In the end 4k will leave us with a resolution that is already lower than what could be possibly displayed per eye on today’s devices.
That’s why 5.7k (5760x2880) became the de-facto standard for nowadays VR180 video, as it leaves some space to crop out your field of vision an reproject it for the eyes.
Rendering a resolution that high with a moderate quality setup in blenders cycles typically takes something around a minute on nowadays hardware using the GPU.
Trying to evade that I came up with three setups which all had their pros and cons. Two of them are closely related.
Use eevee’s default camera with 160° fov and reproject that using cycles
180° Mirrors 2.1. Orthogonal Projection 2.2. Classic Camera 90° fov
I will give every setup it’s own headline to dive into the details. You should be familiar with normal 3D rendering, as I am not going to cover this here.
Use eevee’s default camera with 160° fov and reproject that using cycles
What sounds rather odd in the first place, actually works quite well. You just set up your scene as you’d normally do. If you want to render a 2D preview of what you’re going to have as a result, set your camera to 90° fov and test.
If you have a player that supports normal (non VR) side by side video, it’s a good idea to first render one of those classic sbs 3D videos, to make sure your 3D camera settings suit your scene. I’m talking about virtual eye distance and stuff like this (again: you should already know about that).
If you’re satisfied with what you see, widen your field of view to 160° while keeping your 3D settings. This time your output should not be side by side, but one video per eye. I rendered into squares of 4096x4096.
The trick of the hybrid setup is, that we are going to use cycles to do the actual equirectangular projection.
I started with a unit cube. Then I scaled it so that two of its sides took up an angle of 160° viewed from the center.
Now when you put a 360° 2D camera into the center of that volume, it will see two big faces covered by 160° field of view each, with 20° remaining for the small sides.
This adds up to a 360° spherical view.
So all we have to do now, is to take our pre rendered (by eevee) squares and put them on the big faces as a texture. Set the shader to emission and set every render pass or bounce in cycles performance settings to 1 (cycles performance settings). We don’t need light bounces here - every incoming ray of light is exactly that. The incoming ray of light we want. Nothing else.
Set up like that, cycles can be surprisingly fast. Unfortunately it’s kinda unusable for anything besides pre lit scenes this way.
But still we have a setup that is faster than directly rendering the scene with a VR180 3D camera setup in cycles, even though we now have two render passes instead of one.
The downside of this setup is that we do not get a full 180° field of view. You can’t set up a 180° fov camera in eevee. That’s impossible with a rectilinear projection. You wouldn’t be able to place an equirectangular camera inside a helper volume in cycles anymore because it would need to be 0 units thick. There is no room for anything else if you are surrounded by two faces with each of them taking up 180° of your vision.
Effectively this, let’s call it VR160, vision comes close to what most VR180 videos actually are. Even googles own VR180 creator fades out to the borders with something that might be as well 10° of your fov in each direction.
Unfortunately there is another bad thing about the intermediate projection using eevee: A wide angle rectilinear projection of more than 90° greatly overemphasizes things close to the border, while scaling down the things in the center of your projection at the same time. This might be a cool effect for some use cases, but in our case we need to use a resolution high enough to ensure that what is in the center of our projection is still visible with an acceptable amount of detail.
Knowing these unpleasant limitations of eevees camara, I tried to trick it into rendering what I want…
…by the use of mirrors!
Given my scene is divided by a plane (a 360° environment split into halves) and the edge of my mirror lies within that plane, than I want my mirror to reflect everything cut by that plane to the edge of my image, with the rest of one side of the space divided by the plane being projected to the image like a 180° fisheye would do. Having a fisheye projection is actually sufficient for VR180 as it has an option for that. Uploaded to youtube these videos will be silently converted to equirectangular in the background and googles VR180 creator will accept them as input.
The best mirror for this job would probably be something parabolic, but I wanted to spare myself those calculations and went for something that could be calculated with dull trigonometry.
So I went for cutouts of a reflective ball. I tested two types of surfaces. Unfortunately non of both gave perfect results. I guess this has something to do with how the exact reflections at the surface of the ball are modeled. The amount of distortions goes down the higher resolution you sphere is, but I found out that at some point a high amplitude but low frequency distortion seems to be more appealing to the eye than a low amplitude high frequency distortion.
The same thing applies for the two primitive sphere types blender has to offer. There is the ico-sphere consisting of some amount of equally sized triangles and the uv-sphere with is quadric faces of variable size which depends on their distance to the poles.
I ended up using the ico-sphere, because the reflections (and the distortions) at its surface where more appealing to me.
The resulting image does not only depend on the type of mirror in use, but also on the camera. If one requirement of the projection is, that things that lie on the dividing plane are projected to the edge of my image, than effectively a ray at the edge of the image that is projected through the camera must hit the edge of the mirror at an angle that projects it into the dividing plane.
Well let that sink in. This boils down to the fact that every rectilinear camera and even the orthogonal camera is suitable for this purpose, given the mirror fits to the camera.
For the sake of simplicity I started with the orthogonal camera.
The Orthogonal Camera
The orthogonal camera can be modeled as parallel rays of light inside a cuboid volume. There is no such thing as a field of view. I thought this might simplify things.
The first thing I’d need to do is make my camera exactly the size of the mirror, so the mirror fills the cuboid projection volume. Now I need to calculate the angle of the mirror surface at its very edge, so that a parallel light ray from the camera will be reflected into the dividing plane.
That’s an easy one it’s 45°. My mirrorball must be cut in a place where it’s (smaller) angle to the middle axis of my projection will be 45°. Referring to trigonometry that will be cos(45°) = 1/2 * √2 ≅ .70710678 units from it’s center (given a sphere with radius 1).
The rectilinear 90° Camera
Another quite simple case would be a camera with 90° field of view. A camera like that would spin up a cone with at max 45° to it’s middle axis. So a ray at the edge will not hit the mirrors edge parallel to the middle axis, but with exactly 45°. The angle between the incoming ray from the camera and the ray reflected by the mirror into the plane is no longer °90 but 135° (90° + 45°).
As a result the calculations for the required mirror become a little more complicated.
I do not know why this results in exactly half the angle I needed for the ortho case (probably there is an easy explanation).
This time I need to cut the mirror ball cos(22.5°) ≅ .92387953 units from its center.
Conclusion
Each of the methods I tried to get VR180 out of eevee has the potential to replace a real equirectangular (or at least fisheye) camera. The first method still needs an additional cycles pass and both of the mirror setup introduce distortions.
I can’t even tell which one of the latter I like more. Their distortions seem to be quite comparable even though they differ in the details. So if the goal is geometrically correct projection I’d probably still go for the two pass solution.
After I had updated my blog from the kinda outdated Ubuntu 14.04 LTS to 18.04 LTS, I notcied that the OpenSMTP Package is bugged, again. While the installation succeeds and the mail server starts up as expected, it dies (without any errormessage), as soon as a secure connection is made. Systemd doesn’t help here - the mail server is not being restarted. Even if that would be the case, one could only receive mails that come in without any SSL being used during transmission.
This seems to be of little use nowadays and I don’t expect anybody (hopefully) to be willing to use it that way.
Therefore I have built myself a working replacement.
Which version is this?
It’s a modified version of the original package, one might obtain issuing the good old
apt source opensmtpd
Surely I might as well use bzr, but that didn’t matter to me right now.
The author doesn’t build a package there, instead he compiles from scratch directly at the affected server and just runs make install. That leads to the situation, that programs are installed to wrong paths and from there on there will be two versions in the system. You’ll need to activate the new one in systemd by hand. The path to the config file will be different, too. Thus, the usual way of getting an official fix via a normal package update, will be blocked.
Thats why I decided not to follow these instructions and built a full package instead.
After patching with the only file that has to be changed, the package can be build with the usual tools. The next update should now be able to replace the the temporary fix, without any user interaction being required.
Nachdem ich meinen Blog gerade vom etwas in die Jahre gekommenen Ubuntu 14.04 LTS auf 18.04 aktualisiert hatte, musste ich feststellen, dass OpenSMTPD schon wieder als verbuggtes Paket vorliegt (und das in einer LTS). Es lässt sich zwar installieren und der Mailserver startet auch wie vorgesehen, allerdings stürzt er (ohne eine Fehlermeldung) ab, sobald eine verschlüsselte Verbindung aufgebaut werden soll. Da hilft dann auch systemd nicht - der Mailserver wird nicht automatisch neu gestartet. Selbst wenn dem so wäre, könnten nur Mails angenommen werden, wenn bei der Übermittlung kein SSL verwendet wird. Das ist heutzutage wohl alles andere als praktikabel und das möchte ja auch sicherlich (hoffentlich?) Niemand.
Daher habe ich mir einen funktionierenden Ersatz gebaut.
Was für eine Version ist das?
Es handelt sich um eine veränderte Version des Originalpaketes, welches man auf dem altbackenen Weg
apt source opensmtpd
bekommt.
Sicherlich geht das auch mit bzr, das interessierte gerade aber nicht.
Eine Anleitung für den Fix habe ich hier entnommen: OpenSMTPD GitHub
Der Author baut allerdings kein Paket, sondern kompiliert direkt im betroffenen System neu und führt dann make install aus. Das führt allerdings dazu, dass die Programme im falschen Pfad liegen und es fortan zwei Versionen im System gibt, wobei die neue händisch in systemd eingefügt werden muss. Auch der Pfad zur Konfigurationsdatei stimmt nicht mehr. Damit wird der normale Weg, über ein ganz normales Paketupdate irgendwann einen offiziellen Fix zu bekommen, ausgehebelt.
Daher habe ich mich entschieden, nicht direkt diesen Instruktionen zu folgen, sondern stattdessen selbst ein neues Paket zu bauen.
Nach dem Patchen mit der einzigen geänderten Dateil, kann das Paket dann mit den üblichen Tools gebaut werden. Das nächste Update sollte es dann ganz normal wieder überschreiben, ohne dass noch etwas gemacht werden muss.
Die Bilder hier haben derzeit alle ein Breite von 8192 Pixeln. Das ist für viele Mobilgeräte ein Problem.
Mögliche Lösungen wären:
alle Bilder kleiner als 4096x4096 pixel skalieren
dadurch gehen eine menge Details Verloren, die dann auch auf den kräftigeren Rechnern fehlen
irgendwas mit Tiles machen
ein anderes JS Library für den Viewer verwenden
das wiederum gefällt mir auch nicht, da die besseren Viewer um ein vielfaches mehr Abhängigkeiten haben und dies ein minimalistisches Blog bleiben soll (Wordpress ist ja nicht umsonst rausgeflogen)
Damit will ich sagen: Mir sind die Probleme bei der Anzeige dieses Posts auf Mobilgeräten durchaus bekannt. Daran etwas zu ändern würde aber in eine ziemliche Bastelei an den Bildern ausarten und ich müsste obendrein noch drauf achten, dass die Metadaten in den Bildern, die das 360° Panorama erst möglich machen, weiterhin stimmig bleiben. Die Bilder gibt es aber auch alle bei Google zu sehen. Wer mobil unterwegs ist, befindet sich vermutlich sowieso mit einem Bein im Google Universum und stört sich nicht groß an dieser Umleitung.
Worum es geht
Seit einiger Zeit bin ich dabei 360 Panoramas zu stitchen. Ich finde das irgendwie entspannend.
Nebenbei hat es den schönen Effekt, dass ich mit einem set von Bildern wirklich die Stimmung eine Szene einfangen kann. Ich bin gerne draußen unterwegs und so kommt meistens so, dass ich kein wirkliches Motiv habe (wie ein bestimmtes Gebäude, einen Gegenstand, oder igitt eine Person), das ich einfangen will, sondern eher den Eindruck einer ganzen Umgebung.
Hier also zum ersten Werk
An dieses Bild bin ich völlig unvorbereitet ran gegangen. Es entstand am 26.11.2017 bei einsetzender Dämmerung. Das war ein Problem. Aber dazu später mehr. Da ich nicht wusste, welchen Winkel eine einzelne Aufnahme abdecken würde aber gleich eine komplizierte HDR Reihe schießen wollte, brauchte ich am Ende fast 45 Minuten, bis ich alle Bilder hatte die ich meinte zu brauchen. Währenddessen hatten sich die Lichtverhältnisse stark verändert. So ließen sich die Bilder nicht mehr ohne Weiteres in HDR wandeln und auch das Stitching wurde zum Problem. Ich war mit einer viel hilft viel Einstellung an die Sache herangegangen und hatte über acht Einzelaufnahmen mit Belichtungsreihen, die ich kombinieren wollte. Das ist bei einem Blickwinkel von über 140° völliger Unsinn. Die nötige Abschätzung der Objektivparameter ist niemals so exakt, dass die Bilder in allen Bereichen 100% Deckungsgleich werden. Durch die Masse an Bildern und den großen Blickwinkel überlappten sich aber immer bis zu 4 Bilder, die der Stichter einfach nicht übereinander bringen wollte. Erst durch Zufall, als ich die Bilder auf vier Reihen begrenzte, um für einen Test ein schnelleres Resultat zu bekommen wurde es besser.
Seit dem weiß ich: Zu viel Überlappung ist nicht gut.
Weitere Probleme bekam ich noch, weil nicht die Linse im Zentrum der Bilder steht, sondern die Mittelachse des Stativs. Bei nahe stehenden Objekten gibt es dadurch starke Verschiebungen der dadurch verdeckten Objekte im Hintergrund.
Dazu kamen dann noch die Verfärbungen durch die langsam voran schreitende Dämmerung. Es war ein Spaß. Die letzten Bilder schoss ich mehr oder weniger in der Finsternis mit Langzeitbelichtung. Sehen konnte man nichts mehr.
Es wird sicher auffallen, das sowohl am Boden als auch beim Himmel ein gähnendes schwarzes Loch klaffen. In späteren Aufnahmen habe ich zumindest das Problem mit dem Himmel lösen können, da sich eine weitere senkrecht nach oben gerichtete Aufnahme überraschend einfach einfügen lässt. Zusätzlich hilft sie durch Überlappungen von höheren Objekten im Panorama sogar dabei, die verbleibenden Aufnahmen in ihrer Position zu stabilisieren, da sie meist Überlappungen mit allen anderen Bildern hat. Beim Boden ist es ein anderes Problem: Da stehe ich. Da steht das Stativ. Da stehen andere Dinge die ich so bei mir habe. Um da noch ein Bild hin zu bekommen, müsste ich mir die Stelle merken und dann die Kamera mit dem Einbeinstativ möglichst exakt über diese Position halten. Vielleicht probiere ich das irgendwann auch mal.
Auf zum Dom!
Was nimmt man, wenn man den Anspruch hat, etwas abzulichten womit sich die lokalen Betrachter auch identifizieren können? Natürlich den Dom. Vermutlich das meist abgelichtetste Bauwerk in Magdeburg. Aber wie setzt man sich dann noch von all den anderen Aufnahmen ab, wenn vermutlich jeder Magdeburg irgendwo auf dem Telefon noch eigene Bilder vom Dom mit sich rumschleppt? Um mir das stitchen zu erleichten, entschied ich mich für eine Nachtaufnahme, damit möglichst wenig bewegliche Objekte durch den aufzunehmenden Bereich wuseln. Das nebenbei ist bei 360° Aufnahmen, die aus mehreren Bildern bestehen immer ein Problem. Wo man mit normalen Bildern immer noch durch die Wahl des Blickwinkels störende Objekte entfernen kann, ist dieses bei 360° Panoramas nicht mehr möglich. Alles was da ist, ist auch im Bild. Bei den richtigen 360° Kameras, die die ganze Szene in einem Bild aufnehmen auch immer man selbst. Was ich als eingefleischter Selfiefan natürlich so richtig toll finde.
Zurück zum Thema: Ich hetzte also am 30.11.2017 kurz nach Mitternacht zum Dom und versuchte in möglichst kurzer Zeit Aufnahmen für mein Dombild zu bekommen, nicht zuletzt weil es auch ziemlich kalt war. Beim letzten Bild durfte ich dann extra noch ein paar Minuten warten, weil ein junges paar sich dazu entschlossen hatte, nicht nur den Domplatz einmal mit dem Auto umrunden zu müssen, sondern dann auch noch einmal diagonal über den Domplatz wenige Meter an mir vorbei gehen zu müssen. Auch bei diesem Bild habe ich die abschließende Aufnahme des Himmels leider vergessen. Das sollte sich später als besonders ärgerlich erweisen, da sich aufgrund der schieren Größe des Doms der obere Rand nicht mehr grade beschneiden ließ. Hätte ich versucht den Rand so zu beschneiden, dass sich in der Projektion oben wenigstens das unschöne, aber wenigstens runde, schwarze Loch ergibt, hätte ich dem Dom seine Spitze kappen müssen. Daher musste ich höher abschneiden und habe jetzt ein schwarzes Loch mit Spitzen dran. wunderschön
Als nächstes Motiv hatte ich mir die Südspitze des Stadtpark Rothehorn ausgesucht. Auch hierbei hatte ich wieder im Hinterkopf ein Motiv zu wählen, dass die meisten Betrachter kennen würden. Auch hier wiederum ein Motiv, das tagsüber geradezu von Menschen überrannt wird und ohne Menschen deshalb nur des Nachts fotografiert werden kann. Frei nach irgendwas läuft immer schief, hatte ich diesmal zwar zum ersten mal auch den Himmel aufgenommen, so dass man nicht beim Blick nach oben sofort an eine Kante stößt, dafür hatte ich aber die Blende von vorherigen Aufnahmen noch auf 5.6 stehen. Da ich ein rein mechanisches Objektiv verwendete, konnte mich die Kamera nicht vor dieser Dummheit warnen. Auch dass ich für jedes Bild fast 20 Sekunden belichten musste, um bei erträglich niedriger Lichtempfindlichkeit nicht noch mehr Rauschen ins Bild zu bekommen, brachte mich nicht darauf, dass hier etwas nicht simmte. Wenigstens richtig matschig konnte es nicht sein. Dazu war es in dieser Nacht (01.11.2017) erheblich zu kalt und der Boden war gefroren. Wäre mir der Patzer mit der Blende eher aufgefallen, hätte ich vielleicht ein klareres Bild bekommen und die Sterne hätten keine Spuren gezogen. Nur so nebenbei: Sternspuren sind beim Stitchen eine ganz tollte Sache. Besonders wenn sie im nächsten Bild schon an einer ganz anderen Stelle sind.
Dieses Bild entstand auf einer Radtour am 11.03.2018. Wohlige -13°C garantierten mir die Abwesenheit von Menschen. Jedenfalls bis auf die paar wenigen, die am Wochenende mit dem SUV ins Hinterland fahren und dort nach einer Rechtfertigung suchen, warum sie ein derartig unsinniges Kfz erworben haben. Der schwarze Krüger ist vermutlich nicht der ganz korrekte Titel, da damit wohl eher ein unscheinbarer Tümpel zwischen den Bäumen weiter hinten im Bild bezeichnet wird. Ansonsten gibt es in diesem Bild eher nichts interessantes. Mir gefielen einfach die Lichtverhältnisse. Ich musste feststellen, dass diese beim Stitchen eher zu einem Problem werden würden, denn bei halbwegs klarem Wetter lässt es sich bei 360° Panoramas nicht vermeiden, das irgendwo im Bild auch die Sonne zu sehen ist. Diese stand schon recht tief über dem Horizont. Wo wir gerade bei Horizont sind: Es war irgendwie nicht möglich den in diesem Bild halbwegs gerade zu bekommen. Irgendwo war immer eine Krümmung. Möglicherweise ist dem auch einfach so an dem Ort, nur habe ich vermutlich jetzt nicht die richtige Krümmung als Referenz für gerade genommen.
Wenig später, ich wollte gerade noch eine kleine Runde im Kreuzhorst drehen, hatte ich mich ziemlich verfahren. Besser gesagt, der Weg den ich nutze war mitten im Wald einfach zu Ende. Ich hätte jetzt umkehren können und meinen Weg wo anders fortsetzen zu können. Dickköpfig wie ich bin, hob ich lieber das Fahrrad auf den Rücken und stapfte geradeaus weiter durch den Wald. Dabei fand ich diese für mich recht interessante Stelle. Die Abwesenheit von Schnee und Eis täuschten dabei darüber hinweg, dass es hier wohl noch etwas kälter war, als beim vorherigen Bild. Daher war dieser Ort dann auch nur begrenzt einladend für eine Pause. Immerhin reichte die Zeit für ein Bild, bei dem ich erstmalig alles so einstellte, dass ich mit dem Ergebnis am Ende auch selbst zufrieden bin.
In der Zwischenzeit habe ich mir dann doch eine echte 360° Kamera gekauft. Nicht weil ich erwartet hatte, damit bessere Bilder machen zu können (im Gegenteil), sondern weil ich damit auch unterwegs ohne Stativ und Ausrüstung 360° Panoramas aufnehmen kann. Die Photo Spheres die heutige Smartphone Kameras live zusammenstitchen können sind mir dann doch zu fehlerhaft gewesen und so richtig in Unkosten gestürzt habe ich mich dabei auch nicht. Für mich, wie schon erwähnt, ein gewaltiger Nachteil: Man ist immer selbst mit im Bild. Zur Verteidigung der Technik sei gesagt: Die teureren Modelle, die nicht als Smartphone Aufsatz daher kommen, sondern eigene Hardware mitbringen, schaffen dann doch etwas bessere Bilder. Faszinierend an der Technik finde ich dabei, dass die verwendeten Linsen bei den meisten Kameras einen Betrachtungswinkel von über 180° haben. Anders würde es bei nur zwei Linsen keine Überlappungen geben. Prinzipiell wären mehr als zwei Linsen aber schon wünschenswert, da es so einen deutlichen Ring um die Aufnahme gibt, in dem die Überlappungen sich treffen, aber beide Linsen wegen der extremen Optik keine detaillierten Informationen mehr liefern.
Nebenbei: Aus dem selben Grund verwende ich für meine mit der klassischen Kamera aufgenommenen Panoramabilder vier Bilder um ein mal herum zu kommen, statt der drei, mit denen ich rein rechnerisch vom Betrachtungswinkel her problemlos auskommen würde. Jedoch sind die Verzerrungen in den Randbereichen dann so stark, dass das Bild dann eine sehr ungleichmäßige Detailschärfe aufweisen würde.
Detailschärfe gibt es dafür in diesem Bild sowieso nirgendwo. Dafür ist es aber vermutlich das einzige, welches auch auf Mobilgeräten angezeigt werden kann, weil die Auflösung dementsprechend geringer ist.
Mit diesem Bild wollte ich eigentlich den plötzlich aufkommenden Frühling dokumentieren - zu sehen ist allerdings noch nicht besonders viel (25.02.2018). Vielleicht ist das Gras ein bisschen grüner als die Wochen davor. Eine Woche später jedoch hatten die Bäume dann volle Blätter. Da war ich wohl zu früh. Bei diesem Bild kann gut sehen, dass es recht schwierig ist, bei 360° Panoramas einen stimmigen Weißabgleich hin zu bekommen. Irgendwas wirkt immer falsch. Tatsächlich passen unsere Augen und das Gehirn selbst den Abgleich jeh nachdem, wohin wir sehen, so schnell an, dass uns unterschiedliche Lichtfarben selten auffallen. Hat man bei einem Panorama in einem Bereich dagegen stark farbiges Licht, kann man das entweder so lassen, was dann bei unterschiedlichen Zoomstufen zu seltsamen Farbverläufen führt, oder man versucht das zu korrigieren und verwendet global einen halbwegs passenden Weißabgleich. Der Sonnenuntergang in diesem Bild färbte das Bild teilweise sehr Organge, was aber auf der anderen Seite des Bildes gar nicht zu sehen war. Dadurch kommt global ein sehr künstlicher Farbeindruck zu stande.
Der Toolkurator ist eine tickenden Zeitbombe in der Modernen IT - insbesonder im Webfeld ist in fast jeder Firma anzutreffen. Warum diese meist harmlos anmutende Person geradezu toxisch für jedes Unternehmen ist, möchte ich im Folgenden lang und breit erläutern.
TLDR; Gerade heraus: Der Toolkurator ist ein verantwortungsloser Idiot. Dessen ist er sich leider nicht bewusst.
Es gibt ihn in zwei Ausprägungen. Bei einer besteht noch Hoffnung, beim Zweiten ist Hopfen und Malz verloren.
Der Youngster:
Die erste Ausprägung ist ein Anfänger, den man nicht mal als Junior Irgendwas bezeichnen kann. Er ist sich seiner Defizite sehr wohl bewusst und ist motiviert sie möglichst schnell aufzuholen. Anstatt sich aber mit dem was er eigentlich leisten soll zu beschäftigen, sucht er nach dem schnellen Erfolg. Er versucht immer brandaktuell zu sein und ist deshalb bestens über den neuesten Buzz/Hype informiert. Da sein Können nicht ausreicht, um Dinge erfolgreich selbst zu schaffen, ist er ständig auf der Suche nach irgendwelchen Tools, die schnelle Lösungen versprechen (und mit schnell ist hier sicher nicht effizient gemeint). Wenn das Programm nicht so verschrien wäre, würde er Dreamweaver nutzen, um sich bloß nicht mit dem beschäftigen zu müssen, was er eigentlich tun sollte. Brandaktuell zu sein hat bei ihm Methode. Keines seiner Werkzeuge ist älter als zwei Jahre. Garantiert nicht genug Zeit, dass irgendjemand damit nennenswerte Expertise hätte aufbauen können. So liegt die Messlatte um zur Avantgarde zu gehören verheißungsvoll niedrig.
Das Alteisen:
Bei diesem Subjekt handelt es sich nicht wirklich um einen Anfänger. Er hätte locker genügend Zeit gehabt, es in irgend einem Feld zum Senior Irgendwas zu bringen. Es mangelt ihm aber an Selbstvertrauen bzw. Vertrauen in die eigene Arbeit. Da er bei Problemen schnell frustriert ist und aufgibt, hat er eben in seiner langen Arbeitszeit keine nennenswerten Kompetenzen erworben und hängt nun gleich alten Kollegen auffällig hinterher. Das heißt, es würde auffallen, wenn er es zuließe und irgendetwas abliefern würde, was sich mit der Arbeit seiner Kollegen vergleichen ließe. Das tut er aber bewusst nicht. Bzw. er ist über die Zeit ein Meister des Vortäuschens geworden. Er liefert nämlich durchaus Resultate, die von Projektmanagern und anderen Entscheidern wahrgenommen werden - und diese Resultate sind auf den ersten Blick überzeugend, weil sie auf seinem System und einem frühen Live Projekt funktionieren. Gegen Erfolg ist schwer zu argumentieren.
Gerade deshalb ist es so wichtig es dennoch zu tun, solange der Toolkurator sein zerstörerisches Potential noch nicht vollends entfaltet hat.
In ganz seltenen Fällen sitzt der Toolkurator selbst in der Geschäftsleitung. Dort entfaltet er sein seltsames Treiben aber nicht, um die eigenen Unzulänglichkeiten zu kompensieren. Er verwendet die Tools auch nichts selbst, was es ungemein schwieriger macht, mit ihm darüber zu reden, weil er sie gar nicht kennt. Das hat er gar nicht nötig, denn er ist nicht in der Situation, dass er was er plant auch umsetzen müsste. Er ist getrieben von den beiden Schlagwörtern einfach und schnell. Beide Begriffe sind im Management synonym für Profit. Nicht umsonst sind es wohl die häufigsten Buzzwords auf den Homepages der Toolanbieter. Mit dem Einsatz aktuellster Tools kann man sich im Gespräch halten, sich als jung und dynamisch verkaufen. Zockermentalität trifft es meiner Meinung nach besser.
Der Toolkurator hat in de heutigen IT Welt einen guten Stand. Der Zeitdruck ist enorm und die Budgets geben kaum Raum für ordentliche Analysen und maßgeschneiderte Software. Nicht selten ist der Toolkurator der Erste, der eine funktionierende Lösung präsentieren kann. Es gilt der Grundsatz:
Wer liefert, hat recht. Doch wie macht er das?
Er ist ein Meister der Suchmaschine. Obendrein kann ihm auf dem Feld der Tools keiner was. Er macht fast nichts anderes. Während der Rest der Firma in kleinen fiesen Details hängt und versucht Probleme älterer Software zu lösen, oder auch detaillierte Kundenwünsche zu implementieren, ist er pausenlos auf der Suche nach fertigen Lösungen. Mit seinem Umfassenden Tool Know-How kann er daher in beeindruckender Zeit die richtige Kombination von Tools ermitteln. Dann kommen noch weitere Tools, um die Tools zu kombinieren, Tools um den Zusammenbau zu Automatisieren und fertig ist der Prototyp, der mit den entsprechenden Tools sofort auf Production deployed werden kann. Management und Firmenleitung sind begeistert.
READY.
LOAD MAGIC,8,1
SEARCHING FOR MAGIC
LOADING
READY.
RUN
So schafft er es, durch seinen scheinbaren Erfolg, die Kompetenz, die er nicht hat, als überflüssig da stehen zu lassen. Ein Relikt aus den Anfängen der IT, wo es noch sagenumwobene Experten gab, die sich ihr Know-How durch verlustreiche Fehlschläge und gerade auch deren Überwindung erarbeitet hatten und für die Unternehmen mal ein Vielfaches seines Lohns auf den Tisch legen mussten, damit sie für sie arbeiteten. Weniger Gehalt, mehr Erfolg? Für Toolkurator und Unternehmen ein Win-Win. Bonus: Toolkurator kann wirklich Jeder werden, dessen Arbeit im Unternehmen irrelevant genug ist, um ihm genug Zeit zu verschaffen, sich ausgiebig mit Blogs, Tool-Fachzeitschriften und eben mit den Tools selbst zu beschäftigen.
Viele werden an dieser Stelle denken:
Der Erfolg gibt ihm Recht!
Wenn dem auf lange Sicht so wäre, würde ich mir nicht die Mühe machen, diese Zeilen zu schreiben. Neid kann die Ursache dafür nicht sein, denn wie ich erwähnte ist es sehr einfach, Toolkurator zu werden. Demnach hätte ich sicher selbst genug Zeit gehabt, gepaart mit meinem altbackenen Know-How zum Master-Toolkurator zu werden.
Was ist es also, was diese Person so gefährlich für ihre Kollegen und das Unternehmen macht?
Der Toolkurator hat keine Ahnung!
Wie jetzt? Gerade wurde doch erklärt, dass er dank Tools trotzdem zum Erfolg kommt?
Ein Beispiel: Der Toolkurator hat keine Ahnung was “die Cloud” ist.
Das was wir heute als “die Cloud” Kennen, wurde entwickelt, um die Probleme mit der Infrastruktur großer Datencenter zu lösen. Dazu zählen:
Server Monokulturen, die schlecht ausgelastet werden können, weil ihre Größe für keinen Anwendungsfall so wirklich passt. Zudem dauerte bei Engpässen der Einkauf zusätzlicher Server viel zu lange.
Netzwerke die per Hand umgesteckt werden mussten, was bei Massen von Kabeln zeitaufwändig und fehleranfällig war.
Unzureichende Kapselung der Dienste, die um die Effizienz des Datacenters zu steigern, auf die gleichen Server installiert wurden, was zu schwer zu testenden unvorhersehbaren Seiteneffekten führen kann.
All diese Probleme haben die Cloud Anbieter mit eigenen proprietären Lösungen längst in den Griff bekommen. Sie bieten:
Verschiedene Instanz Klassen, so dass es für jeden Dienst die passende Instanz Klasse gibt und man beliebig viele gleiche Instanzen dieses Dienstes starten kann. Wenn die Klasse nicht mehr passt, nimmt man halt eine andere.
Komplett virtualisierte Netzwerke, die es einem erlauben, virtuelle Firewalls und Switches schnell zu konfigurieren und Dienste ebenso schnell nach belieben zu verbinden und zu Isolieren.
Durch Virtualisierung der Instanzen, oder auch Vorhalten realer Systeme in verschiedenen Größen, sind die Dienste jederzeit voneinander gekapselt. Seiteneffekte zwischen Diensten, die durch die gemeinsame Nutzung von Hardware entstehen, treten so nicht mehr auf. Woran man jetzt merkt, dass der Toolkurator keine Ahnung von Cloud Infrastruktur hat, ist der simple Fakt, dass er sie nicht nutzt. Stattdessen benutzt er Container. An Containern ist so erst mal nichts falsch, aber er benutzt sie falsch.
Container Infrastruktur wurde mit exakt den gleichen Zielen entwickelt, die auch den Anstoß für die Cloud gaben. Lösung 2. ist quasi äquivalent, die anderen beiden Punkte kurz umrissen: Container brauchen keine Instanz Klassen. Stattdessen versucht man damit mehrere Dienste auf einem geteilten Betriebssystemkern laufen zu lassen, wie es auch schon vor Cloud und Containern der Fall war, aber diese durch zusätzliche Maßnahmen besser zu Isolieren, so dass weniger Seiteneffekte auftreten und man viele Dienste auf einem Server stapeln kann, bis dieser so Ausgelastet ist, das er kosteneffizient läuft.
Container lösen hier also keine neuen Probleme, sondern sind ebenfalls für große Datacenter mit komplexen Netzwerken und Server Monokultur ausgelegt.
Auch die Auslastung kann nicht als Argument genommen werden. In der Cloud hat die kleinste Instanz ungefähr die Leistung eines Raspberry-PI und viel kleiner sollte man auch für Microservices nicht gehen, um ein bisschen Luft für Unvorhergesehenes und Bursts zu haben. Nicht umsonst bieten die kleinsten Instanzen bei AWS die Fähigkeit bei Bursts auf Basis von akkumulierenden Creditpoints kurzzeitig mehr Leistung zu bieten.
Welches Problem löst also der Toolkurator mit Containern in der Cloud?
Der Toolkurator möchte in oberster Priorität das altbekannte “auf meinem System funktioniert das” zu einem gültigen Argument machen. Cloud Systeme sind auf seinem System nicht ausführbar und müssen in der Cloud entwickelt werden, unter der Verwendung der anbieterspezifischen API. Da der Toolkurator sich im Unternehmen hochbuzzt, hat er in seiner Entwicklungsphase keinen Zugriff darauf und kann nicht mit automatisierten Cloud Systemen glänzen. Er braucht ein Demo System, welches nahtlos von seiner Entwicklermaschine auf Live Systeme übertragbar ist. Er argumentiert gerne damit, dass sein System anbieterunabhängig ist und theoretisch sogar über mehrere Anbieter hinweg funktionieren würde. Das ist zwar richtig, aber operativ aufgrund der Latenzen kompletter Bullshit, weshalb höchstens die Portabilität zwischen Anbietern ein Argument wäre. Wer die Qualitäten der Anbieter kennt, wird nicht mitten im Projekt den Anbieter wechseln wollen. Selbst wenn, wäre es auch mit Containern ein ziemlich aufwändiger Prozess.
Was ist nun so schlimm an Containern in der Cloud?
Cloud und Container sind zwei ähnliche Lösungen desselben Problems. Da es keinen Sinn macht, nur einen Service pro Micro Instanz in Containern aus zu rollen, baut der Toolkurator erst mal alle Vorteile der Cloud zurück. Er nutzt nichts davon. Stattdessen baut er mit Cloud Infrastruktur ein altbekanntes Datencenter auf, in dem er dann mit Containern arbeiten kann. Das ist nicht nur operativ offensichtlich Unfug, sondern auch ökonomisch betrachtet. Mit steigender Instanz Größe gibt es selten Preisnachlässe, stattdessen steigen die Preise schneller, als die Leistung der Instanzen. In einem Datencenter, das Cloud Virtualisierung anbietet, ein Datencenter für Container einzurichten ist also teurer, als die Cloud Virtualisierung mit ihren Lösungen für die gleichen Probleme direkt zu nutzen. Außerdem ist es nicht sinnvoll einen weiteren Abstraktionslayer einzuziehen, der keinen weiteren Nutzen bietet. Wer ein Container Image bauen kann, kann auch ein Instanz Image für die Cloud bauen, bzw. seine Software direkt auf ein passendes Basisimage deployen. Es ist kein Container erforderlich.
Der Ansatz ist genauso widersinnig, als wenn man Container dazu nutzen würde, um darin VMs zusammen zu bauen und mit diesen Containern VMs auszuliefern. Das hat zum Glück noch niemand medienwirksam versucht, weshalb wir es noch unbiased als widersinnig wahrnehmen können.
Schneller skalieren als in der Cloud kann man auch mit Containern nicht, denn das System würde anfangen zu oszillieren, wenn man die gemessene Last nicht einigermaßen mittelt und statt in Minuten in Sekunden reagiert, was mit Containern prinzipiell möglich wäre. Also ergibt sich auch hier kein operativer Vorteil. Die eigene Cloud Infrastruktur hat bei den meisten Anbietern eine hervorragende API und lässt sich bestens Automatisieren, wofür es auch ernstzunehmende Tools gibt. Auch hier bietet der Container in der VM keine Vorteile.
Am Rande erwähnt: Googles Container Engine ist eine gut funktionierende Implementation des Containers Ansatzes, ohne einen zusätzlichen Layer an Cloud Techniken, die man mit Containern doppeln würde. Auf diese Art Container zu deployen macht durchaus Sinn. Das tut der Toolkurator aber nicht.
Wenn der Toolkurator also Unsinn entwirft, wieso lässt man ihn gewähren?
Das Problem liegt bei dem, was in Management Etagen heutzutage als Erfolg definiert wird. Denn auch dort liegt die Messlatte so niedrig wie nie zuvor. Erfolg ist dann, wenn das Setup sich auf der Entwicklungsumgebung des Toolkurators so verhält, wie vom Kunden gewünscht. Diese Umgebung ist, ja dank Tools eins zu eins übertragbar auf das Produktivsystem, oder nicht? Oder nicht?
Nein!
All die Tools und Abstraktionen mit denen der Toolkurator arbeitet, werden niemals die gleichen Bedingungen herstellen können, wie sie auf dem skalierenden Produktivsystem vorliegen. Insbesondere dann nicht, wenn das System unter eine Last gerät, die sich nicht mit dem schicken extra slim sub Irgendwas-Dings des Toolkurators verarbeiten lässt. Ganz zu schweigen von dem noch größeren Problem, eine realistische Last überhaupt zu erzeugen (dazu komme ich noch).
Kurz gesagt: Der Toolkurator testet nicht richtig!
Wie denn auch. Er wüsste ja nicht mal, was er testen soll. Wenn er das wüsste, wüsste er immer noch nicht wie. Er kennt die Komponenten, des von ihm zusammengetoolten Systems nicht ausreichend, um sie testen zu können. Er verlässt sich voll und ganz darauf, dass seine Community von Toolkuratoren das schon gemacht haben wird. Dabei bedenkt er nicht, dass seine Zusammenstellung von Tools garantiert noch nie in Kombination getestet wurde, weil es das Produkt sonst schon gäbe und er gerade nicht daran sitzen würde.
Wenn er eine Datenbank deployed, weiß er nicht, was in ihrer Konfiguration steht. Im Optimalfall Defaultwerte - Worst Case: Irgend ein undokumentierter Hack, den ein anderer Toolkurator aus einem Blog abgeschrieben hat - ohne ihn zu verstehen - um irgend ein ganz anderes Problem zu lösen. Er weiß auch nicht, dass seine als verteilt, horizontal skalierend geplante Datenbank niemals effizient skalieren wird, weil das Backend - von dem er auch nichts weiß - nach jedem Write einen Read auf dieselben Daten schickt, um das Frontend zu aktualisieren und zu testen, ob die Daten wirklich geschrieben wurden.
Erklärung: Deshalb müssen alle DB Nodes warten, bis die Daten auf allen relevanten Nodes geschrieben wurde (100% Konsistenz Anforderung). Richtig wäre es, das Frontend mit den gerade geschriebenen Daten zu aktualisieren und auf den Abschluss des Writes asynchron zu warten, um ggf. auf Fehler zu reagieren. Eine Datenbank die nicht mitbekommt, dass ihr Write fehlgeschlagen ist, ist keine Datenbank.
Warum macht man das nicht gleich so?
Weil man dann nicht dieselbe Funktion verwenden könnte, die man zum normalen Abfragen von Daten benutzt und die Fehlerbehandlung einiges schwerer wird, wenn das System schon weitergelaufen ist und der Fehler nachträglich behoben werden muss.
Ein System das blockiert, bis der Read nach dem Write durch ist, ist halt viel einfacher zu schreiben. Es skaliert bloß schlechter als eine einzige Node, die keine Daten synchronisieren muss. Toolkuratoren, die Tools für Toolkuratoren schreiben.
Deployed der Toolkurator einen Webserver - erraten: Defaultwerte. Er könnte nicht sagen wieviele Verbindungen das System gleichzeitig halten kann, wann er aufgrund von Ressourcenmangel skalieren muss, usw. Und das ist selbstverständlich nur ein Beispiel. Einen Webserver für ein spezifisches Projekt optimal zu konfigurieren ist eine Kunst - die der Toolkurator nicht beherrscht.
Aber wozu denn einen Webserver? Heutzutage bringt doch jede dem Toolkurator bekannte Programmiersprache entweder selbst einen Webserver mit, oder hat ein Projekt in der Community, dass eben dies leistet und so richtig Buzz macht.
Lassen wir doch einfach das große, böse Internet direkt auf dieses Projekt einhämmern und schlimmstenfalls mit einem fiesen kleinen Request abstürzen, den man im Webserver schon hätte ausfiltern können. Immer und immer wieder.
Noch nicht genug?
Der Toolkurator richtet dir in Windeseile eine CI Pipeline ein, die jeden kleinen Commit sofort auf Live schleudert. Menschliche QA? Im Moment des Livegangs abgeschafft (Ooops!). Ein paar Tage später fällt dann auf, dass wirklich jeder Mist auf Live landet, egal ob er funktioniert, oder nicht. Staging und Beta Systeme braucht man dank CI nicht mehr, wer sollte die auch testen, wenn jeder Commit potentiell sofort live gehen soll. Also Tool automatisiertes Testen. Schnell ein neues Tool, mit dem schnell und einfach die ganze Firma zu Testern umfunktioniert werden kann.
Wie das geht?
Ganz schnell und einfach: Es wird nicht die Software getestet, sondern eine ehemals menschliche Frontend QA mit einem Tool automatisiert. Im Wesentlichen also eine Klickstrecke automatisiert, die für jede auf die Schnelle erdenkliche Eingabe, die Ausgabe überprüft.
Wenn man so eine 100% Testabdeckung schafft, ist doch alles wieder gut?
Nein, ist es nicht. Damit hat man nur 100% aller über das Frontend möglichen (Fehl-) Eingaben abgetestet. Das Frontend sendet aber keine UI Klickstrecke sondern schnöde HTTP Requests. Diese lassen sich beliebig umformulieren und so eine Vielzahl von Angriffen zu, die die Abdeckung der Frontend Klickstrecke in den Promillebereich zurückfallen lassen.
Das müsste aber auch bei “normalen” Projekten desaströs sein, oder?
Nur zum Teil. Wie schon erwähnt würde man normalerweise schon im Webserver Requests filtern, die keine sinnvolle Funktion im Backend ansteuern. Dazu kommt, dass bei altbacken und langsam entwickelten Projekten meist jede Komponente von irgendeinem Mitarbeiter des Unternehmens mal angefasst, konfiguriert und vielleicht sogar ein bisschen getestet wurde. Daher sind ein guter Teil der Schwächen bekannt und wenn es nicht vorher schon gemacht wurde, wäre jetzt genügend Know-How da, Backend Tests zu schreiben und die Interaktion aller Komponenten zu Prüfen. So könnte man sogar eine halbwegs funktionierende CI Pipeline zusammen bauen. Nachträglich und unter Zeitdruck macht man dabei vermutlich eher mehr kaputt, als dass korrigiert wird.
Es fehlt aber noch eine dritte Klasse von Tests: Die Lasttests. Aus Frontend Tests lassen sich unter keinen Bedingungen Lasttests bauen.
Die Aufgaben sind Disjunkt, keine Synergie. Nichts. Nada. Vergiss einfach alles, was dir irgendwelche Startups oder aufmerksamkeitshaschende Blogs erzählen wollen.
Wieso?
Generiere mal einfach mit automatisierten Browserklicks die Last, die mindestens Tausend (vielleicht sogar eine Million?) Browser erzeugen können! Auf welchem System? Welcher Server (Cluster?) schafft mehr als zehn bis zwanzig, völlig unabhängige Full Blown Browser gleichzeitig? Wie modelliert man aus diesen reinen Funktionstests realistische Usage Patterns? Keine Ahnung? Ich auch nicht! Der beste Lasttests kommt meiner Erfahrung nach aus einem Mitschnitt des Live Verkehrs eines frühen Prototypen. Aber diesen Mitschnitt bekommt man nicht so einfach aus einem getoolten System heraus. Es gibt keinen vernünftigen Ort, wo man mal eben einen Wireshark, oder *dump dazwischen hängen könnte. Selbst wenn man einen hätte, könnte man nur Replays fahren. Viele Systeme sind aber absolut nicht Stateless (nicht zu verwechseln mit einer eventuellen stateless API), so dass man z.B. die selbe Rechnung nicht zweimal buchen kann und dasselbe Verhalten erreicht. Jeder Replay ergibt nur eine Fehlermeldung, die vermutlich viel weniger Arbeit auf dem System erzeugt und schon wieder kein realistisches Lastverhalten ergibt.
An dieser Stelle hat uns der Toolkurator ein Minenfeld aus lauter Blackboxen hinterlassen.
Komponenten die der Toolkurator ohne Review zusammengeschustert hat, die man eigentlich alle einzeln und in Kombination hätte testen müssen. Man hätte auch automatisierbare Testcases dafür schreiben können, oder zumindest downloaden und einbauen können. Die Zeit die bis zum Livegang gespart wurde, weil der Toolkurator sich nicht mit den einzelnen Komponenten seines epischen Spaghettimonster aus PHP, Ruby, Python, NodeJS und völlig unbekannten Binaries beschäftigen musste, fehlt jetzt Live vor den zurecht unzufriedenen Kunden.
Um es in Zahlen zu nennen es wurden in etwa fünf Monate reguläre Entwicklungsdauer in vier bis sechs Wochen eingestampft. Das fehlende Know-How muss jetzt während der Havarie erworben werden. Unter enormem Druck. Dadurch sinkt die Konzentration und man arbeitet im schlimmsten Fall langsamer und fehleranfälliger, als in der regulären Entwicklungsphase. Diese Arbeit macht aber nicht der Toolkurator. Dazu fehlt ihm die Qualifikation.
Wenn die Firma das überlebt, wird höchstwahrscheinlich nicht mal der Toolkurator den Kopf dafür hinhalten müssen. Er kann sich auf eine breite Community von
Bei XY hat das aber auch funktioniert!
berufen, die allesamt coole Boilerplate Websites haben, auf den wieder nur einfach und schnell steht, aber niemand ins Detail geht, wie er denn nun eigentlich ein konkretes Problem gelöst hat, was die konkreten Anwendungsbereiche für ihre Tools sind und am Wichtigsten: Wofür man sie besser nicht benutzen sollte. Es arbeiten heutzutage sowieso alle so und bestimmt war nur die schlechte Infrastruktur schuld, die der Tooklurator natürlich nicht mitgestaltet hat.
Davon hört man heutzutage noch erstaunlich wenig und man könnte mir jetzt zurecht Schwarzmalerei unterstellen.
Nur ist es leider so, dass es in Deutschland noch keine ausgeprägte “Why we failed:” Kultur gibt. Die wenigen Vorträge die es gibt, beschäftigen sich größtenteils mit Missmanagement und Startups, deren Businessidee schon völliger Schwachsinn war. Wer Erfolg hatte, zieht die Augen auf sich und bestimmt die Richtung des Hype - dort wo diese Herangehensweise scheitert, scheitert jeder leise für sich alleine.
Natürlich ist das gewähren lassen eines Toolkurators auch extremes Missmanagement, aber in den Führungsetagen träumt jeder davon ein klein bisschen wie Google zu sein. Die Arbeiten ja auch mit unglaublich vielen Tools.
Nur das Google seine Tools selbst schreibt, sicherlich auch massiv testet und Special-Ops-Tool-Teams in Hotstandby hat, die für Probleme Zeit haben, während die regulären Truppen normal weiter arbeiten.
Ihr habt aber nur ein oder zwei Toolkuratoren, deren größte Leistung es wäre, ein Tool zu schreiben, welches andere Tools automatisiert. Ungetestet. Was dann im Extremfall von anderen Toolkuratoren massenhaft geladen wird, weil sie darauf vertrauen, dass die Toolkuratorencommunity das schon getestet haben wird.
Zur Klarstellung: Ich spreche mich ausdrücklich nicht gegen Software Reuse aus. Das ist einer der Grundpfeiler der OpenSource Community. C ist Software Ruse von Assembler. Go ist Software Reuse von C. Jedes Programm dass eine Library linkt ist Software Reuse. Jede Distribution, stellt nur bereits existente Software zusammen.
Doch eben da liegt der Unterschied zum Treiben der Toolkuratoren: Eine Distribution bringt jede Library pro Revision nur einmal mit und teste gnadenlos alle gelinkten Programme, ob sie damit funktionieren. So kann man innerhalb einer Stable Version gefahrlos weiterarbeiten, ohne Angst zu haben, dass sich jederzeit eine Abhängigkeit ändern könnte. Der Toolkurator erschafft ein Knäul aus automatisch von extern geladener Software, dass nur im Zeitlichen Umfeld des letzten manuell überwachten Build stabil sein kann, selbst wenn jede Komponente in ihrem Container stabil wäre. Diese Software ist aufgrund einmalig angepasster Abhängigkeiten unwartbar. Die Release Cycles (wenn man es denn so nennen will) der Tools sind viel zu kurz und was einmal funktionierte, kann mit der nächsten Version der Tools schon wieder unmöglich sein. So schaffen die Toolkuratoren genau zu diesem Augenblick einmal reproduzierbare Builds, bei denen es keine Long Term Stability Garantien geben kann. Schreiten die Versionen voran, kann es schnell sein, dass sich ein vergleichbare Projekt mit den bestehenden Tools, ohne massive Handarbeit, überhaupt nicht mehr zusammen bauen lässt. _Der Zeitpunkt an dem die eigentlich Arbeit gemacht werden muss, rutscht also nur hinter den vermeintlichen Livegang und muss jetzt ohne Budget auf kosten anstehender Projekte eingeschoben werden. _Oder man lässt das Projekt sterben - es ist immerhin schon über ein Jahr alt und der Kunde lässt sich nicht für ein neues Budget gewinnen, das deutlich über dem Ersten liegen wird.
Ist die CI Pipeline eines unter “läuft ja” verbuchten Projekts einmal defekt, wird der nächste erfolgreiche Build desselben Projekts einiges schwerer sein, als nur eine Migration in eine neuere Version derselben Distribution, denn jetzt greifen keine Defaults mehr und der Buildprozess muss angepasst werden. Es muss eine zum ersten Livegang kompatible Konfiguration geschaffen werden. Die erste Konfiguration ist aber dank Tools weitestgehend unbekannt. Also kann man trotz CI entweder keine Sicherheitsrelevanten Updates vornehmen, oder man hat mit der CI eine Dauerbaustelle, die in kleinen Betrieben mehr Zeit verschlingt, als wenn man die Software altmodisch in getesteten Releases ausliefert.
Hat man Massen an Mitarbeitern, die alle hinter der selben CI arbeiten rechnet es sich, ein Team ausschließlich für die CI ab zu stellen. Ich habe nie erlebt, dass dafür in kleinen Unternehmen ein regelmäßiges Zeitkontingent vorgehalten wurde. Also bleibt dieser Job nicht selten an einer einzigen Person in Teilzeit hängen, oder die CI bleibt bis zum End of Live des Projekts wie sie ist, weil schon längst wieder neue Projekte anstehen, die Tools und CI brauchen.
Daher mein gut gemeinter Rat: Winkt ein Job in einer Firma mit Toolkurator - nimm ihn nicht an. Hat sich in deiner Firma ein Toolkurator entwickelt - kündige.
Ich sage nicht, dass ich eine Patentlösung habe, wie man mit sauber entwickelter Software heute noch konkurrenzfähig bleiben kann, aber: Wenn diese Art auf “wird schon gutgehen” zu zocken, wirklich die neue Art der Softwareentwicklung sein soll, wird das wohl nur eine reihenweise Insolvenz der “Bad Player” korrigieren.
Im nächsten Beitrag möchte ich meine These, dass Systemadministrator, bzw. DevOps Engineer inzwischen einer der undankbarsten Jobs in durchschnittlichen Webbuden geworden ist, näher erklären und versuchen, dies mit absichtlich überspitzen Beispielen verständlich zu machen.
Meiner Meinung nach trägt der/die/das Administratordingens in der Firma mit die größte Verantwortung und ein in letzter Zeit immer mehr unkalkulierbares Risiko. Grade letzteres ist den jungen motivierten Nachrückern im seltensten Fall bewusst. Natürlich stehen üblicherweise die Gehälter dazu in keiner Relation.
Diejenigen, die an dieser Situation etwas ändern könnten, wollen nicht mit Details belästigt werden. Lange vermutete ich dahinter böse Absicht. Heut bin ich mir da nicht mehr so sicher. Viel mehr sind die Probleme der Infrastruktur häufig zu komplex und erfordern einiges an Fachwissen, um sie überhaupt verstehen zu können.
Heutzutage werden DevOpse gerne als Freiberufler gesucht, die vermeintlich in der Infrastruktur ausgemachtes Problem lösen sollen, Dinge™ automatisieren sollen und danach wieder entbehrlich sind.
Nach meiner Erfahrung liegen die Probleme, die das DevOps lösen soll, gar nicht so häufig in der Infrastruktur. Wodurch das DevOps automatisch vor einem Problem steht, dass es gar nicht lösen kann, aber (besonders im Fall des Freiberuflers) die Verantwortung dafür zugeschoben bekommt, wenn das Problem auch nach längeren Anstrengungen und den damit verbundenen Kosten nicht verschwindet.
Daher bin ich der Meinung, dass Erfahrung in diesem Metier jegliche Motivation tötet.
Dies war eigentlich eher humoristisch gemeint und ich dachte mir, wer weiß, worüber ich rede, wird grinsen und vermutlich unbeirrt in seinem Twitterstream fortfahren.
Doch natürlich gibt es immer einen Querulanten, der jedes Wort auf die Goldwage legen muss…
… und sich auch mit einem kleinen Wink á la use your own brain nicht abwimmeln lässt.
Jetzt wisst ihr es also: Der @plappertux trägt die alleinige Verantwortung dafür, dass dies ein sehr lange Blogeintrag wird:
Der Versuch, zumindest einfache echtzeit 3D Grafik auf Webseiten anzubieten, war immer mit Schmerzen verbunden. Bevor WebGL letztendlich von Microsoft und Apple übernommen wurde, gab es einige Technologien, die sich darum bemühten, der quasi de facto Standard für 3D Grafik im Internet zu werden.
Unwillig alle zu unterstützen, stellte ich mir die Frage:
Was, wenn ich überhaupt keine Rendering Technologie verwende?
Offensichtlich muss ich dann das komplette Rendering selbst machen.
Das klingt nach einer Herausforderung? Nun, das hängt davon ab, was du vor hast. Wenn es nur um eine triviale Projektion, eine einfache Lichtquelle und rudimentäre Texturen geht - ist das nicht so schwer, wie man annehmen würde.
Da ich 3D Grafik unter der Verwendung von OpenGL2.1 gelernt habe, konnte ich mich nie wirklich an die “neue” frei programmierbare Pipeline im heutigen OpenGL / WebGL gewöhnen. Mir gefiel die Definition von frei darin nicht, denn es ist handelt sich nicht um frei wie in du darfst das machen, tatsächlich handelt es sich um ein du musst das machen!
Obwohl ich inzwischen einigen OpenGL4.0 Code geschrieben habe, waren meine Shader zusammenkopierte Einzeiler.
Dies vorweg gesagt, habe ich aus zwei Gründen an diesem Projekt gearbeitet:
um endlich zu verstehen, was Shader sind und was man durch ihre Nutzung erreichen kann
um herauszufinden, ob die Leistung möglicherweise ausreicht, um als Notfallalternative genutzt werden zu können, wenn kein WebGL verfügbar ist. Nur um das klarzustellen: Mir ist bekannt, dass es 3D rendering unterstützung in Flash und Miscrosofts Silverlight gibt. Ich halte diese Technologien lediglich für ungeeignet die heutigen Anforderungen des Internets zu erfüllen – insbesondere, wenn man auf Portabilität hin arbeitet und nicht das Selbe mit mehreren verschiedenen proprietären Technologien bauen will.
Prerequisite
Was man benötigt, um mit dem Rendern zu beginnen, ist exakt das selbe, was man für hardwarebeschleunigtes Rendern benötigen würde – dennoch würde ich es hier gerne kurz auflisten.
ein vertex model der Objekte die man rendern möchte
Texturemapping Koordinaten
Normal Vektoren für Dreiecke
ein vertex model der Objekte die man rendern möchte
Da ich einen Globus rendern wollte, war dies ziemlich einfach. Alles was ich benötigte, war ein 5120 seitiger Ikosaeder. Das ist eine ziemlich hohe Anzahl von Dreiecken für ein Objekt, welches nur durch Software gerendert werden soll. Aber die nächst kleinere Anzahl von Dreiecken, die mathematisch möglich gewesen wäre, hätte nur 1280 davon gehabt, was schon eher wie etwas aussah, was man bei einem Tabletop Spiel rollen würde, als wie eine Kugel. Obwohl das Texturemapping dadurch ein wenig komplizierter wird, bevorzuge ich die Form des Ikosaeders gegenüber der üblichen Annäherung einer Kugel, da alle seine Seiten gleich groß sind. Die Wireframe Ansicht eine normalen Kugelapproximation wirkt unausgewogen auf mich. Ihre Seiten werden unendlich klein an den Polen. Ich bin mir sicher, dass es eine verblüffend einfache Formel gibt, um meinen Ikosaeder im Code zu berechnen, aber ich habe nur einige regex Ersetzungen auf ein Modell angewendet, welches ich als Wavefront .obj in Blender exportiert hatte. Da ich absolut keine Ahnung davon habe, wie man eine Weltkarte auf einen Ikosaeder überträgt, musste ich mich ohnehin auf Blender verlassen.
Texturemapping Koordinaten
Wie ich schon zuvor anmerkte, ist dies schwarze Magie für mich. Um eine Textur auf irgend etwas anderes als ein Rechteck abzubilden, benötigt man eine Abbildung der Dreiecke seines Objekts auf die Teile der Textur, die sie auffüllen sollen.
Es scheint so, als gäbe es keine perfekte Lösung für eine auf einem Ikosaeder basierte Kugel. Einfach auf UV unwrap zu klicken war ausreichend für mein Projekt. Wie man sieht, ist auch ein Ikosaeder kein optimales Objekt für die Abbildung einer Textur auf eine Kugel. Ich bezweifle, dass es eins gibt. Solange man eine rechtwinklige Fläche auf eine Kugel abbilden will, werden das obere und untere Ende der Textur auf die Pole abgebildet, wodurch der gesamte Bereich auf lediglich ein paar Dreiecke projiziert wird. Erstaunlicherweise generierte Blender einige Texturkoordinaten, welche größer als eins waren. Diese sollten sich normalerweise um die Textur herumwickeln. Ich habe mich jedoch dazu entschieden, sie zu ignorieren, da es einige extra “if”s für jeden einzelnen Pixel, der gerendert werden sollte, bedeuten würde und ich nicht erwartete, dass meine Herangehensweise dafür schnell genug sei (letztendlich lag ich damit richtig).
Normal Vektoren für Dreiecke
Erneut kann man normal Vektoren einfach im eigenen Code berechnen (Kreuzprodukt), aber solange die Objekte eine statische Form haben, die nur durch die Modelview Matrix (dazu komme ich noch) modifiziert werden, braucht man das nicht. Normal Vektoren sind praktisch für einige Dinge. Sie helfen einem, um heraus zu finden, ob ein Dreieck nach innen, oder nach außen weist, sie werden für Lichtberechnungen benötigt und sie helfen dabei, Dreiecke welche nicht aus der Kameraperspektive gesehen werden können, da sie in die Gegenrichtung weisen, zu finden. Dies (Backface Culling) ist die einzige Technologie, für die ich normal Vektoren verwendet habe. CPU basiertes Texturmapping ist ein sehr aufwendiger Prozess, weshalb jedes Dreieck welches man nicht zeichnen muss, dabei hilft, das Rendern zu beschleunigen. Natürlich lässt sich das nur auf nichttransparente Objekte anwenden. Andererseits könnten und sollten auch von der Kamera weg orientierte Dreiecke gesehen werden. Bis auf die Textur konnte ich alles von Blender bekommen, wodurch ich bereit war, in den Rendering Prozess ein zu tauchen.
ToDo:
Vertices vom 3D Objektraum zu 2D Bildschirmkoordinaten projizieren
unsichtbare Dreiecke entfernen
berechnen welche Bildpunkte sich wirklich in einem Dreieck befinden
Texturpixel auf Dreiecke abbilden All diese Schritte werden normalerweise durch die Hardware ausgeführt. Zumindest für Schritt zwei bis vier sind wir es mehr oder weniger gewöhnt, dass die Hardware sich wie eine Blackbox verhält. Ich bin davon keine Ausnahme, so dass ich keine Ahnung davon hatte, wie dies zu tun sei, aber beginnen wir am Anfang.
Vertices vom 3D Objektraum zu 2D Bildschirmkoordinaten projizieren
Es ist egal, ob man die Hardwarebeschleunigung verwenden will, oder nicht, man muss dennoch einige Matrizen erstellen, um seine Vertices zu projizieren. Nun… das stimmt nicht zu 100%. Tatsächlich kann man seine Vektoralgebra auch komplett ohne die Verwendung von Matrizen berechnen, aber dadurch wird der Prozess kein Stück schneller. Also habe ich die selben Matrizen verwenden, welche ich auch für WebGL genutzt hätte. Es erhöht die Leistung beträchtlich, wenn man die Matrizen zuvor miteinander multipliziert (zumindest für alle Vertices, auf die die selben Transformationen angewendet werden sollen):
combined = VPcorr * projection * camera * model
Von Rechts nach Links erklärt:
model: repräsentiert alle Transformationen, welche nur auf ein einzelnes Objekt, oder nur einen Teil davon angewendet werden sollen. (typischerweise Rotation, Translation und Skalierung).
camera: wenn sich die Kamera bewegt, sollte man diese Matrix verschieben und rotieren. Dies hält die Kamera von allem anderen unabhängig. Wenn du nerdig bist, kannst du stattdessen die entgegengerichtete Transformation auf alle Objekte außer der Kamera anwenden. Wenn also die Kamera drei Schritte vorwärts in irgendeine Richtung macht, kann man genauso die ganze Welt in die entgegengesetzte Richtung verschieben. Die Kamera Matrix ist also nur ein Konstrukt zur Bequemlichkeit.
projection: Dies ist der Ort, wo es magisch zugeht. Ich werde mich nicht über Details auslassen. Falls jemand daran interessiert ist, sollte er die entsprechenden Absätze in der OpenGL Dokumentation lesen. Dort habe ich gelernt wie man diese Matrix Komponente für Komponente aufbaut. Will man eine orthographische Projektion erreichen, gibt es nicht viel mehr als Skalierung und Verschiebung in dieser Matrix. Will man hingegen wie ich eine perspektivische Projektion, wird es ein Stück komplizierter, aber erneut verschone ich dich mit den Details. Ein letztes Detail gibt es noch zu erwähnen: Wenn man nicht die hardwarebeschleunigte Rendering Pipeline verwendet, muss man jede x und y Koordinate durch die dazugehörige z Koordinate teilen. Die Hardware tut dies implizit. Vergisst man das also, wird die Projektion eher wie eine fehlerbehaftete orthographische Projektion aussehen.
Was ist mit VPcorr?
Ich bin froh, dass dir das aufgefallen ist. Jetzt wo du danach fragst: Dies ist eine weitere Berechnung, die man nicht machen müsste, wenn man Hardware Rendering verwendet, da die Hardware die virtuelle Projektion automatisch in Bildschirmkoordinaten einpasst. Die Koordinaten, die man aus der Projektion erhält, befinden sich immer noch zwischen minus Eins und Eins. Offensichtlich ist das nicht sehr nützlich, um Pixel auf eine Zeichenfläche zu bringen, deren Koordinaten immer positiv sind und die ihren Ursprung bei Null/Null in der Ecke hat. VPcorr skaliert das Koordinatensystem um die hälfte der Zeichenflächengröße in jede Richtung und verschiebt den Ursprung in die Mitte der Zeichenfläche. Von diesem Punkt an können wir diese Koordinaten direkt als Koordinaten auf der Zeichenfläche benutzen. Es gibt zwar immer noch eine z Koordinatem, welche wir später noch benutzen werden, aber für den Arbeitsschritt Zeichenflächenkoordinaten zu bekommen, brauchen wir diese nicht mehr.
Jetzt wo wir eine kombinierte Matrix haben, was machen wir damit?
Der hardwarebeschleunigte Fall: Man hätte die Matrizen nicht einmal multiplizieren müssen – stattdessen reicht es, sie an einen Vertex Shader zu übergeben, welcher die Berechnungen für uns macht.
Wenn man wirklich alles selbst macht: Multipliziere jeden einzelnen Vertex des Modells mit der kombinierten Matrix, speichere sie zwischen und vergiss nicht am Ende durch die z Koordinate zu teilen. Das Resultat im Speicher entspricht den Vertices projiziert auf die Zeichenfläche für ein Einzelbild. Wenn sich das Objekt bewegt, muss man offensichtlich alles für das nächste Einzelbild wiederholen, aber das ist noch nicht der Teil, der die CPU aufheizt.
Unsichtbare Dreiecke entfernen
Da die Geschwindigkeit des Renderns großteils davon abhängt, wie viele Dreiecke man rendern will, ist es immer eine gute Idee, jene zu entfernen, die anderenfalls durch Dreiecke überzeichnet würden, die sich näher an der Kamera befinden.
Solange man nicht mit Transparenzen arbeitet, ist die simpelste Technik, die man implementieren kann Backface Culling. Alles, was man dazu tun muss ist dies:
Modelview- und Projektionsmatrix auf die normal Vektoren anwenden
das Skalarprodukt zwischen den normal Vektoren und der Kamera berechnen
alle Dreiecke für welche das Ergebnis größer als Null ist verwerfen, denn es repräsentiert einen Winkel von über 90°, was man als von der Kamera abgewandt interprätieren kann Das klingt einfach? Ich muss zugeben, irgendwie habe ich das verbockt. Das zusätzliche Anwenden der Projektionsmatrix führte zu seltsamen Ergebnissen, so dass ich nur die Modelviewmatrix verwendet habe, bevor ich das Skalarprodukt gebildet habe. Das ist inakkurat, da eine perspektivische Projektion die normal Vektoren ebenfalls verändert. In meinem Fall blieben mehr abgewandte Dreiecke zurück, als meine Backface Culling Implementierung finden konnte.
Wenn dein Backface Culling nicht gut ist, oder du mit Transparenzen arbeitest, oder du mit Objekten arbeitest, die komplexer sind als geometrische Standartobjekte wie Kugeln, oder Würfel – kurz gesagt: immer – dann gibt es da noch eine Sache die du tun solltest:
Alle Dreiecke müssen nach ihrer Position auf der z Achse sortiert werden.
Wann man das nicht macht und einfach alle Dreiecke in der Reihenfolge zeichnet, wie sie sich im Puffer befinden, dann wird man Dreiecke, die weiter weg von der Kamera sind, über solche zeichnen, die näher dran sind. Falls du etwas empfindlich bis, könnte dies dazu führen, dass du dich übergeben musst - dein Gehirn hat nie etwas derartiges gesehen (das ist mein Ernst).
Ich habe einfach QSort mit einer Callbackfunktion verwendet, die z Koordinaten vergleicht.
Ich bitte darum, das jetzt nicht falsch zu verstehen: Die Dreiecke nach ihrer z Koordinate zu sortieren, entfernt natürlich keine weiteren Dreiecke, welche nicht gezeichnet werden müssten. Es schützt nur davor, sie zum falschen Zeitpunkt zu zeichnen. Sie sind immern noch da, aber man kann damit sicher stellen, dass Dreiecke die näher an der Kamera sind, darüber gezeichnet werden. Zumindest ist das Resultat auf dem Bildschirm jetzt korrekt.
berechnen, welche Bildpunkte sich wirklich in einem Dreieck befinden
Das Grab für die Rechenleistung liegt hier. Über dieses Problem habe ich nie richtig nachgedacht, obwohl ich dazu irgendwas an der Uni gelesen haben muss. Ich hatte nicht das Bedürfnis in alten Papieren zu wühlen, also habe ich versucht diese Nuss mit meinem eigenen Kopf zu knacken. Lasst uns damit beginnen eine minimale, rechteckige Fläche zu definieren, die das Dreieck enthält. Die bekommt man, indem man die minimalen und maximalen x und y Koordinaten des Dreiecks bestimmt. Sie definieren zwei Ecken des Rechtecks, was ausreicht, um über alle Pixel zu iterieren. Um das Dreick zu zeichnen, muss man für jeden Pixel des Rechtecks überprüfen, ob er sich innerhalb, oder außerhalb des Dreiecks befindet. Aber wie überprüft man das?
Der triviale Ansatz ist es, alle drei Vertices an den Ecken zu nehmen und sie zu Vektoren konvertieren, indem man sie voneinander subtrahiert. (Mögen sie A, B, C genannt werden.)
v1 = A - B
v2 = B - C
v3 = C - A
Diese Vektoren rotieren (mathematisch gesehen Unfug) um das Zentrum des Dreiecks. Gab es da nicht eine Formel, um zu überprüfen, auf welcher Seite einer Grade sich ein Punkt befindet? Gegeben eben diese, müsste ein Punkt, welcher sich auf der gleichen (richtigen) Seite aller drei Graden befindet, im Dreieck liegen. Die relevante Seite hängt davon ab, wie rum die Vektoren um das Zentrum weisen und welche Seite es dann ist.
Auf der Suche nach einem Codeschnipsel, den ich anpassen könnte, stolperte ich über eine Seite, die eine viel bessere Lösung erklärte (englisch). Der Author empfiehlt stattdessen eine Koordinatensystemtransformation durchzuführen, wodurch man weniger Instruktionen benötigt.
Nimmt man einen Vektor als Ursprung und die Subtraktion des Ursprung von den übrigen beiden als Einheitsvektor, erhält man ein frisches neues Koordinatensystem. Dieses Koordinatensystem ist nur für den Sonderfall kartesisch, dass der Winkel zwischen den beiden Einheitsvektoren 90° betrug, aber das ist für die weitere Verwendung egal. Man kann immer noch jeden Punkt in diesem Koordinatensystem durch eine Linearkombination der beiden Einheitsvektoren beschreiben. Streng genommen ist dies die Definition eines Koordinatensystems. In Pseudocode sieht das dann so aus:
P = u * v1 + v * v2
Wobei P ein Punkt ist, u & v beliebige Faktoren und v1 & v2 die Einheitsvektoren.
Das Schöne daran ist, dass man beweisen kann, das jeder der Punkte innerhalb des Dreiecks die folgenden Bedingungen erfüllen muss:
(u >= 0) && (v >= 0) && (u + v < 1)
Um P zu erhalten, muss man lediglich den neuen Ursprung wieder subtrahieren, u & v zu erhalten ist etwas komplizierter, aber es ist immer noch weniger aufwändig (gemessen in Instruktionen), zu testen, ob ein Punkt Teil eines gefüllten Dreiecks ist. Wenn es dich interessiert, solltest du die original Seite lesen (englisch).
Wissend, welche Pixel zu einem Dreieck gehören, könnte man es mit einer konstanten Farbe füllen. Aber was ist mit Texturen? Lies weiter!
Texturpixel auf Dreiecke abbilden
Wieder einmal hatte ich Anfangs keine Ahnung wie man das anstellen sollte. Dass heist, bevor ich die Faktoren u & v aus dem letzten Schritt hatte. Die abgebildeten Dreiecke auf der Textur mögen zwar total verzerrt, gedreht und … in anderen Worten, verschwurbelt aussehen, aber was für die Dreiecke in Bildschirmkoordinaten gilt, ist ebenfalls gültig für die abgebildeten Dreiecke. Wenn man sie zu einem Ursprung und zwei Einheitsvektoren konvertiert, wie ich es bereits zuvor gemacht habe, kann man genau die gleichen Faktoren u & v benutzen und eine Linearkombination der Einheitsvektoren und Faktoren bilden. Das ist wirklich alles was man benötigt, um den passenden Texturpixel für einen Bildschirmpixel zu erhalten. Der Texturpixel ist zwar immer noch relativ zum berechneten Ursprung, aber das kann man beheben, indem man den Vektor zurück addiert, den wir zuvor subtrahiert hatten, um den neuen Ursprung zu erhalten.
Mit diesem Wissen, sollte es recht einleuchtend sein, dass der Test, ob sich Pixel in einem Dreieck befinden und das übertragen einer Farbe aus der Textur, in der selben Schleife erledigt werden sollte.
Was ist mit Licht?
Ja, das stimmt natürlich. Alle meine Pixel, die auf diese Art abgebildet wurden, haben die volle Intensität der Textur. Um es kurz zu halten, Licht und Reflexionen sind Berechnungen mit den normal Verktoren der Dreiecke, einem Vektor, der auf die Lichtquelle(n) zeigt und einem Vektor in Richtung der Kamera. Man muss Winkel berechnen, sie gewichten, addieren und das Ergebnis mit der Farbe der Textur multiplizieren und kann so alle möglichen virtuellen Lichteindrücke erreichen.
Eine virtuelle Sonne zu haben, wäre sicherlich interessant gewesen, aber mein Code ist bereits langsam genug… .
Das sieht jetzt aber nicht gerade wie heutige Computerspiele aus. Könnte man Anti-Aliasing hinzufügen?
Natürlich kann man das. Es ist sogar einfach. Aber zur gleichen Zeit würde die Zeit zur Berechnung explodieren. Ich gebe mal einen Hinweis, wie man das generell anstellen würde. Man würde nicht testen, ob ein Pixel Teil eines Dreiecks ist, sondern, ob ein Subpixel es ist. Anstatt einen rechteckigen Bereich um das Dreieck Pixel für Pixel abzusuchen, würde man nur einen halben Pixel, einen viertel … und so weiter. Da dies ein zweidimensionales Koordinatensystem ist, würde die Berechnungszeit quadratisch steigen. Um eine bessere Texturqualität zu erhalten, würde man zum Beispiel nicht nur einen einzelnen Pixel kopieren, sondern eine n-Pixel Umgebung mit einem gewichteten Gausskern abtasten. Dies wiederum wäre eine furchtbare Sache, um sie auf der CPU zu berechnen.
Trying to provide at least some real-time 3D graphics on websites has always been a pain. Before WebGL finally got adopted by Microsoft and Apple, there were numerous technologies struggling to get accepted as the de facto 3D standard for the web.
Not willing to support all of them, I asked myself the Question:
What if I don’t use any 3D rendering technology at all?
Obviously I’d have to do all the rendering myself.
Sounds challenging? Well, it depends on what you are trying to accomplish. If it’s just some basic projection, a simple lightsource and rudimentary texturing - it’s not as hard as you might expect.
Having learned 3D graphics using OpenGL2.1, I had a hard time getting used to the “new” freely programmable pipeline in nowadays OpenGL / WebGL. I didn’t appreciate the definition of free in these versions, because it is not free like in you may do this, actually it is you have to!
Although I already have written some OpenGL 4.0 code, my shaders were copypasted one-liners.
That said, I worked on this for two reasons:
finally understand, what shaders do and what can be done by using them
find out, if the performance might actually be good enough, to use it as a fallback, if there is no WebGL available Just to point that out: I do know, that there is 3D rendering support in Flash and Microsofts Silverlight. I just don’t think any of these technologies is suitable for today’s web requirements – especially if you are going for portability and don’t want to implement the same thing using several different proprietary technologies.
Prerequisite
What you need to get started rendering is exactly the same, you would need for using hardware accelerated rendering - nevertheless I’d like to list it here.
a vertex model of the object(s) you’d like to render
texture mapping coordinates
normal vectors for triangles
a vertex model of the object(s) you’d like to render
Since I was going to render a globe, this was pretty straight forward. All it took was a 5120 sided icosahedron. This is quite a high trianglecount for an object to be rendered in plain software, but the next lower trianglecount mathematical possible would have been only 1280 triangles, which looked rather like something you would roll for a tabletop game, than like a sphere. Although this makes the texture mapping a bit harder in the end, I prefer the icosahedrons shape to the usual approximation of a sphere, because all its sides are identical. The wireframe of a standard sphere approximation looks unbalanced to me, it’s faces become infinitely small at the poles. I’m sure there is a surprisingly simple formula to get my icosahedron calculated in code, but I just ran some regex replacements on a model exported as wavefront .obj from blender. Because I have absolutely no clue about how to map a world maps image on an icosahedron, I was relying on it anyways.
texture mapping coordinates
As I mentioned earlier, this is plain black magic to me. To apply a texture to anything else than a rectangle, you need a mapping of your objects triangles to the parts of your texture you want do have filled into each specific triangle.It seems there is no perfect solution for an icosahedron based sphere, but just hitting UV unwrap was sufficient for my project. As you can see, an icosahedron is not an optimal object for sphere texture mapping, either. I doubt there is any. As long as you use a rectangular area to be mapped to a sphere, the upper and lower end of the texture will be mapped to the poles, thus mapping the entire area to just a few triangles. Surprisingly blender generated some mapping coordinates larger than one. These were meant to wrap around the texture. I have chosen to ignore them, as this would require some extra “if”s for each and every pixel to be rendered and I didn’t expect my approach to be fast enough for this (in the end I was right).
normal vectors for triangles
Again, you can easily calculate normal vectors in your own code (cross-product), but as long as your objects have a static shape which gets only morphed by the modelview matrix (I’ll get to this later), you don’t need to. Applying the same matrix to the normal vectors will always keep them in sync. Normal vectors come in handy for quite a few things. They help you to determine if a triangle is inside- or outside facing, they are needed to apply light calculations and they help you to detect triangles which can’t be seen from the cameras point of view, because they are backfacing. This (backface culling) is the only technique I used normal vectors for. CPU based texture mapping is a very expensive process, so every triangle you do not need to draw helps speeding up your rendering. Of course you can only use this on non transparent objects, otherwise backfacing triangles can and should be seen.
I got everything except for the texture out of blender so I was ready to dive into the rendering process.
ToDo:
project vertices from 3D object space to 2D screen coordinates
remove invisible triangles
calculate which screen pixel actually lie within a triangle
map texture pixels to screen pixels All these steps are normally performed by the hardware. At least for step two till four we are more or less used to the hardware acting as a black box. I am no exception to that rule, so I had no idea how to do this, but lets start at the beginning.
Projecting Vertices from 3D Object Space to 2D Screen Coordinates
It does not matter whether you are going to use hardware acceleration or not, you still have to provide certain matrices, to project your vertices. Well… that’s not 100% true, in fact you could do your vector algebra without using matrices at all, but that won’t speed up the process in any way. So I used the same matrices I would have used for WebGL. It greatly enhances performance if we pre-multiply all matrices (at least for all vertices undergoing the same transformation):
combined = VPcorr * projection * camera * model
Explained from right to left:
model: represents all the transformations which should be applied only to a singe object, or even only parts of it. (typically rotation, translation and scaling)
camera: if your camera is moving, you should translate and rotate this matrix. This keeps the camera independent from everything else. If you are nerdy you could apply the opposite transformation to all objects, like if the camera takes three steps forward in some direction, you could also move the world three steps in the opposite direction. The camera matrix is just a convenient construct.
projection: this is where the magic happens. I won’t go into the details, if anyone is interested in that, read the corresponding abstracts in the OpenGL documentation. This is the place were I learned to set this matrix up component by component. If you are going do do an orthographic projection, there isn’t much more like scaling and translating represented by this matrix. If you set up a perspective projection like I did, it gets a bit more complicated, but again, I’ll spare you the details. There is one detail left to mention: Without using the hardware accelerated rendering pipeline, you need to divide each and every x and y coordinate by their corresponding z coordinate. The hardware does this implicit for you. So if you forget to do this in the software-only case, your perspective projection will look more like a broken orthographic projection.
So what about VPcorr?
I’m glad you paid attention. Now that you ask for it: This is another calculation you do not need to perform, if you use hardware rendering, because the hardware automatically fits your virtual projection output to screen coordinates. The coordinates you get out of the projection are still located between minus one and one at the x and y axis. Obviously this is of little use to put pixels onto a canvas with always positive coordinates and an origin at zero/zero in the corner. VPcorr scales the coordinate system by half the canvas size in each direction and moves the origin to the middle of the canvas. From that point on, we can use these coordinates directly as canvas coordinates. We still have a z axis coordinate left, which we will use later on, but for the task of getting canvas coordinates, we don’t need it anymore.
Now that we have a combined matrix, what are we gonna do with it?
The hardware accelerated case: We wouldn’t even have to combine these – instead it is sufficient to provide them to a vertex shader, which does the calculations for us.
Really doing it yourself: Multiply each and every vertex of your model by the combined matrix, buffer them and don’t forget to divide by z in the end. The result in the buffer is your vertices projected to screen coordinates for one frame. If your Object moves, you’ll obviously have to do it all over the next frame, but this is not the part which is going to heat up your CPU.
Removing invisible Triangles
Because the rendering speed is largely dependent on how many triangles you are going to draw, it’s a good idea to remove those, which otherwise would be drawn over by a triangle nearer to the camera.
As long as you’re not working with transparencies, the simplest technique you might implement is backface culling. All you have to do is:
apply modelview and projection to your normal vectors
calculate the dot product between normal and camera location
drop all triangles for which this results in a value larger than zero it represents an angle larger than 90°, which you can interpret as showing the camera its backside Sounds easy? I have to confess, I somehow messed this step up. Applying the projection matrix leaded to strange results, so I only applied the modelview matrix and calculated the dot product. This is inaccurate because a perspective projection also alters the normal vectors. In my case I was left with more backfacing triangles than my culling implementation could detect.
If your backface culling has flaws, or you are working with transparencies, OR you’re working with objects more complex than geometric primitives like cubes or spheres – in short: always – there is one more thing you need to do:
You have to sort all triangles by their position at the z axis.
If you don’t do this and just draw all triangles by their position in your objects buffer, you’ll end up drawing triangles that are farther away from the camera on top of triangles that are close by. If you are a sensitive person this might make you throw up - your brain has never seen something like this (not kidding).
I simply used qsort with a callback that compares z coordinates.
Don’t get me wrong, sorting triangles by their z coordinates does not remove any more triangles, which don’t need to be drawn. It just saves you from drawing them at the wrong point in time. They’re still there, but you just made sure that triangles that are closer to the camera, are drawn over them. At least the result on screen is correct now.
Calculating which Screen Pixels actually lie within a Triangle
The performance grave starts here. I never really thought about this problem, although I must have heard something about this at university. I wasn’t eager to dig up that old stuff, so I tried to crack this one with my own head. Lets start by defining a minimal screen aligned bounding box, which contains the triangle. We get it by taking the minimal and maximal x and y coordinates defining the triangle. They define two edges of the bounding box, which is enough to iterate over each pixel. To draw the triangle we must test for each pixel of the bounding box, if its inside or outside of the triangle. But how to test for it?
The trivial approach is taking all three edge vertices and convert them to vectors by subtracting them. (Lets name them A, B, C)
v1 = A - B
v2 = B - C
v3 = C - A
These vectors rotate (mathematically incorrect) around the center of the triangle. Wasn’t there some formula to check on which side of a vector a point is located? Given that, a point which is on one side of all three vectors has to be inside. The relevant side depends on the way the vectors rotate around the center and which side it actually is.
Searching for some code snipplet I could adopt, I stumbled upon a site which describes something much better. The author suggests to do a coordinate system transformation instead, resulting in fewer instructions.
If you take one vector as origin and the subtraction of the origin from the remaining two as unit vectors, you get a shiny new coordinate system. This new coordinate system is only cartesian, if the angle at the origin was 90°, but that doesn’t matter. You can still define every point in that coordinate system by a linear combination of its unit vectors. Actually that’s how a coordinate system is defined. In pseudocode it looks like this:
P = u * v1 + v * v2
With P being a point, u & v some factors and v1 & v2 your unit vectors.
The beauty about this is, that you can prove each pixel inside the triangle to meet the following conditions:
(u >= 0) && (v >= 0) && (u + v < 1)
To get P you only need to subtract your new origin, u & v are harder to get, but it still takes less effort (measured in instructions) to test if a point is part of a filled triangle. If you are interested, you should read the original site.
By knowing the pixels belonging to a triangle, we could fill it with a constant color. But what about texture? Read on!
Mapping Texture Pixels to Screen Pixels
Again, initially I had no idea how to do this. That is, until a had the factors u & v from the last step. The mapped triangles on the texture may look totally distorted, rotated and … in other words, messed up, but what goes for the triangles on screen, is still valid for the mapped triangles. If we convert them to an origin and two unit vectors, like we did before, we can use the very same factors u & v and do a linear combination of the unit vectors and these factors. That’s really all we need to get the corresponding texture pixel for each screen pixel. The texture pixel is still relative to the origin, but that can be fixed by adding the vector back, we subtracted earlier to get to the new origin.
Knowing this, it seems pretty much obvious, that testing for pixels inside a triangle and mapping texture color to it, should be done inside the same loop.
What about light?
Yes you are right. All of my pixel mapped like this have the full intensity of the texture. To keep it short, light and reflections are doing math with your triangles normal vectors, a vector pointing to your lightsource(s) and a vector pointing to your camera. Calculate angles, weight them, add them and multiply the result with your texture color and you can achieve all sorts of virtual light impressions.
Having a virtual sun might have been interesting, but my code is already slow enough… .
This doesn’t look like nowadays computer games. Could you apply some anti-aliasing?
Surely I could. It’s easy, but at the same time it blows up computation time. I give you an idea on how to do it. We could start by not testing if a pixel is part of a triangle, but if a sub-pixel is. Instead of stepping through the bounding box pixel by pixel, we would only advance half a pixel, a quarter and so on. Since this is a two dimensional coordinate system, your computation time goes up squared. To get a better texture quality, we would need not to copy a single pixel, but to sample the whole n-pixel environment with a weighted gauss kernel for example. Again this would be a horrible thing to do on the CPU.
Es handelt sich um eine veränderte Version des Originalpaketes, welches man bekommt, wenn man OpenSMTPD unter Ubuntu 14.04 installiert, ohne externe Paketquellen hinzugefügt zu haben.
Dazu habe ich OpenSMTPD direkt aus dem Sourcecode des Projekts neu gebaut, ohne dabei distributionsspezifische Paches zu verwenden.
Das resultierende Paket ist eine etwas merkwürdige Mischung aus Teilen des Ubuntu Pakets (mit abgewandeltem Postinstallationsskript) und aktualisierter Software aus meinem Build. Zum Zeitpunkt, als ich OpenSMTPD kompiliert habe, war das Quellarchiv mit der Version 201602131907p1 “latest” und liegt noch hinter der Version 5.7.3p2.
Durch den vom Standardpaket abweichenden Build ergeben sich ein paar Fallstricke:
es wurde gegen libasr (github) gelinkt, welches auch gleich mit enthalten ist
der Nutzername und die Gruppe entsprechen denen, die OpenSMTPD standmässig verwended und damit nicht denen, die das normale Ubuntu Paket verwenden würde
es erwartet ein Rootzertifikat Bundle unter /etc/ssl/cert.pem, es sollte ein symlink auf /etc/ssl/certs/ca-certificates.crt angelegt werden, wenn verbundene Server verifiziert werden sollen
Jetzt wo du fragst… meinen ersten eigenen Mailserver habe ich in 2003 mit sendmail betrieben. Dazu habe ich einen alten Computer verwendet, den ich ohnehin schon als Router für mein brandneues DSL laufen hatte. Als ein Virus damit begann, die Rechner zufälliger Privatpersonen zu missbrauchen, um sich selbst zu verbreiten (das war etwas Neues zu der Zeit), brach meine Internetverbindung ein, weil es mir nicht möglich war, sendmail dazu zu bewegen Mails von bestimmten Adressen zu verwerfen. Es war nur dazu in der Lage, Mails zu verwerfen, nachdem die Mail komplett angenommen war. Infolgedessen war meine Verbindung mit dem Traffic von Mails verstopft, die nur angenommen wurden, um niemals zugestellt zu werden. Dadurch bin ich für einige Jahre auf postfix gewechselt, da es mit postfix ziemlich einfach ist, Verbindungen zu beenden, direkt nachdem ein bestimmtes Wort im Kopf der Mail erkannt wurde. Vor Jahren hatte ich aufgehört, meinen eigenen Mailserver zu betreiben. Das aufkommende und allgegenwärtige Gmail machte ihn überflüssig und sogar hiesige Anbieter konnten die von mir benötigten Funktionen bieten.
Leider bin ich nie wirklich davon losgekommen, Mailserver einzurichten und zu warten. Es gab immer irgendeinen Kunden, der (aus für mich nicht nachvollziehbaren Gründen) darauf bestand, Mails von seinen eigenen Systemen verarbeiten zu lassen. Das wurde in letzter Zeit unangenehm, als immer mehr Anbieter damit begannen, Mails die nicht von den “großen” Anbietern, oder von Servern die neuere Technologien nicht verwendeten, als Spam zu betrachten.
Um mal zum Punkt zu kommen - postfix könnte einfach nicht mehr das sein, was man heutzutage benutzen will, wenn es darum geht mit Mails umzugehen.
Ich habe mir exim4 flüchtig angeschaut, welches verspricht einfacher zu konfigurieren zu sein als postifx und dabei noch performanter. Zum letzteren kann ich keine Aussage treffen, aber die Konfiguration ist definitiv nicht einfacher und das obwohl (möglicherweise sogar gerade weil) damit eine neue Konfigurationssprache vorgestellt wurde.
Als der Konfigurationsdialog mich fragte, ob ich lieber haufenweise kleine Dateien, oder eine einzelne Datei mit mehreren hundert Zeilen haben wollte, fragte ich mich doch, ob das wirklich ernst gemeint ist.
Also dachte ich mir, es könnte mal wieder an der Zeit sein, einen neuen Mailserver auszuprobieren, der verspricht einfacher, sicherer, performanter usw. zu sein. Nicht, dass ich das nicht alles schon mal gehört hätte, aber man weiß ja nie.
Ich entschied OpenSMTPD eine Chance zu geben.
Bevor ich das jedoch tat, hörte ich, dass die Version die man mit Ubuntu 14.04 LTS bekommt veraltet ist und - was noch schlimmer ist - reichlich instabil. Es gab keinen einfachen Weg, eine aktuelle Version aus PPAs oder wenigstens vorgebauten Paketen zu bekommen (wenn man nicht gerade bereit ist, seine Paketquellen mit debian testing zu mischen und ab dem Tag ein Leben in der Hölle zu fristen).
Dies ist die Version, die ich jetzt selbst verwende und bis jetzt funktioniert sie.
Wirst du neuere Builds liefern?
Vermutlich nicht. Ich erwarte keine großen Bugs oder neue Releases, bevor Ubuntu 16.04 freigegeben wird, wodurch wir alle eine brauchbare Version erhalten werden (hoffentlich).
Wie hast du es gebaut?
Tatsächlich indem ich docker verwendet habe. Es hat einige Versuche gebraucht bis ich eine Build Umgebung hatte, die in der Lage war Irgendetwas zu bauen und ich wollte meinen Arbeitsrechner nicht in Unordnung bringen.
Dieses Irgendetwas habe ich genommen und über den Inhalt des originalen Binärpakets gelegt, wobei mir docker diff eine Menge geholfen hat. Das ist natürlich nicht der übliche Weg, um Softwarepakete zu bauen, aber nochmals: Für meine Zwecke ist das ausreichend und ich erwarte, dass es eh in einigen Monaten nicht mehr gebraucht wird.
OpenSMTP ist BSD Software und wurde nicht mit dem Ziel entwickelt, auf diversen Linuxdistributionen möglichst einfach gebaut und verwendet werden zu können. Es war nicht meine Absicht einen Backport von Ubuntu 15.10 zu versuchen. Wenn du also einen solchen suchst, … solltest wo anders weitersuchen, oder vielleicht selbst einen machen.
Wirst du noch für andere Architekturen / Distributionen ein Paket bauen?
Solange ich dafür nicht einen angemessenen Betrag erhalte - ist die Antwort: Nein.
Nichtsdestotrotz hoffe ich, dass dieses Paket vielleicht noch für andere von Nutzen ist.
This is a modified version of the default debian package you get, if you try to install OpenSMTPD on Ubuntu 14.04 with no extra package sources enabled.
I rebuild OpenSMTPD from the projects original sources using no distribution related Patches.
The resulting package is a weird mixture of old stuff from the original package (postinstalltion slightly modified) and a fresh build which dates past version 5.7.3p2.
usernames and groups are OpenSMTPDs defaults - and break Ubuntus naming scheme
it expects a root certificate bundle at /etc/ssl/cert.pem so you probably want to symlink /etc/ssl/certs/ca-certificates.crtin order to be able to validate peer servers certificates Hint:
Now that you ask for it … my first own mailserver was build using standard sendmail in 2003. I used an old computer I had running as a router for my own brand new DSL those days. When a virus started to use random peoples computers as SMTP server to spread itself (that was something new at that time) my whole internet connection broke down, because I found no way tho make sendmail reject mails to certain addresses. It could only drop the mail after reading it in completely. My line was congested with traffic resulting from receiving mails which were never delivered. That made me use postfix for a couple of years, because in postfix it pretty easy dropping an incoming connection right after reading a trigger in the header. I stopped using my own servers for mail several years ago. The upcoming and omnipresent Gmail obsoleted my systems and even local providers caught up.
Unfortunately I never managed to get rid of having to set up and maintain mailservers. I always had customers who (for reasons unknown) insisted in having their own systems handling mails. This recently became an issue, as more and more providers started do consider mails as spam, if it does not come from one of the big players, or the sending server did not implement some new technology.
To get to the point - postfix just might not be what you want to use nowadays when you have to deal with mail.
I had an eye on exim4, which promised to be simpler in configuration than sendmail and postfix and even more performant. I can not speak for the latter, but its configuration is definitely not simpler, even though (and maybe because) they invented a whole new configuration language.
When the configuration dialogue asked me if I want dozens of small files, or if I would rather like one big file of several hundred lines, I was like “Are you kidding me?”
So I thought it might be just the right time to try a new mailserver, promising to be simpler, more secure, more performant and so on. It sounds like I heard that stuff several times before, but you never know.
I decided to give OpenSMTP a try.
Before I did so, I heard the version shipped with Ubuntu 14.04 LTS was outdated and - even worse - unstable. There was no easy way of getting a recent version from PPAs or prebuild packages (unless you want to merge your package sources with debian testing and live in hell from that day on).
This is the version I am currently using and it works for me.
Will you provide newer builds?
Probably not. I do not expect any major bugs or new releases prior the launch of Ubuntu 16.04 LTS which will give us all a decent version (hopefully).
How did you build it?
Actually using docker. It took a few trials to set up a build environment that was capable of getting at least something as a result and I did not want to mess up my development machine.
I took that something and dropped it into the original binary package sources, using docker diff a lot. I know that’s far from how it’s meant to be done, but again: It works for my purpose and I expect this to be obsoleted within a few months.
OpenSMTP is BSD software and not developed with the objective to be build and easily used on several different Linux distributions. I did not mean to do a backport from Ubuntu 15.10, so I did not try to. If you are looking for a backport, well … look somewhere else, or try it yourself.
Will you build for different architectures / distributions?
Unless you are willing to pay a reasonable amount of money - the answer is no.
Nevertheless I hope this is of some use for someone.