Difference between revisions of "BGRABitmap tutorial 15"

From Lazarus wiki
Jump to navigationJump to search
(→‎Scene object: clearing references)
m (→‎Deriving from TBGRAScene3D: Resolved long code line)
 
(One intermediate revision by the same user not shown)
Line 7: Line 7:
 
== Scene object ==
 
== Scene object ==
  
The scene object is in the BGRAScene3D unit. Here is a simple example :
+
The scene object is in the BGRAScene3D unit. Here is a simple example:
<syntaxhighlight>uses BGRAScene3D, BGRABitmap, BGRABitmapTypes;  
+
 
 +
<syntaxhighlight lang="pascal">
 +
uses BGRAScene3D, BGRABitmap, BGRABitmapTypes;  
  
 
procedure TForm1.FormPaint(Sender: TObject);
 
procedure TForm1.FormPaint(Sender: TObject);
Line 49: Line 51:
 
The scene object provides the CreateObject function, which returns an interface to the created object. Objects inside a scene are freed automatically when you free the scene.
 
The scene object provides the CreateObject function, which returns an interface to the created object. Objects inside a scene are freed automatically when you free the scene.
  
An object has a MainPart property which allows to create and access to vertices. Coordinates can be given as three single values, TPoint3D records, or an array of single values which length must be a multiple of 3. When you create a vertex, you receive an IBGRAVertex3D interface which can be used to create faces. You can add sub parts inside MainPart by using CreatePart method. There can be nested subparts, each being rotated with its own matrix relative to main part.
+
An object has a MainPart property which allows you to create and access vertices. Coordinates can be given as three single values, TPoint3D records, or an array of single values which length must be a multiple of 3. When you create a vertex, you receive an IBGRAVertex3D interface which can be used to create faces. You can add sub parts inside MainPart by using the CreatePart method. There can be nested subparts, each being rotated with its own matrix relative to the main part.
  
 
The X axis points to the right, the Y axis points to bottom, and the Z axis points forward (behind the screen). It means that XY works the same way as in a bitmap, and there is a depth value. The faces must be in clockwise order to be visible.
 
The X axis points to the right, the Y axis points to bottom, and the Z axis points forward (behind the screen). It means that XY works the same way as in a bitmap, and there is a depth value. The faces must be in clockwise order to be visible.
Line 59: Line 61:
 
As you can see, the pyramid is viewed from one side, so we can only see one face. There is no lighting either.
 
As you can see, the pyramid is viewed from one side, so we can only see one face. There is no lighting either.
  
In the 'with' block, add the following lines :
+
In the 'with' block, add the following lines:
<syntaxhighlight>   MainPart.Scale(1.3);
+
 
 +
<syntaxhighlight lang="pascal">
 +
    MainPart.Scale(1.3);
 
     MainPart.RotateYDeg(30);
 
     MainPart.RotateYDeg(30);
 
     MainPart.RotateXDeg(20);
 
     MainPart.RotateXDeg(20);
     MainPart.Translate(0,-5,0);  </syntaxhighlight>
+
     MainPart.Translate(0,-5,0);   
The first line makes the object a little bigger. The two rotations are applied. The first is around the Y axis and the second around the X axis. To figure out the rotation sign, imagine you look in the direction of the axis. A positive value in degree means a clockwise rotation, and a positive value in radian means an anti-clockwise rotation.
+
</syntaxhighlight>
 +
 
 +
The first line makes the object a little bigger. Then two rotations are applied. The first is around the Y axis and the second around the X axis. To figure out the rotation sign, imagine you are looking in the direction of the axis. A positive value in degrees means a clockwise rotation, and a positive value in radians means an anti-clockwise rotation.
  
 
Finally a vertical translation is applied to center the object.
 
Finally a vertical translation is applied to center the object.
Line 70: Line 76:
 
[[Image:BGRATutorial15b.png]]
 
[[Image:BGRATutorial15b.png]]
  
Now let's add some lighting :
+
Now let's add some lighting:
<syntaxhighlight> //set ambiant lightness to dark (1 is normal lightness, 2 is complete whiteness)
+
 
 +
<syntaxhighlight lang="pascal">
 +
  //set ambiant lightness to dark (1 is normal lightness, 2 is complete whiteness)
 
   scene.AmbiantLightness := 0.5;
 
   scene.AmbiantLightness := 0.5;
 
   //add a directional light from top-left, maximum lightness will be 0.5 + 1 = 1.5
 
   //add a directional light from top-left, maximum lightness will be 0.5 + 1 = 1.5
   scene.AddDirectionalLight(Point3D(1,1,1), 1);  </syntaxhighlight>
+
   scene.AddDirectionalLight(Point3D(1,1,1), 1);   
 +
</syntaxhighlight>
  
 
The example uses lightness values. 0 means black, 1 means unchanged color, and 2 means white. The directional takes a Point3D parameter to indicate the direction of the ray. It does not need to be normalized.
 
The example uses lightness values. 0 means black, 1 means unchanged color, and 2 means white. The directional takes a Point3D parameter to indicate the direction of the ray. It does not need to be normalized.
Line 82: Line 91:
 
== Deriving from TBGRAScene3D ==
 
== Deriving from TBGRAScene3D ==
  
The scene code can be embedded in an object. Here is the previous example in a single unit :
+
The scene code can be embedded in an object. Here is the previous example in a single unit:
<syntaxhighlight>unit ex1;
+
 
 +
<syntaxhighlight lang="pascal">
 +
unit ex1;
  
 
{$mode objfpc}{$H+}
 
{$mode objfpc}{$H+}
Line 146: Line 157:
 
begin
 
begin
 
   //fill background
 
   //fill background
   Surface.GradientFill(0,0,Surface.Width,Surface.Height,SandColor,MergeBGRA(SandColor,BGRABlack),gtRadial,PointF(0,0),PointF(Surface.Width,Surface.Height),dmSet);
+
   Surface.GradientFill(0,0,Surface.Width,Surface.Height,SandColor,MergeBGRA(SandColor,BGRABlack), gtRadial,PointF(0,0),PointF(Surface.Width,Surface.Height),dmSet);
  
 
   inherited Render;
 
   inherited Render;
 
end;
 
end;
  
end.</syntaxhighlight>
+
end.
 +
</syntaxhighlight>
 +
 
 +
Now, to draw the scene, simply do:
  
Now, to draw the scene, simply do :
+
<syntaxhighlight lang="pascal">
<syntaxhighlight>uses BGRABitmap, BGRABitmapTypes, BGRAScene3D, ex1;
+
uses BGRABitmap, BGRABitmapTypes, BGRAScene3D, ex1;
  
 
procedure TForm1.FormPaint(Sender: TObject);
 
procedure TForm1.FormPaint(Sender: TObject);
Line 170: Line 184:
 
   bmp.Draw(Canvas,0,0);
 
   bmp.Draw(Canvas,0,0);
 
   bmp.Free;
 
   bmp.Free;
end;  </syntaxhighlight>
+
end;   
 +
</syntaxhighlight>
  
 
By the way, antialiasing and a background have been added. Note that antialiasing works only for simple scenes. Complex scenes with texture and transparent colors may not render correctly.
 
By the way, antialiasing and a background have been added. Note that antialiasing works only for simple scenes. Complex scenes with texture and transparent colors may not render correctly.

Latest revision as of 07:51, 5 January 2020

Deutsch (de) English (en)


Home | Tutorial 1 | Tutorial 2 | Tutorial 3 | Tutorial 4 | Tutorial 5 | Tutorial 6 | Tutorial 7 | Tutorial 8 | Tutorial 9 | Tutorial 10 | Tutorial 11 | Tutorial 12 | Tutorial 13 | Tutorial 14 | Tutorial 15 | Tutorial 16 | Edit

This tutorial shows how to render 3D objects using the TBGRAScene3D object.

Scene object

The scene object is in the BGRAScene3D unit. Here is a simple example:

uses BGRAScene3D, BGRABitmap, BGRABitmapTypes; 

procedure TForm1.FormPaint(Sender: TObject);
var
  scene: TBGRAScene3D;
  bmp: TBGRABitmap;
  base: array of IBGRAVertex3D;
  topV: IBGRAVertex3D;
begin
  bmp := TBGRABitmap.Create(ClientWidth,ClientHeight,BGRABlack);

  scene := TBGRAScene3D.Create(bmp);
  //create a pyramid
  with scene.CreateObject(BGRA(255,240,128)) do
  begin
    //create vertices
    topV := MainPart.Add(0,-15,0);
    //pyramid base is in a clockwise order if we look at the pyramid from below
    base := MainPart.Add([-20,15,-20, 20,15,-20, 20,15,20, -20,15,20]);

    AddFace(base);
    //add four faces, the three vertices are in a clockwise order
    AddFace([base[0],topV,base[1]]);
    AddFace([base[1],topV,base[2]]);
    AddFace([base[2],topV,base[3]]);
    AddFace([base[3],topV,base[0]]);
    topV := nil;
    base := nil;
  end;
  scene.Render;
  scene.Free;

  bmp.Draw(Canvas,0,0);
  bmp.Free;
end;

The scene object draws itself on a TBGRABitmap object. You can either pass the bitmap as a parameter when creating the object, as done here, or assign the Surface property afterwards. The scene is automatically centered in the bitmap.

The scene object provides the CreateObject function, which returns an interface to the created object. Objects inside a scene are freed automatically when you free the scene.

An object has a MainPart property which allows you to create and access vertices. Coordinates can be given as three single values, TPoint3D records, or an array of single values which length must be a multiple of 3. When you create a vertex, you receive an IBGRAVertex3D interface which can be used to create faces. You can add sub parts inside MainPart by using the CreatePart method. There can be nested subparts, each being rotated with its own matrix relative to the main part.

The X axis points to the right, the Y axis points to bottom, and the Z axis points forward (behind the screen). It means that XY works the same way as in a bitmap, and there is a depth value. The faces must be in clockwise order to be visible.

Here the color is set for the whole object when creating it, but you can set it individually for each face, or for each vertex.

BGRATutorial15a.png

As you can see, the pyramid is viewed from one side, so we can only see one face. There is no lighting either.

In the 'with' block, add the following lines:

    MainPart.Scale(1.3);
    MainPart.RotateYDeg(30);
    MainPart.RotateXDeg(20);
    MainPart.Translate(0,-5,0);

The first line makes the object a little bigger. Then two rotations are applied. The first is around the Y axis and the second around the X axis. To figure out the rotation sign, imagine you are looking in the direction of the axis. A positive value in degrees means a clockwise rotation, and a positive value in radians means an anti-clockwise rotation.

Finally a vertical translation is applied to center the object.

BGRATutorial15b.png

Now let's add some lighting:

  //set ambiant lightness to dark (1 is normal lightness, 2 is complete whiteness)
  scene.AmbiantLightness := 0.5;
  //add a directional light from top-left, maximum lightness will be 0.5 + 1 = 1.5
  scene.AddDirectionalLight(Point3D(1,1,1), 1);

The example uses lightness values. 0 means black, 1 means unchanged color, and 2 means white. The directional takes a Point3D parameter to indicate the direction of the ray. It does not need to be normalized.

BGRATutorial15c.png

Deriving from TBGRAScene3D

The scene code can be embedded in an object. Here is the previous example in a single unit:

unit ex1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, BGRAScene3D, BGRABitmapTypes;

type

  { TExample1 }

  TExample1 = class(TBGRAScene3D)
    SandColor: TBGRAPixel;
    constructor Create;
    procedure Render; override;
  end;

implementation

{ TExample1 }

constructor TExample1.Create;
var
  base: array of IBGRAVertex3D;
  top: IBGRAVertex3D;
begin
  inherited Create;

  SandColor := BGRA(255,240,128);

  //create a pyramid
  with CreateObject(SandColor) do
  begin
    top := MainPart.Add(0,-15,0);
    //pyramid base is in a clockwise order if we look at the pyramid from below
    base := MainPart.Add([-20,15,-20, 20,15,-20, 20,15,20, -20,15,20]);
    AddFace(base);
    //add four faces, the three vertices are in a clockwise order
    AddFace([base[0],top,base[1]]);
    AddFace([base[1],top,base[2]]);
    AddFace([base[2],top,base[3]]);
    AddFace([base[3],top,base[0]]);

    MainPart.Scale(1.3);
    MainPart.RotateYDeg(30);
    MainPart.RotateXDeg(20);
    MainPart.Translate(0,-5,0);
  end;

  //set ambiant lightness to dark (1 is normal lightness, 2 is complete whiteness)
  AmbiantLightness := 0.5;
  //add a directional light from top-left, maximum lightness will be 0.5 + 1 = 1.5
  AddDirectionalLight(Point3D(1,1,1),1);

  //we can have antialiasing because it is a simple scene
  Antialiasing := True;
end;

procedure TExample1.Render;
begin
  //fill background
  Surface.GradientFill(0,0,Surface.Width,Surface.Height,SandColor,MergeBGRA(SandColor,BGRABlack), gtRadial,PointF(0,0),PointF(Surface.Width,Surface.Height),dmSet);

  inherited Render;
end;

end.

Now, to draw the scene, simply do:

uses BGRABitmap, BGRABitmapTypes, BGRAScene3D, ex1;

procedure TForm1.FormPaint(Sender: TObject);
var
  bmp: TBGRABitmap;
  scene: TBGRAScene3D;
begin
  bmp := TBGRABitmap.Create(ClientWidth,ClientHeight,BGRABlack);

  scene := TExample1.Create;
  scene.Surface := bmp;
  scene.Render;
  scene.Free;

  bmp.Draw(Canvas,0,0);
  bmp.Free;
end;

By the way, antialiasing and a background have been added. Note that antialiasing works only for simple scenes. Complex scenes with texture and transparent colors may not render correctly.

BGRATutorial15d.png


Previous tutorial (canvas 2D) | Next tutorial (textures with 3D objects)