In the last post we used pixel coordinates to draw objects on a screen. This time we use a cartesian coordinate system which we will transform into screen coordinates step by step.
Cartesian Coordinate System |
Pixel Coordinate System |
Objects
Each object will contain a list of points, a list of lines where each start and end point of these lines target 2 points within the points' list and a list of triangles consisting of 3 points of the points' list. This will make it easy for us if we have to transform objects.
To draw these objects we use an algorithm similar to OpenGL's rasterization. Triangles are perfect with rasterization, therefore we use this to fill our objects. It sounds confusing, but even a triangle has a triangle. To avoid confusion we will call these classes differently: Triangle2D derived from Object2D and Surface2D (no derivation) for rasterization (drawing).
After this we use Bresenham's algorithm to draw boundary lines of our objects. We will skip complicated polygons and stay with simple objects:
To draw these objects we use an algorithm similar to OpenGL's rasterization. Triangles are perfect with rasterization, therefore we use this to fill our objects. It sounds confusing, but even a triangle has a triangle. To avoid confusion we will call these classes differently: Triangle2D derived from Object2D and Surface2D (no derivation) for rasterization (drawing).
After this we use Bresenham's algorithm to draw boundary lines of our objects. We will skip complicated polygons and stay with simple objects:
- Rectangle2D (lines and fill)
- Ellipse2D (lines and fill)
- Triangle2D (lines and fill)
- Polyline2D (lines only)
Transformations
Transformations can be divided into
- Translation
- Rotation
- Scale
- Mirroring
Translation
Also called move. It moves points along x- and y-axis to its new position. You an simply calculate:
and
You can also use matrix multiplication to get the new position, but therefore you need homogeneous coordinates, which is always 1 in our program.
and
You can also use matrix multiplication to get the new position, but therefore you need homogeneous coordinates, which is always 1 in our program.
Rotation
When we talk about rotations, we mean to rotate around the origin of our cartesian coordinate system.
This can be described in the following homogeneous matrix multiplication:
Scale
Scaling an object means to stretch it along x- and y-axis.
Calculations applies in multiplying x and y values by a scale value sx and sy:.
Calculations applies in multiplying x and y values by a scale value sx and sy:.
Like rotation and translation, we can use a homogeneous matrix calculation
Viewing
Imagine holding a picture frame in your hands and looking through it. You will see certain objects within that frame.
Now imagine that you have some squares in that picture frame representing your pixels (see second figure). Each object covers certain pixels with their color. Even though you see a 3D scene in your 2D picture frame right now, transforming a 2D scene in a 2D picture where our frame can be anywhere of any size works similar.
First we need to place our Viewport (red rectangle) in our coordinate system and see where our black point is located (here 0.5/1.0).
Our aim is to create a normalized square where our point is somewhere between -1 and +1 in both x- and y-direction. Any value outside of our scale means it is outside of our viewport and therefore we have to clip it (next issue).
Last but not least we use this normalized square to get a fitting pixel coordinate for our point. Consider that Point (-1/+1) represents pixel point (0/0) and Point (+1/-1) represents (width/height), depending of our image size. In other words: x-direction is like always, while y-direction is opposite..
First we scale the point by width/2 and height/2, mirror it along y-direction (y*(-1)) and translate it by width/2 and height/2. After that, we know where to draw our picture on our picture with x between 0 and width and y between 0 and height..
Our Camera2D will handle the creation of the correct TransformMatrix2D, We will also add a camera rotation, in case that we don't want to have a normal view. Our rotation happens to move counter clockwise (to the left), which happens when you rotate a camera clockwise (to the right)
Graphic Pipeline
Now that we know how to transform objects and how to calculate them to proper screen coordinates, we need to define the correct order of this:
- Object Coordinates
- scale
- rotate
- translate
- World Coordinates
- Rotation around View Center
- Normalization from -1 to +1
- Screen Transformation from 0 to Width and 0 to Height
- Screen Coordinates
Rasterization
We already know how to draw a line, but we still don't know how to draw a triangle (the easiest object in the world) and fill it. Rasterization is a fast way and ideal for rendering triangles on a frame buffer (our Renderer). Normally we also need a z-buffer to figure out which pixel is closer to our camera lense (or our eye). But because we use a 2D scene we can ignore this and simply draw on our picture.
Our first task is to get our 3 points of the triangle and sort them along y-direction with P1 being the upper most point, P2 the middle point and P3 the lower most point. If two points happens to have the same y value, we also sort along x-direction so that we can keep looking left-right.
Under normal circumstances we split our triangle into 2 parts. The middle point (here P2) is going parallel to x-axis. Inspired by Digital Differential Analyzer (DDA) we calculate a left border (P1->P2) and a right border (P1->P3) and walk along that border.
We stop at each Y.5 value in y-direction and start walking in x-direction waiting at each X.5 value. as long as we are within both borders we can color that pixel and continue going torwards right border until we step outside. Then we go another step along y-direction and repeat the same process until we reach P2. In our 2nd part we will have 2 new borders (here P2->P3 and P1->P3), doing the same work like in part 1.
Rasterization: 1st part in blue, 2nd part in orange |
After rasterization our triangle looks like this:
Consider that a triangle can also look in a different direction so that left border is (P1->P3) and having 2 right borders (P1->P2 and P2->P3). Calculating a cross product (P1->P2 x P1->P3) helps us figuring out in which direction a triange may look.
Triangle looking in opposite direction |
Framework
There are hardly any changes in our already known classes. I changed private Painter::drawLine() method into Painter::bresenham(), because I want to add another line drawing algorithm called DDA which also works for floating point numbers. Bresenham is only accurate with integer values.
Our new render class is called Renderer2D, it contains a Camera2D and a Scene2D instance. A scene has a list of Object2Ds, like Ellipse2D or Rectangle2D. I limited it to these 2 types because they are the easiest to implement. Both are created at coordinate origin.
The most difficult part is to create points which describe the border of an object and how to connect them with border lines and surface triangles. A rectangle consists of 2 triangles, which will be saved as Surface2D. For an ellipse we choose a pivot point and connect it with 2 other points on the border line which are neighbors. It would look like a fan.
Renderer2D first draws some lines which forms a cartesian coordinate system. This helps us to identify if our objects are drawn correctly. Next we draw each Object2D by using rasterization to fill it and Painter to draw lines. You will also notice that we transform the coordinates in Renderer2D::render(). That's because each object can move freely in the world, but every person sees them in a different view.
With the settings in these 2 source files we create an image which looks like this:
Summary
Our framework finally has basic finally has many basic elements needed for modern computer graphics, except for the fact that we will never use GPU. We learned How to transform objects and how to calculate each value into screen coordinates. We also learned rasterization, a render algorithm which is also used in OpenGL.
In the next issue we will discuss textures and how to add them on a object. We will change Image so that it can load PPM files and transform them into a Texture.
Tested with:
In the next issue we will discuss textures and how to add them on a object. We will change Image so that it can load PPM files and transform them into a Texture.
Repository:
https://github.com/rumpfc/CGG/tree/master/cgg02_2D_transform_viewingTested with:
- Visual Studio 2013 (Windows 8.1)
- GNU Compiler 4.9.2 (Ubuntu 12.04)
No comments:
Post a Comment