Skip to content

"RasterrainObjPas" - Simple Ray Tracer in Object Pascal

I always wanted to implement a ray tracer in my favourite Object Pascal programming language. The thing is that it is not trivial, especially when you start reading books like "Physically Based Rendering" you realize that implementing a ray tracer might take a lot of time and work.

Recently on YouTube I have come across a serie of 9 episodes "Raytracer from Scratch in C++" by Caleb Piercy. In these shorter and longer movies you can see the process of writing the ray tracer C++ console Windows application that generates a bmp file with a nice image from the very beginning of setting up the project until the fully functional program. Caleb uses a simple code editor and a makefile to build his application.

The original C++ source code of this program is available as open source from Sourceforge. The project is called "rasterrain".

I have decided to give it a try and translate "rasterrain" project to Object Pascal with Delphi XE7.

The first step was to create in C++Builder XE7 a console project and move over to it the original C++ code from SourceForge.

Delphi XE7 (since version XE6) has a dedicated unit called "System.Math.Vectors" that defines classes, routines, types and variables for using vectors and matrixes. Especially useful in the context of implementing a ray tracer is TPoint3D record type that contains the most important linear algebra routines like dotProduct, crossProduct and so on.

The second useful code for implementing a ray tracer are global procedures in the "FMX.Types3D" unit including: RayCastCuboidIntersect, RayCastEllipsoidIntersect, RayCastPlaneIntersect, RayCastTriangleIntersect and RayCastSphereIntersect.

Another very useful type for working with color is the TAlphaColorF record type defined in System.UITypes namespace that stores R,G,B and A information as float values and defines a number of useful operations like adding two colors and so on.

Initially I started from using these prebuilt Delphi types, but quickly realized that it gets complicated not only to translate from C++ to Object Pascal, but also rewriting a lot of code. At the end I have decided to try to mimic C++ code as match as possible in Object Pascal.

The "RasterrainObjPas" is made up of two projects: one multi-device FireMonkey and the second Windows VCL. Both projects are using the same "TSimpleRayTracer" class defined in the "uSimpleRayTracer" unit. The actual ray tracer Object Pascal code does not contain any GUI-specific code and can be used in VCL, FireMonkey or even in a console app. The VCL project can be compiled for Windows only and FireMonkey can be built not only for Windows, but also for Mac, iOS and Android.

Simple Ray Tracer in Object Pascal is available for download from Embarcadero Code Central and the project is called "RasterrainObjPas".

The ray tracer has a very simple interface. The "RGBType" is a platform-agnostic data structure for exchanging color information. The actual color is stored as a record with three float values, for red, green and blue intensities.

type
  RGBType = record
    r, g, b: Double;
  end;
  TSimpleRayTracer = class
  private
    //.. private declaration
  public
    constructor Create(aWidth, aHeight: integer; aAntiAliasingLevel: integer = 1);
    destructor Destroy; override;
    function getWidth: integer;
    function getHeight: integer;
    function getPixelData(x, y: integer): RGBType;
    procedure CalculatePixelColors;
  end;

In the constructor you can specify the size of the image to be generated, including the anti-aliasing level, which is by default "1", which means no anti-aliasing. After constructing an instance of the ray tracer you need to call its "CalculatePixelColors" procedure that calculates colors of every pixel and store them in an internal array of RGB values that can be read using "getPixelData" method that takes x, y coordinates of a pixel and returns "RGBType" value. This can be used to create either a FireMonkey bitmap or a VCL bitmap for display and this is the job of "TRendererFMX" and "TRendererVCL" classes that encapsulate the TSimpleRayTracer class and provide "DoRender" functions that return either a VCL or FireMonkey bitmaps.

Here is the implementation of the FireMonkey "renderer" that converts platform-neutral color information from simple ray tracer class into a FireMonkey bitmap to be displayed in a multi-device application.

unit uRendererFMX;

interface

uses System.UITypes, FMX.Graphics, uSimpleRayTracer;

type
  TRendererFMX = class
  private
    FSimpleRayTracer: TSimpleRayTracer;
    function GetPixelColor(x,y: integer): TAlphaColor;
  public
    constructor Create(aWidth, aHeight: integer; aAntiAlias: integer = 1);
    destructor Destroy; override;
    function DoRender: TBitmap;
 end;

implementation

{ TRendererFMX }

constructor TRendererFMX.Create(aWidth, aHeight: integer; aAntiAlias: integer = 1);
begin
  FSimpleRayTracer := TSimpleRayTracer.Create(aWidth, aHeight, aAntiAlias);
end;

destructor TRendererFMX.Destroy;
begin
  FSimpleRayTracer.Free;
  inherited;
