龙书学习笔记(三)

在将第五章每个示例代码过了一遍之后,大致明白了光照这一章的内容,主要分为四点:

一、光照的类型分为三种,并且均通过结构D3DCOLORVALUE或D3DXCOLOR来表示光线的颜色

  1. 环境光(Ambient Light)经其它表面反射到达物体表面,并照亮整个场景,通常用做较低代价的粗略模拟。
  2. 漫射光(Diffuse Light)沿着特定的方向传播,到达某个表面后将沿着各个方向均匀反射,因此从各个方向观察物体表面亮度均相同。
  3. 镜面光(Specular Light)沿着特定的方向传播,到达一表面后将沿着另一个方向严格反射,从而形成只有在一定角度范围内才可以观察到的高亮度照射。相比其它类型光而言,镜面光的计算量要大得多,并且Direct3D默认不进行镜面光计算,因此需要利用以下代码进行绘制状态设置。
Device->SetRenderState(D3DRS_SPECULARENABLE, true);

二、物体的颜色由其反射的光的颜色决定,D3D则通过设置材质来定义物体表面对各类颜色光的反射比例,其结构体如下:

typedef struct D3DMATERIAL9 {
    D3DCOLORVALUE Diffuse;     // 对漫射光的反射率
    D3DCOLORVALUE Ambient;     // 对环境光的反射率
    D3DCOLORVALUE Specular;    // 对镜面光的反射率
    D3DCOLORVALUE Emissive;    // 增强物体的亮度,让它看起来好像在发光
    float Power;               // 增加镜面高光点的锐度
}

三、D3D通过顶点的法线方向来确定光线到达表面时的入射角,而由于光照计算是对每个顶点进行的,所以D3D需要知道同一个顶点在不同的三角元中的局部朝向。这使得同一坐标的顶点无法贡献其法线信息,因为在不同的三角元中它的法线信息是不同的,相应的,在这种情况下,使用索引的优势也就不怎么明显了。

  通常情况下,某个三角元中的每个顶点,其法向量均与面的法向量相同(即该三角元两条边的向量的叉乘结果)。而有时为了表示曲面时,则会将顶点法向量取值为所有共享该顶点的三角元面法向量的平均值。

  同时,考虑到在变换过程中顶点法线有可能不再是规范化的(不清楚这句话的含义......),所以在变换完成之后,需要通过设置绘制状态来使其重新规范化。

Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);

四、D3D支持三种类型的光源:

  1. 点光源(Point lights),在世界坐标系中有固定的位置,并向所有方向发射光线。
  2. 方向光(Directional lights),没有位置信息,是一种所发射光线相互平行的、沿着某一特定方向传播的光线。
  3. 聚光灯(Spot lights),就是手电筒,分为较亮的内圆锥形光柱和较暗的外圆锥形光柱,也是沿着某一特定方向传播。

光源的结构体定义如下:

typedef struct D3DLIGHT9 {
    // 需要的光源类型,可以设置为D3DLIGHT_POINT、D3DLIGHT_SPOT和D3DLIGHT_DIRECTIONAL三种取值
    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, *LPD3DLIGHT;


之后是东拼西凑起来弄出来的光照测试程序,在找到效率更高的图形创建方案之前,我不想再一个一个的拼接三角形了。

这次完成的是三个三角锥,目的是测试三种光源类型的区别,其中坐标和法向量均直接复制第五章的示例,而材质分别设置为反射白色光、反射蓝色光和反射绿色光。另外,在点光源和方向光的测试中,摄影机的位置将会随着按压键盘方向键而产生改变,在聚光灯的测试中,则会根据同样的方式来改变聚光灯的照射方向。

首先,需要在d3dUtility.h头文件中添加某些函数声明,目的是简化光源和材质的初始化难度:

namespace d3d {
    .
    .
    .
    // 光源的初始化设置
    D3DLIGHT9 InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color);
    D3DLIGHT9 InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color);
    D3DLIGHT9 InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color);

    // 材质的初始化设置
    D3DMATERIAL9 InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p);

    const D3DMATERIAL9 WHITE_MTRL  = InitMtrl(WHITE, WHITE, WHITE, BLACK, 2.0f);
    const D3DMATERIAL9 RED_MTRL    = InitMtrl(RED, RED, RED, BLACK, 2.0f);
    const D3DMATERIAL9 GREEN_MTRL  = InitMtrl(GREEN, GREEN, GREEN, BLACK, 2.0f);
    const D3DMATERIAL9 BLUE_MTRL   = InitMtrl(BLUE, BLUE, BLUE, BLACK, 2.0f);
    const D3DMATERIAL9 YELLOW_MTRL = InitMtrl(YELLOW, YELLOW, YELLOW, BLACK, 2.0f);
}

