Tải bản đầy đủ - 0 (trang)
5 Project structure: the scene and objects

5 Project structure: the scene and objects

Tải bản đầy đủ - 0trang

660



CHAPTER 26



Introduction to 3D



Figure 26.3

A high-level look at the

architecture of the

sample application



The problem is, 3D rendering systems show you absolutely nothing until you have all

the major pieces in place. If I added bits to the code as they were explained, you

wouldn’t see anything rendered on-screen until the last few sections in this chapter. I

don’t know about you, but when talking about something like 3D, I want to see pixels!

In this section, you’ll structure the project to make it easier to plug additional

pieces in later. There will be a number of things, like the camera, projections, shaders,

and more, that will be included in here in the barest possible way simply to make sure

you can render on-screen.

You can avoid these problems if you use a 3D toolkit like Babylon 3D. Although I’m

a big fan of that toolkit, I’d rather show you how Silverlight is working and let you

make toolkit decisions yourself with the underlying knowledge in place.

But in acknowledgement of the popularity and, quite frankly, necessity of toolkits,

I’ve structured this example application following patterns and APIs that are as close

to the Babylon approach without adding extra work. When you finish the example,

you’ll have a basic 3D application architecture that will help you understand 3D as well

as enable you to, relatively easily, port to a 3D toolkit later if you wish. Figure 26.3

shows the architecture.

In this section, you’ll build the skeleton of the application architecture. There will

be a fair bit of code in place you don’t yet understand, but by the end of the next

chapter, all will be revealed. The code you create here will provide the minimum

amount of structure necessary to avoid a constant refactoring, as well as provide the

support objects required to render 3D content.



26.5.1 The scene

When rendering in 3D, you typically need some sort of master object that defines all the

parameters for what you want to render. It includes the angles through which the user

will see the content, a list of the content itself, and other things like lights and effects.

The object responsible for managing all of that information is typically called a

scene or stage, taking inspiration from movie and theater, respectively. Scene is generally the more common name, and more appropriate for having multiple instances, so

you’ll use that here.

A 3D system could have multiple scenes. Consider for a moment the old 2D video

games where you had a “walking around in the wildness” mode, and then a special



www.it-ebooks.info



Project structure: the scene and objects



661



“fighting the bad guy” mode. Pokemon and similar come to mind, but mostly because

my son has recently discovered the brain-melting pastime of playing it on my wife’s

ancient Gameboy.8 To make it easy to render, each of those could be set up as separate

scenes, with their own lighting, objects, camera, and everything else required. Each

scene should be entirely self-contained. The rendering system should simply ask it for

what needs to appear in a particular frame.

You could even extend this idea to allow each scene to have a reference to a UserControl that contains all the 2D Silverlight content you wish to overlay on the scene.

The scene itself could serve as the view model (chapter 33) for that overlay content, so

you get the best of both worlds.

In the root of your Silverlight application, delete the stock Scene class, then create

a new class named Scene, with the code shown in listing 26.4.

Listing 26.4 The Scene class

using

using

using

using

using

using

using



System;

System.Collections.Generic;

System.Windows.Controls;

System.Windows.Graphics;

Microsoft.Xna.Framework;

Microsoft.Xna.Framework.Content;

Microsoft.Xna.Framework.Graphics;



namespace Silverlight3dApp

{

public class Scene

{

public Color BackgroundColor { get; set; }

public Camera Camera { get; private set; }

public ContentManager Content { get; private set; }

public List Objects =

new List();

public GraphicsDevice GraphicsDevice

{

get { return GraphicsDeviceManager.Current.GraphicsDevice; }

}



Create list of

Scene objects



Get the

device to

render on



public Scene()

{

Content = new ContentManager(null, "Content");

Camera = new Camera();

Camera.SetFieldOfView(GraphicsDevice.Viewport.Width,

GraphicsDevice.Viewport.Height);

LoadObjects();

}

8



It was hidden in a box, high up on a shelf, but somehow he still found it. Pretty sure he can sense the presence

of ICs and LCD displays through some freaky geek telepathy.



www.it-ebooks.info



662



CHAPTER 26



Introduction to 3D



public void LoadObjects()

{

}



You’ll create objects

here in the next

sections



public void Draw()

{

GraphicsDevice.Clear(

ClearOptions.Target | ClearOptions.DepthBuffer,

BackgroundColor, 1.0f, 0);

foreach (SceneObject o in Objects)

{

o.Draw(this);

}



Render each

object



}

}

}



