Hallo,
ich habe ja vor einigen Monaten nach Hilfe gefragt um Backface-Culling und Z-Buffering in mein Isometrie-Zeichenprogramm einzubinden.
Leider hatte ich aufgrund von Zwischenfällen die mir einige Zeit und Nerv geraubt hat keine Zeit mich weiter um das Programm zu kümmern und konnte in der Zeit nur Backface-Culling implementieren und habe das Z-Buffering stehen gelassen.
Vor einigen Tagen kam mir jedoch eine Idee wie ich das (theoretisch) einfach lösen kann und momentan sieht es schon ziemlich gut aus, habe jedoch einen kleinen Fehler drinnen.
Und zwar probiere ich es gerade so, dass ich jedes Polygon einzeln auf ein Bitmap zeichnen lasse in der Position die es später auch haben soll. (Dazu sind noch Bilder angefügt, da Bilder bekannterweise mehr als 1000 Worte sagen
)
Nun habe ich pro Polygon ein Bitmap bei der ich dann Pixel für Pixel nach gezeichneten Stellen suche und, falls ein gezeichneter Pixel vorhanden ist, dessen Position im 3D-Raum zurückrechne um dann berechnen zu können wie "nah" der Pixel von der Kamera ist. Am Schluss kombiniere ich einfach alle Bitmaps zu einem und lasse anhand der Pixeltiefe entscheiden ob es zu sehen ist oder nicht. Ich glaube genau da liegt der Fehle, beim bestimmen welche Tiefe der Pixel hat. (Im Bild im Anhang kann man den Fehler gut erkennen wenn man reinscrollt und beide Ergebnisse mal ein vergleicht. Beim vom Programm erzeugten sieht verläuft zum beispiel die Linie vom 2. Polygon nicht parallel zu den anderen vom 1. Polygon, das kann ja schon mal nicht stimmen
)
Um die Koordinate wieder zurück zu rechnen benutze ich folgende Methode: (Das ganze Projekt ist auch im Anhang)
Delphi-Quellcode:
TPolygon3D = array of TVector3D;
function CalculateCoordinate(APolygon3D : TPolygon3D; APoint : TPointF) : TVector3D;
var
APolygon : TPolygon;
LLength,c : integer;
LDistance, LMaxDistance : double;
LPercent : double;
LX,LY,LZ : double;
begin
APolygon := Polygon3Dto2D(APolygon3D);
LLength := Length(APolygon);
LMaxDistance := 0;
for c := 0 to LLength-1 do
begin
LDistance := APoint.Distance(APolygon[c]);
LMaxDistance := LMaxDistance + LDistance;
end;
LX := 0;
LY := 0;
LZ := 0;
for c := 0 to LLength-1 do
begin
LDistance := APoint.Distance(APolygon[c]);
LPercent := (1 / LMaxDistance) * LDistance;
LX := LX + (APolygon3D[c].X * LPercent);
LY := LY + (APolygon3D[c].Y * LPercent);
LZ := LZ + (APolygon3D[c].Z * LPercent);
end;
Result := Vector3D(LX,LY,LZ);
end;
um einen 3D Isometrischen Punkt ins 2D zu berechnen das hier:
Delphi-Quellcode:
function Polar2D(AX,AY,AAngle,ADistance : Double) : TPointF;
begin
Result.x := (Cos(DegToRad(AAngle)) * ADistance) + AX;
Result.y := (Sin(DegToRad(AAngle)) * ADistance) + AY;
end;
function IsoToScreen(AVector : TVector3D) : TPointF;
var
LPoint : TPointF;
begin
LPoint := PointF(0,0);
LPoint := Polar2D(LPoint.X, LPoint.Y, 30, AVector.X);
LPoint := Polar2D(LPoint.X, LPoint.Y, 150, AVector.Y);
LPoint := Polar2D(LPoint.X, LPoint.Y, 270, AVector.Z);
Result := LPoint;
end;
Das erstellen der "PixelMatrix"
Delphi-Quellcode:
procedure TPixelBuffer.CreateMatrix(APolygon: TPolygon3D; AFillColor : TAlphaColor);
var
LBitmap : TBitmap;
LData : TBitmapData;
x: Integer;
y: Integer;
LX,LY : integer;
LRect : TRect;
LCam : TVector3d;
begin
LBitmap := TBitmap.Create(500,500);
LBitmap.Canvas.BeginScene();
LBitmap.Canvas.Fill.Color := AFillColor;
LBitmap.Canvas.FillPolygon(Polygon3dTo2d(APolygon),1);
LBitmap.Canvas.EndScene;
LBitmap.SaveToFile(Random(10).ToString + '.png');
LBitmap.Map(TMapAccess.Read,LData);
LRect := PolygonRect(Polygon3Dto2D(APolygon));
for x := LRect.Left to LRect.Right do
begin
for y := LRect.Top to LRect.Bottom do
begin
if LData.GetPixel(X,Y) <> $00000000 then
begin
LX := X;
LY := Y;
Pixels[LX,LY].Color := LData.GetPixel(LX,LY);
Pixels[LX,LY].Vector := CalculateCoordinate(APolygon,PointF(LX,LY));
LCam := Vector3D(Pixels[LX,LY].Vector.X-Pixels[LX,LY].Vector.Z,Pixels[LX,LY].Vector.Y-Pixels[LX,LY].Vector.Z,0);
Pixels[LX,LY].Depth := LCam.Distance(Pixels[LX,LY].Vector); // Hier wird die Tiefe berechnet
end;
end;
end;
LBitmap.Free;
end;
Und das Rendern:
Delphi-Quellcode:
procedure TRenderer.Render(APolygons: array of TPolygon3D);
var
c: Integer;
LBuffer : TPixelBuffer;
Colors : array of TAlphaColor;
LBitmap : TBitmap;
LData : TBitmapData;
x: Integer;
y: Integer;
begin
Colors := [$FFFF0000,$FFFF00FF,$FF00FF00,$FF0000FF];
for c := 0 to Length(Apolygons)-1 do
begin
LBuffer := TPixelBuffer.Create(500,500);
LBuffer.CreateMatrix(APolygons[c],Colors[c]);
Buffers.Add(LBuffer);
end;
LBitmap := TBitmap.Create(500,500);
LBitmap.Map(TMapAccess.Write, LData);
LBuffer := TPixelBuffer.Create(500,500);
for x := 0 to 499 do // Hier wird noch mal die finale Matrix erstellt
begin
for y := 0 to 499 do
begin
for c := 0 to Buffers.Count-1 do
begin
if (Buffers[c].Pixels[x,y].Depth < Lbuffer.Pixels[x,y].Depth) then // Überprüfung ob der Pixel gezeichnet werden soll oder ob er überdeckt ist
begin
Lbuffer.Pixels[x,y].Depth := Buffers[c].Pixels[x,y].Depth;
Lbuffer.Pixels[x,y].Height := Buffers[c].Pixels[x,y].Height;
LBuffer.Pixels[x,y].Color := Buffers[c].Pixels[x,y].Color;
end;
end;
end;
end;
for x := 0 to 499 do
begin
for y := 0 to 499 do
begin
LData.SetPixel(X,Y,LBuffer.Pixels[X,Y].Color);
end;
end;
LBitmap.SaveToFile('./test.png');
end;
Im anhang sieht man das es auch schon fast funktioniert, jedoch glaube ich das die Tiefenberechnung fehlerhaft ist oder das vielleicht die
CalculateCoordinate auch nicht wirklich gut gelöst ist :/
Ich hoffe ich habe mich so ausgedrückt das man mein Anliegen gut versteht, ist schon ziemlich spät und muss nun schlafen sonst raubt mir Delphi wieder mal die ganze Nacht... und den Tag darauf sowieso!
Edit: Ach ja der code wurde auf die schnelle geschrieben als "proof of concept", im Zeichenprogramm hat der nichts zu suchen
Freundliche Grüsse