【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 3766|回复: 5

[推荐]D3D中的粒子系统(全)

[复制链接]
发表于 2008-9-23 10:58:14 | 显示全部楼层 |阅读模式

许多自然现象是由很多小的小颗粒组成的,它们有相似的行为。(例如,雪花落下,闪烁的火焰,冲出枪管的“子弹”),粒子系统用来模拟这种现象。

14.1 粒子和点精灵(Point Sprite)

粒子是一个很小的对象,它通常用来模拟数学中的一个点。点元是用来显示粒子的很好的方案,可是点元被光栅化成一个简单的像素。这没给我们多少灵活性,因为我们想有各种大小不同的粒子,并且把整个纹理平滑映射到这些粒子上。在Direct3D 8.0以前,因为点元方法的局限性而完全不使用他们。代替的方法是程序员将使用公告板去显示粒子,一个板是一个方格,世界矩阵用它来确定方向,使它总是朝向照相机。

Direct3D 8.0引入一个特殊的点元叫点精灵,多数时候被应用在粒子系统中。与一般的点元不同的是,点精灵有纹理映射并能改变大小。与公告板不同的是,能用一个简单的点描述一个点精灵,节省内存和处理时间,因为我们只是必须保存和处理一个点,而公告板则是四个。
14.1.1 结构的格式

我们使用下面的顶点结构来描述粒子的位置和颜色:
struct sParticle
{
D3DXVECTOR3 position;
D3DCOLOR color;
};

const DWORD PARTICLE_FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;

这个结构只保存粒子的位置和颜色,这取决于你程序的需要,你能够用同样的结构去保存一套纹理坐标。

增加一个浮点变量给Particle结构去指定粒子的大小是可能的。我们必须增加一个D3DFVF_PSIZE标记给我们的灵活的顶点格式,以反映这个变化。每个粒子维护自己的大小很有用,因为它允许我们以具体情况指定并改变粒子的大小。可是,大多数的图形卡不支持控制粒子的大小,因此我们不使用它。(检查D3DFVFCAPS_PSIZE在D3 DCAPS9结构的FVFCaps成员)代替的方法是用渲染状态(render states)去控制粒子的大小,就像你很快看到的,有尺寸成员的顶点结构的例子:
strict Particle

{

D3DXVECTOR3 _position;

D3DCOLOR _color;

float _size;

static const DWORD FVF;

};

const DWORD Particle::FVF = D3DFVF XYZ | D3DFVF DIFFUSE | D3DFVF_PSIZE;

注意:通过vertex shader,能够获取每个粒子的大小,即使你的硬件不支持D3DFVFCAPS_PSIZE。
14.1.2点精灵(Point Sprite)渲染状态

点精灵的行为大部分由渲染状态(render states)来控制,现在让我们来看一下这些渲染状态:

D3DRS_POINTSPRITEENABLE—A Boolean value. The default value is false.

True表示将当前的纹理全部映射到点精灵上。

False 表示用指定的纹理坐标映射到点精灵的点(图素)上。

_device->SetRenderState(D3DRS_POINTSPRITEENABLE, true);
D3DRS_POINTSPRITEENABLE
bool value. When TRUE, texture coordinates of point primitives are set so that full textures are mapped on each point. When FALSE, the vertex texture coordinates are used for the entire point. The default value is FALSE. You can achieve DirectX 7 style single-pixel points by setting D3DRS_POINTSCALEENABLE to FALSE and D3DRS_POINTSIZE to 1.0, which are the default values.

D3DRS_POINTSCALEENABLE—A Boolean value. The default value is false.

True表示用视图空间单位来解释点的大小。视图空间单位的3D空间点在照相机中,点精灵将会自动缩放,这取决到它有多远, 像其他对象一样,离照相机近的粒子比离照相机远的粒子要大。

False 表示点的大小将用屏幕空间单位来解释。屏幕空间单位是屏幕上的像素单位。. 因此如果你指定false, 例如, 设置点精灵的尺寸为3, 则点精灵在屏幕区域中的尺寸为3?像素。.

_device->SetRenderState(D3DRS_POINTSCALEENABLE, true);
D3DRS_POINTSCALEENABLE
bool value that controls computation of size for point primitives. When TRUE, the point size is interpreted as a camera space value and is scaled by the distance function and the frustum to viewport y-axis scaling to compute the final screen-space.

D3DRS_POINTSIZE—表示点精灵的尺寸. 这个值可以任意指定视图空间或屏幕空间的点精灵的尺寸, 取决于D3DRS_POINTSCALEENABLE状态如何设置. 下面的代码段设置点的尺寸为2.5个单位。:

_device->SetRenderState( D3DRS_POINTSIZE, float_to_dword(2.5f) );
D3DRS_POINTSIZE
A float value that specifies the size to use for point size computation in cases where point size is not specified for each vertex. This value is not used when the vertex contains point size. This value is in screen space units if D3DRS_POINTSCALEENABLE is FALSE; otherwise this value is in world space units. The default value is the value a driver returns. If a driver returns 0 or 1, the default value is 64, which allows software point size emulation. Because the IDirect3DDevice9::SetRenderState method accepts DWORD values, your application must cast a variable that contains the value, as shown in the following code example.
m_pDevice9->SetRenderState(D3DRS_POINTSIZE, *((DWORD*)&pointSize));
DWORD float_to_dword(float f)
{
return *((DWORD*)&f);
}

D3DRS_POINTSIZE_MIN—表示点精灵的最小尺寸。例子,将设置最小值为0.2:

_device->SetRenderState(D3DRS_POINTSIZE_MIN, float_to_dword(0.2f));
D3DRS_POINTSIZE_MIN
A float value that specifies the minimum size of point primitives. Point primitives are clamped to this size during rendering. Setting this to values smaller than 1.0 results in points dropping out when the point does not cover a pixel center and antialiasing is disabled or being rendered with reduced intensity when antialiasing is enabled. The default value is 1.0f. The range for this value is greater than or equal to 0.0f. Because the IDirect3DDevice9::SetRenderState method accepts DWORD values, your application must cast a variable that contains the value, as shown in the following code example.
m_pDevice9->SetRenderState(D3DRS_POINTSIZE_MIN, *((DWORD*)&pointSizeMin));