end;

function TRendererFMX.DoRender: TBitmap;
var bmp: TBitmap; data: TBitmapData; w,h,x,y: integer; c: TAlphaColor;
begin
  FSimpleRayTracer.CalculatePixelColors;
  w := FSimpleRayTracer.getWidth;
  h := FSimpleRayTracer.getHeight;
  bmp := TBitmap.Create;
  bmp.Width := w;
  bmp.Height := h;
  bmp.Map(TMapAccess.Write, data);
  try
    for x := 0 to w - 1 do
      for y := 0 to h - 1 do
      begin
        c := GetPixelColor(x, h-y);
        data.SetPixel(x, y, c);
      end;
  finally
    bmp.Unmap(data);
  end;

  Result := bmp;
end;

function TRendererFMX.GetPixelColor(x, y: integer): TAlphaColor;
var c: RGBType; colorF: TAlphaColorF;
begin
  c := FSimpleRayTracer.getPixelData(x, y);
  colorF := TAlphaColorF.Create(c.r, c.g, c.b);
  Result := colorF.ToAlphaColor;
end;

end.

And here is the code in the FireMonkey form that displays the bitmap in an image control and shows how much time it took to generate the image.

uses uRendererFMX, System.Diagnostics;

procedure TFormRenderFMX.btnRenderClick(Sender: TObject);
var sw: TStopwatch; r: TRendererFMX; bmp: TBitmap;
begin
  LabelTime.Text := 'Rendering... Please wait.';
  self.Invalidate;
  Application.ProcessMessages;

  sw := TStopwatch.StartNew;
  r := TRendererFMX.Create(round(Image1.Width), round(Image1.Height), round(TrackBarAntiAliasingLevel.Value));
  bmp := r.DoRender;
  Image1.Bitmap.Assign(bmp);
  sw.Stop;
  LabelTime.Text := IntToStr(sw.ElapsedMilliseconds) + ' msec (' + IntToStr(sw.ElapsedTicks) + ' ticks)';
end;

There is also a track bar on the form to select anti-aliasing. In the original C++ code the value for controlling anti-aliasing is hardcoded to "1", which means that for calculating the color of the pixel there is just one ray used. The higher the "anti-aliasing" level, the more rays are used to calculate color. For example for the value of 2 there are 4 rays used and the resulting color is the average. For the value of 3 there is an internal 3×3 grid, which means 9 rays and so on. The higher the anti-aliasing level, the actual color calculation takes longer, but the quality is better. The image embedded in this post has been rendered with anti-aliasing level 10. Which means there were 100 rays used to calculate every pixel and on my virtual machine it took around 40 seconds.

I’m not going to go deeply into the internals of the actual TSimpleRayTracer class, because its functionality is discussed in a very detailed way in original YouTube episodes "RayTracer from Scratch in C++".

The full source code of Simple Ray Tracer in Object Pascal can downloaded from the Embarcadero CodeCentral.

"Modernize Your VCL App Today!" webinar recording and demos

It was fun to do the "Modernize Your VCL App Today!" webinar:-)

During the session I have promised to make the recording and demos source code available.

The recording is rather big (548MB) and can be downloaded from here and demos source code has been uploaded to the Embarcadero CodeCentral.

  • mastapp. Original Delphi 7 BDE demo project
  • MarineAdventures. Mastapp upgraded to Delphi XE7 with VCL styles and JumpList component
  • StylingDemo1. Demo illustrating working with "StyledElements" property for customizing styled VCL controls.
  • JumpListFiles. Demo of the new XE7 TJumpList component
  • MetroGrid1. Demo showing VCL Metropolis UI with user selectable VCL styles.
  • Ribbon. Demo project showing Ribbon UI.
  • ParallelTasksAndFutures. Standard Parallel Programming Library demo illustrating syntax for tasks and future
  • ParallelMandelTest. Mandelbrot fractal generation application with added support for parallel "for" loop
  • FireDACResource. Enterprise Mobility Services demo with FireDAC.

The session was logically divided into two parts. The first one was about using the latest XE7 technologies to modernize the application user interface, application logic and data access. The second one was on moving to multitier, scalable architectures like EMS and DataSnap.

"Modernize Your VCL Applications Today" webinar, Dec 16th, 11am CET

If you have an existing VCL application and you want to modernize it to the latest technologies included in Delphi XE7, C++Builder XE7 and RAD Studio XE7 - this webinar is for YOU!

Next week on Tuesday, December 16th, 11am Amsterdam time, I’m going to do a live, demo-oriented technical free webinar that will cover working with VCL styles, new Parallel Programming Library, migrating to FireDAC database access framework and building scalable, multitier solutions with DataSnap and Enterprise Mobility Services (EMS) technologies.

