📄 renderer.cpp
字号:
/////////////////////////////////////////////////////////////////////////////
//
// 3D Math Primer for Games and Graphics Development
//
// Renderer.cpp - Very simple low-level 3D renderer interface
//
// Visit gamemath.com for the latest version of this file.
//
// --------------------------------------------------------------------------
//
// This file implements a very simple 3D renderer via Direct3D. Our goal was
// to provide a simple, cross-platform rendering engine. If you do not want
// to learn DirectX (and there are MANY people who work in graphics related
// firlds who have no need to know DirectX) then you don't need to look
// at the inside of this file - you can just get the benefits of a simple,
// clean rendering API without digging through mountains of DirectX
// documentation.
//
// <soapbox>
//
// Nowadays lots of people want to learn Direct3D, before they know graphics.
// As is typical of Microsoft, some people act as if the two are the same,
// like DirectX is "how things are done." Graphics is an academic pursuit
// unto itself, and many researchers spent many years investigating
// techniques and interfaces for rendering, long before Microsoft got
// involved in DirectX. It is more important to learn how graphics work
// from a platform- and API-independent standpoint than it is to learn how
// DirectX works, with all its quirks and shortcomings. And DirectX changes
// every year - sometimes radically. Most serious projects do most of their
// high-level graphics work using a graphics abstraction (such as this one)
// and leave the messy details hidden in a lower level. Certainly any
// cross-platform project must do this, but even ones only destinated for
// one platform can make use of this insulation. Please don't put DirectX
// code anywhere in your high level code. In fact you may notice that all
// of the files on gamemath.com can be used to make a platform-independent
// game loop, with the "game" files (the project-specific files) never
// including a big huge mess of stuff like <windows.h> or <xtl.h>.
//
// Many other books or Internet sources provide so-called "wrappers"
// around DirectX, which are designed to make it "easy" to write DirectX
// applications. Supposedly, you can use their code without knowing any
// DirectX. However, let us differentiate between a "wrapper" and an
// "abstraction layer." A wrapper is a very thin translation layer,
// usually designed to do nothing more than translate calls. It often
// can decrease compile times, facilitate DLL linkage, or provide shorthand
// for commonly used operations. However, a "wrapper" really doesn't
// help insulate you from the implementation details any or provide any
// platform independence. An abstraction layer, on the other hand,
// is designed to provide insulation from implementation details and/or
// platform independance. We have provided an abstraction layer, not a
// wrapper. This is evidenced by the fact that the header file does
// not contain a single platform independent element and could be ported
// to any number of platforms without a line of code being changed.
//
// Many wrappers or abstraction layers provided in books and on the Internet
// are useless because they are just as complicated or have as many quirks
// (or bugs) as the "messy" internals they proport to hide you from.
// (And what's more - they are usually more poorly documented!)
// Therefore, you not only have to learn DirectX, for example, but you have
// to learn their weird API as well. We have tried to design our interface
// layer so that it provides basic graphics functionality in the most
// straightforward manner possible, truly hiding DirectX details. Of course,
// with any interface, there is a learning curve associated with it. We
// hope that our design and comments make this curve as short as possible.
//
// </soapbox>
//
// So, the interface is completely platform independent, and much of the
// implentation is as well. All that said, however, eventually you have to
// impement the interface on a particular platform. It is possible, and
// in many ways advantageous, to make this file sit atop different
// lower level layers - i.e. one for DirectX, one for OpenGL, one for
// PlayStationII, etc. For simplicity, we have not done this. We have
// chosen one API (Direct3D) and made calls to it directly in this file.
// This makes it basically impossible to support multiple platforms or
// APIs easily without duplciating the entire contents of the file, but the
// purpose of this code is ease of understanding the API most people are
// most interested in learning - DirectX.
//
// We have also not concerned ourselved with optimization too much. This
// is an "engine" designed for you to look at the guts and learn how things
// work. Optimization would complicate the internals significantly and
// make things more difficult to understand.
//
// There is only one instance of class Renderer, a global variable
// gRenderer. (It is a "singleton" in C++ terminology.) Some internal
// state variables are declared here as private members. Other internal
// state variables are declared statically in the various C++ files -
// declaring all the variables here as private would be a drain on compile
// times, since it would require declaring more structures and including
// other files that are not necessary. C++ has the unfortunate property
// that all class internal members are visible to clients of the class, even
// if they are not accessible. In other words, they must be processed by
// the compiler, and if they are changed, seemingly unrelated files are
// often recompiled. Putting state variables statically, rather than in
// the private section, avoids this problem. A C++ purist might object,
// but a person waiting on the compiler will not.
//
/////////////////////////////////////////////////////////////////////////////
#include <assert.h>
#include "CommonStuff.h"
#include "Renderer.h"
#include "WinMain.h"
#include "MathUtil.h"
#include "Bitmap.h"
#include <d3d8.h>
/////////////////////////////////////////////////////////////////////////////
//
// local data
//
/////////////////////////////////////////////////////////////////////////////
// Direct3D interface object
static LPDIRECT3D8 pD3D = NULL;
// Direct3D device interface
static LPDIRECT3DDEVICE8 pD3DDevice = NULL;
// List of video modes
static int videoModeCount;
static VideoMode *videoModeList;
// The clip matrix. This transforms camera space points to clip
// space, aka "canonical view volume space." it is computed
// by computeClipMatrix(). D3D calls this matrix the "projection"
// matrix
static D3DMATRIX clipMatrix;
// Instance stack system. This is an OpenGL-like system to manage the
// current reference frame. For example, by default, without instancing,
// all 3D coordinates submitted to the rendering system are assumed to be
// in world space. Now, let's say we "instance" into an object's local
// reference frame, by specifying the position and orientation of the
// object. Now any 3D coordinates we submit will be transformed from
// local space to world space and then into camera space. Instancing
// can be performed multiple times, for example, to render a tire within a
// car.
struct InstanceInfo {
// The model->world matrix
Matrix4x3 modelToWorldMatrix;
};
const int kMaxInstanceDepth = 8;
static int instanceStackPtr = 0;
static InstanceInfo instanceStack[kMaxInstanceDepth];
// The model->clip matrix. This matrix takes a point in the current
// reference frame, and transforms it to clip space. Note that this is a
// 4x4 matrix. We could have our own nice 4x4 matrix class, but since they
// are actually not used very much outside of the graphcis internals, we just
// use D3D's here
static D3DMATRIX modelToClipMatrix;
// The modelToClipMatrix is only used by the software vertex processing
// routines. it's relatively expeisive to compute. So we won't compute
// it every time. We'll just keep track of if it's valid or not,
// and compute it on demand whenever it is needed. This flag keeps
// tradck of whether the modelToClipMatrix is "dirty" and needs to be
// recomputed.
static bool needToComputeModelToClipMatrix = true;
// Model->camera matrix. This matrix takes a point in the current
// reference frame, and transforms it to camera space. This
// transform does not contain the zoom or perspective projection, and so
// we are using our 4x3 matrix class. This matrix is computed when
// the modelToClipMatrix is computed
static Matrix4x3 modelToCameraMatrix;
// The current D3D "material" which controls material properties for
// the standard local illumunation model. We will only be using a few
// of these values.
static D3DMATERIAL8 d3dMaterial;
// Floating point projection values, for software projection
static float windowCenterX, windowCenterY;
static float halfWindowSizeX, halfWindowSizeY;
// Precomputed far fog distance, as a clip-space z value.
static float farFogClipSpaceZ;
// The D3D directional light
static D3DLIGHT8 d3dDirectionalLight;
// Texture cache variables. See the notes above the resetTextureCache()
// for more details.
struct TextureCacheEntry {
// Symbolic name of the texture (usually a filename)
char name[kMaxTextureNameChars];
// Size
int xSize, ySize;
// Direct3D interface object. We're going to let D3D manage
// the memory
LPDIRECT3DTEXTURE8 d3dTexture;
};
// max number of textures - you may need more than this
const int kTextureCacheSize = 512;
// next available texture slot
static int nextTextureSlot;
// The texture cache bookeeping info
static TextureCacheEntry textureCacheList[kTextureCacheSize];
/////////////////////////////////////////////////////////////////////////////
//
// local utility helper functions
//
/////////////////////////////////////////////////////////////////////////////
//---------------------------------------------------------------------------
// setD3DRenderState
//
// Basically just a thin wrapper around pD3DDevice->SetRenderState,
// with added pointer safety check and validation of the return code,
// for debugging.
//
// For speed, we could cache the states to prevent setting states
// redundantly. However, most of the higher level code should already be
// doing that, and hopefully Microsoft is doing the same as well.
static void setD3DRenderState(D3DRENDERSTATETYPE state, unsigned value) {
// Make sure device exists
if (pD3DDevice == NULL) {
assert(false);
return;
}
// !SPEED! We could check if the state actually changed here.
// Or, we could assume that the good folks in Redmond will
// do this for us. Ahem.....
// Set the state
HRESULT result = pD3DDevice->SetRenderState(state, value);
// Check for error
assert(SUCCEEDED(result));
}
//---------------------------------------------------------------------------
// setD3DDirectionalLight
//
// Install the directional light into D3D render context
static void setD3DDirectionalLight() {
// Make sure device exists
if (pD3DDevice == NULL) {
assert(false);
return;
}
// Set light to D3D at light index 0
HRESULT result = pD3DDevice->SetLight(0, &d3dDirectionalLight);
// Check for error
assert(SUCCEEDED(result));
}
//---------------------------------------------------------------------------
// checkMesh
//
// Debug utility function to check that a triangle mesh is valid.
//
// Returns true if the mesh is OK to be rendered
static bool checkMesh(const void *vertexList, int vertexCount, const RenderTri *triList, int triCount) {
// No triangles?
if (triCount < 1) {
return false;
}
// NULL pointers?
if ((vertexList == NULL) || (triList == NULL)) {
assert(false);
return false;
}
// Check in a debug build that all the indices are valid
#ifdef _DEBUG
for (int i = 0 ; i < triCount ; ++i) {
for (int j = 0 ; j < 3 ; ++j) {
int index = triList[i].index[j];
if ((index < 0) || (index >= vertexCount)) {
assert(false);
return false;
}
}
}
#endif
// Mesh is OK
return true;
}
/////////////////////////////////////////////////////////////////////////////
//
// class Renderer master init stuff
//
/////////////////////////////////////////////////////////////////////////////
// Global renderer class object
Renderer gRenderer;
//---------------------------------------------------------------------------
// Renderer::Renderer
//
// Constructor - Reset internal state variables
Renderer::Renderer() {
// Slam some internal variables
screenX = 0;
screenY = 0;
cameraPos.zero();
cameraOrient.identity();
zoomX = 1.0f; // 90 degree field of view
zoomY = 0.0f; // auto-compute
nearClipPlane = 1.0f;
farClipPlane = 1000.0f;
windowX1 = 0;
windowY1 = 0;
windowX2 = 0;
windowY2 = 0;
windowSizeX = 0;
windowSizeY = 0;
depthBufferRead = true;
depthBufferWrite = true;
blendEnable = true;
sourceBlendMode = eSourceBlendModeSrcAlpha;
destBlendMode = eDestBlendModeInvSrcAlpha;
constantARGB = MAKE_ARGB(255,0,0,0);
constantOpacity = 1.0f;
fogEnable = false;
fogColor = MAKE_RGB(255,255,255);
fogNear = 0.0f;
fogFar = 1000.0f;
lightEnable = true;
ambientLightColor = MAKE_RGB(64,64,64);
directionalLightVector.x = .707f;
directionalLightVector.y = -.707f;
directionalLightVector.z = 0.0f;
directionalLightColor = MAKE_RGB(192,192,192);
backfaceMode = eBackfaceModeCCW;
currentTextureHandle = 0;
textureClamp = false;
// And now set the camera, to force some stuff to be
// recomputed
setCamera(kZeroVector, kEulerAnglesIdentity);
// Set level 0 instance (the world) reference frame
instanceStack[0].modelToWorldMatrix.identity();
}
//---------------------------------------------------------------------------
// Renderer::getVideoModeCount
//
// Returns the number of viewo modes available, or 0 if 3D is no workey
int Renderer::getVideoModeCount() {
// Check if we already know
if (videoModeCount > 0) {
return videoModeCount;
}
// List has not yet been created. Nothing should be allocated yet
assert(pD3D == NULL);
assert(pD3DDevice == NULL);
// Create a Direct3D object
pD3D = Direct3DCreate8(D3D_SDK_VERSION);
if (pD3D == NULL) {
videoModeCount = 0;
return 0;
}
// Enumerate the adapter modes in two passes. On the first pass,
// we'll just count the number of modes. On the second pass,
// we'll actually fill in the mode list
for (int pass = 0 ; pass < 2 ; ++pass) {
// Enumerate modes
int modeIndex = 0;
while (modeIndex < pD3D->GetAdapterModeCount(D3DADAPTER_DEFAULT)) {
// Enumerate the next mode.
D3DDISPLAYMODE mode;
HRESULT result = pD3D->EnumAdapterModes(
D3DADAPTER_DEFAULT,
modeIndex,
&mode
);
if (FAILED(result)) {
break;
}
++modeIndex;
// Convert D3D mode structure to our own
VideoMode ourMode;
ourMode.xRes = mode.Width;
ourMode.yRes = mode.Height;
ourMode.refreshHz = mode.RefreshRate;
switch (mode.Format) {
case D3DFMT_A8R8G8B8:
ourMode.bitsPerPixel = 32;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -