1. Trang chủ >
  2. Công Nghệ Thông Tin >
  3. Quản trị mạng >

ScreenY = -Vector.y * m_nViewportHeight / 2 + m_nViewportY + m_nViewportHeight / 2

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (11.88 MB, 1,060 trang )


TeamLRN



The final call is to CGameApp::SetupGameState which creates the application projection matrix and

the view matrix. (Because we are not yet allowing camera movement, the view matrix can be

initialized and left as an identity matrix).

CGameApp::CreateDisplay

The first thing we will do is create a string for our window title and use two local variables to hold the

desired width and height of our window (this demo window will be 400x400).

bool CGameApp::CreateDisplay()

{

LPTSTR WindowTitle = _T("Software Render");

USHORT Width

= 400;

USHORT Height

= 400;

HDC

hDC

= NULL;

RECT

rc;



If you are not familiar with basic Windows programming techniques then it is strongly recommended

that you take the Game Institute course Introduction to C++ Programming. It is vital that you know

how to do this.

Next we fill in our WNDCLASS structure so that we can register the type of window we wish to create

with the operating system.

// Register the new windows window class.

WNDCLASS wc;

wc.style

= CS_BYTEALIGNCLIENT | CS_HREDRAW | CS_VREDRAW;

wc.lpfnWndProc

= StaticWndProc;

wc.cbClsExtra

= 0;

wc.cbWndExtra

= 0;

wc.hInstance

= (HINSTANCE)GetModuleHandle(NULL);

wc.hIcon

= LoadIcon( wc.hInstance, MAKEINTRESOURCE(IDI_ICON));

wc.hCursor

= LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH )GetStockObject(BLACK_BRUSH);

wc.lpszMenuName = NULL;

wc.lpszClassName = WindowTitle;

RegisterClass(&wc);



We specified a style that forces the horizontal position of the window to be byte aligned. This allows

certain optimizations when we are copying the frame buffer to the window and the speed gain is quite

significant. The other styles simply specify that we want Windows to repaint the window when it is

resized horizontally (CS_HREDRAW) or vertically (CS_VREDRAW).

We set the icon to the one stored in the executable’s resource, a standard cursor, and the background

brush to black. The string ‘Software Render’ will be the window class name used to create an instance

of the window. Note that after calling RegisterClass no window has yet been created. We have simply

provided a template describing appearance and behavior.



www.gameinstitute.com 3D Graphics Programming with DX9



TeamLRN



Next we create the application window using the Win32 CreateWindow function. We pass in the

window class name (this is the name we assigned when we registered the class: Basic Demo). The

second parameter is the string that we would like displayed in the window caption bar (we use the

same string).

// Create the rendering window

m_hWnd = CreateWindow( WindowTitle,

WindowTitle,

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

Width,

Height,

NULL,

LoadMenu( wc.hInstance,MAKEINTRESOURCE(IDR_MENU) ),

wc.hInstance, this );

// Bail on error

if (!m_hWnd) return false;



We ask for a 400x400 overlapped window and assign a menu to this window that gets loaded from our

resource data. This menu can be viewed through the resource editor and holds commands that allow

cube rotation manipulation and other directives. If the window is not created successfully, we return

‘false’ to the calling function.

Next, we retrieve the client area of our newly created window and assign the dimensions of the client

area to our four class variables. These variables hold the rendering viewport dimensions needed for

mapping the 2D projection space points to screen space.

// Retrieve the final client size of the window

::GetClientRect( m_hWnd, &rc );

m_nViewX

= rc.left;

m_nViewY

= rc.top;

m_nViewWidth = rc.right - rc.left;

m_nViewHeight = rc.bottom - rc.top;



Once our window is created, we will create the frame buffer. This is the bitmap where all rendering

will take place. We then show the window, and return ‘true’ to indicate successful initialization.

// Build the frame buffer

if (!BuildFrameBuffer( Width, Height )) return false;

// Show the window

ShowWindow(m_hWnd, SW_SHOW);

// Success!

return true;

}



www.gameinstitute.com 3D Graphics Programming with DX9



TeamLRN



CGameApp::BuildFrameBuffer

We will need two things in order to render to the frame buffer. We need the frame buffer itself, which

will be a bitmap, and we need a device context that we can use to draw onto the bitmap surface. The

first thing we do in the following function, is retrieve a temporary device context for the application

window and then (if not already created) we create a compatible device context that the frame buffer

can use. We will store the handle to this device context in the CGameApp member variable

m_hdcFrameBuffer.

bool CGameApp::BuildFrameBuffer( ULONG Width, ULONG Height )

{

HDC hDC = ::GetDC( m_hWnd );

if ( !m_hdcFrameBuffer ) m_hdcFrameBuffer = ::CreateCompatibleDC( hDC );



Next we create a bitmap that is compatible with the application window and store the returned handle

to that bitmap in the CGameApp member variable m_hbmFrameBuffer. We also take care to release

any previously allocated frame buffer data prior to this step, not shown here.

m_hbmFrameBuffer = CreateCompatibleBitmap( hDC, Width, Height );

if ( !m_hbmFrameBuffer ) return false;



We select this bitmap into the device context we created for it earlier and it is ready to be used as our

frame buffer. Note that when you select an object into a device context, any previously selected object

of the same type is returned from the function call. You should store this object and make sure that you

select the default object back into the device context before you destroy it. For this reason we made a

copy of the default bitmap returned from the SelectObject function and stored it in the CGameApp

member variable m_hbmSelectOut. You should do this with any objects that you intend to select into a

device context, including pens and brushes. If you fail to restore a device context to its default state

before releasing it, your application (as well as any other applications running concurrently) may not

perform properly until the operating system is rebooted. On earlier versions of Windows this is

especially true; device contexts were a very limited resource.

m_hbmSelectOut = (HBITMAP)::SelectObject( m_hdcFrameBuffer,

m_hbmFrameBuffer );



Finally we release the window DC (because we only used it to create a compatible DC for the bitmap)

and set the frame buffer DC so that it renders transparently.

::ReleaseDC( m_hWnd, hDC );

::SetBkMode( m_hdcFrameBuffer, TRANSPARENT );

return true;

}



www.gameinstitute.com 3D Graphics Programming with DX9



TeamLRN



CGameApp::BuildObjects()

The CGameApp class has a single mesh which will hold our cube. We call the Mesh’s AddPolygon

function to add the 6 faces of our cube.

bool CGameApp::BuildObjects()

{

CPolygon *pPoly = NULL;

if ( m_Mesh.AddPolygon( 6 ) < 0 ) return false;



For each polygon we now add four vertices that define the model space coordinates of the corner points

of that face. This is similar to the cube example code we looked at earlier in this lesson:

// Front Face

pPoly = m_Mesh.m_pPolygon[0];

if ( pPoly->AddVertex( 4 ) < 0 ) return false;

pPoly->m_pVertex[0]

pPoly->m_pVertex[1]

pPoly->m_pVertex[2]

pPoly->m_pVertex[3]



=

=

=

=



CVertex( -2, 2, -2

CVertex( 2, 2, -2

CVertex( 2, -2, -2

CVertex( -2, -2, -2



);

);

);

);



// Top Face

pPoly = m_Mesh.m_pPolygon[1];

if ( pPoly->AddVertex( 4 ) < 0 ) return false;

pPoly->m_pVertex[0]

pPoly->m_pVertex[1]

pPoly->m_pVertex[2]

pPoly->m_pVertex[3]



=

=

=

=



CVertex( -2,

CVertex( 2,

CVertex( 2,

CVertex( -2,



2, 2 );

2, 2 );

2, -2 );

2, -2 );



// Back Face

pPoly = m_Mesh.m_pPolygon[2];

if ( pPoly->AddVertex( 4 ) < 0 ) return false;

pPoly->m_pVertex[0]

pPoly->m_pVertex[1]

pPoly->m_pVertex[2]

pPoly->m_pVertex[3]



=

=

=

=



CVertex( -2, -2,

CVertex( 2, -2,

CVertex( 2, 2,

CVertex( -2, 2,



2

2

2

2



);

);

);

),



// Bottom Face

pPoly = m_Mesh.m_pPolygon[3];

if ( pPoly->AddVertex( 4 ) < 0 ) return false;

pPoly->m_pVertex[0]

pPoly->m_pVertex[1]

pPoly->m_pVertex[2]

pPoly->m_pVertex[3]



=

=

=

=



CVertex( -2, -2, -2 );

CVertex( 2, -2, -2 );

CVertex( 2, -2, 2 );

CVertex( -2, -2, 2 );



// Left Face

pPoly = m_Mesh.m_pPolygon[4];

if ( pPoly->AddVertex( 4 ) < 0 ) return false;



www.gameinstitute.com 3D Graphics Programming with DX9



TeamLRN



pPoly->m_pVertex[0]

pPoly->m_pVertex[1]

pPoly->m_pVertex[2]

pPoly->m_pVertex[3]



=

=

=

=



CVertex(

CVertex(

CVertex(

CVertex(



-2, 2, 2 );

-2, 2, -2 );

-2, -2, -2 );

-2, -2, 2 );



// Right Face

pPoly = m_Mesh.m_pPolygon[5];

if ( pPoly->AddVertex( 4 ) < 0 ) return false;

pPoly->m_pVertex[0]

pPoly->m_pVertex[1]

pPoly->m_pVertex[2]

pPoly->m_pVertex[3]



=

=

=

=



CVertex(

CVertex(

CVertex(

CVertex(



2, 2, -2

2, 2, 2

2, -2, 2

2, -2, -2



);

);

);

);



We now have our mesh created and all polygons defined. Next we need to assign this single mesh to

both objects in our game world. This is a classic example of instancing mesh data. Our world will

contain two objects, but only one mesh will be used by both:

// Our two objects should reference this mesh

m_pObject[ 0 ].m_pMesh = &m_Mesh;

m_pObject[ 1 ].m_pMesh = &m_Mesh;



Finally we set each object world matrix so that they are positioned in different locations in world

space. Object0 will be centered at world space vector (-3.5, 2, 14) and Object1 will be positioned at

world space vector (3.5, -2, 14).

// Set both objects matrices so that they are offset slightly

D3DXMatrixTranslation(&m_pObject[0].m_mtxWorld, -3.5f, 2.0f, 14.0f );

D3DXMatrixTranslation(&m_pObject[1].m_mtxWorld, 3.5f, -2.0f, 14.0f );

// Success!

return true;

}



Because we are setting the view matrix to identity, our camera will be located at world space position

(0, 0, 0) with a look vector of (0, 0, 1). This means that both cubes will be located at a distance of 14

units in front of the camera. Both will be offset horizontally and vertically from the camera 3.5 units

and 2.0 units respectively in opposing directions.

Notice that we use the D3DX library to build our translation matrix for each object. Both cubes will

initially not be rotated with regards to the world space axes.

CGameApp::SetupGameState

void CGameApp::SetupGameState()

{

float

fAspect;

D3DXMatrixIdentity( &m_mtxView );



www.gameinstitute.com 3D Graphics Programming with DX9



TeamLRN



The first thing this function does is set the application view matrix to an identity matrix. Remember

that in this demo we are not going to be manipulating the view matrix. Thus we can set it up once at

application start-up and forget about it. Remember that an identity matrix provides no translation

values and will align objects with the standard world axes. Again, this is equivalent to us explicitly

placing our camera at world space coordinate (0,0,0) looking down the positive Z axis with a look

vector of (0,0,1) and an up vector of (0,1,0).



Identity View Matrix

Right Vector



Up Vector



Look Vector



1

0

0



0

1

0



0

0

1



0



0



0



0

0

0

1



Å----------------------------------Translation Vector (0,0,0)----------------------Ỉ

In later demo applications we will manipulate the view matrix to allow us to move the camera about

the 3D world. When we do this we will have to rebuild the view matrix every time the camera position

or rotation changes.

Our next task is to build the projection matrix using the D3DXMatrixPerspectiveFovLH function. In

order to avoid image distortion when mapping from the projection window to the viewport, we

calculate the aspect ratio of the viewport and pass it into the function. Here we are asking for a

projection matrix that gives us a vertical FOV of 60 degrees (D3DXToRadian is a helper function that

automatically converts degrees to radians).

fAspect = (float)m_nViewWidth / (float)m_nViewHeight;

D3DXMatrixPerspectiveFovLH( &m_mtxProjection, D3DXToRadian( 60.0f ),

fAspect, 1.01f, 1000.0f );



The last two parameters to the above function can be ignored for now as they are used for clipping and

depth buffer coordinate mapping which are not used in this application. The resulting matrix is stored

in the CGameApp::m_mtxProjection member variable. Finally, we set both objects to a true rotation

status:

// Enable rotation for both objects

m_bRotation1 = true;

m_bRotation2 = true;

}



www.gameinstitute.com 3D Graphics Programming with DX9



TeamLRN



CGameApp::BeginGame

When InitInstance returns, we call the CGameApp::BeginGame function. This is the function that will

contain the main message processing and render loop. It will not return program flow back to WinMain

until the user chooses to close the application. This is very similar to how MFC encapsulates the

message pump within the CWinApp::Run function.

int CGameApp::BeginGame()

{

MSG msg;

// Start main loop

while (1)

{

// Did we receive a message, or are we idling ?

if ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )

{

if (msg.message == WM_QUIT) break;

TranslateMessage( &msg );

DispatchMessage ( &msg );

}

else

{

FrameAdvance();

}

} // Until quit message is received

return 0;

}



The BeginGame function sits in a loop calling CGameApp::FrameAdvance in order to redraw the

scene. Before rendering we check to make sure that there are no messages in the message pump which

need to be handled. If there are messages, we need to remove them and send them off to the OS for

processing. These messages will eventually be routed to our StaticWndProc function that handles the

application level processing of these messages.

The only message we are interested in for now is the WM_QUIT message. It tells us that the user has

attempted to close the application. We will need to break out of our infinite loop and return back to our

WinMain function, where the function will end and the application will be shut down.



CGameApp::FrameAdvance

The FrameAdvance function is called repeatedly by the BeginGame function. It will apply rotations to

the objects, clear the frame buffer to erase the previous frame, render each of the objects to the frame

buffer and copy the contents of the frame buffer to the main application window whereby a new frame

is displayed to the user.



www.gameinstitute.com 3D Graphics Programming with DX9



TeamLRN



void CGameApp::FrameAdvance()

{

CMesh

*pMesh = NULL;

TCHAR

lpszFPS[30];



The first thing we do is advance the timer since we need to keep track of the time that has passed

between the previous frame and the current one. The CTimer::Tick function retrieves the current time

from the high performance counter and updates its internal variables so that we can access the data

later on. The parameter passed in is a frame rate ceiling value. This locks the frame rate to prevent it

from updating too quickly on very fast computers. In our demo we use a value of 60. This means we

desire to update the screen no more than 60 times per second. The CTimer::Tick function will burn up

any extra time to make this so:

m_Timer.Tick( 60.0f );



Next we call the CGameApp::AnimateObjects function. This is the function that applies the rotations

to the object world matrices.

AnimateObjects();



Then we call CGameApp::ClearFrameBuffer to erase the previous frame image from our frame buffer

bitmap. It uses the frame buffer device context to draw a large rectangle over the entire bitmap. The

color of the rectangle is the value passed into this function (in our demo, bright white). This allows us

to have any background color we want on the frame buffer. Be sure to check the source code for

implementation details.

ClearFrameBuffer( 0x00FFFFFF );



Having our clean frame buffer and all rotations applied to our object world matrices, we are ready to

draw those objects in their newly rotated positions. We now begin our render loop. For each object we

get a pointer to its mesh and then loop through each of the polygons. For each polygon we call the

CGameApp::DrawPrimitive function which will take care of rendering the wire frame polygon to the

frame buffer.

for ( ULONG i = 0; i < NumberOfObjects; i++ )

{

pMesh = m_pObject[i].m_pMesh;

// Loop through each polygon

for ( ULONG f = 0; f < pMesh->m_nPolygonCount; f++ )

{

DrawPrimitive( pMesh->m_pPolygon[f], &m_pObject[i].m_mtxWorld );

}

}



When the above code exits, all objects have had their polygons rendered into the frame buffer. The

scene is now ready to be displayed to the user. However, before we do that we call



www.gameinstitute.com 3D Graphics Programming with DX9



TeamLRN



CTimer::GetFrameRate and pass it a string to fill with frame rate information. This string is also added

to the frame buffer:

m_Timer.GetFrameRate( lpszFPS );

TextOut( m_hdcFrameBuffer, 5, 5, lpszFPS, strlen( lpszFPS ) );



Finally we present the newly rendered frame to the user. The CGameApp::PresentFrameBuffer call

performs the copying of the frame buffer bitmap to the application window client area.

PresentFrameBuffer();

}



