• 33. 粒子系统


    粒子系统保存和操纵一种称为粒子的元素。粒子系统的职责就是对粒子施加不同的作用力,为它们增加一种真实的外观。粒子系统的类型包括爆炸、烟、火、火花等,而其他类型的效果可由许多小的元素构成。粒子是一个小物体,包含多种属性,如位置、速度(方向)和质量。游戏中,粒子通常是添加了纹理的小正方形,通常面向摄象机。在实现点状sprite时已经介绍过这一点。点状sprite可以由粒子系统使用,从而有效地生成多种不同的效果。粒子系统示例如图6.5所示。

      通常,粒子相互之间独立作用,但却限制在粒子系统范围内。例如,所有的粒子也许从相同位置开始或从比较接近的位置开始,但它们都有自己的属性,将彼此分隔开。同样,通常在游戏中,粒子之间并不发生碰撞,通常那样会降低帧率。每个粒子可以有大量不同的属性,这包括前面提到的那些属性。此外,还有大小、能级、颜色、颜色增量(单位时间内颜色的变化量)等。对粒子系统的要求将确定系统中需要实现它的属性。多数粒子系统是通用系统,能处理不同类型的粒子。这是一种很有用的方法,但对规划和实现而言,这变得很复杂,对初学者更是如此。本章将介绍一个简单的粒子系统,它完全满足将要显示的效果。

        脚本系统可以让粒子系统变得非常复杂。在粒子系统中使用脚本,可以方便地为游戏创建、修改和添加粒子系统,但付出的代价是增加了实现的复杂性。如果想要开发一个灵活完整的、可用于创建任意效果的通用粒子系统,那就值得付出额外的努力开发一个基于脚本的通用粒子系统。

      本章将要创建的粒子系统是一种很简单的雨。本书将创建一个使用点状sprite、图像为雨滴纹理的系统,同时每个粒子还要在场景中下落,直到满足某个条件和粒子被销毁为止。实际上,粒子并未销毁,而是被重新初始化回到天空中而已,这样看上去雨像是连绵不断。雨粒子系统的完整演示程序可以在本书配套光盘的CHAPTER6文件夹中找到,名称为ParticleSystem。该演示程序由3个文件构成:main.cpp源文件、RainPS.h头文件以及RainPS.cpp源文件。

           每个粒子的定义可以有多个属性。在简单的演示程序中只要知道粒子的位置和速度即可。雨滴从天空中的某个随机位置开始,该位置由粒子系统的位置、区域宽度、高度和深度确定。在生成一个新粒子时,它的位置随机分布在系统区域内,并随机赋给它一个速度。一旦完成初始化工作,粒子系统就会更新环境中所有粒子的位置。每一帧都是如此,更新工作持续到程序退出为止。

    粒子系统每次将创建多达1000个新的粒子。系统可以存储的最大粒子数是3000。这里有两个类。一个针对粒子系统。粒子只需要跟踪雨滴的位置和速度即可。粒子系统将跟踪一列粒子,这些粒子的位置、最大的粒子数,当前正在使用的粒子总数,时间计数器,以及根据效果的宽度、高度和深度得到的系统效果区域。

           粒子系统除了构造函数之外,还包括4个函数。这4个函数分别是Initialize()、Shutdown()、Update()和CreateParticle()。Initialize()函数初始化整个粒子系统,只需要在程序运行时调用一次。Shutdown()函数删除粒子链表,这样在使用完系统时就可以释放动态内存。Update()函数结尾处用于更新场景中所有现有粒子的位置。如果需要创建新粒子,那么在Update()函数结尾处可以生成新粒子。CreateParticle()函数根据发送给它的数量生成一批新粒子。CreateParticle()函数只是简单地将新生成的粒子添加到链表末端。一旦链表满了,那么就直到有一些粒子被销毁时才可以添加新粒子。如果粒子超出了粒子系统的高度,那么就销毁粒子。由于粒子系统要比产生粒子的位置低,因此一旦雨滴低于系统高度,就可以知道粒子被销毁了。粒子和粒子系统的声明如程序清单6.8所示。

    RainPS.h

    #ifndef _RAIN_PS_H_
    #define _RAIN_PS_H_


    // Max number of new particles.
    #define UPDATE_AMT 1000


    // 粒子
    class CParticle
    {
    public:
    CParticle()
    {
    m_pos[
    0] = 0; m_pos[1] = 0; m_pos[2] = 0;
    m_vel[
    0] = 0; m_vel[1] = 0; m_vel[2] = 0;
    }

    // 粒子的位置
    float m_pos[3];
    // 粒子的速度
    float m_vel[3];
    };


    // 粒子系统
    class CRainPS
    {
    public:
    // @vX, vY, vZ 赋值给float m_velocity[3]
    // @dX, dY, dZ 赋值给float m_velDelta[3]
    // 将vX,vZ,dX,dZ设置成0,使粒子只沿着Y轴运动,在x,z轴没有运动
    CRainPS(float vX = 0, float vY = -4.0, float vZ = 0,
    float dX = 0, float dY = 1.0, float dZ = 0);

    // Init system.
    // @x,y,z 赋值给float m_pos[3]
    // @maxP 最大粒子数目
    // @w,h,d 粒子系统效果区域的宽度、高度和深度
    bool Initialize(float x = 0, float y = -3, float z = 0,
    int maxP = 3000, float w = 2.0, float h = 3.0, float d = 2.0);

    // Update all particles based on time.
    // @scalar 时间参数
    void Update(float scalar);

    /*
    CreateParticle()函数以函数准备创建的粒子总数为参数。该函数首先循环想要创建
    的粒子数。对每个循环而言,都在链表末端增加一个新的粒子,并将粒子总数加1。
    如果到了链表末端,就跳出循环,以避免超出数组边界。初始化新粒子时,要用区域中
    的随机数设置粒子的位置和速度。区域是由粒子系统的位置以及效果的宽度、高度和深
    度确定的。用0~1之间的随机数乘上面积就可以确保新粒子在该区域中。
    */
    // 创建amount个粒子
    // @amount 要创建的粒子的数目
    void CreateParticle(int amount);

    // Release all resources.
    void Shutdown();

    // Width, height, and depth of the area.
    float m_width;
    float m_height;
    float m_depth;

    // 用于初始化粒子位置的初始值
    float m_pos[3];

    // 存储粒子的列表list
    CParticle *m_particles;

    // 最大粒子数目
    int m_maxParticles;

    // 当前粒子数目
    int m_particleCount;

    // 时间计数器
    float m_totalTime;

    // 用于初始化粒子速度的初始值
    float m_velocity[3];
    // 用于初始化粒子速度的系数值
    float m_velDelta[3];
    };

    #endif

      RainPS.cpp

    #include<math.h>
    #include
    <stdlib.h>
    #include
    "RainPS.h"

    // 得到一个绝对值小于1的浮点数
    #define GET_RANDOM ( ( (float)rand() - (float)rand() ) / RAND_MAX)

    CRainPS::CRainPS(
    float vX, float vY, float vZ, float dX, float dY, float dZ)
    {
    m_width
    = 0; m_height = 0; m_depth = 0;
    m_particles
    = 0; m_maxParticles = 0;
    m_particleCount
    = 0; m_totalTime = 0;

    m_pos[
    0] = m_pos[1] = m_pos[2] = 0;

    m_velocity[
    0] = vX;
    m_velocity[
    1] = vY;
    m_velocity[
    2] = vZ;

    m_velDelta[
    0] = dX;
    m_velDelta[
    1] = dY;
    m_velDelta[
    2] = dZ;
    }


    bool CRainPS::Initialize(float x, float y, float z, int maxP,
    float w, float h, float d)
    {
    // Release all previous data.
    Shutdown();

    m_width
    = w; m_height = h; m_depth = d;
    m_particles
    = NULL;
    m_maxParticles
    = maxP;
    m_particleCount
    = 0;
    m_totalTime
    = 0;
    m_pos[
    0] = x; m_pos[1] = y; m_pos[2] = z;

    // Error checking.
    if(m_maxParticles <= 0) m_maxParticles = 1;

    // Allocate space for the particles.
    m_particles = new CParticle[m_maxParticles];
    if(!m_particles) return false;

    return true;
    }


    void CRainPS::CreateParticle(int amount)
    {
    // Simply loop through and create the amount of particles.
    for(int i = 0; i < amount; i++)
    {
    if(m_particleCount >= m_maxParticles) break;

    // Create a particle at the next free slot.
    m_particles[m_particleCount].m_pos[0] = m_pos[0] + GET_RANDOM * m_width;
    m_particles[m_particleCount].m_pos[
    1] = m_height + GET_RANDOM * m_height;
    m_particles[m_particleCount].m_pos[
    2] = m_pos[2] + GET_RANDOM * m_depth;

    m_particles[m_particleCount].m_vel[
    0] = m_velocity[0] + GET_RANDOM * m_velDelta[0];
    m_particles[m_particleCount].m_vel[
    1] = m_velocity[1] + GET_RANDOM * m_velDelta[1];
    m_particles[m_particleCount].m_vel[
    2] = m_velocity[2] + GET_RANDOM * m_velDelta[2];

    m_particleCount
    ++;
    }
    }


    void CRainPS::Update(float scalar)
    {
    int numParticles = 0;

    // Loop through and update.
    for(int i = 0; i < m_particleCount;)
    {
    m_particles[i].m_pos[
    0] += m_particles[i].m_vel[0] * scalar;
    m_particles[i].m_pos[
    1] += m_particles[i].m_vel[1] * scalar;
    m_particles[i].m_pos[
    2] += m_particles[i].m_vel[2] * scalar;

    // If particle goes past min height, destroy.
    // 因为雨滴的下落只改变Y值
    if(m_particles[i].m_pos[1] <= m_pos[1])
    {
    m_particleCount
    --;
    m_particles[i]
    = m_particles[m_particleCount];
    }
    else
    i
    ++;
    }

    // Calculate how many new particles we should create.
    m_totalTime += scalar;
    numParticles
    = (int)(UPDATE_AMT * m_totalTime);
    if(m_totalTime > 1) m_totalTime = 0;

    // Create them.
    CreateParticle(numParticles);
    }


    void CRainPS::Shutdown()
    {
    if(m_particles)
    delete[] m_particles;

    m_particles
    = NULL;
    m_particleCount
    = 0;
    m_totalTime
    = 0;
    }

      main.cpp

    #include<d3d9.h>
    #include
    <d3dx9.h>
    #include
    "RainPS.h"

    #define WINDOW_CLASS "UGPDX"
    #define WINDOW_NAME "Particle System"
    #define WINDOW_WIDTH 640
    #define WINDOW_HEIGHT 480

    // Function Prototypes...
    bool InitializeD3D(HWND hWnd, bool fullscreen);
    bool InitializeObjects();
    void RenderScene();
    void Shutdown();


    // Direct3D object and device.
    LPDIRECT3D9 g_D3D = NULL;
    LPDIRECT3DDEVICE9 g_D3DDevice
    = NULL;

    // Matrices.
    D3DXMATRIX g_projection;

    // Vertex buffer to hold the geometry.
    LPDIRECT3DVERTEXBUFFER9 g_VertexBuffer = NULL;

    // Holds a texture image.
    LPDIRECT3DTEXTURE9 g_Texture = NULL;


    // Used for point sprites.
    inline unsigned long FtoDW(float val) { return *((unsigned long*)&val); }

    // Particle system.
    CRainPS g_rainPS(0, -4, 0, 0, 1, 0);


    LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
    switch(msg)
    {
    case WM_DESTROY:
    PostQuitMessage(
    0);
    return 0;
    break;

    case WM_KEYUP:
    if(wParam == VK_ESCAPE) PostQuitMessage(0);
    break;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
    }


    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE prevhInst, LPSTR cmdLine, int show)
    {
    // Register the window class
    WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
    GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
    WINDOW_CLASS, NULL };
    RegisterClassEx(
    &wc);

    // Create the application's window
    HWND hWnd = CreateWindow(WINDOW_CLASS, WINDOW_NAME, WS_OVERLAPPEDWINDOW,
    100, 100, WINDOW_WIDTH, WINDOW_HEIGHT,
    GetDesktopWindow(), NULL, wc.hInstance, NULL);

    // Initialize Direct3D
    if(InitializeD3D(hWnd, false))
    {
    // Show the window
    ShowWindow(hWnd, SW_SHOWDEFAULT);
    UpdateWindow(hWnd);

    // Enter the message loop
    MSG msg;
    ZeroMemory(
    &msg, sizeof(msg));

    while(msg.message != WM_QUIT)
    {
    if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
    {
    TranslateMessage(
    &msg);
    DispatchMessage(
    &msg);
    }
    else
    RenderScene();
    }
    }

    // Release any and all resources.
    Shutdown();

    // Unregister our window.
    UnregisterClass(WINDOW_CLASS, wc.hInstance);
    return 0;
    }


    bool InitializeD3D(HWND hWnd, bool fullscreen)
    {
    D3DDISPLAYMODE displayMode;

    // Create the D3D object.
    g_D3D = Direct3DCreate9(D3D_SDK_VERSION);
    if(g_D3D == NULL) return false;

    // Get the desktop display mode.
    if(FAILED(g_D3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &displayMode)))
    return false;

    // Set up the structure used to create the D3DDevice
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(
    &d3dpp, sizeof(d3dpp));

    if(fullscreen)
    {
    d3dpp.Windowed
    = FALSE;
    d3dpp.BackBufferWidth
    = WINDOW_WIDTH;
    d3dpp.BackBufferHeight
    = WINDOW_HEIGHT;
    }
    else
    d3dpp.Windowed
    = TRUE;
    d3dpp.SwapEffect
    = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat
    = displayMode.Format;

    // Create the D3DDevice
    if(FAILED(g_D3D->CreateDevice(D3DADAPTER_DEFAULT,
    D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING
    |
    D3DCREATE_PUREDEVICE,
    &d3dpp, &g_D3DDevice))) return false;

    // Initialize any objects we will be displaying.
    if(!InitializeObjects()) return false;

    return true;
    }


    bool InitializeObjects()
    {
    // Initialize particle system.
    g_rainPS.Initialize(0, -3, 0, 3000, 2, 3, 2);
    // 初始的时候这是一个很大的更新参数,创建3000个粒子
    g_rainPS.Update(10);

    // Create the vertex buffer. 创建3000个顶点 D3DFVF_XYZ | D3DUSAGE_POINTS
    // 每个粒子其实就是一个顶点结构
    // 因为将在RenderScene()函数中使用它。由于顶点缓存是动态的,因此可以动态设置,
    // 而无需考虑与静态缓存相关的性能问题
    if(FAILED(g_D3DDevice->CreateVertexBuffer(sizeof(CParticle) *
    3000, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY |
    D3DUSAGE_POINTS, D3DFVF_XYZ, D3DPOOL_DEFAULT,
    &g_VertexBuffer, NULL))) return false;


    // Load the texture image from file.
    if(D3DXCreateTextureFromFile(g_D3DDevice, "sprite.tga", &g_Texture) != D3D_OK) return false;

    // Set the image states to get a good quality image.
    g_D3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
    g_D3DDevice
    ->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

    // Set default rendering states.
    g_D3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
    g_D3DDevice
    ->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

    // Set the projection matrix.
    D3DXMatrixPerspectiveFovLH(&g_projection, 45.0f, WINDOW_WIDTH/WINDOW_HEIGHT, 0.1f, 1000.0f);

    g_D3DDevice
    ->SetTransform(D3DTS_PROJECTION, &g_projection);

    return true;
    }


    void RenderScene()
    {
    // Update the particle system.
    g_rainPS.Update(0.2f);

    // 因为创建顶点缓存用的是动态缓存D3DUSAGE_DYNAMIC,所以可以在每一帧渲染之前改变顶点缓存的数据
    // Buffer pointer and size of particles we are copying.
    void *p;
    int num = sizeof(CParticle) * g_rainPS.m_particleCount;

    // Copy particles in the vertex buffer.
    if(FAILED(g_VertexBuffer->Lock(0, num, (void**)&p, 0))) return;
    memcpy(p, g_rainPS.m_particles, num);
    g_VertexBuffer
    ->Unlock();


    // Clear the backbuffer.
    g_D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0);

    // Begin the scene. Start rendering.
    g_D3DDevice->BeginScene();

    // Draw square.
    g_D3DDevice->SetTexture(0, g_Texture);

    g_D3DDevice
    ->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
    g_D3DDevice
    ->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);

    // 当启用sprite时,不用为顶点指定纹理坐标,系统会自动帮你处理
    g_D3DDevice->SetRenderState(D3DRS_POINTSPRITEENABLE, TRUE);
    g_D3DDevice
    ->SetRenderState(D3DRS_POINTSCALEENABLE, TRUE);
    g_D3DDevice
    ->SetRenderState(D3DRS_POINTSIZE, FtoDW(0.02f));
    g_D3DDevice
    ->SetRenderState(D3DRS_POINTSIZE_MIN, FtoDW(0.02f));
    g_D3DDevice
    ->SetRenderState(D3DRS_POINTSCALE_A, FtoDW(0.0f));
    g_D3DDevice
    ->SetRenderState(D3DRS_POINTSCALE_B, FtoDW(0.0f));
    g_D3DDevice
    ->SetRenderState(D3DRS_POINTSCALE_C, FtoDW(1.0f));

    g_D3DDevice
    ->SetFVF(D3DFVF_XYZ);
    g_D3DDevice
    ->SetStreamSource(0, g_VertexBuffer, 0, sizeof(CParticle));
    g_D3DDevice
    ->DrawPrimitive(D3DPT_POINTLIST, 0, g_rainPS.m_particleCount);

    g_D3DDevice
    ->SetRenderState(D3DRS_POINTSPRITEENABLE, FALSE);
    g_D3DDevice
    ->SetRenderState(D3DRS_POINTSCALEENABLE, FALSE);
    g_D3DDevice
    ->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);

    // End the scene. Stop rendering.
    g_D3DDevice->EndScene();

    // Display the scene.
    g_D3DDevice->Present(NULL, NULL, NULL, NULL);
    }


    void Shutdown()
    {
    if(g_D3DDevice != NULL)
    g_D3DDevice
    ->Release();
    g_D3DDevice
    = NULL;

    if(g_D3D != NULL)
    g_D3D
    ->Release();
    g_D3D
    = NULL;

    //因为粒子系统使用了动态内存,所以使用完粒子系统后,必须确保释放动态内存
    if(g_VertexBuffer != NULL)
    g_VertexBuffer
    ->Release();
    g_VertexBuffer
    = NULL;

    if(g_Texture != NULL)
    g_Texture
    ->Release();
    g_Texture
    = NULL;

    g_rainPS.Shutdown();
    }

    /*
    因为粒子系统使用了动态内存,所以使用完粒子系统后,必须确保释放动态内存。
    RenderScene()函数使用动态的Direct3D顶点缓存,所以可以使用3D场景中现有的当前粒子设置该顶点缓存。
    当使用点状sprite时,使用系统释放的粒子当前总数,这样只有那些sprite可以被渲染,
    而不是所有不存在的粒子都要被渲染。
    */

      

  • 相关阅读:
    20165328《信息安全系统设计基础》实验二固件程序设计实验报告
    20165328《信息安全系统设计基础》第六周学习总结
    2018-2019-1 20165305 20165319 20165328 实验一 开发环境的熟悉
    2018-2019-1 20165328《信息安全系统设计基础》第四周学习总结
    2018-2019-1 20165328 《信息安全系统设计基础》第三周学习总结及实验报告
    20165328《信息安全系统设计基础》第一周总结
    20165358课程总结
    20165328 实验五《网络安全编程》实验报告
    20165218 2018-2019-1 《信息安全系统》第八章学习总结
    2018-2019-1 20165218 实验三 实时系统
  • 原文地址:https://www.cnblogs.com/kex1n/p/2167956.html
Copyright © 2020-2023  润新知