D3DRS_POINTSIZE_MAX—表示点精灵的最大尺寸。例子,将设置最大值为5.0:

_device->SetRenderState(D3DRS_POINTSIZE_MAX, float_to_dword(5.0f));
D3DRS_POINTSIZE_MAX
A float value that specifies the maximum size to which point sprites will be clamped. The value must be less than or equal to the MaxPointSize member of D3DCAPS9 and greater than or equal to D3DRS_POINTSIZE_MIN. The default value is 64.0. Because the IDirect3DDevice9::SetRenderState method accepts DWORD values, your application must cast a variable that contains the value, as shown in the following code example.
m_pDevice9->SetRenderState(D3DRS_PONTSIZE_MAX, *((DWORD*)&pointSizeMax));

D3DRS_POINTSCALE_A, D3DRS_POINTSCALE_B, D3DRS_POINTSCALE_C—这3个常量表示如何根据距离控制点精灵的尺寸—这个距离是点精灵到照相机的距离。
D3DRS_POINTSCALE_A
A float value that controls for distance-based size attenuation for point primitives. Active only when D3DRS_POINTSCALEENABLE is TRUE. The default value is 1.0f. The range for this value is greater than or equal to 0.0f. Because the IDirect3DDevice9::SetRenderState method accepts DWORD values, your application must cast a variable that contains the value, as shown in the following code example.
m_pDevice9->SetRenderState(D3DRS_POINTSCALE_A, *((DWORD*)&pointScaleA));
D3DRS_POINTSCALE_B
A float value that controls for distance-based size attenuation for point primitives. Active only when D3DRS_POINTSCALEENABLE is TRUE. The default value is 0.0f. The range for this value is greater than or equal to 0.0f. Because the IDirect3DDevice9::SetRenderState method accepts DWORD values, your application must cast a variable that contains the value, as shown in the following code example.
m_pDevice9->SetRenderState(D3DRS_POINTSCALE_B, *((DWORD*)&pointScaleB));
D3DRS_POINTSCALE_C
A float value that controls for distance-based size attenuation for point primitives. Active only when D3DRS_POINTSCALEENABLE is TRUE. The default value is 0.0f. The range for this value is greater than or equal to 0.0f. Because the IDirect3DDevice9::SetRenderState method accepts DWORD values, your application must cast a variable that contains the value, as shown in the following code example.
m_pDevice9->SetRenderState(D3DRS_POINTSCALE_C, *((DWORD*)&pointScaleC));

D3D用以下的公式去计算点精灵的最终尺寸,这取决于距离和这3个常量。

其中:

FinalSize:距离计算后,点精灵的最后尺寸。

ViewportHeight:视口的高度。

Size:分别为D3DRS_POINTSCALE_A, D3DRS_POINTSCALE_B, and D3DRS_POINTSCALE_C值。
D:在视图空间中点精灵与照相机的距离。因为照相机被放置在视图空间中的原点,这个值是:,也是点精灵所在的位置。

下面代码设置点精灵的距离常量,因此远处的点精灵将变小。
_device->SetRenderState(D3DRS_POINTSCALE_A, float_to_dword(0.0f));

_device->SetRenderState(D3DRS_POINTSCALE_B, float_to_dword(0.0f));

_device->SetRenderState(D3DRS_POINTSCALE_C, float_to_dword(1.0f));

14.1.3 粒子和他们的属性

一个粒子系统是由除了位置、颜色以外的更多的属性组成,例如,一个粒子有速度。然而,这些额外的属性对于渲染粒子来说不是必须的。因此,我们在单独的结构中保存渲染粒子所必须的数据和属性。当我们创建、显示或更新粒子时,我们使用属性来工作。当我们准备渲染时,我们从sParticle(粒子)结构中COPY位置和颜色。

对于我们模拟的具体粒子系统,粒子的属性也是不同的。因此我们能够归纳一些通用的属性,大多数系统用不上这么多,一些系统需要的属性这里可能还没有。
struct sParticleAttribute
{
sParticleAttribute()
{
life_time = 0.0f;
age = 0.0f;
is_alive = true;
}

D3DXVECTOR3 position;
D3DXVECTOR3 velocity;
D3DXVECTOR3 acceleration;
float life_time; // how long the particle lives for before dying
float age; // current age of the particle
D3DXCOLOR color; // current color of the particle
D3DXCOLOR color_fade; // how the color fades with respect to time
bool is_alive;
};

[此贴子已经被作者于2009-5-12 16:12:12编辑过]
 楼主| 发表于 2008-11-30 08:37:10 | 显示全部楼层
position—粒子在世界空间中的位置
velocity—粒子的速度,每秒多少个单位。
acceleration—粒子的加速度, 每秒多少个单位。
life_time—粒子的生命周期. 例如,当一个时间段后,我们可以杀死一个激光柱的粒子.
age—粒子的当前年龄。
color—粒子的颜色。
color_fade—粒子随时间的变化而褪去的颜色。
is_alive—True 表示粒子活着;false 表示粒子死了。
14.2粒子系统的组成
粒子系统是粒子的集合,用来保存和显示这些粒子。粒子系统维护所有粒子的全部属性,影响系统中的所有粒子:粒子的尺寸,起始的位置及应用在粒子上的纹理等。粒子系统的方法负责更新、显示、杀死和创建粒子。
虽然不同的具体(与抽象是相对的)粒子系统有不同的行为,我们归纳并找到一些所有的粒子系统共有的基本属性,我们把这些公共的属性放到一个抽象的cParticleSystem基类,它是我们所有的具体粒子系统的父类,现在让我们看一下cParticleSystem类:
    class cParticleSystem
    {
    protected:
        IDirect3DDevice9*            m_device;
        D3DXVECTOR3                    m_origin;
        cBoundingBox                m_bounding_box;
        float                        m_emit_rate;        // rate new particles are added to system
        float                        m_size;                // size of particles
            IDirect3DTexture9*            m_texture;
        IDirect3DVertexBuffer9*     m_vertex_buffer;
        list<sParticleAttribute>    m_particles;
        int                            m_max_particles;    // max allowed particles system can have
        // following three data elements used for rendering the particle system efficiently
        DWORD    m_vb_num;        // particle number in vertex buffer
        DWORD    m_vb_offset;    // offset in vertex buffer to lock
        DWORD    m_vb_batch_num;    // number of vertices to lock starting at m_vb_offset
    public:
        cParticleSystem();
        virtual ~cParticleSystem();
        virtual bool init(IDirect3DDevice9*    device, const char* texture_filename);
        virtual void reset();
        // sometimes we don't want to free the memory of a dead particle, but rather respawn it instead.
        virtual void reset_particle(sParticleAttribute* particl_attr) = 0;
        virtual void add_particle();
        virtual void update(float time_delta) = 0;
        virtual void pre_render();
        virtual void render();
        virtual void post_render();
        bool is_empty();
        bool is_dead();
    protected:
        virtual void remove_dead_particles();
    };
一些数据成员:
·        m_origin—粒子系统的原点, 这是粒子系统产生时的位置。
·        m_bounding_box—创建粒子系统使用的边界盒,用于限制粒子的活动范围。例如,假如我们让雪系统只落在一个围绕高山的峰顶的体积内,我们会定义一个包括这个体积的边界盒, 出界的粒子将会被杀死。
·        m_emit_rate—新增加到系统中的粒子的速度。通常的标准是每秒。
·        m_size—系统中所有粒子的尺寸。
·        m_particles—系统中粒子属性的一个列表。 我们用这个列表创建,释放及更新粒子。 当我们准备画粒子时, 我们COPY列表节点的一部分到顶点缓存并画粒子,同时我们COPY另外一批粒子,然后重复这一过程直到绘制完所有粒子。
·        m_max_particles—在给定的时间内,系统中允许的粒子最大数。例如,如果创建粒子的速度比释放快的话, 随着时间的增长粒子的数量将会是巨大的,这个成员将避免出现这样的问题。
·        m_vb_num—在给定的时间内顶点缓存中能够保存的粒子的数量,这个值与实际的粒子系统中的粒子数量无关。
注意:m_vb_offset和m_vb_batch_num数据成员在渲染粒子系统时使用,我们在稍后讨论。
方法:
cParticleSystem/ ~cParticleSystem—用来初始化默认值和用来释放设备接口 (vertex buffer, texture)。
    cParticleSystem::cParticleSystem()
    {
        m_device        = NULL;
        m_vertex_buffer = NULL;
        m_texture        = NULL;
    }
    cParticleSystem::~cParticleSystem()
    {
        safe_release<IDirect3DVertexBuffer9*>(m_vertex_buffer);
        safe_release<IDirect3DTexture9*>(m_texture);
    }
init—这个方法做与设备无关的初始化工作,比如创建用来保存点精灵的顶点缓存或创建纹理。
    bool cParticleSystem::init(IDirect3DDevice9* device, const char* texture_filename)
    {
        // Vertex buffer's number does not equal the number of particles in our system.
        // We use the vertex buffer to draw a portion of our particles at a time.
        // The arbitrary number we choose for the vertex buffer is specified by the m_vb_num variable.
        m_device = device;
        HRESULT hr;
        hr = device->CreateVertexBuffer(
                m_vb_num * sizeof(sParticle),
                D3DUSAGE_DYNAMIC | D3DUSAGE_POINTS | D3DUSAGE_WRITEONLY,
                PARTICLE_FVF,
                D3DPOOL_DEFAULT,    // D3DPOOL_MANAGED can't be used with D3DUSAGE_DYNAMIC
                &m_vertex_buffer,
                NULL);
        if(FAILED(hr))
        {
            MessageBox(NULL, "CreateVertexBuffer() - FAILED", "ParticleSystem", MB_OK);
            return false;
        }
        hr = D3DXCreateTextureFromFile(device, texture_filename, &m_texture);
        if(FAILED(hr))
        {
            MessageBox(NULL, "D3DXCreateTextureFromFile() - FAILED", "ParticleSystem", MB_OK);
            return false;
        }
        return true;
    }
o       注意: 我们使用动态的顶点缓存(D3DUSAGE DYNAMIC)。 因为我们需要在每帧中更新我们的粒子,意思是我们将会去存取顶点缓存的内存,回想一下,访问一个静态的顶点缓存慢得不可接受, 所以我们使用动态的顶点缓存。
o       查看我们用过的 D3DUSAGE_POINTS标记,它说明顶点缓存将保存点精灵。
o       顶点缓存的尺寸是由m_vb_num预先确定的,而且与系统中粒子的数量无关。 也就是说, m_vb_num将小于等于系统中粒子的数量。 这是因为渲染粒子系统是一批一批的,不是一次渲染全部。
o       我们使用默认的内存池(pool)代替通常使用的托管内存池,因为动态顶点缓存不能用在托管内存池中。
reset—这个方法重新设置系统中每个粒子的属性:
    void cParticleSystem::reset()
    {
        for(list<sParticleAttribute>::iterator iter = m_particles.begin(); iter != m_particles.end(); iter++)
            reset_particle(&(*iter));
    }
reset_particle—这个方法重新设置粒子的属性。如何重设粒子的属性,这依赖于具体粒子系统的特性。因此我们定义这个方法为虚拟的,等待子类去实现。
add_particle—这个方法用来在系统中增加一个粒子。在增加它到粒子列表之前,使用reset_particle方法先初始化粒子:
    void cParticleSystem::add_particle()
    {
        sParticleAttribute attr;
        reset_particle(&attr);
        m_particles.push_back(attr);
    }