This code sets up the main class in your 3D system: the Scene class. The scene has a

background color to be used when nothing is renderable in a particular location, a list

of objects to be rendered, a camera to define how you see the scene, and the device

the scene is to render on. It also includes a reference to the ContentManager class provided by the Silverlight toolkit. You’ll use that later in the chapter when you work with

textures.

The Draw method is the only interesting public method in the scene. This is what

will be called from the DrawingSurface for each and every frame to be rendered.

The compiler will tell you that it doesn’t see the SceneObject or Camera types

defined anywhere, so you can’t yet compile or run. You’ll add those shortly. But before

you do that, let’s update the MainPage code-behind to use the new Scene class.

Listing 26.5 shows the MainPage updates that use the Scene class inside the Draw

event of the DrawingSurface.

Listing 26.5 Updating the MainPage code-behind to use the scene

private Scene _scene;

private void SetupScene()

{

_scene = new Scene();

_scene.BackgroundColor = new

Microsoft.Xna.Framework.Color(0xaf, 0xcf, 0xff);

}



Color



private void MainDrawingSurface_Draw(object sender, DrawEventArgs e)

{

if (_scene == null)

SetupScene();

if (_scene != null)

{

_scene.Draw();



Draw the scene



www.it-ebooks.info



Project structure: the scene and objects

e.InvalidateSurface();

}

}



663

We want to

draw it again



The listing sets up the scene in support of rendering in the Draw event. The majority

of the work is done inside the scene itself, so all this does is create the scene and set its

background color.

It’s important to note that the XNA Color class isn’t the same as the Silverlight/

XAML Color class. Because of that, you’ll need to make sure you’re using the correct

one. In this case, I fully qualified the name, but you could use a using alias like this if

you wanted to:

using xnafx = Microsoft.Xna.Framework;



If you set up that alias, you could use xnafx.Color when referring to the Color class.

You could also remove the Silverlight namespaces from the using statements, but

because you’re in the code-behind for a page, that’s probably not a good strategy.

The Draw event handler is called once for each frame to be rendered. Had I not

included the call to InvalidateSurface, the handler would be called once and only

once unless XNA detects a refresh is needed for some external reason. The

InvalidateSurface call tells XNA “Once you’ve completed processing this frame’s

data, go ahead and request the next one—I’ll probably have changes.” This is especially important for systems that animate. In fact, you could get away without this call

until you learn about animation later in this chapter.

Because not everyone who develops in Silverlight 3D is going to need maximum

frame rate rendering, making effective use of this method can save CPU and graphics

card cycles. To force a call to the Draw event from outside the event, for example, in

response to some mouse movement or a click on a Rotate button, call the Invalidate

method on the DrawingSurface directly.

In this code, the first time the Draw event is called, you make a call to set up the

scene. This is done inside the Draw event handler because certain XNA object methods—specifically some of the ones on GraphicsDevice—are available only during that

time, on that thread. You won’t have to worry about them in our example, but if you

expand on it, you may need to.

The Scene class included a list of SceneObject instances. That list represents all

the renderable geometry in the scene.



26.5.2 Renderable scene objects

Scenes typically have a number of geometry objects that must be rendered as part of

the scene. Consider a scene in your favorite movie: it probably had a number of

planes for the ground, sky, and so forth, plus maybe buildings or other structures, and

actors or other moving items. In our project structure, each of those things would be

represented as a SceneObject.



www.it-ebooks.info



664



CHAPTER 26



Introduction to 3D



You’ll implement specific SceneObjects later in the chapter. To start, listing 26.6

has the base class they’ll all use. Create a folder named Model and place this code in a

class named SceneObject in that folder.

Listing 26.6 The SceneObject class

using Microsoft.Xna.Framework.Graphics;

