6、光照和材质

  Copyright © MikeFeng  QQ: 76848502

  D3D支持三种光:环境光(Ambient Light),漫反射(Diffuse Light)和镜面反射(Specular Light)。镜面反射需要更多计算,因此D3D默认关闭镜面反射。可以通过给SetRenderState传D3DRS_AMBIENT来设置环境光。
  在现实世界中,我们看到一个红色的物体是因为其他颜色的光线被这个物体吸收了,我们看到的是这个物体反射的红色光。D3D引入材质这个概念来模拟现实。
   Typpdef struct D3DMATERIAL9 {
        D3DCOLORVALUE Diffuse, Ambient, Specular, Emissive;
        Float Power;
   } D3DMATERIAL9;
  D3DCOLORVALUE是一个由r, g,b,a四个元素组成的色彩结构。
  Diffuse, Ambient, Specular分别代表这三种光的反射量,而Emissive让物体使物体更亮,看上去就像本身在发光一样。Power描述镜面反射高亮的程度,值越高越亮。注意如果一个物体吸收所有颜色的光,那么它就是黑色的。可以通过SetMaterial函数来设定材质。
   D3DMATERIAL9 blueMaterial;
  
   Device->SetMaterial(&blueMaterial)
  D3D引入顶点法线和面法线来解决光照问题。面法线描述了一个多边形的朝向,顶点法线的朝向可以自己定义。D3D必须知道顶点法线的方向以便计算它的反射光。
  在定义顶点的时候可以加入顶点法线的朝向。
struct Vertex
{
float _x, _y, _z;
float _nx, _ny, _nz;
static const DWORD FVF;
}
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL;
  顶点颜色的信息被法线信息替代了,因为我们需要根据材质和法线信息自己计算反射光。简单的图形可以由观察得到顶点法线的信息,但是复杂的图形需要通过计算得到。一种计算方法是将顶点法线等同于面法线。对于一个三角形来说,只要叉积的方式求出面发现即可。另一种更科学的方法是法线平均法。例如三棱锥的情况,最上面那个顶点的法线Vn是由包含着个定点的三个侧面的法线V1, V2, V3平均数决定的。即Vn=(V1+V2+V3)/3。前面学过由于矩阵转换可能会导致法线不偏移的现象,我们可以通过
Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
来重新归一(Renormalize)法线。
D3D中的光源有3种类型:点光源(向四周发射的光源),平行光源,锥形光源(类似于电筒光)。在锥形光源中有两个角度来描述其大小,一个描述内锥,一个描述外锥。D3D中描述光源的结构是D3DLIGHT9:
typedef struct _D3DLIGHT9 {
D3DLIGHTTYPE Type;
D3DCOLORVALUE Diffuse;
D3DCOLORVALUE Specular;
D3DCOLORVALUE Ambient;
D3DVECTOR Position;
D3DVECTOR Direction;
float Range;
float Falloff;
float Attenuation0;
float Attenuation1;
float Attenuation2;
float Theta;
float Phi;
} D3DLIGHT9;
可以通过SetLight函数来设定光源。可以通过LightEnable函数来打开关闭光源。
给一个物体添加光照分为以下几步:
     启用光照
     为每个物体创建材质,并在渲染前为这些物体配上材质
     创建一个或多个光源,设置并启用它们
     启用附加的光照状态,例如镜面高光。
设定光源最好不要放在OnCreateDevice中,因为切换全屏/窗口或者改变窗口大小时会丢失光源信息。应放在OnResetDevice中。
 
