Bildmanipulation und Texturzuordnung mit HTML5 -Leinwand?
-
23-10-2019 - |
Frage
In einem 3D -Motor, an dem ich arbeite, habe ich erfolgreich einen Würfel in 3D gezogen. Die einzige Methode, um die Seiten zu füllen, besteht darin, für mich entweder eine feste Farbe oder einen Gradienten zu verwenden. Um die Dinge aufregender zu machen, würde ich gerne Texturzuordnung mit einer einfachen Bitmap implementieren.
Der Punkt ist, dass ich kaum Artikel oder Codeproben zum Thema Bildmanipulation in JavaScript finden kann. Darüber hinaus scheint die Bildunterstützung in HTML5 -Leinwand auf Beschneidung beschränkt zu sein.
Wie könnte ich eine Bitmap strecken, damit eine rechteckige Bitmap ein unreguläres Würfelgesicht füllen kann? In 2D ist ein projiziertes quadratisches Würfelgesicht aufgrund der Perspektive nicht von einer quadratischen Form, daher muss ich es dehnen, um es in jedes Viereck zu passen.
Hoffentlich verdeutlicht dieses Bild meinen Standpunkt. Das linke Gesicht ist jetzt mit einem weißen/schwarzen Gradienten gefüllt. Wie könnte ich es mit einer Bitmap füllen, nachdem es texturgestopft wurde?
Hat jemand irgendwelche Tipps zur perspektivischen Texturzuordnung (oder zum Bildmanipulation) mit JavaScript und HTML5 -Leinwand?
Bearbeiten: Ich habe es zum Laufen gebracht, dank 6502!
Es ist jedoch eher CPU -intensiv, also würde ich gerne Optimierungsideen hören.
Lösung
Ich denke, Sie werden nie ein genaues Ergebnis erzielen ... Ich habe einige Zeit damit verbracht, wie man 3D -Grafiken mithilfe von Canvas -2D -Kontext durchführt, und fand es lebensfähig, eine Gouraud -Schattierung von Texturzuordnung durchzuführen, indem sie geeignete 2D -Gradienten und -Matrizen berechnen:
- Feste Polygone sind natürlich einfach
- Die Gouraud -Füllung ist nur für eine Komponente möglich (dh Sie können kein Dreieck haben, bei dem jeder Scheitelpunkt eine willkürliche RGB ist, die mit einer bilinearen Interpolation gefüllt ist. Sie können diese Füllung jedoch mit drei beliebigen Farbtönen einer einzelnen Farbe durchführen).
- Die lineare Texturzuordnung kann mithilfe von Clipping und Bildzeichnung erfolgen
Ich würde eine perspektive korrekte Texturzuordnung mithilfe von Mesh-Unterteilung (wie auf PS1) implementieren.
Ich fand jedoch viele Probleme ... zum Beispiel Bildzeichnung mit einer Matrix-Transformation (für die Texturzuordnung erforderlich) ist für Chrome und IMO recht ungenau, es ist unmöglich, ein pixel genaues Ergebnis zu erzielen. Im Allgemeinen gibt es keine Möglichkeit, das Antialiasing beim Zeichnen auf eine Leinwand auszuschalten, und dies bedeutet, dass Sie sich bei der Unterteilung in Dreiecken sichtbare durchsichtige durchdachte Linien erhalten. Ich fand auch, dass Multipass-Rendering bei Chrome wirklich schlecht funktioniert (wahrscheinlich wegen der Implementierung des HW-Accterated-Renderings).
Im Allgemeinen ist diese Art von Rendering sicherlich ein Stress für Webbrowser, und anscheinend werden diese Anwendungsfälle (zum Beispiel seltsame Matrizen) nicht sehr gut getestet. Ich konnte sogar Firefox so schlecht krachen, dass es das gesamte X Susbsystem auf meinem Ubuntu niedergeschlagen hat.
Sie können die Ergebnisse meiner Bemühungen sehen hier oder als Video hier... IMO beeindruckt sicherlich, dass dies in einem Browser ohne Verwendung von 3D -Erweiterungen erfolgen kann, aber ich denke nicht, dass aktuelle Probleme in Zukunft behoben werden.
Wie auch immer, die Grundidee, die zum Zeichnen eines Bildes verwendet wurde, so dass die 4 Ecken in bestimmten Pixel -Position enden, besteht darin, zwei Dreiecke zu zeichnen, von denen jede bilineare Interpolation verwendet.
Im folgenden Code gehe ich davon aus, dass Sie ein Bildobjekt haben texture
und 4 Ecken von jeweils ein Objekt mit Feldern x,y,u,v
wo x,y
sind Pixelkoordinaten auf der Ziel -Leinwand und u,v
sind Pixelkoordinaten auf texture
:
function textureMap(ctx, texture, pts) {
var tris = [[0, 1, 2], [2, 3, 0]]; // Split in two triangles
for (var t=0; t<2; t++) {
var pp = tris[t];
var x0 = pts[pp[0]].x, x1 = pts[pp[1]].x, x2 = pts[pp[2]].x;
var y0 = pts[pp[0]].y, y1 = pts[pp[1]].y, y2 = pts[pp[2]].y;
var u0 = pts[pp[0]].u, u1 = pts[pp[1]].u, u2 = pts[pp[2]].u;
var v0 = pts[pp[0]].v, v1 = pts[pp[1]].v, v2 = pts[pp[2]].v;
// Set clipping area so that only pixels inside the triangle will
// be affected by the image drawing operation
ctx.save(); ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2); ctx.closePath(); ctx.clip();
// Compute matrix transform
var delta = u0*v1 + v0*u2 + u1*v2 - v1*u2 - v0*u1 - u0*v2;
var delta_a = x0*v1 + v0*x2 + x1*v2 - v1*x2 - v0*x1 - x0*v2;
var delta_b = u0*x1 + x0*u2 + u1*x2 - x1*u2 - x0*u1 - u0*x2;
var delta_c = u0*v1*x2 + v0*x1*u2 + x0*u1*v2 - x0*v1*u2
- v0*u1*x2 - u0*x1*v2;
var delta_d = y0*v1 + v0*y2 + y1*v2 - v1*y2 - v0*y1 - y0*v2;
var delta_e = u0*y1 + y0*u2 + u1*y2 - y1*u2 - y0*u1 - u0*y2;
var delta_f = u0*v1*y2 + v0*y1*u2 + y0*u1*v2 - y0*v1*u2
- v0*u1*y2 - u0*y1*v2;
// Draw the transformed image
ctx.transform(delta_a/delta, delta_d/delta,
delta_b/delta, delta_e/delta,
delta_c/delta, delta_f/delta);
ctx.drawImage(texture, 0, 0);
ctx.restore();
}
}
Diese hässlichen seltsamen Formeln für all diese "Delta" -Variablen werden verwendet, um zwei lineare Systeme von drei Gleichungen in drei Unbekannten zu lösen Cramers Methode und Sarrus Schema für 3x3 -Determinanten.
Insbesondere suchen wir nach den Werten von a
, b
, ... f
so dass die folgenden Gleichungen zufrieden sind
a*u0 + b*v0 + c = x0
a*u1 + b*v1 + c = x1
a*u2 + b*v2 + c = x2
d*u0 + e*v0 + f = y0
d*u1 + e*v1 + f = y1
d*u2 + e*v2 + f = y2
delta
ist die Determinante der Matrix
u0 v0 1
u1 v1 1
u2 v2 1
und zum Beispiel delta_a
ist die Determinante derselben Matrix, wenn Sie die erste Spalte durch ersetzen x0
, x1
, x2
. Mit diesen können Sie berechnen a = delta_a / delta
.