之后是这些函数的实际定义,值得注意的是,不同分类的光源,其结构体所包含的数据成员也是不同的,下列代码需要添加到d3dUtility.cpp文件当中:

// 方向光的设置
D3DLIGHT9 d3d::InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color)
{
    D3DLIGHT9 light;
    ::ZeroMemory(&light, sizeof(light));

    light.Type      = D3DLIGHT_DIRECTIONAL;
    light.Ambient   = *color * 0.6f;
    light.Diffuse   = *color;
    light.Specular  = *color * 0.6f;
    light.Direction = *direction;

    return light;
}
// 点光源的设置
D3DLIGHT9 d3d::InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color)
{
    D3DLIGHT9 light;
    ::ZeroMemory(&light, sizeof(light));

    light.Type      = D3DLIGHT_POINT;
    light.Ambient   = *color * 0.6f;
    light.Diffuse   = *color;
    light.Specular  = *color * 0.6f;
    light.Position  = *position;
    light.Range        = 1000.0f;
    light.Falloff      = 1.0f;
    light.Attenuation0 = 1.0f;
    light.Attenuation1 = 0.0f;
    light.Attenuation2 = 0.0f;

    return light;
}
// 聚光灯的设置
D3DLIGHT9 d3d::InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color)
{
    D3DLIGHT9 light;
    ::ZeroMemory(&light, sizeof(light));

    light.Type      = D3DLIGHT_SPOT;
    light.Ambient   = *color * 0.0f;
    light.Diffuse   = *color;
    light.Specular  = *color * 0.6f;
    light.Position  = *position;
    light.Direction = *direction;
    light.Range        = 1000.0f;
    light.Falloff      = 1.0f;
    light.Attenuation0 = 1.0f;
    light.Attenuation1 = 0.0f;
    light.Attenuation2 = 0.0f;
    light.Theta        = 0.4f;
    light.Phi          = 0.9f;

    return light;
}
// 物体材质的设置
D3DMATERIAL9 d3d::InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p)
{
    D3DMATERIAL9 mtrl;
    mtrl.Ambient  = a;
    mtrl.Diffuse  = d;
    mtrl.Specular = s;
    mtrl.Emissive = e;
    mtrl.Power    = p;
    return mtrl;
}


之后是主要的源代码,依旧是从全局变量Device、屏幕高度Height和屏幕宽度Width的定义开始::

/*
 *
 * File: lightTest.cpp
 *
 * Author: EnoWang
 *
 * Desc: 绘制了三个不同材质的三棱锥,用于测试三种不同的光源
 *
 */
#include "d3dUtility.h"

IDirect3DDevice9* Device = 0; 

const int Width  = 640;
const int Height = 480;

IDirect3DVertexBuffer9* Pyramid = 0;
D3DXMATRIX Worlds[3];
D3DMATERIAL9 Marl[3];

相比之前的程序而言,这次我新设置了两个全局数组,一是Worlds数组,用于存储不同的世界坐标变换值,将分别作用于三个材质不同的三棱锥;二是Marl数组,它存储了三种不同的材质信息,在绘制时才会依此数据对三棱锥的材质进行设置。而由于Worlds数组和Marl数组的建立,这个光照测试程序只需创建一个三棱锥顶点缓存。

接下来创建顶点结构体:

// 顶点结构体
struct Vertex {
    Vertex(){ }

    Vertex(float x, float y, float z, float nx, float ny, float nz)
    {
        _x  = x;  _y  = y;    _z  = z;
        _nx = nx; _ny = ny; _nz = nz;
    }
    float  _x,  _y,  _z;
    float _nx, _ny, _nz;

    static const DWORD FVF;
};
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL;

与之前的程序相比,这次的顶点结构体没有颜色数据成员,新增了顶点法线数据成员,以确定光线到达表面时的入射角,FVF的值也变为D3DFVF_XYZ | D3DFVF_NORMAL。

接下来定义框架函数:

// 框架函数
bool Setup()
{
    // 创建三棱锥的顶点缓存
    Device->CreateVertexBuffer(
        12 * sizeof(Vertex),
        D3DUSAGE_WRITEONLY,
        Vertex::FVF,
        D3DPOOL_MANAGED,
        &Pyramid,
        0);

    // 向三棱锥的顶点缓存中写入数据
    Vertex* vertex;
    Pyramid->Lock(0, 0, (void**)&vertex, 0);

    vertex[0] = Vertex(-1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);
    vertex[1] = Vertex( 0.0f, 1.0f,  0.0f, 0.0f, 0.707f, -0.707f);
    vertex[2] = Vertex( 1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);

    vertex[3] = Vertex(-1.0f, 0.0f,  1.0f, -0.707f, 0.707f, 0.0f);
    vertex[4] = Vertex( 0.0f, 1.0f,  0.0f, -0.707f, 0.707f, 0.0f);
    vertex[5] = Vertex(-1.0f, 0.0f, -1.0f, -0.707f, 0.707f, 0.0f);

    vertex[6] = Vertex( 1.0f, 0.0f, -1.0f, 0.707f, 0.707f, 0.0f);
    vertex[7] = Vertex( 0.0f, 1.0f,  0.0f, 0.707f, 0.707f, 0.0f);
    vertex[8] = Vertex( 1.0f, 0.0f,  1.0f, 0.707f, 0.707f, 0.0f);

    vertex[9]  = Vertex( 1.0f, 0.0f,  1.0f, 0.0f, 0.707f, 0.707f);
    vertex[10] = Vertex( 0.0f, 1.0f,  0.0f, 0.0f, 0.707f, 0.707f);
    vertex[11] = Vertex(-1.0f, 0.0f,  1.0f, 0.0f, 0.707f, 0.707f);

    Pyramid->Unlock();

可以看到,对于由几个三角元共享的顶点,随着所组成三角元的改变,虽然它的位置信息不会变化,但是法向量却会随之改变,即:顶点的法向量不会被共享。这时如果使用索引来绘制图形,相比直接用顶点绘图而言,它的优势也就不是那么明显了。正因为如此,这里抛弃了索引缓存,直接采用顶点缓存来记录图像绘制信息。

接下来设置一开始创建的世界坐标变换数组和材质信息数组:

    // 设置世界坐标系
    D3DXMatrixTranslation(&Worlds[0], 0.0f, 2.0f, 0.0f);
    D3DXMatrixTranslation(&Worlds[1],-1.4f,-1.4f, 0.0f);
    D3DXMatrixTranslation(&Worlds[2], 1.4f,-1.4f, 0.0f);

    // 创建材质
    Marl[0] = d3d::WHITE_MTRL;
    Marl[1] = d3d::BLUE_MTRL;
    Marl[2] = d3d::GREEN_MTRL;

对于之后将要绘制的三棱锥,它们的世界坐标呈现正上方、左下方、右下方的分布,且三个三棱锥的局部中心坐标与原点的距离均为2.0f;而它们材质则分别为:反射白色光,反射蓝色光和反射绿色光。

之后设置光源,因为首先要进行测试的是方向光,所以进行如下设置:

    // 设置方向光光源
    D3DXVECTOR3 dir(1.0f, 0.3f, 0.6f);
    D3DXCOLOR color = d3d::WHITE;
    D3DLIGHT9 dirLight = d3d::InitDirectionalLight(&dir, &color);
    // 注册并设置光照开关状态
    Device->SetLight(0, &dirLight);
    Device->LightEnable(0, true);

    // 重新规范化法向量,并启用镜面高光
    Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
    Device->SetRenderState(D3DRS_SPECULARENABLE, true);

可以看到,这次设置的方向光光源为白光,其中环境光、漫射光和镜面光的亮度值分别设置为:WHITE(255, 255, 255)、0.3WHITE和0.6WHITE。

另外,SetLight函数的作用是,在Direct3D所维护的光源列表当中,对要使用的光源进行注册;而LightEnable函数的作用则是开灯和光灯。

接下来跳过摄影机,直接设置投影矩阵:

    // 设置投影矩阵
    D3DXMATRIX proj;
    D3DXMatrixPerspectiveFovLH(
            &proj,
            D3DX_PI * 0.5f,
            (float)Width / (float)Height,
            1.0f,
            1000.0f);
    Device->SetTransform(D3DTS_PROJECTION, &proj);

    return true;
}

void Cleanup()
{
    d3d::Release<IDirect3DVertexBuffer9*>(Pyramid);
}

现在开始定义绘制函数:

bool Display(float timeDelta)
{
    if( Device )
    {
        // 更新场景和摄影机位置
        static float angle  = (3.0f * D3DX_PI) / 2.0f;
        static float height = 5.0f;

        if (::GetAsyncKeyState(VK_LEFT) & 0x8000f)
            angle -= 1.0f * timeDelta;

        if (::GetAsyncKeyState(VK_RIGHT) & 0x8000f)
            angle += 1.0f * timeDelta;

        if (::GetAsyncKeyState(VK_UP) & 0x8000f)
            height += 3.0f * timeDelta;

        if (::GetAsyncKeyState(VK_DOWN) & 0x8000f)
            height -= 3.0f * timeDelta;

        D3DXVECTOR3 position(cosf(angle) * 3.0f, height, sinf(angle) * 4.0f);
        D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
        D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
        D3DXMATRIX Camera;
        D3DXMatrixLookAtLH(&Camera, &position, &target, &up);

        Device->SetTransform(D3DTS_VIEW, &Camera);

首先可以看到,这次设置的镜头更新方式并非之前两个程序所定义的随时间自动旋转,而是会对某些由键盘传递的信息进行响应,从而改变摄影机的位置。简单来说,按住上下左右任意方向键,即可使摄影机转向相应的方向。至于0x8000f的由来,可以在这里看到详细的解释:http://blog.sina.com.cn/s/blog_868579d801011d6l.html

之所以不在Setup函数中设置摄影机,而是选择将摄影机的设置放在将会于主函数中一遍又一遍执行Display函数中,也是因为摄影机的位置会因为angel和height的改变而随时产生改变。

接下来开始正式的绘制:

        // 开始绘制
        Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0);
        Device->BeginScene();

        Device->SetStreamSource(0, Pyramid, 0, sizeof(Vertex));
        Device->SetFVF(Vertex::FVF);

        for (int cnt = 0; cnt < 3; ++cnt) {
            Device->SetMaterial(&Marl[cnt]);
            Device->SetTransform(D3DTS_WORLD, &Worlds[cnt]);
            Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 4);
        }

        Device->EndScene();
        Device->Present(0, 0, 0, 0);
    }
    return true;
}

可以看到,正式的绘制在for循环中发生,每次的for循环,都会从设置新的材质和设置新的世界坐标变换开始,在绘制图像结束。三次for循环结束之后,正好绘制了三个材质不同、中心点坐标也不同的三棱锥。

最后,回调函数和Windows主函数依然和前两个程序没有区别,故省略。

方向光的实际效果如下:



接下来测试点光源,对Setup函数最后的光源设置进行修改即可:

    // 设置点光源
    D3DXVECTOR3 position(0.0f, 0.0f, 0.0f);
    D3DXCOLOR color = d3d::WHITE;
    D3DLIGHT9 poiLight = d3d::InitPointLight(&position, &color);

    // 注册并设置光照开关状态
    Device->SetLight(0, &poiLight);
    Device->LightEnable(0, true);

如上所示,点光源的坐标为(0, 0, 0),白色三棱锥的上方正好处于镜面光和漫射光无法照射到的位置。



接下来测试聚光灯,相比点光源和方向光而言,其作用方式比较特殊,代码改动较多,因此给出除回调函数和主函数之外的所有代码,并标记出修改位置:

#include "d3dUtility.h"

IDirect3DDevice9* Device = 0; 

const int Width  = 640;
const int Height = 480;

IDirect3DVertexBuffer9* Pyramid = 0;
D3DXMATRIX Worlds[3];
D3DMATERIAL9 Marl[3];

D3DLIGHT9 spotLight; // 第一处修改

第一处修改,将spotLight设置为全局变量,因为它的初始化设置只需要一次,所以放置在Setup函数中,而由于它的照射方向会随时改变,所以剩余部分的设置放置在Display函数中。

// 顶点结构体
struct Vertex {
    Vertex(){ }

    Vertex(float x, float y, float z, float nx, float ny, float nz)
    {
        _x  = x;  _y  = y;    _z  = z;
        _nx = nx; _ny = ny; _nz = nz;
    }
    float  _x,  _y,  _z;
    float _nx, _ny, _nz;

    static const DWORD FVF;
};
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL;

// 框架函数
bool Setup()
{
    // 创建三棱锥的顶点缓存
    Device->CreateVertexBuffer(
        12 * sizeof(Vertex),
        D3DUSAGE_WRITEONLY,
        Vertex::FVF,
        D3DPOOL_MANAGED,
        &Pyramid,
        0);

    // 向三棱锥的顶点缓存中写入数据
    Vertex* vertex;
    Pyramid->Lock(0, 0, (void**)&vertex, 0);

    vertex[0] = Vertex(-1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);
    vertex[1] = Vertex( 0.0f, 1.0f,  0.0f, 0.0f, 0.707f, -0.707f);
    vertex[2] = Vertex( 1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);

    vertex[3] = Vertex(-1.0f, 0.0f,  1.0f, -0.707f, 0.707f, 0.0f);
    vertex[4] = Vertex( 0.0f, 1.0f,  0.0f, -0.707f, 0.707f, 0.0f);
    vertex[5] = Vertex(-1.0f, 0.0f, -1.0f, -0.707f, 0.707f, 0.0f);

    vertex[6] = Vertex( 1.0f, 0.0f, -1.0f, 0.707f, 0.707f, 0.0f);
    vertex[7] = Vertex( 0.0f, 1.0f,  0.0f, 0.707f, 0.707f, 0.0f);
    vertex[8] = Vertex( 1.0f, 0.0f,  1.0f, 0.707f, 0.707f, 0.0f);

    vertex[9]  = Vertex( 1.0f, 0.0f,  1.0f, 0.0f, 0.707f, 0.707f);
    vertex[10] = Vertex( 0.0f, 1.0f,  0.0f, 0.0f, 0.707f, 0.707f);
    vertex[11] = Vertex(-1.0f, 0.0f,  1.0f, 0.0f, 0.707f, 0.707f);

    Pyramid->Unlock();

    // 设置世界坐标系
    D3DXMatrixTranslation(&Worlds[0], 0.0f, 2.0f, 0.0f);
    D3DXMatrixTranslation(&Worlds[1],-1.4f,-1.4f, 0.0f);
    D3DXMatrixTranslation(&Worlds[2], 1.4f,-1.4f, 0.0f);

    // 创建材质
    Marl[0] = d3d::WHITE_MTRL;
    Marl[1] = d3d::BLUE_MTRL;
    Marl[2] = d3d::GREEN_MTRL;

    // 第二处修改
    for (int cnt = 0; cnt < 3; ++cnt)
        Marl[cnt].Power = 20.0f;

第二处修改,增强高光点的锐度。

    // 第三处修改,设置聚光灯
    D3DXVECTOR3 position(0.0f, 0.0f, -5.0f);
    D3DXVECTOR3 direction(0.0f, 0.0f, 1.0f);
    D3DXCOLOR color = d3d::WHITE;
    spotLight = d3d::InitSpotLight(&position, &direction, &color);

第三处修改,设置聚光灯

    // 注册并设置光照开关状态
    Device->SetLight(0, &spotLight);
    Device->LightEnable(0, true);

    // 重新规范化法向量,并启用镜面高光
    Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
    Device->SetRenderState(D3DRS_SPECULARENABLE, true);

    // 第四处修改:设置摄影机
    D3DXVECTOR3 pos(0.0f, 0.0f,-5.0f);
    D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
    D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
    D3DXMATRIX Camera;
    D3DXMatrixLookAtLH(&Camera, &pos, &target, &up);
    Device->SetTransform(D3DTS_VIEW, &Camera);

为了凸显聚光灯的特殊性,这次演示不需要更新摄影机的位置,而是更新聚光灯的照射方向,因此摄影机的设置放回了Setup函数,同时这也是第四处修改。

    // 设置投影矩阵
    D3DXMATRIX proj;
    D3DXMatrixPerspectiveFovLH(
            &proj,
            D3DX_PI * 0.5f,
            (float)Width / (float)Height,
            1.0f,
            1000.0f);
    Device->SetTransform(D3DTS_PROJECTION, &proj);

    return true;
}

