This version connects the FrameBuffer
with the Java GUI system and allows us to write interactive 3D applications.
Basic Renderer
A "renderer" is a collection of algorithms that takes as its input a
Scene
data structure and produces as its output a
FrameBuffer
data structure.
Renderer
+--------------+
Scene | | FrameBuffer
data ====> | Rendering | ====> data
structure | algorithms | structure
| |
+--------------+
A Scene
data structure contains information that describes a "virtual scene"
that we want to take a "picture" of. The renderer is kind of like a digital
camera that takes a picture of the scene and stores the picture's data in
the FrameBuffer data structure. The FrameBuffer holds the actual pixel
information that describes the picture of the scene.
The rendering algorithms can be implemented in hardware (a graphics card or GPU) or in software. In this class we will write a software renderer using the Java programming language.
Our software renderer is made up of four "packages" of Java classes. Each package is contained in its own directory. The name of the directory is the name of the package.
The first package is the collection of input data structures. This is called
the renderer.scene
package. The data structure files in the scene package are:
- Scene.java
- Camera.java
- Model.java
- Vertex.java
- LineSegment.java
The second package is the output data structure. It is called the
renderer.framebuffer
package and contains the files
- FrameBuffer.java.
- FrameBufferPanel.java.
The third package is a collection of algorithms that manipulate the
data structures from the other two packages. This package is called
the renderer.pipeline
package. The algorithm files are:
- Pipeline.java
- Projection.java
- Rasterize.java
The fourth package is a library of geometric models. This package is
called the renderer.models_L
package. It contains a number of
files for geometric shapes such as Sphere
,
Cylinder
, Cube
,
Cone
, Pyramid
,
Tetrahedron
, Dodecahedron
,
and mathematical curves and surfaces.
There is also a fifth collection of source files, a collection of client programs that use the renderer. These files are in the top level directory of the renderer.
Here is a brief description of the data structures from the
renderer.scene
package and renderer.framebuffer
packages.
- A
Scene
object has aCamera
object and aList
ofModel
objects. - A
Camera
object has boolean which determines if the camera is a perspective camera or an orthographic camera. - A
Model
object has aList
ofVertex
objects and aList
ofLineSegment
objects. - A
Vertex
object has three doubles, the x, y, z coordinates of a point in 3-dimensional space. - A
LineSegment
object has a list of two integers. The two integers are indices into theModel
's list of vertices. This lets aLineSegment
object represent the two endpoints of a line segment in 3-dimensional space. - A
FrameBuffer
object represents a two-dimensional array of pixel data. Pixel data represents the red, green, and blue color of each pixel in the image that the render produces. TheFrameBuffer
also defines a sub-array of pixel data called aFrameBuffer.Viewport
. - A
FrameBufferPanel
object is the link between aFrameBuffer
object and the Java GUI system. AFrameBufferPanel
is aJComponent
that can be added to a GUIContainer
. We can use GUI event handlers to tie GUIAWTEvent
objects to the 3D scene we are displaying in aFrameBufferPanel
'sFrameBuffer
.
Scene
A Scene
object represents a collection of geometric models positioned
in the three dimensional space. The models are in front of a Camera
which
is located at the origin and looks down the negative z-axis. Each Model
object in a Scene
object represents a distinct geometric shape in the scene.
A Model
object is a list of Vertex
objects and a list of LineSegment
objects.
Each LineSegment
object refers to two of the Model's Vertex
objects. The
Vertex
objects represent points in the camera's coordinate system. The
model's line segments represent the geometric object as a "wire-frame",
that is, the geometric object is drawn as a collection of "edges". This
is a fairly simplistic way of doing 3D graphics and we will improve this
in later renderers.
https://www.google.com/search?q=computer+graphics+wireframe&tbm=isch
Camera
The Camera
data structure represents a camera located at the origin,
looking down the negative z-axis. A Camera
has associated to it a
"view volume" that determines what part of space the camera "sees" when
we use the camera to take a picture (that is, when we render a Scene
).
A camera can "take a picture" two ways, using a perspective projection or a parallel (orthographic) projection. Each way of taking a picture has a different shape for its view volume.
For the perspective projection, the view volume is an infinitely long pyramid
that is formed by the pyramid with its apex at the origin and its base in the
plane z = -1
with edges x = -1
, x = +1
, y = -1
,
and y = +1
.
For the orthographic projection, the view volume is an infinitely long
rectangular cylinder parallel to the z-axis and with sides x = -1
,
x = +1
, y = -1
, and y = +1
(an infinite parallelepiped).
When the graphics rendering pipeline uses a Camera
to
render a Scene
, the renderer "sees" only the geometry
from the scene that is contained in the camera's view volume. (Notice that
this means the orthographic camera will see geometry that is behind the camera.
In fact, the perspective camera also sees geometry that is behind the camera.)
The plane z = -1
is the camera's image plane. The rectangle in the image
plane with corners (-1, -1, -1)
and (+1, +1, -1)
is the camera's
view rectangle. The view rectangle is like the film in a real camera, it is
where the camera's image appears when you take a picture. The contents
of the camera's view rectangle is what gets rasterized, by the renderer's
Rasterize_Clip_AntiAlias_Line
pipeline stage, into a
FrameBuffer
's viewport.
https://threejs.org/examples/#webgl_camera
http://math.hws.edu/graphicsbook/demos/c3/transform-equivalence-3d.html
Model, LineSegment and Vertex
A Model
object represents a distinct geometric
object in a Scene
. A Model
data structure is mainly a List
of Vertex
objects and another List
of LineSegment
objects.
The Vertex
objects represents points from the
geometric object that we are modeling. In the real world, a geometric
object has an infinite number of points. In 3D graphics, we "approximate"
a geometric object by listing just enough points to adequately describe
the object. For example, in the real world, a rectangle contains an
infinite number of points, but it can be adequately modeled by just its
four corner points. (Think about a circle. How many points does it take
to adequately model a circle? Look at the Circle
model.)
Each LineSegment
object contains two integers that
are the indices of two Vertex
objects from the
renderer.scene.Model
's vertex list. Each Vertex
object contains the xyz-coordinates, in the camera coordinate system, for
one of the line segment's two endpoints.
We use the LineSegment
objects to "fill in" some of
the space between the model's vertices. For example, while a rectangle can
be approximated by its four corner points, those same four points could also
represent just two parallel line segments. By using four line segments that
connect around the four points, we get a good representation of a rectangle.
If we modeled a circle using just points, we would probably need to draw hundreds of points. But if we connect every two adjacent points with a short line segment, we can get a good model of a circle with just a few dozen points.
Our Model
's represent geometric objects as a "wire-frame" of line
segments, that is, a geometric object is drawn as a collection of "edges".
This is a fairly simplistic way of doing 3D graphics and we will improve
this in later renderers.
https://www.google.com/search?q=computer+graphics+wireframe&tbm=isch
Scene Tree Data Structure
When you put all of the above information together, you see that
a Scene
object is the root of a simple tree data structure.
Scene
/ \
/ \
Camera List<Model>
/ | \
/ | \
Model Model Model
/ \
/ \
List<Vertex> List<LineSegment>
/ \ / \
/ \ / \
Vertex Vertex LineSegment LineSegment
/ | \ / | \ | |
/ | \ / | \ | |
x y z x y z int[2] int[2]
FrameBuffer
A FrameBuffer
object holds an array of pixel data that represents an image
that can be displayed on a computer's screen. For each pixel in the image,
the framebuffer's array holds three byte values, one byte that represents
the red component of the pixel's color, one byte that represents the green
component, and one byte that represents the blue component of the pixel's
color. Each of these three bytes is only eight bits in size, so each of the
three colors has only 256 shades (but there are 256^3 = 16,777,216 distinct
colors). The three bytes of color for each pixel are packed into one Java
integer (which has four bytes, so one of the integer's bytes is not used).
If a FrameBuffer has dimensions n
rows of pixels by m
columns of pixels,
then the FrameBuffer holds n*m
integers. The pixel data is NOT stored as
a "two-dimensional" n
by m
array of integers nor is it stored as a
"three-dimensional" n
by m
by 3 array of bytes. It is stored as a
one-dimensional array of integers of length n*m
. This array is in "row
major" form, meaning that the first m
integers in the array are the
pixels from the image's first row. The next m
integers are the pixels
from the image's second row, etc. Finally, the first row of pixels is
the top row of the image when it is displayed on the computer's screen.
The FrameBuffer data structure also defines a Viewport which is a
rectangular sub-array of the pixel data in the framebuffer. The viewport
is the active part of the framebuffer, the part of the framebuffer that
the renderer is actually writing into. The viewport has width and height
dimensions, w
and h
, with w <= m
and h <= n
.
Quite often the viewport will be the whole framebuffer. But the viewport idea
makes it easy for us to implement effects like "split screen" (two independent images in the
FrameBuffer), or "picture in a picture" (a smaller picture superimposed on
a larger picture). In future renderers (starting with renderer 7), another
use of a viewport that is smaller than the whole FrameBuffer is when we
want the viewport to have the same aspect ratio as the Camera‘s view
rectangle.
https://en.wikipedia.org/wiki/Picture-in-picture
Renderer
Here is a brief overview of how the renderer algorithms process a Scene data structure to produce a filled in viewport within the FrameBuffer object.
First of all, remember that:
- A
Scene
object contains aCamera
and a list ofModel
objects. - A
Model
object contains lists ofVertex
andPrimitive
objects. - A
LineSegment
object refers to two of theVertex
objects. - A
Vertex
object contains three coordinate that are relative to the Camera.
The main job of the renderer is to "draw" in the FrameBuffer's viewport appropriate pixels for each LineSgement in each Model from the Scene. The "appropriate pixels" are the pixels "seen" by the camera. At its top level, the renderer iterates through the Scene object's list of Model objects, and for each Model object the renderer iterates through the Model object's list of LineSegment objects. When the renderer has drilled down to a LineSegment object, then it can render the line segment into the framebuffer's viewport. So the renderer really renders line segments.
The renderer does its work on a LineSegment
object in
a "pipeline" of stages. This simple renderer has just two pipeline stages.
The stages that a LineSegment
object passes through
in this renderer are
Projection
(of the model's vertices),Rasterize
(the model's line segments).
To understand the algorithms used in the "project then rasterize" process,
we need to trace through the rendering pipeline what happens to each
Vertex
and LineSegment
object from a Model
.
Start with a Model's list of vertices.
v0 ... vn A Model's list of Vertex objects
\ /
\ /
|
| camera coordinates (of v0 ... vn)
|
+-------+
| |
| P1 | Projection (of the vertices)
| |
+-------+
|
| image plane coordinates (of v0 ... vn)
|
/ \
/ \
/ \
| P2 | Rasterization & clipping (of each line segment)
\ /
\ /
\ /
|
| pixels (for each clipped line segment)
|
\|/
FrameBuffer.ViewPort
Vertex Projection
The Projection
stage takes the model's list of vertices
in three dimensional (camera) space and computes the two-dimensional coordinates
of where each vertex "projects" onto the camera's image plane (the plane
with equation z = -1
). The projection stage takes the vertices inside of
the camera's view volume and projects them into the camera's view rectangle
(and points outside of the camera's view volume will, of course, project
to points outside of the view rectangle).
Let us derive the formulas for the perspective projection transformation (the formulas for the parallel projection transformation are pretty obvious). We will derive the x-coordinate formula; the y-coordinate formula is similar.
Let (x_c, y_c, z_c)
denote a point in the 3-dimensional camera coordinate
system. Let (x_p, y_p, -1)
denote the point's perspective projection into
the image plane, z = -1
. Here is a "picture" of just the xz-plane from
camera space. This picture shows the point (x_c, z_c)
and its projection to
the point (x_p, -1)
in the image plane.
x /
| /
x_c + + (x_c, z_c)
| /|
| / |
| / |
| / |
| / |
| / |
| / |
| / |
x_p + + |
| /| |
| / | |
| / | |
| / | |
| / | |
| / | |
+-------+--------+------------> -z
(0,0) -1 z_c
We are looking for a formula that computes x_p
in terms of x_c
and z_c
.
There are two similar triangles in this picture that share a vertex at the
origin. Using the properties of similar triangles we have the following
ratios. (Remember that these are ratios of positive lengths, so we write
-z_c
, since z_c
is on the negative z-axis).
x_p x_c
----- = -----
1 -z_c
If we solve this ratio for the unknown, x_p
, we get the projection formula,
x_p = -x_c / z_c.
The equivalent formula for the y-coordinate is
y_p = -y_c / z_c.
Line Rasterization
The Rasterize_Clip_AntiAlias_Line
stage first takes the two-dimensional
coordinates of a vertex in the camera's image plane and computes that vertex's
location in a "logical pixel plane". This is referred to as the "viewport transformation".
The purpose of the logical pixel plane and the viewport transformation is to make
the rasterization stage easier to implement.
The camera's image plane contains a view rectangle with edges x = +1,
x = -1, y = +1
, and y = -1
. The pixel plane contains a logical
viewport rectangle with edges x = 0.5, x = w+0.5, y = 0.5
, and
y = h+0.5
(where h
and w
are the height and width of
the framebuffer's viewport).
Recall that the role of the camera's view rectangle is to determine what part of a scene is visible to the camera. Vertices inside of the camera's view rectangle should end up as pixels in the framebuffer's viewport. Another way to say this is that we want only that part of each projected line segment contained in the view rectangle to be visible to our renderer and rasterized into the framebuffer's viewport.
Any point inside of the image plane's view rectangle should be transformed to a point inside of the pixel plane's logical viewport. Any vertex outside of the image plane's view rectangle should be transformed to a point outside of the pixel plane's logical viewport.
View Rectangle
(in the Camera's image plane)
y-axis
|
| (+1,+1)
+---------|---------+
| | |
| | |
| | |
| | |
| | |
-------------+---------------- x-axis
| | |
| | |
| | |
| | |
| | |
+---------|---------+
(-1,-1) |
|
|
||
||
|| Viewport Transformation
||
||
\/
Logical Viewport
(w+0.5, h+0.5)
+----------------------------------------------+
| . . . . . . . . . . . . . . . . . . . . . . .|
| . . . . . . . . . . . . . . . . . . . . . . .|
| . . . . . . . . . . . . . . . . . . . . . . .|
| . . . . . . . . . . . . . . . . . . . . . . .|
| . . . . . . . . . . . . . . . . . . . . . . .| The logical pixels
| . . . . . . . . . . . . . . . . . . . . . . .| are the points in the
| . . . . . . . . . . . . . . . . . . . . . . .| logical viewport with
| . . . . . . . . . . . . . . . . . . . . . . .| integer coordinates.
| . . . . . . . . . . . . . . . . . . . . . . .|
| . . . . . . . . . . . . . . . . . . . . . . .|
| . . . . . . . . . . . . . . . . . . . . . . .|
| . . . . . . . . . . . . . . . . . . . . . . .|
| . . . . . . . . . . . . . . . . . . . . . . .|
+----------------------------------------------+
(0.5, 0.5)
||
||
|| Rasterizer
||
||
\/
Viewport
(in the FrameBuffer)
(0,0)
+-------------------------------------------+
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| The physical pixels
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| are the entries in
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| the FrameBuffer
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| array.
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
+-------------------------------------------+
(w-1,h-1)
After the viewport transformation of the two endpoints of a line segment, the rasterization stage will convert the given line segment in the pixel plane into pixels in the framebuffer's viewport. The rasterization stage computes all the pixels in the framebuffer's viewport that are on the line segment connecting the transformed vertices v0 and v1. Any point inside the logical viewport that is on this line segment is rasterized to a pixel inside the framebuffer's viewport. Any point on this line segment that is outside the logical viewport should not be rasterized to a pixel in the framebuffer.
View Rectangle to Logical Viewport Transformation
The view rectangle in the camera's view plane has
-1 <= x <= 1,
-1 <= y <= 1.
The logical viewport in the pixel plane has
0.5 <= x < w + 0.5,
0.5 <= y < h + 0.5,
where
w
= number of horizontal pixels in the framebuffer's viewport,h
= number of vertical pixels in the framebuffer's viewport.
We want a transformation (formulas) that sends points from the camera's view rectangle to proportional points in the pixel plane's logical viewport.
The goal of this transformation is to put a logical pixel with integer
coordinates at the center of each square physical pixel. The logical pixel
with integer coordinates (m, n)
represents the square physical pixel with
m - 0.5 <= x < m + 0.5,
n - 0.5 <= y < n + 0.5.
Notice that logical pixels have integer coordinates (m,n)
with
1 <= m <= w
1 <= n <= h.
Let us derive the formulas for the viewport transformation (we will derive the x-coordinate formula; the y-coordinate formula is similar).
Let x_p
denote an x-coordinate in the image plane and let x_vp
denote an
x-coordinate in the viewport. If a vertex is on the left edge of the view
rectangle (with x_p = -1
), then it should be transformed to the left edge of
the viewport (with x_vp = 0.5
). And if a vertex is on the right edge of the
view rectangle (with x_p = 1
), then it should be transformed to the right
edge of the viewport (with x_vp = w + 0.5
). These two facts are all we need
to know to find the linear function for the transformation of the x-coordinate.
We need to calculate the slope m
and intercept b
of a linear function
x_vp = m * x_p + b
that converts image plane coordinates into viewport coordinates. We know, from what we said above about the left and right edges of the view rectangle, that
0.5 = (m * -1) + b,
w + 0.5 = (m * 1) + b.
If we add these last two equations together we get
w + 1 = 2*b
or
b = (w + 1)/2.
If we use b
to solve for m
we have
0.5 = (m * -1) + (w + 1)/2
1 = -2*m + w + 1
2*m = w
m = w/2.
So the linear transformation of the x-coordinate is
x_vp = (w/2) * x_p + (w+1)/2
= 0.5 + w/2 * (x_p + 1).
The equivalent formula for the y-coordinate is
y_vp = 0.5 + h/2 * (y_p + 1).
Rasterizing a LineSegment
Now we want to discuss the precise algorithm for how the rasterizer converts a line segment in the pixel-plane into a specific choice of pixels in the viewport.
Here is a picture of part of a line segment in the pixel-plane with
logical pixel x-coordinates between i
and i+3
and
with logical pixel y-coordinates between j
and j+6
.
+-------+-------+-------+------/+
| | | | / |
j+6 | . | . | . | ./ |
| | | | / |
+-------+-------+-------+--/----+
| | | | / |
j+5 | . | . | . |/ . |
| | | / |
+-------+-------+------/+-------+
| | | / | |
j+4 | . | . | ./ | . |
| | | / | |
+-------+-------+--/----+-------+
| | | / | |
j+3 | . | . |/ . | . |
| | / | |
+-------+------/+-------+-------+
| | / | | |
j+2 | . | ./ | . | . |
| | / | | |
+-------+--/----+-------+-------+
| | / | | |
j+1 | . |/ . | . | . |
| / | | |
+------/+-------+-------+-------+
| / | | | |
j | ./ | . | . | . |
| / | | | |
+--/----+-------+-------+-------+
i i+1 i+2 i+3 logical pixel coordinates
The rasterizing algorithm can "walk" this line segment along either
the x-coordinate axis from i
to i+3
or along the
y-coordinate axis from j
to j+6
. In either case,
for each logical pixel coordinate along the chosen axis, the algorithm
should pick the logical pixel closest to the line segment and turn on
the associated physical pixel.
If our line has the equation y = m*x + b
, with slope m
and y-intercept b
(in pixel-plane coordinates), then walking the
line along the x-coordinate axis means that for each logical pixel
x-coordinate i
, we compute the logical pixel y-coordinate
Math.round( m*i + b ).
On the other hand, walking the line along the y-coordinate axis means we
should use the linear equation x = (y - b)/m
and for each logical
pixel y-coordinate j
, we compute the logical pixel x-coordinate
Math.round( (y - b)/m ).
Let us try this algorithm in the above picture along each of the two logical pixel coordinate axes.
If we rasterize this line segment along the x-coordinate axis, then we
need to chose a logical pixel for each x
equal to
i, i+1, i+2
, and i+3
. Always choosing the logical pixel
(vertically) closest to the line, we get these pixels.
+-------+-------+-------+------/+
| | | |#####/#|
j+6 | . | . | . |###./##|
| | | |###/###|
+-------+-------+-------+--/----+
| | | | / |
j+5 | . | . | . |/ . |
| | | / |
+-------+-------+------/+-------+
| | |#####/#| |
j+4 | . | . |###./##| . |
| | |###/###| |
+-------+-------+--/----+-------+
| | | / | |
j+3 | . | . |/ . | . |
| | / | |
+-------+------/+-------+-------+
| |#####/#| | |
j+2 | . |###./##| . | . |
| |###/###| | |
+-------+--/----+-------+-------+
| | / | | |
j+1 | . |/ . | . | . |
| / | | |
+------/+-------+-------+-------+
|#####/#| | | |
j |###./##| . | . | . |
|###/###| | | |
+--/----+-------+-------+-------+
i i+1 i+2 i+3 logical pixel coordinates
Make sure you agree that these are the correctly chosen pixels. Notice that our rasterized line has "holes" in it. This line has slope strictly greater than 1. Every time we move one step to the right, we move more that one step up because the slope is greater than 1, so
rise/run > 1,
so
rise > run,
but run = 1
, so we always have rise > 1
, which causes us
to skip over a pixel when we round our y-coordinate to the nearest logical
pixel.
If we rasterize this line segment along the y-coordinate axis, then we
need to chose a logical pixel for each y
equal to
j, j+1, j+2, j+3, j+4, j+5
and j+6
. Always choosing the
logical pixel (horizontally) closest to the line, we get these pixels.
+-------+-------+-------+------/+
| | | |#####/#|
j+6 | . | . | . |###./##|
| | | |###/###|
+-------+-------+-------+--/----+
| | | |#/#####|
j+5 | . | . | . |/##.###|
| | | /#######|
+-------+-------+------/+-------+
| | |#####/#| |
j+4 | . | . |###./##| . |
| | |###/###| |
+-------+-------+--/----+-------+
| | |#/#####| |
j+3 | . | . |/##.###| . |
| | /#######| |
+-------+------/+-------+-------+
| |#####/#| | |
j+2 | . |###./##| . | . |
| |###/###| | |
+-------+--/----+-------+-------+
| |#/#####| | |
j+1 | . |/##.###| . | . |
| /#######| | |
+------/+-------+-------+-------+
|#####/#| | | |
j |###./##| . | . | . |
|###/###| | | |
+--/----+-------+-------+-------+
i i+1 i+2 i+3 logical pixel coordinates
Make sure you agree that these are the correctly chosen pixels. In each row of logical pixels, we should choose the logical pixel that is closest (horizontally) to the line.
We see that while we can rasterize a line in either the x-direction or the y-direction, we should chose the direction based on the slope of the line. Lines with slope between -1 and +1 should be rasterized in the x-direction. Lines with slope less than -1 or greater than +1 should be rasterized in the y-direction.
Here is a pseudo-code summary of the rasterization algorithm. Suppose we are
rasterizing a line from logical pixel (x0, y0)
to logical pixel
(x1, y1)
(so x0, y0, x1, y1
are all integer values). If the
line has slope less than 1, we use the following loop.
double y = y0;
for (int x = x0; x <= x1; x += 1, y += m)
{
int x_vp = x - 1; // viewport coordinate
int y_vp = h - (int)Math.round(y); // viewport coordinate
vp.setPixelVP(x_vp, y_vp, Color.white);
}
Notice how x
is always incremented by 1 so that it moves from one,
integer valued, logical pixel coordinate to the next, integer valued, logical
pixel coordinate. On the other hand, the slope m
need not be an integer.
As we increment x
by 1, we increment y
by m
(since
"over 1, up m
" means slope = m
), so the values of y
need not be integer values, so we need to round each y
value to its
nearest logical pixel integer coordinate.
If the line has slope greater than 1, we use the following loop.
double x = x0;
for (int y = y0; y <= y1; y += 1, x += m)
{
int x_vp = (int)Math.round(x) - 1; // viewport coordinate
int y_vp = h - y; // viewport coordinate
vp.setPixelVP(x_vp, y_vp, Color.white);
}
The above code has a slight simplification in it. When the slope of the line is greater than 1, we recompute the slope as slope in the y-direction with
change-in-x / change-in-y
so that the slope becomes less than 1.
Clipping in the Rasterizer
When part (or all) of a line segment is outside of the camera's view volume, we should clip off the part of the line segment that is not in the view volume.
We have several choices of when (and how) we can clip line segments.
- before projection (in camera coordinates),
- after projection (in the view plane),
- during rasterization.
In this renderer we clip line segments during rasterization. (In the next renderer we will clip line segments in the view plane, after projection but before rasterization.)
We clip line segments during rasterization by not putting into the framebuffer any line segment fragment that is out of the framebuffer's viewport. This works, but it is not such a great technique because it requires that we compute every fragment of every line segment and then check if it fits in the viewport. This could be a big waste of CPU time. If a line segment extends from within the viewport to millions of pixels outside the viewport, then we would be needlessly computing a lot of pixels just to discard them. Even worse, if no part of the line segment is in the view rectangle, we would still be rasterizing the whole line segment.
In this renderer, line clipping is optional and can be turned on and off. Notice that when line clipping is turned off, if a model moves off the left or right side of the window it "wraps around" to the other side of the window. But if a model moves off the top or bottom of the window there are a number of error messages reported in the console window by the framebuffer. The cause of this is that the FrameBuffer stores its pixel data as a one dimensional array in "row major" form. Also, the setPixel() methods in FrameBuffer do not do any bounds checking. So if setPixel() is asked to set a pixel that is a little bit off the right edge of a row of pixels, then the method will just compute the appropriate array entry in the one-dimensional "array of rows" and set a pixel that is just a bit to the right of the left edge of the window and one row down from the row it was supposed to be in! If you let a model move to the right for a very long time, you will notice that it is slowly moving down the screen (and if you move a model to the left for a very long time, you will notice that it is slowly moving up the screen).
Package | Description |
---|---|
renderer.framebuffer |
Provides the renderer with an output data structure
to hold the pixel values computed by the renderer.
|
renderer.models_L |
A library of predefined wireframe models.
|
renderer.models_L.turtlegraphics |
A library the implements Turtle Geometry with a few predefined examples of turtle graphics.
|
renderer.pipeline |
The 3D graphics rendering pipeline stages.
|
renderer.scene |
Data structures for describing a 3D scene to the renderer.
|
renderer.scene.primitives |
Geometric primitives that describe the relationships between vertices of a model.
|
renderer.scene.util |
Utility classes and methods for working with scenes and models.
|