I have been doing "VCL Modernization" live presentations in previous few weeks in a number of different cities in Europe. If you have missed the live event, now it is the opportunity to see it online!

That’s incredible how many applications have been written in the past 20 years with Delphi using the VCL, the Visual Component Library. These applications do all kinds of things and typically interact with databases. In this session I’m going to cover how to move these projects to the latest technologies at different levels of a typical application from user interface, through application logic to database access. We are also going to see what it takes to move to mobile technologies and multitier architectures. The day after my webinar there will be Object Pascal master session from Marcu Cantu, so I will not be going to deep into the new features of the programming language of Delphi and focus on everything else;-)

The webinar is free, it will take approximately an hour and will be followed with the Q&A session, so anybody will be able to ask questions and hopefully get answers!

In order to join the free webinar you need to register here!

Making "Color to Gray" conversion algorithm parallel

One of the most important new features in RAD Studio XE7 is new "Parallel Programming Library". It is available for both Object Pascal and C++, and for both VCL and FireMonkey, multi-device projects.

During the recent CodeRage 9 virtual conference there were two very informative sessions on parallel programming from Allen Bauer and Danny Wind.

A few days ago I was doing VCL application modernisation workshop in Utrecht and thought it would be good to take an existing VCL application and see what it takes to make it parallel.

A few years ago I was playing with different ways of accessing individual pixels in the VCL bitmap on the example of "Color to Gray" algorithm. Clearly using bitmap’s scanline is a few levels of magnitude faster than access through "Pixels" property. The differences in speed are discussed here: http://blogs.embarcadero.com/pawelglowacki/2010/04/15/39051

In the "color to gray" algorithm the color of every pixel is calculated independently from each other is a nested "for" loop. This is a perfect case where we can benefit from refactoring the code to use a parallel loop instead.

Let’s consider a simple example of an existing code that we would like to convert:

procedure ToGrayPixelsNoParallel(aBitmap: Graphics.TBitmap; cg: TColor2Grayscale = c2gLuminosity);
var w, h: integer;
begin
  if aBitmap <> nil then
    for h := 0 to aBitmap.Height - 1 do
      for w := 0 to aBitmap.Width - 1 do
        aBitmap.Canvas.Pixels[w,h] :=
          ColorToGray(aBitmap.Canvas.Pixels[w,h], cg);
end;

Here we have two nested "for" loops. On my machine I have only two virtual cores and on a physical one - just four. That is why there is no point in turning both loops to "parallel". We only want to convert the "outer" one. Here is the code after "parallelization":

procedure ToGrayPixelsParallel(aBitmap: Graphics.TBitmap; cg: TColor2Grayscale = c2gLuminosity);
var h: integer;
begin
  if aBitmap <> nil then
    TParallel.For(0, aBitmap.Height-1,
      procedure(h: integer)
      var w: integer;
      begin
        for w := 0 to aBitmap.Width - 1 do
          aBitmap.Canvas.Pixels[w,h] :=
            ColorToGray(aBitmap.Canvas.Pixels[w,h], cg);
      end
    );
end;

The key thing to look for is using local variables. Notice that I had to move "w: integer" local variable into the body of the anonymous method that is being executed. If I would not do it, the same variable would be shared by different threads of execution leading to errors.

And yes. The parallel versions of both "pixels" and "scan line" color to gray are working as expected on average two times faster on my two virtual cores inside of my virtual Windows machine running on Mac.

The source code of the test "PixelsVsScanLineParallelToGray" Delphi XE7 VCL project is available for download from Embarcadero Code Central.

VCL FireDAC Workshop in Warsaw

Good fun!

I have promised a while ago to stop blogging here, but it is going to be a parallel pattern for a while;-)

http://community.embarcadero.com/index.php/blogs/entry/modernize-your-vcl-database-apps-with-firedac-and-delphi-xe7-workshop-in-warsaw

Modernize Your VCL Database Apps with FireDAC and Delphi XE7 workshop in Warsaw

I’m just back from the city I grew up - Warsaw. At the 15th floor of the "all-glass" sky scraper there were 29 Delphi-addicted programmers playing with Delphi XE7 FireDAC database access technology on their on laptops.

FireDAC and Object Pascal in Action clearly rulez!

My "C++ 3D FireMonkey Programming" CodeRage 9 session

The CodeRage 9 virtual conference has started and I’m now online at the C++ track watching parallel programming session by David I. This is all about the new Parallel Programming Library introduced in C++Builder XE7, Delphi XE7, RAD Studio XE7 and Appmethod 2014 September release.

My C++ "3D FireMonkey Programming" session is next and I have just uploaded my live demos source code to Code Central here: http://cc.embarcadero.com/item/30030

There are three C++ demos built with Appmethod:

  • SimpleEarth3D demonstrates basics of building a multi-device, cross-platform, interactive 3D app. It is based on the Form3D and uses a sphere component and a texture material component for loading "Earth" texture graphics. There is also "float animation" component to rotate Earth and OnClick event on the sphere that shows how to add interactivity to your 3D view.
  • SpaceDebris is a demo that shows how to use TModel3D component to load arbitrary 3D geometry. Instead of using a Form3D, this project is using a regular Form class and displays 3D inside of the Viewport3D component. It is also using explicit Camera and Light components.
  • CustomerInfo3D project shows how to add a "3D-twist" to your traditional database, enterprise applications. The project contains two Layer3D components placed inside of the Viewport3D that are used to host traditional two-dimensional controls like labels and buttons to display data coming from the "Customer" in-memory FireDAC "TFDMemTable" component using Visual LiveBindings. Initially the app looks like a regular 2D app, but when you click on the "info" button in the customer information a secondary layer displays with a 3D rotation more details about the current customer.

The source code is of this demos is available at the http://cc.embarcadero.com/item/30030

My blog has moved to community.embarcadero.com!

This is most likely my latest blog post on this server.
There is a new Embarcadero social website community.embarcadero.com where my blog has been migrated.
I’m looking forward to see you online on my new blog at
http://community.embarcadero.com/index.php/blogs/blogger/listall/pawel-glowacki-embarcadero-com

DataSnap "CUSTOMERS" FireDAC JSON Reflection Demo Code

My "RAD in Action: Build Modern Apps with Enterprise Mobility Services" webinar is happening right now. During my 50 minutes session I’m demonstrating using DataSnap framework for building multi-tier database application with FireDAC JSON Reflection framework. That’s very powerful and very easy to code.

During the demo I have illustrated the following best practices:

  • InterBase „EMPLOYEE” sample database
  • FireDAC database access components
  • DataSnap server deployed to a web server
  • Secure HTTPS communication
  • Role-based Authentication and Authorization
  • DataSnap REST Client Module and Proxies
  • FireDAC In-Memory Database Tables
  • Visual LiveBindings for connecting UI to data
  • FireMonkey Mobile iOS/Android Client

During the session I have promised to make the source code of the demo available, so here it is! I have uploaded "Customers" demo source code to Code Central here: http://cc.embarcadero.com/item/29916.

DataSnap "Simple Calculator" REST demo

It was so much fun to create one of the "Developer Skills Sprint" sessions last week.
During the live Q’n'A session I have promised to make the source code of my DataSnap "Simple Calculator" REST server and client projects available.

Here it is! It is now available from the Code Central at http://cc.embarcadero.com/item/29915

A few years ago, back in the Delphi XE time frame, I have created a series of DataSnap "Delphi Labs". The source code of these Delphi Labs has been updated to Delphi XE3. But the technology does not stand still. I do not think anymore that it makes sense to make just the cosmetic adjustments to these examples. In fact these demos need a major upgrade to use the latest technologies and best practices.

  • Server architecture. For performance reasons it is best to implement DataSnap servers as web applications and deploy to a web server. Instead of "DataSnap Server" wizard, use "DataSnap REST Application" or "DataSnap WebBroker Application".
  • Communication Protocol. In the world of highly disconnected apps the best communication protocol is standard HTTP or HTTPS.
  • Security. On top of HTTPS transport security, make sure to use encryption filters and role-based authentication and authorisation.
  • Client Connectivity. Instead of using "DBX" use "REST". There are two wizards for creating client-side proxy classes: "DataSnap Client Module" and "DataSnap REST Client Module". The first one is based on the DBExpress technology and uses "TSQLConnection" component for connectivity. Don’t use it. Use the second, REST-based wizard, that generates REST-style client proxies and uses "TDSRESTConnection" component for connectivity.

The "Simple Calculator REST" demo is the new generation version of my first "Delphi Lab" and covers the basics of creating DataSnap server and client projects.

The second historical demo in the "Delphi Labs" session was about creating multi-tier database applications with DataSnap. The new generation version of this demo is in the works and will be presented this Wednesday, August 20th, during my global "Build Modern Apps with Enterprise Mobility Services" webinar. I’m going to move away from deprecated IAppServer interface and use FireDAC JSON Reflection framework for creating new generation multi-tier database applications.

See you online! In the meantime make sure to register for the webinar!

Bad Behavior has blocked 2 access attempts in the last 7 days.

Close