Example 1            Cube Without Lighting
前面有个旋转平面的例子,现在为了要使用光照,立体的东西效果好一点。下面是一个立体方块的例子,和平面的类似,不过顶点定义中多了顶点法线。
// cube.h
#ifndef __cubeH__
#define __cubeH__
#include "dxstdafx.h"
struct Vertex
{
        Vertex (){}
        Vertex (
                float x , float y, float z,
                float nx , float ny, float nz,
                float u , float v)
        {
                _x = x ; _y = y; _z = z;
                _nx = nx ; _ny = ny; _nz = nz;
                _u = u ; _v = v;
        }
        float _x , _y, _z;
        float _nx , _ny, _nz;
        float _u , _v; // texture coordinates
};
#define FVF_VERTEX ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 )
class Cube
{
public :
        Cube (IDirect3DDevice9* device);
        ~ Cube ();
        HRESULT OnResetDevice ();
        VOID OnLostDevice ();
        bool OnFrameMove ( double fTime ) ;
        bool OnFrameRender ( D3DMATERIAL9* mtrl, IDirect3DTexture9* tex );
private :
        HRESULT Create ();
        D3DMATERIAL9                     m_Mtrl ;
        IDirect3DDevice9 *       m_device;
        IDirect3DVertexBuffer9 * m_vb;
        IDirect3DIndexBuffer9 * m_ib;
};
#endif //__cubeH__
// 其他函数和 plain.h 中类似,因此只列出 Create 函数。其余函数请参照前面的 Plain
HRESULT Cube ::Create()
{
        m_device ->CreateVertexBuffer(
                24 * sizeof (Vertex),
                D3DUSAGE_WRITEONLY ,
                FVF_VERTEX ,
                D3DPOOL_MANAGED ,
                & m_vb ,
                NULL );
        Vertex * v;
        m_vb ->Lock(0, 0, (void**)&v, 0);
        // build box
        // x, y, z, nx, ny, nz, tu, tv
        // fill in the front face vertex data
        v [0] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
        v [1] = Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
        v [2] = Vertex( 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);
        v [3] = Vertex( 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
        // fill in the back face vertex data
        v [4] = Vertex(-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
        v [5] = Vertex( 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
        v [6] = Vertex( 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
        v [7] = Vertex(-1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
        // fill in the top face vertex data
        v [8] = Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
        v [9] = Vertex(-1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
        v [10] = Vertex( 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f);
        v [11] = Vertex( 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
        // fill in the bottom face vertex data
        v [12] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f);
        v [13] = Vertex( 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f);
        v [14] = Vertex( 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f);
        v [15] = Vertex(-1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f);
        // fill in the left face vertex data
        v [16] = Vertex(-1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
        v [17] = Vertex(-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
        v [18] = Vertex(-1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
        v [19] = Vertex(-1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
        // fill in the right face vertex data
        v [20] = Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
        v [21] = Vertex( 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
        v [22] = Vertex( 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
        v [23] = Vertex( 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
        m_vb ->Unlock();
        m_device ->CreateIndexBuffer(
                36 * sizeof (WORD),
                D3DUSAGE_WRITEONLY ,
                D3DFMT_INDEX16 ,
                D3DPOOL_MANAGED ,
                & m_ib ,
                0);
        WORD * i = 0;
        m_ib ->Lock(0, 0, (void**)&i, 0);
        // fill in the front face index data
        i [0] = 0; i[1] = 1; i[2] = 2;
        i [3] = 0; i[4] = 2; i[5] = 3;
        // fill in the back face index data
        i [6] = 4; i[7] = 5; i[8] = 6;
        i [9] = 4; i[10] = 6; i[11] = 7;
        // fill in the top face index data
        i [12] = 8; i[13] = 9; i[14] = 10;
        i [15] = 8; i[16] = 10; i[17] = 11;
        // fill in the bottom face index data
        i [18] = 12; i[19] = 13; i[20] = 14;
        i [21] = 12; i[22] = 14; i[23] = 15;
        // fill in the left face index data
        i [24] = 16; i[25] = 17; i[26] = 18;
        i [27] = 16; i[28] = 18; i[29] = 19;
        // fill in the right face index data
        i [30] = 20; i[31] = 21; i[32] = 22;
        i [33] = 20; i[34] = 22; i[35] = 23;
        m_ib ->Unlock();
        return S_OK ;
}
主函数和旋转平面基本相同,只是声明了一个不同的全局变量:
Cube * g_pCube = NULL;
在未启用光照模式[ SetRenderState ( D3DRS_LIGHTING, FALSE) ]的情况下效果如图:
6、光照和材质
Example 2            Cube With Material & Lighting
Step 1 启用光照 ( D3D 中默认启用 )
pd3dDevice ->SetRenderState( D3DRS_LIGHTING, TRUE );
Step 2 为每个物体创建材质,并在渲染前为这些物体配上材质
在主回调函数OnCreateDevice中定义光源
在上例Cube的构造函数添加材质代码,使其如下
Cube ::Cube(IDirect3DDevice9* device)
{
        // save a ptr to the device
        m_device = device ;
        m_Mtrl .Ambient = D3DXCOLOR(1,0,0,0);
        m_Mtrl .Diffuse = D3DXCOLOR(0,0,0,0);
        m_Mtrl .Specular = D3DXCOLOR(1,1,1,0);
        m_Mtrl .Emissive = D3DXCOLOR(0,1,0,0);
m_Mtrl.Power = 5.0f;
}
其中m_Mtrl是cube.h中定义的成员变量D3DMATERIAL9,Emissive对于材质来说很重要,因为它决定了我们看见的纹理颜色范围,这里定义的是只显示绿色。
在Cube::OnFrameRender中添加如下代码
bool Cube ::OnFrameRender( D3DMATERIAL9* mtrl, IDirect3DTexture9* tex)
{
        if ( mtrl )
                m_device ->SetMaterial(mtrl);
        else if (& m_Mtrl )
                m_device ->SetMaterial(&m_Mtrl);
        if ( tex )
                m_device ->SetTexture(0, tex);
        m_device ->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
        m_device ->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
        m_device ->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
        m_device ->SetStreamSource(0, m_vb, 0, sizeof(Vertex));
        m_device ->SetIndices(m_ib);
        m_device ->SetFVF(FVF_VERTEX);
        m_device ->DrawIndexedPrimitive(
                D3DPT_TRIANGLELIST ,
                0,                 
                0,                 
                24,
                0,
                12);
        return true ;
}
  如果主回调函数中未给此函数设置材质,那么它将使用自己的成员变量m_Mtrl作为材质。
Step 3 创建一个或多个光源,设置并启用它们
在主回调函数OnCreateDevice中添加如下代码
        D3DLIGHT9 dir ;
        :: ZeroMemory (&dir, sizeof(dir));
        dir .Type      = D3DLIGHT_DIRECTIONAL;
        dir .Diffuse   = D3DXCOLOR(1,1,0,1 );
        dir .Specular = D3DXCOLOR(0,0,1 1 );
        dir .Ambient   = D3DXCOLOR(1,0,0,1);
        dir .Direction = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
        pd3dDevice ->SetLight(0, &dir);
        pd3dDevice ->LightEnable(0, true);
上面的参数只允许光源产生蓝色镜面反射光,以及红色+绿色环境光。该光源是平行光源。
在主回调函数OnFrameRender中添加如下代码,以重新归一法线和启用镜面反射
      pd3dDevice ->SetRenderState(D3DRS_NORMALIZENORMALS, true);
        pd3dDevice ->SetRenderState(D3DRS_SPECULARENABLE, true);
注意不要忘记在适当时间禁用镜面反射,以提高效率。
      pd3dDevice ->SetRenderState(D3DRS_SPECULARENABLE, false);
Step 4 启用附加的光照状态 (未设置)
蓝色部分为镜面反射,绿色部分为材质本身反射。因为光源由x正方向照射过来,因此镜面反射只有在右侧才有效果。最终效果如下
6、光照和材质

你可能感兴趣的:(6、光照和材质)