Showing posts with label Direct3D. Show all posts
Showing posts with label Direct3D. Show all posts

Wednesday, December 9, 2009

Game Deployment Woes (Just Make It Flash!)

In one of my previous posts I released a Pong clone game that, albeit not polished, was a fully working game and I was quite proud of it. Of course, as sometimes happens with the deployment of Windows-based games, I was dismayed when I learned nobody who actually took an interest (thanks to you faithful few!) was able to run the game :(

That's when the fun began. I asked one friend to make sure he had the DirectX runtime installed and even to reinstall it - no luck. I then learned the C++ runtime was required and had the same friend perform that install - no luck. I tried two different machines with the same installations - no luck. Finally I learned it was not just requirements on the target systems; it turns out I had included a debug version of a DirectX library in my project, which allowed the program to be created and executed, just not in a way that it would work!

You see, as a developer, I have all of the tools required to run any program I create. But other machines don't necessarily have these tools/environments. So I think I'll use the following plan for deploying my next game.
  1. Create a game, coding and testing in DEBUG mode
  2. Change the project from DEBUG to RELEASE mode
  3. Replace DEBUG libraries with RELEASE libraries
  4. Build the project in RELEASE mode
  5. Create an installer project to install the game, the C++ runtime, and the DirectX runtime
  1. Create a Flash game that can be played from a web page in any browser!
Now all I need to do is learn to program a game in Flash...

Tuesday, November 3, 2009

Pong Postmortem: Completing a Game in 24 Hours!

For the uninitiated (or techno-illiterate), a "postmortem" is a wrap-up discussion summarizing the good, bad, and ugly of a project following its development. What follows is a summary of the creation of my first completed game, a clone of the Atari classic Pong.

My Pong clone was created in 24 hours, though not in the type of all-nighter hack-a-thon you might expect or even a typical three work days. I have a full-time day job, so development is typically done late evenings, but fortunately I kept track of the amount of time spent on specific components. Note that I started with a working Direct3D application from Luna's book Introduction to 3D Game Programming with DirectX 9.0c: A Shader Approach. Here is the hours-to-effort breakdown:

  • 10 Hours - I dedicated the first 10 hours to rendering the basics of the game: the board, two paddles, and the ball. This included creation of graphical "placeholders"; I'm not an artist but I realized I could come back and make the art fancy after everything worked. Getting the ball in motion and then allowing it to bounce off the walls and paddles (Collision Detection) was the second major focus. Finally, the simple mechanic of allowing the player to move a paddle up/down brought the game into a playable state.

  • 4 Hours - The primary effort of the next stretch was to implement a basic computer opponent (Artificial Intelligence), and I started with a perfect opponent whose vertical position matched the ball's position at every moment. When adding different game modes, this perfect opponent became part of the game's Practice Mode. I added the ball's motion blur effect just to improve aesthetics and moved hard-coded values to an include file for easier maintenance and testing.

  • 4 Hours - In this phase I worked on creating a computer opponent that moved more realistically but was also fallible; a perfect opponent gets boring to play against! I added different difficulty levels (easy/medium/hard), which works by choosing a random "reaction time delay" between when the player hits the ball and the computer opponent is allowed to hit the ball. Obviously, as difficulty increases, the computer's reaction time improves to make it a more formidable opponent.

  • 4 Hours - At this point the game was playable with differing levels of difficulty but no clear way of selecting game settings. I created the menu system and added scoring to determine the winner.

  • 2 Hours - The last step before I considered the game completed was to work on the art, which could clearly still use some work! :)

Final Thoughts:
I knew this project would be relatively simple because there are only four game objects to keep track of: two paddles, one ball, and one board. However, due to its simple nature, I was inclined to hack it together quickly and did not follow strict OOP (object-oriented programming) principles. The majority of the game code is in a single monolithic file. I also resorted to reusing an existing class designed to output simple statistics to display text-based menus. But worse, the same class used to show all menus and text became my game state manager since I needed to know which menus had been processed. I then extended it to display current score values and to show which player had won the game. Although I could have put a lot of time into doing it the right way, the purpose of this effort was to get something done. In future endeavors I plan to follow better OOP design.

The best part of this project is that I actually completed a working game! I am proud of the subtle but effective motion blur effect. The game doesn't have all the bells and whistles (seriously, no sounds or music yet) but it is playable, implements Collision Detection and Artificial Intelligence, and has a clearly defined winner at the end. Some may even consider it fun :)

Thursday, July 16, 2009

Quick Random Update

So, Gamasutra is on Twitter too. You may recognize them, or at least you should if you have any business as a video game developer/designer. Gamasutra is one of the most popular game-oriented websites with full-featured articles and commentaries on the state of the industry, game reviews, yadda yadda yadda. So now you can check out not only the website, but also the Twitter feed. Oops, sorry if that last link didn't work; I suppose I should have linked it the proper way: @Gamasutra!

Enough hype, this post is mostly me checking in for a quick progress update. I blasted through reading about lighting, textures, blending, and stenciling in my Direct3D book and then found out the exercises required a fair deal of re-reading. Lesson learned, I have to work through chapter exercises before moving on to more difficult chapters! The same applies to the Game Design Concepts course I'm taking -- it's much easier to keep up with the readings than work through the exercises, some of which I have to skip. FYI, you can follow discussion for the course on Twitter too; just search for #GDCU!

The topic of my next post will be the power of sine waves. Yep, I'm talking about those tricky oscillating waves you learned about way back in high school. I've found some interesting applications and want to show off some of the cool stuff you can do with them. And now that I'm finally understanding the basics of HLSL (High Level Shader Language) shaders, the part that tripped me up big time in the last book I read, you can expect some posts on that stuff too.

Just curious, when was the last time you used something from high school you never thought you would need again? What's the trickiest math you've used in game development, and does calculus ever figure into the equation (pun... fully... intended!)?

Saturday, July 4, 2009

The Problem with the Mythical Black Box

In programming, we occasionally make use of something known as a "black box." This Mythical Black Box is any library or chunk of code written by someone else that allows us to abstract a problem so we don't have to understand the details of solving it. For example, if we pass the values 3 and 4 to a "calculator black box", which might be a class library written by another developer, we would expect it to return the value 12 for multiplication. We don't care how it works internally, whether it computes 3+3+3+3 or 4+4+4; all we care is that it returns the correct value. A black box diagram for an encryption algorithm might look something like the following.

Password => { Encryption Black Box } => Encrypted Password

Jeff Atwood of Coding Horror has recently expressed seemingly contrasting views on this topic, both touting and refuting the benefits of abstraction. And for good reason, because it can be a good thing or a bad thing depending on whose library you choose. For one thing, you should never introduce code into your project if you aren't 100% certain it will fulfill its promise. Many developers (myself included) prefer the "roll your own" method for most tasks simply because you know you can trust your own code (not that it will be error-free, just that it won't be malicious). But sometimes it makes sense to use a well-tested library from another developer, such as the ever-popular JQuery library for Javascript development. In such cases I suggest ensuring you trust the developer (e.g. they're well known, such as Microsoft, and would be ridiculed if the library failed) or that you have full access to the source code. Many open source projects are reviewed by large numbers of people such that bugs are caught and fixed quickly.

Unfortunately, even if you take my suggestions your black box may someday fail. The whole reason I bring this up is that I faced this exact problem with my SkyCop demo. I developed it using the Common Files Framework presented in Engel's Beginning Direct3D Game Programming, 2nd Edition and provided in a specific version of the Microsoft DirectX SDK. When I ugraded to the most recent build of the SDK, my demo suddently stopped working and I started getting reports of others not being able to run it either. I ran into numerous problems trying to get it to work with the latest DirectX SDK and believe the Common Files Framework was removed altogether in the latest version. Having relied too heavily on that framework, I wasn't sure how to resolve the compilation errors and feared I would have to scrap the project altogether.

Fortunately, in his Introduction to 3D Game Programming with DirectX 9.0c: A Shader Approach, Frank Luna fully describes a working game framework instead of glossing over it and providing another black box. I was able to essentially rip out the core of my SkyCop demo, converting it to use the new framework, and am pleased to have an updated demo below that should work on any DirectX 9 (or newer) version, but let me know if you still have any trouble running it.

SkyCop v1.1 Demo Download

Black boxes can be a good thing when they're well-written by a trusted developer, but it's also a good idea to review the code (if possible) and at least have a basic understanding of how it works. In my case, as a learning newbie, I was happy to avoid the initial complexity and that later bit me from behind. One thing to keep in mind, though, is that black boxes come in many shapes and sizes. My black box was a game framework but the concept could also apply to a game engine, a single portion (Renderer, Physics, Sound, etc.) of a game engine, or some other game components I'm not even aware of yet.

Do you know what's going on under the hood of your game framework/engine? What black boxes are you relying on to develop your games?

Tuesday, April 28, 2009

Point-Plane Collision Detection Explained

In my previous post I presented a function for detecting collision of a point in 3D space with a plane (surface of an object). It works well enough in my SkyCop demo because all I need to check is if the tip of the primary ship intersects the surface of other ships. Here's a description of the code, broken into seven steps to help you understand this basic form of collision detection.

1)Understand the Plane Equation: Ax + By + Cz + D = 0
The plane equation tells us that a point (x,y,z) is on a plane having normal vector (A,B,C) and distance D from the origin when Ax + By + Cz + D = 0. If we plug in A, B, C, D, x, y, z and get any value other than zero, the point (x,y,z) is not on the plane. Also note that since the dot product of vectors (A,B,C) and (x,y,z) equals Ax + By + Cz, we can rewrite the Plane Equation as DotProduct(N, P) + D = 0 for N=(A,B,C) and P=(x,y,z).
 
2)Get the collision surface's normal vector.
As noted above, in order to detect if a point is on our collision surface (such as a wall we want to prevent the player from running through), we need to know the normal vector perpendicular to the surface. A normal vector is computed by taking the cross product of two vectors on the plane.

The simplest 2D plane we can create in 3D space is a triangle with three vertices (call them vP1, vP2, and vP3) and I will focus on a triangular collision surface for simplicity. We compute the normal vector by generating a vector from vP1 to vP2 and another from vP2 to vP3 and then take the cross product of those two vectors to obtain the normal vector. Finally, the normal vector is normalized (made to have a length of 1.0) to simplify calculations.
    vN1 = (vP2 - vP1);
    vN2 = (vP3 - vP2);
    D3DXVec3Cross(&vNormal, &vN1, &vN2);
    D3DXVec3Normalize(&vNormal, &vNormal);
3)Get plane distance using the Plane Equation.
Using our revision of the Plane Equation, DotProduct(N, P) + D = 0, we can solve for D to get D = -DotProduct(N, P). So we compute D by plugging in the normal vector and any point on the plane; any of the triangle surface's three vertices will suffice.
    d = - D3DXVec3Dot(&vP1, &vNormal);
4)Classify the start and destination points.
Now that we've determined the collision surface's normal vector and distance from the origin (A, B, C, and D) we can use the Plane Equation for something really cool: determining if a given point lies on the plane! When we plug a point P=(x,y,z) into the left side of our revised plane equation DotProduct(N, P) + D we get a result value, p. If p > 0, the point is in front of the plane. If p < 0, the point is behind the plane. And of course we know that if p = 0 the point lies on the plane.

Since we're only concerned with detecting collisions for moving objects, there must be a start position where the object moved from (pstart) and a destination position (pdest) to which the object is moved. The trick is realizing that a collision only occurs if these two positions have different locations in relation to the plane -- if they're both in front of or both behind the plane, no collision occurred and we can return from the function.
    p = (D3DXVec3Dot(&vNormal, &pStart) + d);
    if ( p > 0.0f ) pStartLoc = PlaneFront;
    else if ( p < 0.0f ) pStartLoc = PlaneBack;
    else pStartLoc = OnPlane;
    
    p = (D3DXVec3Dot(&vNormal, &pDest) + d);
    if( p > 0.0f ) pDestLoc = PlaneFront;
    else if (p < 0.0f ) pDestLoc = PlaneBack;
    else pDestLoc = OnPlane;
        
    if (pStartLoc == pDestLoc) return false;
5)Get the ray.
At this point we know that an intersection did occur! Great, but there is a small problem; we don't know where it occurred, which is equally important. The plane that was crossed is an infinitely expanding plane in 3D space, often called a hyperplane, so we need to check if the collision occurred within the bounds of our collision surface's borders. Computing the vector, called a "ray", from our object's start position (pstart) to its destination position (pdest) helps determine where the collision occurred. The vector is normalized to simplify calculations.
    ray = pDest - pStart;
    D3DXVec3Normalize(&ray, &ray);
6)Get the intersection point.
Vector math tells us that we can access points along a ray from pstart to pdest using the formula (pstart + ray * t). Since we know that a point along our ray from pstart to pdest definitely intersects the plane, we use the plane equation to determine the value of t below. Note that I've plugged the intersection point (pstart + ray * t) into the Plane Equation instead of a generic (x,y,z) because we know that point lies on the plane. I've also renamed pstart to "s" and the ray to "r" for brevity.
  • A(sx + rx*t) + B(sy + ry*t) + C(sz + rz*t) + D = 0
  • A*sx + A*rx*t + B*sy + B*ry*t + C*sz + C*rz*t + D = 0
  • DotProduct(N, s) + t*DotProduct(N, r) + D = 0
  • t = - (DotProduct(N, s) + D) / DotProduct(N, r)
We've already computed the normal vector N=(A,B,C) as well as the plane distance D and the ray so we can calcuate t. Then we plug it into our formula to get the actual intersection point on the plane.
    t = - (d + D3DXVec3Dot(&vNormal, &pStart)) 
        / D3DXVec3Dot(&vNormal, &ray);
    
    intersect = pStart + (ray * t);
7)Determine if intersection hit the collision surface!
We're almost there! We determined the intersection point where our object collided on a hyperplane corresponding to the collision surface. So the final question is, "Does the intersection point fall within the bounds of our collision surface?" In order to answer that question we compute vectors from the intersection point to the surface vertices and measure the angles between those vectors. If we form a complete circle, we know the point lies within the bounds of our collision surface!
    v1 = intersect - vP1;
    v2 = intersect - vP2;
    v3 = intersect - vP3;
    D3DXVec3Normalize(&v1, &v1);
    D3DXVec3Normalize(&v2, &v2);
    D3DXVec3Normalize(&v3, &v3);
    
    // Angles around intersection should total 360 degrees (2 PI)
    thetaSum = acos(D3DXVec3Dot(&v1, &v2)) 
             + acos(D3DXVec3Dot(&v2, &v3)) 
             + acos(D3DXVec3Dot(&v3, &v1));
    
    if (fabs(thetaSum - (2 * D3DX_PI)) < 0.1)
        return true;
    else
        return false;
There are a couple caveats to this method of collision detection. The first is that the triangle surface vertices vP1-vP3 must be specified in clockwise order so the surface normal vector is computed correctly. We also have to allow for a small margin of error in the calculation of the sum of angles due to the lack of floating-point precision. Finally, I've read that this method should only be used on convex (as opposed to concave) polygons which do not curve inward on themselves.

I know this is very low-level and there are probably simpler ways to do it (bounding boxes/spheres come to mind...), but it's kinda neat to see how the math makes it work! Please feel free to post comments/questions to let me know what you think of this tutorial. I hope it provides a clearer understanding of what's going on under-the-hood in basic point-plane collision detection.

Friday, April 24, 2009

Quick 'n Dirty Point-Plane Collision Detection

A couple weeks ago I was in a frenzy to implement collision detection in my SkyCop demo. Sure, I planned to go back and study it in more detail at some point, but for the moment I just wanted to get something done so I could avoid flying through enemy ships and start blowing them up. After a few days of searching, I quickly realized that collision detection is no trivial task; it's a field of study all on its own! I started with this flipcode article that served as a good introduction but left me a bit confused. After more research and trial-and-error I had a working example function that I would like to share with anyone needing to feed that same primal urge to get something working quickly.

The function, written in C++ for Direct3D 9, determines if a single point will cross a triangular plane (parameters vP1-vP3 specified in clockwise order). Parameter prevPos is the initial position your character/camera/object moves from and curPos is the position to which it will be moved. For example, if you want to detect if an FPS player will collide with a wall, you would call this function with the camera's initial position as prevPos and its predicted next position [prevPos + (speed * direction)] as curPos. Submit a comment if you have any questions, and keep in mind I will discuss the code in detail in a future post.

Btw, I'm sure there are engines that handle all this stuff for you, but since I haven't gotten into those yet it was a nice exercise for me. And I do apologize for the ugly formatting. I'll work on making code selections look better in future posts!

enum PlanePosition
{
   PlaneFront,
   PlaneBack,
   OnPlane
};

bool IntersectsTriangle(D3DXVECTOR3 prevPos, D3DXVECTOR3 curPos, 
          D3DXVECTOR3 vP1, D3DXVECTOR3 vP2, D3DXVECTOR3 vP3)
{
    float p, d, t, thetaSum;
    PlanePosition prevLoc, curLoc;
    D3DXVECTOR3 ray, v1, v2, v3, vNormal, intersect;
 
    // Compute normal vector for the plane
    v1 = (vP2 - vP1);
    v2 = (vP3 - vP2);
    D3DXVec3Cross(&vNormal, &v1, &v2);
    D3DXVec3Normalize(&vNormal, &vNormal);

    
    // Compute plane distance
    d = - D3DXVec3Dot(&vP1, &vNormal);

    
    // Classify positions using planar equation
    p = (D3DXVec3Dot(&vNormal, &prevPos) + d);
    if ( p > 0.0f ) prevLoc = PlaneFront;
    else if ( p < 0.0f ) prevLoc = PlaneBack;
    else prevLoc = OnPlane;

    
    p = (D3DXVec3Dot(&vNormal, &curPos) + d);
    if( p > 0.0f ) curLoc = PlaneFront;
    else if (p < 0.0f ) curLoc = PlaneBack;
    else curLoc = OnPlane;
        
    if (prevLoc == curLoc) return false;
   
    // Crossed the plane, did intersect occur inside triangle?
    // Get normalized ray
    ray = curPos - prevPos;
    D3DXVec3Normalize(&ray, &ray);

    
    // t = point along ray where intersection occurs
    t = - (d + D3DXVec3Dot(&vNormal, &prevPos)) 
        / D3DXVec3Dot(&vNormal, &ray);

    
    // Get intersection point on the plane
    intersect = prevPos + (ray * t);

    
    // Determine if intersection is within triangle plane
    // Angles around intersection should total 360 degrees (2 PI) 
    v1 = intersect - vP1;
    v2 = intersect - vP2;
    v3 = intersect - vP3;
    D3DXVec3Normalize(&v1, &v1);
    D3DXVec3Normalize(&v2, &v2);
    D3DXVec3Normalize(&v3, &v3);
 
    thetaSum = acos(D3DXVec3Dot(&v1, &v2)) 
             + acos(D3DXVec3Dot(&v2, &v3)) 
             + acos(D3DXVec3Dot(&v3, &v1));

    
    // If sum == 2PI we have an intersection!
    if ((thetaSum >= ((2 * D3DX_PI) - 0.1)) && 
        (thetaSum <= ((2 * D3DX_PI) + 0.1)))
    {
        return true;
    }

    
    return false;
}

Thursday, April 2, 2009

Winding Road Ahead

Tonight I was awakened to the importance of reading a book in its entirety before posting a book review. My thrill of chapters 6-8 on the concepts of Direct3D animation and texturing in Beginning Direct3D Game Programming, 2nd Edition has quickly been eroded by the exponential complexity of chapters 9 and 10 on the High-Level Shader Language (HLSL). I am excited to learn about lighting/shading, environment mapping, bump mapping, reflection, and other techniques described in these chapters. But two frustrating evenings of incomprehensible reading have led me to conclude, as noted by a few other reviews on Amazon as well, that the book is not truly written with the beginner in mind. So I'm taking a new path.

I'm shelving the book... for now. I think it is still a good reference (up until chapter 9 anyway) and may be of use once I gain a better introduction to the concepts described in the later chapters. Until then, there are other resources, such as DirectXTutorial.com and the SDK Documentation itself, that will hopefully provide a more gradual learning curve. Could this be a major turning point in my journey to become a Game Developer? Surely the resources I select to learn Direct3D will affect my growth, but only time will tell...

What resources have you used for learning Direct3D? What worthwhile programming reference/guide would you recommend to someone who has never written a single line of Direct3D code?

Monday, March 23, 2009

Texturing Fundamentals

In my instroduction to SkyCop, I briefly mentioned textures without describing what they are or how to use them in game development. I fear I may still not be capable of providing a full explanation, but here is what I know so far.

The tricky part of defining the word "texture" is that it can be both a noun AND a verb. That is, you use a texture file (like the image at left) to texture an object. The process of texturing can be thought of as clothing an object. No object in a Direct3D game is particularly interesting without some color or texture applied to it, and textures are the most common means of adding realism to our favorite games.

To understand how a 3D object is textured, consider the SkyCop ship below. Every Direct3D object starts as a wireframe model having a certain number of vertices. Each vertex (think point) consists of a position (x, y, z) value and a texture coordinate (u, v) value. The texture coordinate points to a position in the texture file; the point referenced in the texture file is called a texel (short for texture element). Direct3D is able to wrap the texture onto the object by using a universal address scheme for texture coordinates; the values range from 0.0 to 1.0 inclusive, both horizontally and vertically. As you can see in the image above, the top-left texel is represented by texture coordinate (0.0, 0.0) and extends to the bottom-right texel at coordinate (1.0, 1.0).

So to map the tip of the SkyCop ship with the green tip in the image, we tell Direct3D to map the vertex at the tip of the ship with horizontal u = 0.5 and vertical v = 0.0. Similarly, we map the left and right wing tips with (0.0, 0.5) and (1.0, 0.5) respectively. Are you starting to see how the texture is wrapped onto the wireframe model?

I was planning to describe the SkyCop background texturing method, but this post is already getting lengthy and I think I have more to learn on environment mapping to get it done "the right way" first. Note that this post barely touched the basics of texture-mapping. There's a lot more to learn about how textures can be used to create cool effects, and this will likely be the focus of upcoming posts.

Monday, March 16, 2009

OOP & Vertex Buffers

In my previous post, I listed some minor issues I had with the book Beginning Direct3D Game Programming, 2nd Edition. And I have another one: the book does not teach good class-based design. Admittedly it's hard to blame the author though -- he has a lot of material to cover and is trying to illustrate core concepts without bogging us down in extraneous code.

However, realizing that my simple flight simulator needed a CShip class, I dutifully created one to encapsulate the required functions (InitDeviceObjects, RestoreDeviceObjects, InvalidateDeviceObjects, etc.) of the Common Files Framework. I thought it pretty slick that I could simply call m_MyShip->Render() inside my application class' Render method, and I didn't have a problem with my custom ship class having it's own vertex buffer until I read Bubba's post (#9) on this forum. Although I certainly am leery about taking coding advice from a guy named "Bubba", I'm curious about his comment that "The renderer should draw all objects from one vertex buffer."

Understandably, if I have multiple ships with the same geometry it is somewhat wasteful to use multiple vertex buffers (one in each ship instance, used by its Render method). So which way is best?

It may be time for me to invest in a "best practices of game development" book.

Book Report: Beginning Direct3D Game Programming, 2nd Edition


Courtesy: Amazon
I've been reading Wolfgang Engel's Beginning Direct3D Game Programming, 2nd Edition for quite some time now. And by that, I mean I've made numerous attempts at reading it over the past couple years. I diligently read through the first six chapters, as I have yet again quite recently, and inevitably get hung up on the texturing concepts in the later chapters. Part of my problem is that there is quite a bit of material covered and I like to try every example in code. I'm thinking I will put in less effort coding and focus more on the concepts for this read through so I can actually finish the book this time ;)

For the most part, I'm happy with what I've learned from this book. Wolfgang does a great job of getting the reader up to speed with the development environment and provides the necessary DirectX 9 SDK and other tools on an included CD. He provides a good overview of the history (DirectX version 2 thru 9) and concepts (HAL, COM, shading and texture mapping) in the first few chapters and then moves on to the more interesting concepts involved in 3-dimensional rendering: axes, vertices, matrix and quaternion rotations, etc. Chapter six is a heavy but highly valuable chapter on the basics of animation in three dimensions and provides you with a framework for creating your own flight simulator. I have of course written my own derivative, which I hope to post for your enjoyment soon.

As much as I enjoy this book, I do take issue with it on some points. Firstly, I believe a large number of mistakes slipped into the book. For example, if the author indicates that matrix values must be entered row-wise, the code sample should do the same; these kind of minor errors have caused me to scratch my head more than once. Secondly, the author tends to explain some concepts with assumed knowledge instead of explaining everything in detail for the student who is "beginning Direct3D game programming." Minor issues aside, I think this book is a great reference and look forward to the upcoming texture-mapping and HLSL (High-Level Shader Language) programming chapters.

If you've read this book, let me know what you thought of it. Also post a comment to let me know your favorite Direct3D books. I want to learn as much as I can and could use some good references!