using Microsoft.Xna.Framework;

using Silverlight3dApp;

namespace Silverlight3DExample.Model

{

public abstract class SceneObject

{

public GraphicsDevice Device { get; set; }

public Effect Effect { get; set; }

protected Matrix _worldMatrix = Matrix.Identity;

public int VertexCount { get; protected set; }

public abstract void LoadContent();

public abstract void Draw(Scene rootScene);



Create geometry

Draw object



protected float _x;

public float X

{

get { return _x; }

set { _x = value; UpdateWorld(); }

}

private float _y;

public float Y

{

get { return _y; }

set { _y = value; UpdateWorld(); }

}

private float _z;

public float Z

{

get { return _z; }

set { _z = value; UpdateWorld(); }

}

public virtual void UpdateWorld()

{

_worldMatrix = Matrix.CreateTranslation(X, Y, Z);

}



For movement

and animation



}

}



The X, Y, and Z coordinate values are primarily for use when you animate later. Their

purpose is to position the object in world space through the use of the world matrix.

I’ll cover all of these concepts when I discuss matrices.



www.it-ebooks.info



Project structure: the scene and objects



665



The Effect class encapsulates a lot of the rendering pipeline in XNA. You’ll learn

more about that when I cover effects and shaders later in the chapter.

The SceneObject class is the base class from which you’ll derive all of the objects

you want to render. In the examples in this chapter, the class is optimized for simple

types that have only a single mesh of points, not multiple connected objects as you

might see on something complex like a helicopter with spinning rotors and a weapons

system. Similarly, something like a 3D bar graph would likely be made up of a number

of individual cubes as opposed to just a single set of vertices.

The final item you need to place in the scene is a camera. This represents the view

the user will see.



26.5.3 The camera

The concept of a camera is a convenient way to render the scene. It defines, through

the use of some matrix math, the view the user will have when looking at the screen.

Your implementation will support only a single camera, but it’s common to allow multiple cameras to exist in a scene, cutting between them as you want to focus on different items. This is similar to how it might be done on live TV.

Listing 26.7 shows the basic implementation of the Camera class. Although the class

must be fully defined here to enable rendering to work, I’ll defer discussion on the

particulars of how it works until later in this chapter.

Listing 26.7 The basic Camera class

using Microsoft.Xna.Framework;

using Silverlight3DExample.Model;



namespace Silverlight3DExample.Model

{

public class Camera

{

public Matrix View { get; private set; }

public Matrix Projection { get; private set; }

public Camera()

{

X = 0; Y = 0; Z = 5;

TargetX = 0; TargetY = 0; TargetZ = 0;

}

public void SetFieldOfView(int viewportWidth,

int viewportHeight)

{

Projection = Matrix.CreatePerspectiveFieldOfView(

MathHelper.ToRadians(45),

(float)viewportWidth /

(float)viewportHeight,

1.0f, 100.0f);

}



www.it-ebooks.info



Location and aim



Updated projection

matrix



666



CHAPTER 26



Introduction to 3D



private float _x;

public float X

{

get { return _x; }

set { _x = value; UpdateView(); }

}

private float _y;

public float Y

{

get { return _y; }

set { _y = value; UpdateView(); }

}

private float _z;

public float Z

{

get { return _z; }

set { _z = value; UpdateView(); }

}

private float _targetX;

public float TargetX

{

get { return _targetX; }

set { _targetX = value; UpdateView(); }

}

private float _targetY;

public float TargetY

{

get { return _targetY; }

set { _targetY = value; UpdateView(); }

}

private float _targetZ;

public float TargetZ

{

get { return _targetZ; }

set { _targetZ = value; UpdateView(); }

}

private void UpdateView()

{

View = Matrix.CreateLookAt(

new Vector3(X,Y,Z),

new Vector3(TargetX, TargetY, TargetZ),

Vector3.Up);

}



Updated

view matrix



}

}



As I mentioned, the camera is just a convenience. What it’s really there for is as a helpful metaphor to enable you to compute the different matrices you need to use when

rendering.



www.it-ebooks.info



Vertices



667



At this point, you finally have enough structure to the application to enable you to