void Cleanup()
{
    d3d::Release<IDirect3DVertexBuffer9*>(Pyramid);
}

bool Display(float timeDelta)
{
    if( Device )
    {
        // 第五处修改,改变聚光灯的照射方向
        static float angle  = (3.0f * D3DX_PI) / 2.0f;

        if (::GetAsyncKeyState(VK_LEFT) & 0x8000f)
            spotLight.Direction.x -= 1.0f * timeDelta;

        if (::GetAsyncKeyState(VK_RIGHT) & 0x8000f)
            spotLight.Direction.x += 1.0f * timeDelta;

        if (::GetAsyncKeyState(VK_UP) & 0x8000f)
            spotLight.Direction.y += 1.0f * timeDelta;

        if (::GetAsyncKeyState(VK_DOWN) & 0x8000f)
            spotLight.Direction.y -= 1.0f * timeDelta;

        Device->SetLight(0, &spotLight);
        Device->LightEnable(0, true);

第五处修改则是在改变聚光灯的照射方向,本来随着方向键改变的是摄影机的位置,在这里修改为改变聚光灯照射的方向,当然,每次修改之后,同样要先对聚光灯进行注册,然后再开启光照。

修改的部位置一共就只有这五处,其余部分和点光源测试/方向光源测试的代码没有区别。

        // 开始绘制
        Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0);
        Device->BeginScene();

        Device->SetStreamSource(0, Pyramid, 0, sizeof(Vertex));
        Device->SetFVF(Vertex::FVF);

        for (int cnt = 0; cnt < 3; ++cnt) {
            Device->SetMaterial(&Marl[cnt]);
            Device->SetTransform(D3DTS_WORLD, &Worlds[cnt]);
            Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 4);
        }

        Device->EndScene();
        Device->Present(0, 0, 0, 0);
    }
    return true;
}

最终运行结果的截图如下(充分说明了这玩意就是一个手电筒):

最后给出龙书源码的下载位置:http://www.d3dcoder.net

时间: 2024-11-15 02:33:11

龙书学习笔记(三)的相关文章

龙书学习笔记(二)