update—这个方法更新系统中所有的粒子。因为这个的方法的执行取决于具体粒子系统的特性,因此我们定义这个方法为抽象的,等待子类去实现。
render—这个方法用来显示系统中所有的粒子。
pre_render—用它来初始化渲染状态,在渲染前设置。 因为系统与系统之间是不同的,所以我们定义它为虚拟的。 默认将执行下列代码:
    void cParticleSystem::pre_render()
    {
        m_device->SetRenderState(D3DRS_LIGHTING,          FALSE);
        m_device->SetRenderState(D3DRS_POINTSPRITEENABLE, TRUE);
        m_device->SetRenderState(D3DRS_POINTSCALEENABLE, TRUE);
        m_device->SetRenderState(D3DRS_POINTSIZE,          float_to_dword(m_size));
        m_device->SetRenderState(D3DRS_POINTSIZE_MIN,      float_to_dword(0.0f));
        // control the size of the particle relative to distance
            m_device->SetRenderState(D3DRS_POINTSCALE_A,    float_to_dword(0.0f));
        m_device->SetRenderState(D3DRS_POINTSCALE_B,    float_to_dword(0.0f));
        m_device->SetRenderState(D3DRS_POINTSCALE_C,    float_to_dword(1.0f));
        // use alpha from texture
            m_device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
        m_device->SetTextureStageState(0, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
        m_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
        m_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
        m_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    }
 楼主| 发表于 2008-11-30 08:37:25 | 显示全部楼层

注意:我们使用alpha混合渲染,以便设置纹理的alpha通道,来设置纹理像素的透明,用它产生多种效果。一种特殊的情况是:获得象纹理那样的非矩形的粒子。例如,获得一个圆形“雪球形”的粒子,我们使用一个简单的带有alpha通道的纹理,它看上去是背景为黑色的带有白色圆形的样子。因此,显示出来时只是一个白圆,这比白色的矩形纹理要好。
post_render—用它去保存所有渲染状态。因为系统与系统间是不同的,所以我们定义它为虚拟的。默认将执行下列代码:
    void cParticleSystem::post_render()
    {
        m_device->SetRenderState(D3DRS_LIGHTING, TRUE);
        m_device->SetRenderState(D3DRS_POINTSPRITEENABLE, FALSE);
        m_device->SetRenderState(D3DRS_POINTSCALEENABLE, FALSE);
        m_device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
    }
is_empty—如果为True则在当前的系统中没有粒子, 否则为false.
    bool cParticleSystem::is_empty()
    {
        return m_particles.empty();
    }
is_dead—如果为True则系统中的所有粒子都是死的,否则为false。
    bool cParticleSystem::is_dead()
    {
        for(list<sParticleAttribute>::iterator iter = m_particles.begin(); iter != m_particles.end(); iter++)
        {
            // Is there at least one living particle? If yes, the system is not dead.
            if(iter->is_alive)
                return false;
        }
        // No living particles found, the system must be dead.
        return true;
    }
remove_dead_particles—搜索属_particle性表,从表中杀死并删除粒子。
    void cParticleSystem::remove_dead_particles()
    {
        list<sParticleAttribute>::iterator iter = m_particles.begin();
        while(iter != m_particles.end())
        {       
            if(! iter->is_alive)
                // erase returns the next iterator, so no need to increment to the next one ourseleves.
                    iter = m_particles.erase(iter);
            else
                iter++;    // next in list
        }
    }


14.2.1 绘制粒子系统
因为粒子系统是动态的,在每一个帧中我们需要更新系统中的粒子,对于渲染粒子系统的一种直观但效率低下的方法如下:
创建一个足够大的顶点缓存保存最大数量的粒子。
每一帧里执行:
A.       更新所有粒子。
B.       COPY所有活着的粒子到顶点缓存。
C.       绘制顶点缓存。
这个方法正确,不过不是最有效率的。第一,顶点缓冲必须足够大以保存系统中所有粒子。但是非常重要的是,当我们从列表拷贝所有粒子到顶点缓冲(步骤B)时,显卡却什么也不做。举个例子,假设我们系统有10,000个粒子,首先我们需要一个能容纳10,000个粒子的顶点缓冲,这是一个很大的内存。另外显卡将停着什么也不做直到列表中的10,000个粒子拷到顶点缓冲,直到我们调用DrawPrimitive。这个特定情况是CPU与显卡不同时工作的一个很好的例子。
更好的办法(SDK中点精灵例程中用到的方法)就象这样:
提示:这是一个简单的描述,但它说明了这一思想。它假定我们总是有500个粒子以填充一个缓存片段,但是这是不可能发生的,因为我们经常杀死并创建粒子,所以从一帧到另一帧粒子数量是变化的。举个例子,假设我们只剩下200个粒子要在当前帧拷贝并渲染。因为200个粒子不能填充整个缓存片段,我们用代码处理这个特定情形。这个特定情形只有在最后的缓存片段中才会出现,因为如果不是最后的片断,就意味着必然有500个粒子将被移到下一缓存片段。
创建一个合适尺寸的顶点缓存(能够保存2000个粒子),然后我们划分顶点缓存为几个小的块,就像这个例子,我们设置每个缓存片断的尺寸为500个粒子。
l然后创建一个全局变量 i = 0 ,用来记录片段。
每一帧里执行:
A.       更新所有粒子。
B.       直到所有粒子渲染完毕。
1.        如果顶点缓存没有满:
a         用D3DLOCK_NOOVERWRITE标记锁定缓存片段i
b         COPY 500个粒子到片段i
2.        如果顶点缓存满了:
a         从起始的地方开始顶点缓冲: i=0
b         用D3DLOCK_NOOVERWRITE标记锁定缓存段i
c          COPY 500个粒子到片段i
3.        渲染片段i.
4.        下一片段: i+ +
备注:顶点缓存是动态的, 因此我们能利用动态锁定标记D3DLOCK_NOOVERWRITE和 D3DLOCK_DISCARD。这两个标记允许我们锁定顶点缓存的某一部分。当顶点缓存中的其他部分被渲染时,它是不能渲染的。例如,假如我们正在使用D3DLOCK_NOOVERWRITE标记渲染片段0时, 当渲染片段0的时候我们能锁定并填充片段1。这样可以防止渲染的延迟。
这个方法更有效率。首先,我们减少顶点缓存的尺寸;然后, CPU与显卡在协调的工作。也就是说,当我们绘制一小批粒子时(graphics card work),同时拷贝另一小批粒子到顶点缓存 (CPU work)。这个动作是连续执行的,直到所有的粒子都被渲染完毕,就像你了解的一样, 显卡在全部顶点缓存被填充的时候是不用处于空闲状态的。
我们现在将注意力转向这一个渲染方案的实现,为了方便使用这个粒子系统的渲染方案, 我们使用 cParticleSystem 类中的下列数据成员:
m_vb_num—在给定时间内我们的顶点缓存能够保存的粒子数量。这个值与实际的粒子系统中的粒子数无关。
m_vb_offset—这个变量是顶点缓存中的偏移,在顶点缓存里我们将用它开始COPY下一批粒子,例如,如果第一批在缓存中是0到499,偏移到第二批COPY的开始处将是500。
m_vb_batch_size—定义一批缓存中的粒子数量。
我们现在介绍渲染方法的代码:
    void cParticleSystem::render()
    {
        // The render method works by filling a section of the vertex buffer with data, then we render that section.
        // While that section is rendering we lock a new section and begin to fill that section.
        // Once that sections filled we render it. This process continues until all the particles have been drawn.
        // The benifit of this method is that we keep the video card and the CPU busy.
        if(m_particles.empty())
            return;
        // set render states
        pre_render();
        m_device->SetTexture(0, m_texture);
        m_device->SetFVF(PARTICLE_FVF);
        m_device->SetStreamSource(0, m_vertex_buffer, 0, sizeof(sParticle));
        //
        // render batches one by one
        //
        // start at beginning if we're at the end of the vertex buffer
        if(m_vb_offset >= m_vb_num)
            m_vb_offset = 0;
        sParticle* v;
        m_vertex_buffer->Lock(
            m_vb_offset * sizeof(sParticle),
            m_vb_batch_num * sizeof(sParticle),
            (void**)&v,
            m_vb_offset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD);
        DWORD num_particles_in_batch = 0;
        // until all particles have been rendered
        for(list<sParticleAttribute>::iterator iter = m_particles.begin(); iter != m_particles.end(); iter++)
        {
            if(! iter->is_alive)
                continue;
            // copy a batch of the living particles to the next vertex buffer segment
            v->position = iter->position;
            v->color    = (D3DCOLOR) iter->color;
            v++;    // next element
            num_particles_in_batch++;
            // if this batch full?
            if(num_particles_in_batch == m_vb_batch_num)
            {
                // draw the last batch of particles that was copied to the vertex buffer
                    m_vertex_buffer->Unlock();
                m_device->DrawPrimitive(D3DPT_POINTLIST, m_vb_offset, m_vb_batch_num);
                //
                // While that batch is drawing, start filling the next batch with particles.
                //
                // move the offset to the start of the next batch
                    m_vb_offset += m_vb_batch_num;
                // Don't offset into memory that is outside the vb's range.
                // If we're at the end, start at the beginning.
                if(m_vb_offset >= m_vb_num)
                    m_vb_offset = 0;
                m_vertex_buffer->Lock(
                    m_vb_offset * sizeof(sParticle),
                    m_vb_batch_num * sizeof(sParticle),
                    (void**)&v,
                    m_vb_offset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD);
                num_particles_in_batch = 0;    // reset for new batch
            }
        }
        m_vertex_buffer->Unlock();
        // Its possible that the LAST batch being filled never got rendered because the condition
        // (num_particles_in_batch == m_vb_batch_num) would not have been satisfied.
        // We draw the last partially filled batch now.   
        if(num_particles_in_batch)
            m_device->DrawPrimitive(D3DPT_POINTLIST, m_vb_offset, num_particles_in_batch);
        m_vb_offset += m_vb_batch_num;    // next block
            post_render();                    // reset render states
    }
14.2.2 随机
如果我们模拟雪花,不能让所有雪花以完全相同的方式落下。我们要让它们按相似的方式落下而不是完全相同的方式。为了使粒子系统的随机功能更简单,我们增加了下列两个函数到d3dUtility.h/cpp文件。
第一个函数在[low_bound, high_bound]区间内随机的返回一个float类型值:
    float get_random_float(float low_bound, float high_bound)
    {
        if(low_bound >= high_bound)    // bad input
            return low_bound;
        // get random float in [0, 1] interval
        float f = (rand() % 10000) * 0.0001f;
        // return float in [low_bound, high_bound] interval
        return f * (high_bound - low_bound) + low_bound;
    }
第二个函数在边界盒的范围内,输出一个随机的向量。
    void get_random_vector(D3DXVECTOR3* out, D3DXVECTOR3* min, D3DXVECTOR3* max)
    {
        out->x = get_random_float(min->x, max->x);
        out->y = get_random_float(min->y, max->y);
        out->z = get_random_float(min->z, max->z);
    }

 楼主| 发表于 2008-11-30 08:41:09 | 显示全部楼层
14.3具体的粒子系统:雪、火、粒子枪
现在让我们用cParticleSystem类开始一个具体的粒子系统,为了说明用意,这些系统的设计很简单,没有用到cParticleSystem类所提供的所有灵活性。我们实现雪、火、粒子枪系统。雪系统模拟下落的雪花,火系统模拟看上去像火焰的爆炸,粒子枪系统从照相机位置向对面发射出粒子(用键盘)。
14.3.1 例子程序:雪
雪系统类定义如下:
    class cSnow : public cParticleSystem
    {
    public:
        cSnow(cBoundingBox* bounding_box, int num_particles);
        virtual void reset_particle(sParticleAttribute* attr);
        virtual void update(float time_delta);
    };
构造函数提供一个点给边界盒结构,边界盒是粒子系统的成员。边界盒描述雪花在哪个范围内(体积范围)下落,如果雪花出了边界盒,它将被杀死并再生。这样,雪系统始终能保存有同样数量的激粒子,构造函数的实现:
    cSnow::cSnow(cBoundingBox* bounding_box, int num_particles)
    {
        m_bounding_box    = *bounding_box;
        m_size            = 0.25f;
        m_vb_num        = 2048;
        m_vb_offset        = 0;
        m_vb_batch_num    = 512;
        for(int i = 0; i < num_particles; i++)
            add_particle();
    }
同样注意:我们指定顶点缓存的尺寸,每一批的尺寸和开始的偏移。
reset_particle方法创建一个雪花,在x、z轴随机的位置并在边界盒的范围内。设置y轴高度为边界盒的顶部。我们给雪花一个速度,以便让雪花下落时稍稍向左倾斜。雪花是白色的。
    void cSnow::reset_particle(sParticleAttribute* attr)
    {
        attr->is_alive = true;
        // get random x, z coordinate for the position of the snow flake
            get_random_vector(&attr->position, &m_bounding_box.m_min, &m_bounding_box.m_max);
        // no randomness for height (y-coordinate).
        // Snow flake always starts at the top of bounding box.
            attr->position.y = m_bounding_box.m_max.y;
        // snow flakes fall downwards and slightly to the left
            attr->velocity.x = get_random_float(0.0f, 1.0f) * (-3.0f);
        attr->velocity.y = get_random_float(0.0f, 1.0f) * (-10.0f);
        attr->velocity.z = 0.0f;
        // white snow flake
        attr->color = WHITE;
    }
update方法更新粒子和粒子间的位置,并且测试粒子是否在系统的边界盒之外,如果它已经跳出边界盒,就再重新创建。
    void cSnow::update(float time_delta)
    {
        for(list<sParticleAttribute>::iterator iter = m_particles.begin(); iter != m_particles.end(); iter++)
        {
            iter->position += iter->velocity * time_delta;
            // is the point outside bounds?
            if(! m_bounding_box.is_point_inside(iter->position))
                // recycle dead particles, so respawn it.
                    reset_particle(&(*iter));
        }
    }
执行程序:
    #include "d3dUtility.h"
    #include "camera.h"
    #include "ParticleSystem.h"
    #include <cstdlib>
    #include <ctime>
    #pragma warning(disable : 4100)
    const int WIDTH = 640;
    const int HEIGHT = 480;
    IDirect3DDevice9*    g_device;
    cParticleSystem*    g_snow;
    cCamera                g_camera(AIR_CRAFT);
        ////////////////////////////////////////////////////////////////////////////////////////////////////
    bool setup()
    {   
        srand((unsigned int)time(NULL));
        // create snow system
        cBoundingBox bounding_box;
        bounding_box.m_min = D3DXVECTOR3(-10.0f, -10.0f, -10.0f);
        bounding_box.m_max = D3DXVECTOR3(10.0f, 10.0f, 10.0f);
        g_snow = new cSnow(&bounding_box, 5000);
        g_snow->init(g_device, "snowflake.dds");
        // setup a basic scnen, the scene will be created the first time this function is called.
            draw_basic_scene(g_device, 1.0f);
        // set the projection matrix
        D3DXMATRIX proj;
        D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI/4.0f, (float)WIDTH/HEIGHT, 1.0f, 1000.0f);
        g_device->SetTransform(D3DTS_PROJECTION, &proj);
        return true;
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    void cleanup()
    {   
        delete g_snow;
        // pass NULL for the first parameter to instruct cleanup
        draw_basic_scene(NULL, 0.0f);
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    bool display(float time_delta)
    {
        // update the camera
        if( GetAsyncKeyState(VK_UP) & 0x8000f )
            g_camera.walk(4.0f * time_delta);
        if( GetAsyncKeyState(VK_DOWN) & 0x8000f )
            g_camera.walk(-4.0f * time_delta);
        if( GetAsyncKeyState(VK_LEFT) & 0x8000f )
            g_camera.yaw(-1.0f * time_delta);
        if( GetAsyncKeyState(VK_RIGHT) & 0x8000f )
            g_camera.yaw(1.0f * time_delta);
        if( GetAsyncKeyState('N') & 0x8000f )
            g_camera.strafe(-4.0f * time_delta);
        if( GetAsyncKeyState('M') & 0x8000f )
            g_camera.strafe(4.0f * time_delta);
        if( GetAsyncKeyState('W') & 0x8000f )
            g_camera.pitch(1.0f * time_delta);
        if( GetAsyncKeyState('S') & 0x8000f )
            g_camera.pitch(-1.0f * time_delta);   
        // update the view matrix representing the camera's new position/orientation
        D3DXMATRIX view_matrix;
        g_camera.get_view_matrix(&view_matrix);
        g_device->SetTransform(D3DTS_VIEW, &view_matrix);   
        g_snow->update(time_delta);
        // render now
        g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0);
        g_device->BeginScene();
        D3DXMATRIX identity_matrix;
        D3DXMatrixIdentity(&identity_matrix);
        g_device->SetTransform(D3DTS_WORLD, &identity_matrix);
        draw_basic_scene(g_device, 1.0f);
        // order important, render snow last.
            g_device->SetTransform(D3DTS_WORLD, &identity_matrix);
        g_snow->render();
        g_device->EndScene();
        g_device->resent(NULL, NULL, NULL, NULL);
        return true;
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM word_param, LPARAM long_param)
    {
        switch(msg)
        {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        case WM_KEYDOWN:
            if(word_param == VK_ESCAPE)
                DestroyWindow(hwnd);
            break;
        }
        return DefWindowProc(hwnd, msg, word_param, long_param);
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    int WINAPI WinMain(HINSTANCE inst, HINSTANCE, PSTR cmd_line, int cmd_show)
    {
        if(! init_d3d(inst, WIDTH, HEIGHT, true, D3DDEVTYPE_HAL, &g_device))
        {
            MessageBox(NULL, "init_d3d() - failed.", 0, MB_OK);
            return 0;
        }
        if(! setup())
        {
            MessageBox(NULL, "Steup() - failed.", 0, MB_OK);
            return 0;
        }
        enter_msg_loop(display);
        cleanup();
        g_device->Release();
        return 0;
    }
 楼主| 发表于 2008-11-30 08:42:47 | 显示全部楼层
14.3.2 例子程序:焰火系统
本例程实现了一个焰火例子系统,运行效果如图14.3所示:
火系统类定义如下:
    class cFirework : public cParticleSystem
    {
    public:
        cFirework(D3DXVECTOR3* origin, int num_particles);
        virtual void reset_particle(sParticleAttribute* attr);
        virtual void update(float time_delta);
        virtual void pre_render();
    };
构造函数需要提供一个点作为粒子系统中的原点,和系统中的粒子数,原点是火焰爆发的那个点。
    cFirework::cFirework(D3DXVECTOR3* origin, int num_particles)
    {
        m_origin        = *origin;
        m_size            = 0.9f;
        m_vb_num        = 2048;
        m_vb_offset     = 0;
        m_vb_batch_num    = 512;
        for(int i = 0; i < num_particles; i++)
            add_particle();
    }
reset_particle方法在原点位置初始化粒子系统,并在边界球内创建一个随机的速度,粒子系统中的每个例子有一个随机的颜色,我们定义粒子只能存活2秒。
    void cFirework::reset_particle(sParticleAttribute* attr)
    {
        attr->is_alive = true;
        attr->position = m_origin;
        D3DXVECTOR3 min = D3DXVECTOR3(-1.0f, -1.0f, -1.0f);
        D3DXVECTOR3 max = D3DXVECTOR3(1.0f, 1.0f, 1.0f);
        get_random_vector(&attr->velocity, &min, &max);
        // normalize to make spherical
            D3DXVec3Normalize(&attr->velocity, &attr->velocity);
        attr->velocity *= 100.0f;
        attr->color = D3DXCOLOR(get_random_float(0.0f, 1.0f),
                                get_random_float(0.0f, 1.0f),
                                get_random_float(0.0f, 1.0f),
                                1.0f);
        attr->age        = 0.0f;
        attr->life_time = 2.0f;    // lives for 2 seconds
    }
update方法更新每个粒子的位置,并在粒子超出自己的生活周期时杀死它。注意:这个系统不移除死掉的粒子,这么做是因为我们想产生一个新的火焰的时候,我们只要简单的重新设置已经存在的死了的火焰系统就可以了。这样我们不必频繁的去产生和释放粒子。
    void cFirework::update(float time_delta)
    {
        for(list<sParticleAttribute>::iterator iter = m_particles.begin(); iter != m_particles.end(); iter++)
        {
            // only update living particles
            if(iter->is_alive)
            {
                iter->position += iter->velocity * time_delta;
                iter->age       += time_delta;
                if(iter->age > iter->life_time)    // kill
                    iter->is_alive = false;
            }
        }
    }
重载pre_render以使绘制粒子时与地板颜色融合。
    void cFirework::pre_render()
    {
        cParticleSystem::pre_render();
        m_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
    }
执行程序:
    #include "d3dUtility.h"
    #include "camera.h"
    #include "ParticleSystem.h"
    #include <cstdlib>
    #include <ctime>
    #pragma warning(disable : 4100)
    const int WIDTH = 640;
    const int HEIGHT = 480;
    IDirect3DDevice9*    g_device;
    cParticleSystem*    g_exploder;
    cCamera                g_camera(AIR_CRAFT);
        ////////////////////////////////////////////////////////////////////////////////////////////////////
    bool setup()
    {   
        srand((unsigned int)time(NULL));
        // create firwworlk system
            D3DXVECTOR3 origin(0.0f, 10.0f, 50.0f);
        g_exploder = new cFirework(&origin, 6000);
        g_exploder->init(g_device, "flare.bmp");
        // setup a basic scnen, the scene will be created the first time this function is called.
            draw_basic_scene(g_device, 1.0f);
        // set the projection matrix
        D3DXMATRIX proj;
        D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI/4.0f, (float)WIDTH/HEIGHT, 1.0f, 1000.0f);
        g_device->SetTransform(D3DTS_PROJECTION, &proj);
        return true;
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    void cleanup()
    {   
        delete g_exploder;
        // pass NULL for the first parameter to instruct cleanup
        draw_basic_scene(NULL, 0.0f);
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    bool display(float time_delta)
    {
        // update the camera
        if( GetAsyncKeyState(VK_UP) & 0x8000f )
            g_camera.walk(4.0f * time_delta);
        if( GetAsyncKeyState(VK_DOWN) & 0x8000f )
            g_camera.walk(-4.0f * time_delta);
        if( GetAsyncKeyState(VK_LEFT) & 0x8000f )
            g_camera.yaw(-1.0f * time_delta);
        if( GetAsyncKeyState(VK_RIGHT) & 0x8000f )
            g_camera.yaw(1.0f * time_delta);
        if( GetAsyncKeyState('N') & 0x8000f )
            g_camera.strafe(-4.0f * time_delta);
        if( GetAsyncKeyState('M') & 0x8000f )
            g_camera.strafe(4.0f * time_delta);
        if( GetAsyncKeyState('W') & 0x8000f )
            g_camera.pitch(1.0f * time_delta);
        if( GetAsyncKeyState('S') & 0x8000f )
            g_camera.pitch(-1.0f * time_delta);   
        // update the view matrix representing the camera's new position/orientation
        D3DXMATRIX view_matrix;
        g_camera.get_view_matrix(&view_matrix);
        g_device->SetTransform(D3DTS_VIEW, &view_matrix);   
        g_exploder->update(time_delta);
        if(g_exploder->is_dead())
            g_exploder->reset();
        // render now
        g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0);
        g_device->BeginScene();
        D3DXMATRIX identity_matrix;
        D3DXMatrixIdentity(&identity_matrix);
        g_device->SetTransform(D3DTS_WORLD, &identity_matrix);
        draw_basic_scene(g_device, 1.0f);
        // order important, render firework last.
            g_device->SetTransform(D3DTS_WORLD, &identity_matrix);
        g_exploder->render();
        g_device->EndScene();
        g_device->resent(NULL, NULL, NULL, NULL);
        return true;
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM word_param, LPARAM long_param)
    {
        switch(msg)
        {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        case WM_KEYDOWN:
            if(word_param == VK_ESCAPE)
                DestroyWindow(hwnd);
            break;
        }
        return DefWindowProc(hwnd, msg, word_param, long_param);
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    int WINAPI WinMain(HINSTANCE inst, HINSTANCE, PSTR cmd_line, int cmd_show)
    {
        if(! init_d3d(inst, WIDTH, HEIGHT, true, D3DDEVTYPE_HAL, &g_device))
        {
            MessageBox(NULL, "init_d3d() - failed.", 0, MB_OK);
            return 0;
        }
        if(! setup())
        {
            MessageBox(NULL, "Steup() - failed.", 0, MB_OK);
            return 0;
        }
        enter_msg_loop(display);
        cleanup();
        g_device->Release();
        return 0;
    }
 楼主| 发表于 2008-11-30 08:43:17 | 显示全部楼层
14.3.3 例子程序:粒子枪
本例程实现了一个粒子枪系统,运行效果如图14.4所示:
下面是粒子枪系统的定义:
    class cParticleGun : public cParticleSystem
    {
    private:
        cCamera* m_camera;
    public:
        cParticleGun(cCamera* camera);
        virtual void reset_particle(sParticleAttribute* attr);
        virtual void update(float time_delta);
    };
构造函数需要提供一个照相机的位置点,这是因为系统需要知道照相机的位置及朝向,以决定在哪创建一个粒子。
    cParticleGun::cParticleGun(cCamera* camera)
    {
        m_camera        = camera;
        m_size            = 0.8f;
        m_vb_num        = 2048;
        m_vb_offset        = 0;
        m_vb_batch_num    = 512;
    }
reset_particle方法设置粒子的位置为当前照相机的位置,并且设置粒子运动的速度为照像机方向的100倍。这样,子弹将射向我们正在看的方向,粒子颜色为绿色。
    void cParticleGun::reset_particle(sParticleAttribute* attr)
    {
        attr->is_alive = true;
        D3DXVECTOR3 camera_dir;   
        m_camera->get_look(&camera_dir);
        attr->position      = m_camera->m_pos;    // change to camera position
            attr->position.y -= 1.0f;                // sightly below camera so it looks like we're carrying a gun
        // travels in the direction the camera is looking
            attr->velocity = camera_dir * 100.0f;
        attr->color        = D3DXCOLOR(0.0f, 1.0f, 0.0f, 1.0f);    // green
        attr->age        = 0.0f;
        attr->life_time = 1.0f;    // lives for 1 seconds
    }
update方法更新粒子的位置,并且杀死超过其生命周期的粒子,然后,我们搜索粒子列表删除已经死了的粒子。
    void cParticleGun::update(float time_delta)
    {
        for(list<sParticleAttribute>::iterator iter = m_particles.begin(); iter != m_particles.end(); iter++)
        {
            iter->position += iter->velocity * time_delta;
            iter->age       += time_delta;
            if(iter->age > iter->life_time)   
                iter->is_alive = false;    // kill
        }
        remove_dead_particles();
    }
执行程序:
    #include "d3dUtility.h"
    #include "camera.h"
    #include "ParticleSystem.h"
    #include <cstdlib>
    #include <ctime>
    #pragma warning(disable : 4100)
    const int WIDTH = 640;
    const int HEIGHT = 480;
    IDirect3DDevice9*    g_device;
    cParticleSystem*    g_gun;
    cCamera                g_camera(AIR_CRAFT);
        ////////////////////////////////////////////////////////////////////////////////////////////////////
    bool setup()
    {   
        srand((unsigned int)time(NULL));
        // create laser
        g_gun = new cParticleGun(&g_camera);
        g_gun->init(g_device, "flare_alpha.dds");
        // setup a basic scnen, the scene will be created the first time this function is called.
            draw_basic_scene(g_device, 1.0f);
        // set the projection matrix
        D3DXMATRIX proj;
        D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI/4.0f, (float)WIDTH/HEIGHT, 1.0f, 1000.0f);
        g_device->SetTransform(D3DTS_PROJECTION, &proj);
        return true;
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    void cleanup()
    {   
        delete g_gun;
        // pass NULL for the first parameter to instruct cleanup
        draw_basic_scene(NULL, 1.0f);
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    bool display(float time_delta)
    {
        // update the camera
        if( GetAsyncKeyState(VK_UP) & 0x8000f )
            g_camera.walk(4.0f * time_delta);
        if( GetAsyncKeyState(VK_DOWN) & 0x8000f )
            g_camera.walk(-4.0f * time_delta);
        if( GetAsyncKeyState(VK_LEFT) & 0x8000f )
            g_camera.yaw(-1.0f * time_delta);
        if( GetAsyncKeyState(VK_RIGHT) & 0x8000f )
            g_camera.yaw(1.0f * time_delta);
        if( GetAsyncKeyState('N') & 0x8000f )
            g_camera.strafe(-4.0f * time_delta);
        if( GetAsyncKeyState('M') & 0x8000f )
            g_camera.strafe(4.0f * time_delta);
        if( GetAsyncKeyState('W') & 0x8000f )
            g_camera.pitch(1.0f * time_delta);
        if( GetAsyncKeyState('S') & 0x8000f )
            g_camera.pitch(-1.0f * time_delta);   
        // update the view matrix representing the camera's new position/orientation
        D3DXMATRIX view_matrix;
        g_camera.get_view_matrix(&view_matrix);
        g_device->SetTransform(D3DTS_VIEW, &view_matrix);   
        g_gun->update(time_delta);
        // render now
        g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0);
        g_device->BeginScene();
        D3DXMATRIX identity_matrix;
        D3DXMatrixIdentity(&identity_matrix);
        g_device->SetTransform(D3DTS_WORLD, &identity_matrix);
        draw_basic_scene(g_device, 1.0f);
        // order important, render firework last.
            g_device->SetTransform(D3DTS_WORLD, &identity_matrix);
        g_gun->render();
        g_device->EndScene();
        g_device->resent(NULL, NULL, NULL, NULL);
        return true;
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM word_param, LPARAM long_param)
    {
        switch(msg)
        {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        case WM_KEYDOWN:
            if(word_param == VK_ESCAPE)
                DestroyWindow(hwnd);
            // Note: we use the message system over GetAsyncKeyState because GetAsyncKeyState was adding
            // particles too fast. The message system is slower and does not add them as fast.
            // This isn't the best solution, but works for illustration purposes.
            if(word_param == VK_SPACE)
                g_gun->add_particle();
            break;
        }
        return DefWindowProc(hwnd, msg, word_param, long_param);
    }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
    int WINAPI WinMain(HINSTANCE inst, HINSTANCE, PSTR cmd_line, int cmd_show)
    {
        if(! init_d3d(inst, WIDTH, HEIGHT, true, D3DDEVTYPE_HAL, &g_device))
        {
            MessageBox(NULL, "init_d3d() - failed.", 0, MB_OK);
            return 0;
        }
        if(! setup())
        {
            MessageBox(NULL, "Steup() - failed.", 0, MB_OK);
            return 0;
        }
        enter_msg_loop(display);
        cleanup();
        g_device->Release();
        return 0;
    }
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 06:49

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表