compile and run it. Do so, and ensure there are no compile errors. You should see a

light blue window background where the purple one used to be. This is the background color of the Scene. If you want to test that, go ahead and change the codebehind to assign a different color to the scene, and verify that’s what comes through.

“She may not look like much, but she’s got it where it counts, kid.”

It’s always a little scary to paste that much code in your application without a thorough understanding of what it does. I’ll make it up to you over the rest of this chapter,

starting right now. The next step along the path to enlightenment is to learn about

the building blocks used when rendering 3D: vertices.



26.6 Vertices

Have you ever worked through a dot-to-dot puzzle,

or played that “box” game on a piece of graph

paper? If you have, you may recognize that vertices

are much like the corners of the boxes, or in the

case of dot-to-dot, they’re the dots. In a 3D system, a

vertex holds its location in 3D space (x, y, z), as well

as color and potentially other information.

The three vertices, shown in figure 26.4, are

named Top, Left, and Right just to make it easier to

Figure 26.4 A triangle showing its

refer to them in the upcoming example. But the 3D

three vertices

system doesn’t know them that way; to it, they’re

simply a set of points in space. The order in which you specify them (clockwise or

counterclockwise) does have an impact, which you’ll learn about later.

In the most basic case, using a simple vertex color shader, each vertex is defined by

a coordinate triad (X, Y, Z) and a color. The coordinates are represented using the

Vector3 structure. There are also Vector2 and Vector4 structures that are used in

other places of the framework.

The Vector3 structure is a convenient structure used for holding three points of

data. In the case of the vertex, it holds X, Y, and Z. In the case of a color, it could be R,

G, and B. The use of these generic classes is prevalent in DirectX and is well supported

by the video card drivers. You’ll also run into the analogs of these types if you create

your own shaders.

The structure also includes a ton of functionality for manipulating the three values

it contains. In case you were wondering why on earth these VectorN classes are so

prevalent in the framework, simply right-click one in the next listing and select Go to

Definition to see all the functions and operators it includes.

In the rest of this section, you’ll use vertices to define a triangle and then render it

using Silverlight/XNA. The triangle will be wrapped up into a new SceneObject

named SimpleTriangle that you’ll add to the scene. This will be the first time you’ll

see any rendered pixels on-screen, so I’m sure you’re pretty excited!



www.it-ebooks.info



668



CHAPTER 26



Introduction to 3D



26.6.1 Building a triangle using vertices

The simplest meaningful and visible shape you can render in 2D is a triangle. A triangle is three vertices that may be used to define a surface. If you have only two vertices,

you end up with a line, which typically isn’t renderable. Only one vertex, and you get a

point. Points aren’t rendered except in the case of special particle systems and

advanced shaders that work on them.

In listing 26.8, you’ll create a new SimpleTriangle class. This will define a triangle

using a hard-coded set of three vertices. Go ahead and create that class in the Model

folder and add the code from this listing.

Listing 26.8 Using vertices to create a simple triangle

using

using

using

using



System;

Microsoft.Xna.Framework.Graphics;

Microsoft.Xna.Framework;

Silverlight3dApp;



namespace Silverlight3DExample.Model

{

public class SimpleTriangle : SceneObject

{

private VertexBuffer _buffer = null;

public override void LoadContent()

{

const int z = 0;



Vertex defined



var leftVertex = new VertexPositionColor();

leftVertex.Position = new Vector3(-1, -1, z);

leftVertex.Color = new Microsoft.Xna.Framework.Color(0xFF, 0, 0);

var topVertex = new VertexPositionColor();

topVertex.Position = new Vector3(0, 1, z);

topVertex.Color = new Microsoft.Xna.Framework.Color(0, 0xFF, 0);



var rightVertex = new VertexPositionColor();

rightVertex.Position = new Vector3(1, -1, z);

rightVertex.Color = new Microsoft.Xna.Framework.Color(0, 0, 0xFF);

var vertices = new VertexPositionColor[]

{ leftVertex, topVertex, rightVertex };

VertexCount = vertices.Length;

_buffer = new VertexBuffer(

Device,

VertexPositionColor.VertexDeclaration,

VertexCount,

BufferUsage.WriteOnly);

_buffer.SetData(vertices, 0, VertexCount);

}



www.it-ebooks.info



Vertex type



669



Vertices

public override void Draw(Scene rootScene)

{

if (_buffer == null)

LoadContent();

_worldMatrix = Matrix.Identity;

Device.RasterizerState = RasterizerState.CullNone;

Device.SetVertexBuffer(_buffer);



No movement

Render both

sides



((BasicEffect)Effect).World = _worldMatrix;

((BasicEffect)Effect).View = rootScene.Camera.View;

((BasicEffect)Effect).Projection = rootScene.Camera.Projection;

foreach (EffectPass pass in Effect.CurrentTechnique.Passes)

{

pass.Apply();

Device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

}

}

}

}



The listing creates a 2D triangle in 3D space using vertices and stores it in the form of

a vertex buffer. A triangle has three corners and therefore three vertices. Each vertex

has a position in space defined using a Vector3. Because it’s 2D, it has no thickness—

no triangle does. If you were to have the camera oriented so that you were seeing the

shape edge on, you wouldn’t see a thing; the triangle is even thinner than paper Mario.

Because the effect and its shaders that you’re using (the BasicEffect) support it,

each vertex also has a color. The left vertex is red, the top vertex is green, and the

right vertex is blue. There are other ways to define how to shade vertices, but you’re

using the simple VertexPositionColor class in this case. For clarity, I broke each vertex out as a separate variable, but you could do all the work directly with array

elements.

The VertexBuffer is the structure used by the rest of the system to process your

list of vertices. The LoadContent method uses the rendering device, which was helpfully provided to you by the GraphicsDeviceManager via the Scene. Next, it needs the

type that corresponds to each vertex you’re using.

The VertexPositionColor type exposes the VertexDeclaration property, which

includes the type information. This property is the sole member of the IVertexType

XNA interface. This isn’t .NET type information—what it actually provides is the size

of each vertex in the buffer and a method for getting any extra data. This level of indirection is needed to be able to support different types of shaders (a simple color

shader in our case) and vertex types, including custom ones.

Finally, the code loads the vertices into the vertex buffer for use during the Draw

operation. All that’s left now is to load the triangle into the scene so it knows to

render it.



www.it-ebooks.info



670



CHAPTER 26



Introduction to 3D



26.6.2 Adding the triangle to the scene

You’ve defined the triangle object, but it’s just sitting out there, not attached to any

scene. To fix that, you’ll load the triangle (and its shader) from the LoadObjects

method of the Scene. Listing 26.9 has the code you’ll need to add to the Scene class.

Listing 26.9 Loading the triangle and its shader

public void LoadObjects()

{

SimpleTriangle o = new SimpleTriangle();

o.Device = GraphicsDevice;

BasicEffect effect = new BasicEffect(o.Device);

effect.AmbientLightColor = new Vector3(1.0f, 1.0f, 1.0f);

effect.VertexColorEnabled = true;

o.Effect = effect;



XNA effect



Objects.Add(o);

}



The code does two important things. The first is that it creates an instance of the

SimpleTriangle class and adds it to the list of objects to be rendered in the scene.

The second is it sets up a BasicEffect, which supplies the required pixel and vertex

shaders vertex.

The BasicEffect, which I’ll cover later in the chapter, is an effect that comes with

XNA. It reads the color information from the vertices and presents the information to

XNA so that you don’t have to worry about writing pixel and vertex shaders for simple

rendering scenarios like this one. Most game engine and 3D toolkit implementations

come with their own additional shaders.

Run the application. When it pops up, it should look like figure 26.5. It’s the

BasicEffect and the vertex colors you supplied that give each vertex its own color.

The shader is what handles the color blending between them.



Figure 26.5 The rendered triangle in

all of its converted-to-grayscaleduring-book-production glory. When

you see this in color on your PC, don’t

be surprised if you suddenly develop a

craving for Pink Floyd.



www.it-ebooks.info



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

5 Project structure: the scene and objects

Tải bản đầy đủ ngay(0 tr)

×