补线代之余抽空把第四章上色学了,之所以说之余,是因为这一章内容确实不怎么多,不过为了巩固知识,便结合刚学的上色又做了一个小程序. 首先进行回顾,这一章学到的一共有四点: 一.Direct3D中颜色用RGB(Red.Green.Blue)三元组表示,用两种结构来保存 D3DCOLOR,即unsigned long,共32位,分成4个8位项,分别保存Alpha(这玩意的作用会在第七章学到).红.绿.蓝,均在0x00~0xff之间取值(就是0~255) 通过结构体来保存(D3DXCOLOR和D3DCO

Ajax学习笔记(三)

三.jQuery库详解 1.使用jQuery之后,javascript操作的不再是HTML元素对应的DOM对象,而是包装DOM对象的jQuery对象.js通过调用jQuery对象的方法来改变它所包装的DOM对象的属性,从而实现动态更新HTML页面. 由此可见,使用jQuery动态更新HTML页面只需以下两个步骤: (1)获取jQuery对象.jQuery对象通常是DOM对象的包装 (2)调用jQuery对象的方法来改变自身.当jQuery对象被改变时,jQuery包装的DOM对象随之改变,HTM

Caliburn.Micro学习笔记(三)----事件聚合IEventAggregator和 Ihandle&lt;T&gt;

Caliburn.Micro学习笔记(三)----事件聚合IEventAggregator和 Ihandle<T> 今天 说一下Caliburn.Micro的IEventAggregator和IHandle<T>分成两篇去讲这一篇写一个简单的例子 看一它的的实现和源码 下一篇用它们做一个多语言的demo 这两个是事件的订阅和广播,很强大,但用的时候要小心发生不必要的冲突. 先看一下它的实现思想 在Caliburn.Micro里EventAggregator要以单例的形式出现这样可以

OpenCV for Python 学习笔记 三

给源图像增加边界 cv2.copyMakeBorder(src,top, bottom, left, right ,borderType,value) src:源图像 top,bottem,left,right: 分别表示四个方向上边界的长度 borderType: 边界的类型 有以下几种: BORDER_REFLICATE # 直接用边界的颜色填充, aaaaaa | abcdefg | gggg BORDER_REFLECT # 倒映,abcdefg | gfedcbamn | nmabcd

NFC学习笔记——三(在windows操作系统上安装libnfc)

本篇翻译文章: 这篇文章主要是说明如何在windows操作系统上安装.配置和使用libnfc. 一.基本信息 1.操作系统: Windows Vista Home Premium SP 2 2.硬件信息: System: Dell Inspiron 1720 Processor: Intel Core 2 Duo CPU T9300 @ 2.5GHz 2.5GHz System type: 32-bit Operating System 3.所需软件: 在windows操作系统上安装软件需要下列

swift学习笔记(三)关于拷贝和引用

在swift提供的基本数据类型中,包括Int ,Float,Double,String,Enumeration,Structure,Dictionary都属于值拷贝类型. 闭包和函数同属引用类型 捕获则为拷贝.捕获即定义这些常量和变量的原作用域已不存在,闭包仍然可以在闭包函数体内引用和修改这些值 class属于引用类型. Array的情况稍微复杂一些,下面主要对集合类型进行分析: 一.关于Dictionary:无论何时将一个字典实例赋给一个常量,或者传递给一个函数方法时,在赋值或调用发生时,都会

加壳学习笔记(三)-简单的脱壳思路&amp;调试思路

首先一些windows的常用API: GetWindowTextA:以ASCII的形式的输入框 GetWindowTextW:以Unicaode宽字符的输入框 GetDlgItemTextA:以ASCII的形式的输入框 GetDlgItemTextW:以Unicaode宽字符的输入框 这些函数在使用的时候会有些参数提前入栈,如这函数要求的参数是字符串数目.还有大小写啦之类的东西,这些东西是要在调用该函数之前入栈,也就是依次push,就是说一般前面几个push接着一个call,那前面的push可能

【Unity 3D】学习笔记三十四:游戏元素——常用编辑器组件

常用编辑器组件 unity的特色之一就是编辑器可视化,很多常用的功能都可以在编辑器中完成.常用的编辑器可分为两种:原有组件和拓展组件.原有组件是编辑器原生的一些功能,拓展组件是编辑器智商通过脚本拓展的新功能. 摄像机 摄像机是unity最为核心组件之一,游戏界面中显示的一切内容都得需要摄像机来照射才能显示.摄像机组件的参数如下: clear flags:背景显示内容,默认的是skybox.前提是必须在render settings 中设置天空盒子材质. background:背景显示颜色,如果没

马哥学习笔记三十二——计算机及操作系统原理

缓存方式: 直接映射 N路关联 缓存策略: write through:通写 write back:回写 进程类别: 交互式进程(IO密集型) 批处理进程(CPU密集型) 实时进程(Real-time) CPU: 时间片长,优先级低IO:时间片短,优先级高 Linux优先级:priority 实时优先级: 1-99,数字越小,优先级越低 静态优先级:100-139,数据越小,优先级越高 实时优先级比静态优先级高 nice值:调整静态优先级   -20,19:100,139   0:120 ps