Commit 61c96650 authored by Nathan H. Bean's avatar Nathan H. Bean

Added basic 3D lesson

parent 14ac8cab
---
title: "Introduction"
pre: "1. "
weight: 1
date: 2020-03-24T10:00:00-05:00
---
The term "3D rendering" refers to converting a three-dimensional representation of a scene into a two-dimensional frame. While there are multiple ways to represent and render three-dimensional scenes (ray-tracing, voxels, etc.), games are dominated by a standardized technique supported by graphics card hardware. This appraoch is so ubiquitous that when we talk about 3D rendering in games, this is the approach we are typically referring to.
Remember that games are "real-time", which means they _must_ present a new frame every 1/30th of a second to create the illusion of motion. Thus, a 3D game must perform the conversion from 3D representation to 2D representation, _plus_ update the game world, within that narrow span of time. To fully support monitors with higher refresh rates, this span may be cut further - 1/60th of a second for a 60-hertz refresh rate, or 1/120th of a second for a 120-hertz refresh rate. Further, to support VR googles, the frame must be rendered _twice_, once from the perspective of each eye, which further halves the amount of time avaible for a single frame.
This need for speed is what has driven the adoption and evolution of graphics cards. The hardware of the graphics cards includes a _graphics processing unit_, a processor that has been optimized for the math needed to support this technique, and dedicated _video memory_, where the data needed to support 3D rendering is stored. The GPU operates in parallel with and semi-independently from the CPU - the game running on the CPU sends instructions to the GPU, which the GPU carries out. Like any multiprocessor program, care must be taken to avoid accessing shared memory (RAM or Video RAM) concurrently. This sharing of memory and transmission of instructions is facilitated by a low-level rendering library, typically [DirectX](https://en.wikipedia.org/wiki/DirectX) or [OpenGL](https://en.wikipedia.org/wiki/OpenGL).
MonoGame supports both, through different kinds of projects, and provides abstractions that provide a platform-independence layer, in the [Xna.Framework.Graphics namespace](https://www.monogame.net/documentation/?page=N_Microsoft_Xna_Framework_Graphics). One important aspect of this layer is that it provides managed memory in conjunction with C#, which is a departure from most game programing (where the developer must manage thier memory explicitly, allocating and deallocating as needed).
## The Graphics Pipeline
This process of rendering using 3D accelerated hardware is often described as the Graphics Pipeline:
![Graphics Pipeline]({{<static "images/graphics-pipeline.png">}})
We'll walk through this process as we write a demonstration project that will render a rotating 3D cube.
\ No newline at end of file
This diff is collapsed.
---
title: "Rendering a Textured Quad"
pre: "3. "
weight: 3
date: 2020-03-24T10:00:00-05:00
---
While the point of a `TriangleStrip` is to optimize by reducing the number of vertices, in most cases it still will have repeats, and it is difficult to define a complex mesh out of a single strip. Thus, in addition to vertices, we can provide _indices_ to specific vertices. The collection of indices contains nothing more than integers referencing vertices in the vertices collection. This means each unique vertex needs to be defined exactly once, and the indices take on the role of defining the triangles by giving the position of each successive vertex in the triangle list in the vertex array.
## Defining a Textured Quad
Let's give this a try by defining a `Quad` class with both vertices and indices:
```csharp
/// <summary>
/// A class representing a quad (a rectangle composed of two triangles)
/// </summary>
public class Quad
{
/// <summary>
/// The vertices of the quad
/// </summary>
VertexPositionTexture[] vertices;
/// <summary>
/// The vertex indices of the quad
/// </summary>
short[] indices;
/// <summary>
/// The effect to use rendering the triangle
/// </summary>
BasicEffect effect;
/// <summary>
/// The game this cube belongs to
/// </summary>
Game game;
}
```
You will note that instead of our vertex structure being a `VertexPositionColor`, this time we're using `VertexPositionTexture`. Instead of giving each vertex a color, this time we'll be giving it a texture coordinate, and having our `effect` apply a texture to the face of the quad.
Note also that we use the `short` data type for our index array. As a quad has only four vertices (one in each corner), we only need four vertices to define one. If our vertices start at index 0, that means we only need to represent indices 1-4, so a short is more than sufficient. With a larger vertex array, we might need to use a larger type of integer.
### Defining the Vertices
As with our triangle, we'll initialize our vertices in a helper method, `InitializeVertices`:
```csharp
/// <summary>
/// Initializes the vertices of our quad
/// </summary>
public void InitializeVertices()
{
vertices = new VertexPositionTexture[4];
// Define vertex 0 (top left)
vertices[0].Position = new Vector3(-1, 1, 0);
vertices[0].TextureCoordinate = new Vector2(0, 0);
// Define vertex 1 (top right)
vertices[1].Position = new Vector3(1, 1, 0);
vertices[1].TextureCoordinate = new Vector2(1, 0);
// define vertex 2 (bottom right)
vertices[2].Position = new Vector3(1, -1, 0);
vertices[2].TextureCoordinate = new Vector2(1, -1);
// define vertex 3 (bottom left)
vertices[3].Position = new Vector3(-1, -1, 0);
vertices[3].TextureCoordinate = new Vector2(-1, -1);
}
```
The quad is two by two, centered on the origin. The texture coordinates are expressed as floats that fall in the range [0 ... 1]. The texture coordinate (0,0) is the upper-left hand corner of our texture, and (1, 1) is the lower-right corner.
### Defining the Indices
Now let's define our indices in thier own helper method, `InitializeIndices`. Let's assume we're using a triangle list, so we'll need to define all six vertices (with a triangle strip we could cut this to 4):
```csharp
/// <summary>
/// Initialize the indices of our quad
/// </summary>
public void InitializeIndices()
{
indices = new short[6];
// Define triangle 0
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
// define triangle 1
indices[3] = 1;
indices[4] = 2;
indices[5] = 3;
}
```
## Initializing the Effect
And we'll need to set up our effect, which we'll again do in a method named `InitializeEffect()`:
```csharp
/// <summary>
/// Initializes the basic effect used to draw the quad
/// </summary>
public void InitializeEffect()
{
effect = new BasicEffect(game.GraphicsDevice);
effect.World = Matrix.Identity;
effect.View = Matrix.CreateLookAt(
new Vector3(0, 0, 4), // The camera position
new Vector3(0, 0, 0), // The camera target,
Vector3.Up // The camera up vector
);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4, // The field-of-view
game.GraphicsDevice.Viewport.AspectRatio, // The aspect ratio
0.1f, // The near plane distance
100.0f // The far plane distance
);
effect.TextureEnabled = true;
effect.Texture = game.Content.Load<Texture2D>("monogame-logo");
}
```
This looks almost identical to our triangle example. The only difference is in the last two lines - instead of setting `effect.VertexColorEnabled`, we're setting `effect.TextureEnabled` to `true`, and providing the _monogame-logo.png_ texture to the `effect`.
### Draw() Method
Now let's write our `Draw()` method:
```csharp
/// <summary>
/// Draws the quad
/// </summary>
public void Draw()
{
effect.CurrentTechnique.Passes[0].Apply();
game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>(
PrimitiveType.TriangleList,
vertices, // The vertex collection
0, // The starting index in the vertex array
4, // The number of indices in the shape
indices, // The index collection
0, // The starting index in the index array
2 // The number of triangles to draw
);
}
```
### The Quad Constructor
To wrap up the `Quad`, we'll need to add a constructor that takes a parameter of type `Game` and invokes our initialization:
```
```
## Adding our Quad to Game1
Now let's use our `Quad` in `Game1`. Add a field for the quad to the `Game1` class:
```csharp
// The quad to draw
Quad quad;
```
And construct it in our `Game1.LoadContent()`. We want to be sure the graphics device is set up before we construct our `Quad`, so this is a good spot:
```csharp
// Create the quad
quad = new Quad(this);
```
And finally, let's render it in our `Game1.Draw()` method:
```csharp
// Draw the quad
quad.Draw();
```
If you run your code, you should now see the textured quad rendered:
![The renderd quad]({{<static "images/basic-3d-3.1.png">}})
Notice that even though our texture has a transparent background, the background is rendered in black. Alpha blending is managed by the `GraphicsDevice.BlendState`, so we'll need to tweak it before we draw the quad:
```csharp
game.GraphicsDevice.BlendState = BlendState.AlphaBlend;
```
Notice the [BlendState](https://www.monogame.net/documentation/?page=T_Microsoft_Xna_Framework_Graphics_BlendState) class is the same one we used with `SpriteBatch` - and it works the same way.
If you run the game now, the logo's background will be properly transparent.
Just as with the `RasterizerState`, it is a good idea to restore the old `BlendState`. So our final `Quad.Draw()` method might look like:
```csharp
/// <summary>
/// Draws the quad
/// </summary>
public void Draw()
{
// Cache the old blend state
BlendState oldBlendState = game.GraphicsDevice.BlendState;
// Enable alpha blending
game.GraphicsDevice.BlendState = BlendState.AlphaBlend;
// Apply our effect
effect.CurrentTechnique.Passes[0].Apply();
// Render the quad
game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>(
PrimitiveType.TriangleList,
vertices, // The vertex collection
0, // The starting index in the vertex array
4, // The number of indices in the shape
indices, // The index collection
0, // The starting index in the index array
2 // The number of triangles to draw
);
// Restore the old blend state
game.GraphicsDevice.BlendState = oldBlendState;
}
```
This `Quad` is very similar to the sprites we've worked with already - in fact, the `SpriteBatch` is an optimized way of drawing a lot of textured quads. It also configures the graphics device and has its own effect, the [SpriteEffect](https://www.monogame.net/documentation/?page=T_Microsoft_Xna_Framework_Graphics_SpriteEffect). Becuase of this optimization, in most cases, you'll want to use the `SpriteBatch` for textured quads. But it is good to understand how it is drawing 2D sprites using the 3D pipeline.
Speaking of... so far we've only drawn 2D shapes in a 3D world. Let's move on to an actual 3D shape next.
\ No newline at end of file
This diff is collapsed.
---
title: "Conclusion"
pre: "5. "
weight: 5
date: 2020-03-24T10:00:00-05:00
---
This wraps up our discussion of the basics of 3D rendering. As you might expect, this is just the basic foundations. From here we'll explore using models, creating lighting effect, animations, and more. But all of these will depend on understanding and using these basic elements, so get comfortable with them!
\ No newline at end of file
+++
title = "Basic 3D Rendering"
date = 2020-03-20T10:53:05-05:00
weight = 10
chapter = true
+++
### Game Development Techniques
# Basic 3D Rendering
It's all triangles!
\ No newline at end of file
......@@ -45,7 +45,7 @@ Finally, we need to add the layer to the `Game1.Components` list:
By adding it to the list, we let the `Game1` instance take care of updating and rendering the `ParallaxLayer`. If you run your code now, you should see the background:
![Background Layer]({{<static "static/images/parallax-4.1.png">}})
![Background Layer]({{<static "images/parallax-4.1.png">}})
But what has happened to our player helicopter? Let's look at our `Game1.Draw` method:
......
......@@ -69,4 +69,4 @@ Since our `playerLayer` component will take care of rendering the player, we can
Now when you run the game, you can once again see the player helicopter, but now over the background!
![Player over background layer]({{<static "static/images/parallax-5.1.png">}})
\ No newline at end of file
![Player over background layer]({{<static "images/parallax-5.1.png">}})
\ No newline at end of file
......@@ -136,4 +136,4 @@ We want our player layer to scroll as fast as the foreground, so let's set it to
Try running the game now. You should see all three layers scrolling, and you can fly your helicopter behind the telephone lines!
![All layers]({{<static "static/images/parallax-7.1.png">}})
\ No newline at end of file
![All layers]({{<static "images/parallax-7.1.png">}})
\ No newline at end of file
......@@ -44,6 +44,6 @@ And our `Draw()` method in the `Game1`'s `Draw()` method:
If you run the project now, you should see particles emerging at position (100, 100) and flaring out into a cone between 0 and -90 degrees:
![screenshot of particles]({{<static "static/images/particles-0.png">}})
![screenshot of particles]({{<static "images/particles-0.png">}})
Now that we have a basic particle system up and running, it's time to make it more flexible and powerful!
\ No newline at end of file
......@@ -2,4 +2,4 @@
{{- if hasPrefix (.Scratch.Get "path") "/" -}}
{{- .Scratch.Set "path" (slicestr (.Scratch.Get "path") 1) -}}
{{- end -}}
{{- .Scratch.Get "path" | absLangURL -}}
\ No newline at end of file
{{- .Site.BaseURL -}}/static/{{- .Scratch.Get "path" -}}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment