Here is the code I'm using. CPlanet uses a CSphere object. I separated them for rendering reasons and I'll spare you the details.
CSphere.cpp
Code:
#define CSPHERE
#include "d3dx9.h"
#include "CQuickCom.h"
#include "CFileLog.h"
#include "d3dx9mesh.h"
//Converts degrees to radians
#define DEGTORAD(x) (x*D3DX_PI/180.0f)
struct CSphereVertex
{
D3DXVECTOR3 pos;
D3DXVECTOR3 normal;
float u,v;
static const DWORD FVF;
CSphereVertex(D3DXVECTOR3 tpos,D3DXVECTOR3 tnormal,float tu,float tv):
pos(tpos),normal(tnormal),u(tu),v(tv){}
};
class CSphere
{
protected:
CFileLog SphereLog;
IDirect3DDevice9 *Device;
ID3DXMesh *SphereMesh;
unsigned int NumTris;
unsigned int NumFaces;
unsigned int NumVertices;
unsigned int NumVertsPerRow;
unsigned int NumVertsPerCol;
float Radius;
public:
CSphere(void) {Device=NULL,NumTris=0,NumFaces=0,
NumVertices=0,NumVertsPerRow=0,NumVertsPerCol=0,Radius=0.0f;};
~CSphere(void)
{
if (SphereMesh) SphereMesh->Release();
}
//Old deprecated function
//void CreateSphere(IDirect3DDevice9 *_device,
// float _radius,
// unsigned int _faces,
// float _texmag,
// D3DXVECTOR3 &pos);
void CreateMeshSphere(IDirect3DDevice9 *_device,
float _radius,
unsigned int _faces);
ID3DXMesh *GetMesh(void) {return SphereMesh;};
unsigned int GetNumTris(void) {return NumTris;};
unsigned int GetNumVertices(void) {return NumVertices;};
};
#endif
CSphere.h
Code:
#include "CSphere.h"
const DWORD CSphereVertex::FVF=D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;
//Create mesh
void CSphere::CreateMeshSphere(IDirect3DDevice9 *_device,float _radius,unsigned int _faces)
{
//Class vars
Device=_device;
Radius=_radius;
NumFaces=_faces;
//Create temp mesh
LPD3DXMESH temp;
if (FAILED(D3DXCreateSphere(Device,Radius,NumFaces,NumFaces,&temp,NULL)))
{
::MessageBox(0,"Failed to create sphere",0,0);
}
//Clone mesh with new FVF
if (FAILED(temp->CloneMeshFVF(D3DXMESH_SYSTEMMEM,CSphereVertex::FVF,Device,&SphereMesh)))
{
::MessageBox(0,"Failed to clone mesh",0,0);
}
//Lock vertex buffer
CSphereVertex *Vertices;
SphereMesh->LockVertexBuffer(0,(void **)&Vertices);
//Setup mapping vars
float angleinc_alpha=360.0f/_faces;
float angleinc_beta=180.0f/_faces;
float alpha=0.0f;
float beta=-180.0f;
float u=0.0f,v=0.0f;
unsigned int vertex_count;
//Set 2*PI - no mul in for loop
float PI2=D3DX_PI*2.0f;
//Map U,V coords
//Produces very good results for 2:1 texture ratios
for (unsigned int i=0;i<SphereMesh->GetNumVertices();i++)
{
u=(DEGTORAD(alpha)+D3DX_PI)/(PI2);
v=DEGTORAD(beta)/D3DX_PI;
Vertices[i].u=u;
Vertices[i].v=v;
alpha+=angleinc_alpha;
vertex_count++;
if (vertex_count>=(_faces))
{
alpha=0.0f;
beta+=angleinc_beta;
//Disabled test area
//Vertices[i+1].v=0.0f;
//Vertices[i+1].u=0.0f;
vertex_count=0;
}
}
//Unlock vertex buffer
SphereMesh->UnlockVertexBuffer();
//Discard temp mesh
temp->Release();
}
CPlanet.cpp
Code:
#include "CPlanet.h"
//Constructor - self explanatory
CPlanet::CPlanet(void)
{
Device=NULL;
Pos=D3DXVECTOR3(0.0f,0.0f,0.0f);
Rotation=D3DXVECTOR3(0.0f,0.0f,0.0f);
RotationVelocity=D3DXVECTOR3(0.0f,0.0f,0.0f);
Texture=NULL;
BumpMap=NULL;
Radius=0.0f;
}
//Destructor - release COM objects - decrement their reference counts
CPlanet::~CPlanet(void)
{
if (Texture) Texture->Release();
if (BumpMap) BumpMap->Release();
}
//Set planet texture
void CPlanet::SetTexture(std::string PlanetFile)
{
if (FAILED(D3DXCreateTextureFromFile(Device,PlanetFile.c_str(),&Texture)))
{
::MessageBox(0,"Failed to load planet texture",0,0);
return;
}
}
//Set bump map -> fake bump mapping not per-pixel
void CPlanet::SetBumpMap(std::string PlanetFile)
{
if (FAILED(D3DXCreateTextureFromFile(Device,PlanetFile.c_str(),&BumpMap)))
{
::MessageBox(0,"Failed to load planet texture",0,0);
return;
}
}
//Create the model -> call CSphere::CreateMeshSphere()
void CPlanet::CreateModel(IDirect3DDevice9 *_Device,float _Radius,int _Faces,D3DXVECTOR3 &pos)
{
Device=_Device;
Radius=_Radius;
Faces=_Faces;
SetPosition(&pos);
Planet.CreateMeshSphere(Device,Radius,_Faces);
}
//Disabled - unsure of atmospheres at this time
void CPlanet::SetAtmosphere(D3DMATERIAL9 *_material,float _radius)
{
//AMaterial=*_material;
//ARadius=_radius;
//AtmosphereFlag=true;
//Atmosphere.CreateMeshSphere(Device,ARadius,Faces);
}
//Render object
void CPlanet::Render(float timeDelta,D3DXVECTOR3 cameraPos)
{
//Set U,V wrapping to true - must do this to get rid of render artifacts
Device->SetRenderState(D3DRS_WRAP0,true);
Device->SetRenderState(D3DRS_WRAP1,true);
//Change model rotation based on velocity presets and time from last frame
Rotation.x+=(RotationVelocity.x*timeDelta);
Rotation.y+=(RotationVelocity.y*timeDelta);
Rotation.z+=(RotationVelocity.z*timeDelta);
//Setup world rotation matrix
D3DXMATRIX rot;
D3DXMatrixRotationYawPitchRoll(&rot,Rotation.x,Rotation.y,Rotation.z);
//Setup translation -> no mul here
rot._41=Pos.x;
rot._42=Pos.y;
rot._43=Pos.z;
//Set transform
Device->SetTransform(D3DTS_WORLD,&rot);
//Set material
D3DXVECTOR3 distvect;
D3DXVec3Subtract(&distvect,&Pos,&cameraPos);
float length=D3DXVec3Length(&distvect);
float actualdist=10000.0f-(length-8000.0f);
if (actualdist>0.0f)
{
AlphaValue=actualdist/10000.0f;
} else AlphaValue=0.0f;
if (AlphaValue>=1.0f) AlphaValue=1.0f;
if (AlphaValue<=0.0f) AlphaValue=0.0f;
Material.Diffuse.a=AlphaValue;
Device->SetMaterial(&Material);
//Set texture
//Device->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_DIFFUSE);
//Device->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_SELECTARG1);
Device->SetTexture(0,Texture);
//Check pointer -> prob disabled on release build
if (!Planet.GetMesh())
{
::MessageBox(0,0,"Null pointer in mesh",0);
}
//Render object
if (FAILED(Planet.GetMesh()->DrawSubset(0)))
{
::MessageBox(0,0,"Cannot draw mesh",0);
}
//Restore render states
Device->SetRenderState(D3DRS_WRAP0,false);
Device->SetRenderState(D3DRS_WRAP1,false);
//Device->SetTextureStageState(1,D3DTSS_COLOROP,D3DTOP_DISABLE);
//Device->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE);
}
CPlanet.h
Code:
#ifndef CPLANET
#define CPLANET
#include "CPrimitives.h"
#include "d3dx9.h"
#include <String>
#include "CQuickCom.h"
#include "CSphere.h"
class CPlanet
{
//Device interface pointer
IDirect3DDevice9 *Device;
//Sphere object
CSphere Planet;
//Model properties
D3DXVECTOR3 Pos;
D3DXVECTOR3 Rotation;
D3DXVECTOR3 RotationVelocity;
//Textures
IDirect3DTexture9 *Texture;
IDirect3DTexture9 *BumpMap;
//Materials
D3DMATERIAL9 Material;
D3DMATERIAL9 AMaterial;
float Radius;
float ARadius;
int Faces;
bool AtmosphereFlag;
public:
float AlphaValue;
CPlanet(void);
~CPlanet(void);
void SetTexture(std::string PlanetFile);
void SetBumpMap(std::string PlanetFile);
void SetPosition(D3DXVECTOR3 *_newpos) {Pos=*_newpos;};
void GetPosition(D3DXVECTOR3 *_curpos) {*_curpos=Pos;};
void SetRotationParams(D3DXVECTOR3 *_rotation,D3DXVECTOR3 *_rotation_velocity)
{
Rotation=*_rotation;
RotationVelocity=*_rotation_velocity;
}
void GetRotationParams(D3DXVECTOR3 *_rotation,D3DXVECTOR3 *_rotation_velocity)
{
*_rotation=Rotation;
*_rotation_velocity=RotationVelocity;
}
void CreateModel(IDirect3DDevice9 *_Device,float _Radius,int _Faces,D3DXVECTOR3 &pos);
void TestCreateModel(IDirect3DDevice9 *_device,float _radius,unsigned int _faces)
{
Device=_device;
Radius=_radius;
Faces=_faces;
Planet.CreateMeshSphere(_device,_radius,_faces);
D3DXComputeNormals(Planet.GetMesh(),NULL);
}
void SetMaterial(D3DMATERIAL9 *_material) {Material=*_material;};
void GetMaterial(D3DMATERIAL9 *_material) {*_material=Material;};
float GetRadius(void) {return Radius;};
float GetAtmosphereRadius(void) {return ARadius;};
void SetAtmosphere(D3DMATERIAL9 *_material,float _radius);
void Render(float timeDelta,D3DXVECTOR3 cameraPos);
void TestRender(float timeDelta);
};
#endif
Hope that helps.
Not the fastest way to do things and I'm reworking the code to use a much better rendering system, but you get the idea.
This is the most important section:
Code:
void TestCreateModel(IDirect3DDevice9 *_device,float _radius,unsigned int _faces)
{
Device=_device;
Radius=_radius;
Faces=_faces;
Planet.CreateMeshSphere(_device,_radius,_faces);
D3DXComputeNormals(Planet.GetMesh(),NULL);
}
You must tell Direct3D to compute the normals for your mesh. This is a simple (upper left vertex is vnum -> clockwise from there)
Code:
D3DXVECTOR v1=Vertex[vnum+1]-Vertex[vnum];
D3DXVECTOR v2=Vertex[vnum+2]-Vertex[vnum+1];
D3DXVECTOR Cross;
D3DXVec3Cross(&Cross,&v1,&v2);
D3DXVec3Normalize(&Cross,&Cross);
//No normal averaging
Vertex[vnum].normal=Cross;
Vertex[vnum+1].normal=Cross;
Vertex[vnum+2].normal=Cross;
But D3DX does this for you.
There is an easier way to compute the normals on a sphere. Since all points are equidistant from the center the normal will always be the normalized distance between the vertex on the sphere and the center of the sphere. Since your sphere's are centered around 0,0,0 in model space you can simply normalize the Vertex to get the normal. You are essentially then clamping the vertex to the surface of a unit sphere.
Code:
D3DXVECTOR3 vNormal;
D3DXVec3Normalize(&vNormal,Vertex[vnum]);
Vertex[vnum].normal=vNormal;
Note that this is per vertex normals, not per triangle face. So this sets your system up quite nicely to do some per-pixel dot3 lighting in a shader.
Code:
mSphere=Mesh.Sphere(device,1.0f,32,16);
mSphere.ComputeNormals();
I wouldn't do it that way. There is a possibility that even though your code works your sphere object might be going out of scope upon returning from Mesh::Sphere(). Also you might be creating a COM resource leak.