CGameApp::AnimateObjects

This function creates the rotation matrices which are later multiplied by each object world matrix to

create a new world matrix which has been rotated from its previous position. This can be done using

fewer lines of code then we will see below (and we will examine a shorter version later). The reason

we have expanded this code is that it better demonstrates the matrix multiplication process.

First we create some local D3DXMATRIX variables to hold Yaw, Pitch and Roll data. Another matrix

(mtxRotate) will hold the concatenated result of multiplying these matrices. We also use three local

float variables that will be used to hold the appropriate angles.

void CGameApp::AnimateObjects()

{

D3DXMATRIX mtxYaw, mtxPitch, mtxRoll, mtxRotate;

float RotationYaw, RotationPitch, RotationRoll;



If the user has not disabled the rotation of Object1 then we create some rotational values. These are

arbitrary values and can be modified. We selected a yaw rotation value of 75 degrees per second, a

pitch rotation value of 50 degrees per second and a roll value of 25 degrees per second. Multiplying

these values by the fraction of a second returned from the CTimer::GetTimeElapsed function scales

them accordingly. If we are running at, say, 4 frames per second, this call would return 0.25 which will

scale the yaw rotation value to 18.75. This allows for rotation to be independent of frame rate.

// Rotate Object 1 by small amount

if ( m_bRotation1 )

{

RotationYaw

= D3DXToRadian( 75.0f * m_Timer.GetTimeElapsed() );

RotationPitch = D3DXToRadian( 50.0f * m_Timer.GetTimeElapsed() );

RotationRoll = D3DXToRadian( 25.0f * m_Timer.GetTimeElapsed() );



Using these values you can see that the object rotates around the X axis at twice the rate it rotates about

the Z axis, and rotates about the Y axis three times the amount it rotates about the Z axis.

With our yaw, pitch and roll rotation values we build three rotation matrices. We also create an identity

matrix to hold the concatenation of all three matrices.

www.gameinstitute.com 3D Graphics Programming with DX9



TeamLRN



// Build rotation matrices

D3DXMatrixIdentity( &mtxRotate );

D3DXMatrixRotationY( &mtxYaw, RotationYaw);

D3DXMatrixRotationX( &mtxPitch,RotationPitch);

D3DXMatrixRotationZ( &mtxRoll, RotationRoll);



The next step is to use the D3DXMatrixMultiply (which multiplies two matrices) function to combine

all of these rotations into a final matrix. This function is an alternative to using the overloaded *

operator. We use D3DXMatrixMultiply to better see the multiplication order.

// Concatenate the rotation matrices

D3DXMatrixMultiply( &mtxRotate, &mtxRotate, &mtxYaw );

D3DXMatrixMultiply( &mtxRotate, &mtxRotate, &mtxPitch );

D3DXMatrixMultiply( &mtxRotate, &mtxRotate, &mtxRoll );



The resulting matrix is returned to us in the mtxRotate variable. It contains all of the rotations for the x,

y and z axes that need to be applied to the first object. All that is left to do is multiply this matrix with

the object’s current world matrix and we are done:

D3DXMatrixMultiply( &m_pObject[ 0 ].m_mtxWorld, &mtxRotate,

&m_pObject[ 0 ].m_mtxWorld );

} // End if Rotation Enabled



Object1 now has its world matrix updated to contain the new rotations. When this matrix is used to

transform the mesh vertices later, the object will be rendered in its new orientation.

We repeat the same steps for Object2 and the function returns.

For completeness, here is some code that could be used to make the function smaller:

void CGameApp::AnimateObjects()

{

D3DXMATRIX mtxYaw, mtxPitch, mtxRoll, mtxRotate;

float RotationYaw, RotationPitch, RotationRoll;

if ( m_bRotation1

{

RotationYaw

RotationPitch

RotationRoll



)

= D3DXToRadian( 75.0f * m_Timer.GetTimeElapsed() );

= D3DXToRadian( 50.0f * m_Timer.GetTimeElapsed() );

= D3DXToRadian( 25.0f * m_Timer.GetTimeElapsed() );



// Build entire rotation matrix

D3DXMatrixRotationYawPitchRoll(&mtxRotate , RotationYaw , RotationPitch,

RotationRoll);

// Multiply with world matrix using operators

m_pObject[0].m_mtxWorld = mtxRotate * m_pObject[0].m_mtxWorld;

} // End if Rotation Enabled



www.gameinstitute.com 3D Graphics Programming with DX9



TeamLRN



if ( m_bRotation2

{

RotationYaw

RotationPitch

RotationRoll



)

= D3DXToRadian( -25.0f * m_Timer.GetTimeElapsed() );

= D3DXToRadian( 50.0f * m_Timer.GetTimeElapsed() );

= D3DXToRadian( -75.0f * m_Timer.GetTimeElapsed() );



// Build entire rotation matrix

D3DXMatrixRotationYawPitchRoll(&mtxRotate , RotationYaw , RotationPitch,

RotationRoll);

// Multiply with world matrix using operators

m_pObject[1].m_mtxWorld = mtxRotate * m_pObject[1].m_mtxWorld;

} // End if rotation enabled

}



As you can see we have used D3DXMatrixRotationYawPitchRoll to build a matrix that contains all

three rotations in one call. The resulting matrix is multiplied with the object world matrices using the

overloaded * operator instead of the D3DXMatrixMultiply function.



CGameApp::DrawPrimitive

The CGameApp::DrawPrimitive function renders our polygons. It is this function that is responsible

for transforming the polygons from model space to screen space and then drawing them to the frame

buffer. This is the heart of our rendering pipeline.

void CGameApp::DrawPrimitive( CPolygon * pPoly, D3DXMATRIX * pmtxWorld )

{

D3DXVECTOR3 vtxPrevious, vtxCurrent;

// Loop round each vertex transforming as we go

for ( USHORT v = 0; v < pPoly->m_nVertexCount + 1; v++ )

{

// Store the current vertex

vtxCurrent = (D3DXVECTOR3&)pPoly->m_pVertex[ v % pPoly->m_nVertexCount ];



First we loop through each vertex in the polygon and store the current vertex in the vtxCurrent vector.

The [v % pPoly->m_nVertexCount] line makes certain that we wrap around to vertex zero again for

the end point of the last line. You will notice that we loop + 1 times more than there are vertices in the

polygon. This is because a final line will be drawn between the last vertex and vertex zero.

The object that this polygon belongs to has had its world matrix passed in so we can multiply each

vertex with this matrix to transform it into world space:

// Multiply the vertex position by the World / object matrix

D3DXVec3TransformCoord( &vtxCurrent, &vtxCurrent, pmtxWorld );



The vertex is now in world space and is ready to be transformed into view space.

www.gameinstitute.com 3D Graphics Programming with DX9



Xem Thêm
Tải bản đầy đủ (.pdf) (1,060 trang)

×