天行健 君子当自强而不息

2D和3D图形引擎的混合(2)

 

本篇是2D和3D图形引擎的混合(1)的续篇。

 

在游戏如最终幻想和寄生前夜(两者都是由Square公司出品)中,可以在欣赏三维模型的同时,享受到非常精美的预先渲染好的背景图像。将二维和三维图形混合在一起,一直以来都是游戏公司高度保密的技术,现在可以 揭开谜底一看究竟。

如何从一个二维图像中获取三维的深度信息呢?有几种方法可以实现三维物体在二维图像中的背景幕效果。

(1)在一个三维建模工具中创建预先进行渲染的背景幕,例如Caligari公司的gamespace lightdiscreet公司的3D Studio Max,并将图像与包含每个像素z值的深度缓冲区一起保存。对于游戏中的每一帧,将图像的深度缓冲区拷贝到背景深度缓冲区中,并继续绘制三维物体。

(2)在层次中创建背景幕,从底层开始,一个接一个地绘制每个图像,并将三维角色绘制到适当的层次上,这样就可以使用后面的层次覆盖较低层次的部分内容(以及三维物体)。

(3)使用一个非常详尽的预先渲染好的背景幕,以及一个在三维建模软件中渲染场景的简化的网格模型。使用网格模型以呈现z数值并进行碰撞检测,三维物体可以使用z缓冲区负责绘制正确的深度。

我们采用第三种方法来绘制。

 

二维背景幕的处理

用一个三维建模软件开发二维的背景幕,例如gamespace light(而不是使用一个绘图程序,因为需要从建模软件中获取多边形的数据),下图显示了一个简单的网格模型以及最终的渲染效果:

一旦场景被渲染好,就需要将它作为一个位图保存到磁盘上,那个位图文件需要被切分成较小的纹理,如下图所示,这个背景幕被切割成多个Direct3D可以处理的纹理,比如背景幕图像为640 x 480,所以纹理将为256 x 256(对于块1245),以及128 x 256(对于块36)。

 

场景网格模型的处理

详尽的层次看起来非常不错,现在想要包含一些三维物体到它里面。首先,需要构造一个简化的场景,可使用两种方法,包括填充每一帧的深度缓冲区,以便三维物体能够正确地与二维的背景幕进行混合;作为运动物体的碰撞网格模型。

因为网格模型必须在每帧中被渲染出来,以便创建场景中的z缓存,使用越少的多边形当然越好,然而必须使用足够的多边形以确保三维物体能够被正确地混合,如下图所示,它显示了最终渲染好的图像,实际的场景网格模型,以及简化的场景网格模型。

当处理一个简化的网格模型时,仅使用了两种材质(没有纹理)。第一种材质代表了实际绘制到背景幕上的多边形区域,而第二种材质隐藏了在交集测试中所使用的多边形,对于第二种材质,使用的alpha的数值为0.0(意味着它是不可见的,不会被实际渲染)。

应该使用正确的多边形数量去渲染场景。如果有太多的多边形,引擎将变得非常缓慢。如果多边形太少,将会在玩游戏时得到贴图错误的信息。请这样思考一下:一个使用了500个多边形的球形网格模型,很明显比一个简化的网格模型复杂许多,在一个简化的网格模型里,仅需要足够的多边形去表示球体,并确保它在进行渲染时覆盖相同的屏幕区域。

下图演示了在创建简化网格模型时常出现的一个错误,那就是使用了太少的多边形。

为了简化网格模型中多边形的数量,切割掉那些看不到的表面或者在交集测试中所使用的表面,同时仅绘制那些实际覆盖三维物体的多边形 。举个例子,如果在背景幕中有一个盒子,而玩家角色从不会接近它,那么在简化的网格模型中就不用绘制它。

对于本例中的背景幕,仅需要绘制如下的简化网格模型:

 

场景的渲染

现在将完成最后一步,以确保背景幕图像能够包含深度信息(通过简化的网格模型)。如果加载了背景幕图像和简化的网格模型,就可以很容易地渲染游戏中的每帧,通过使用如下步骤:

(1)将z缓冲区清除为1.0(并确保z缓冲区被启动)。

(2)渲染简化的网格模型(因而填充场景的z缓冲区),跳过那些数值为0.0的多边形(它们是不可见的)。

(3)禁用z缓冲区。

(4)使用ID3DXSprite位块传送背景幕纹理。

(5)启动z缓冲区。


绝大多数新近的显卡都允许处理
1024 x 1024像素大小的纹理,这意味着可以将整个背景幕图像加载到内存中,而不需要将它切割成6个小纹理。

在绘制好背景幕后,剩下的就是将三维物体(网格模型)绘制到场景中,因为Z缓冲区包含了所有与每个像素相关的深度数值。请不要犹豫,随心所欲绘制角色、物体、甚至是增强背景的图像。

 

下载源码和工程

 

代码:

/************************************************************************************
PURPOSE:
     3D in 2D test.
************************************************************************************/


#include "core_common.h"
#include "core_framework.h"
#include "core_graphics.h"
#include "core_input.h"

#define ANIM_NONE   -1
#define ANIM_WALK    1
#define ANIM_IDLE    2

const float g_angles[13] = {
       0.0f, 4.71f, 0.0f, 5.495f, 1.57f, 0.0f,  
       0.785f, 0.0f,  3.14f, 3.925f, 0.0f, 0.0f, 2.355f 
};

class APP : public FRAMEWORK
{
private:  
    CAMERA          m_camera;
       
    INPUT           m_input;
    INPUT_DEVICE    m_keyboard; 

    TEXTURE         m_background[6];

    
// the simplified scene mesh and object
    MESH            m_scene_mesh;    
    OBJECT          m_scene;

    
// 3D meshes and objects
    MESH            m_monster_mesh;
    OBJECT          m_monster;
    ANIMATION       m_monster_anim;
    
    
static const float m_above_floor;

public:
    BOOL init()
    {
        
if(! create_display(g_hwnd, get_client_width(g_hwnd), get_client_height(g_hwnd), 16, TRUE, TRUE))
            
return FALSE;
        
        set_perspective(0.6021124f, 1.3333f, 1.0f, 10000.0f);                

        ShowCursor(TRUE);                          

        
// initialize input and input device
        m_input.create(g_hwnd, get_window_inst());
        m_keyboard.create_keyboard(&m_input);     

        
// load the backdrop textures
        for(short i = 0; i < 6; i++)
        {
            
char filename[81];
    
            sprintf(filename, "..\\data\\scene%u.bmp", i+1);

            
if(! m_background[i].load(filename, 0, D3DFMT_UNKNOWN))
                
return FALSE;
        }

        
// load the scene mesh and configure object

        
if(! m_scene_mesh.load("..\\Data\\Scene.x", ".\\"))
            
return FALSE;

        m_scene.create(&m_scene_mesh);

        
// load the monster mesh and setup monster object

        
if(! m_monster_mesh.load("..\\data\\yodan.x", "..\\data\\"))
            
return FALSE;

        m_monster_anim.load("..\\data\\yodan.x", &m_monster_mesh);
        m_monster_anim.set_loop(TRUE, "Idle");
        m_monster_anim.set_loop(TRUE, "Walk");

        m_monster.create(&m_monster_mesh);
        
        
// position the camera for the scene        

        m_camera.move(0.0f, 200.0f, -650.0f);
        m_camera.rotate(0.348888f, 0.0f, 0.0f);

        g_d3d_device->SetTransform(D3DTS_VIEW, m_camera.get_view_matrix());        
        
        
return TRUE;
    }

    BOOL frame()
    {
        
static DWORD time_begin = timeGetTime();       

        
// calculate elapsed time (plus speed boost)
        DWORD time_end = timeGetTime();
        
ulong time_elapsed = time_end - time_begin;
        time_begin = time_end;

        
// read keyboard data        
        m_keyboard.read();

        
// process input and update everything, ESC quits program.
        if(m_keyboard.get_key_state(KEY_ESC))
            
return FALSE;
        
        
// process movement

        
long dir = 0;

        
if(m_keyboard.get_key_state(KEY_UP))
            dir |= 1;

        
if(m_keyboard.get_key_state(KEY_RIGHT))
            dir |= 2;

        
if(m_keyboard.get_key_state(KEY_DOWN))
            dir |= 4;

        
if(m_keyboard.get_key_state(KEY_LEFT))
            dir |= 8;

        
float x_move, z_move;

        x_move = z_move = 0.0f;

        
if(dir)
        {
            m_monster.rotate(0.0f, g_angles[dir], 0.0f);

            x_move =  cos(g_angles[dir]) * (time_elapsed * 0.25f);
            z_move = -sin(g_angles[dir]) * (time_elapsed * 0.25f);
        }

        
float x_pos, y_pos, z_pos;

        
// get monster coordinates in local variables (make it easier)
        x_pos = m_monster.get_x_pos();
        y_pos = m_monster.get_y_pos();
        z_pos = m_monster.get_z_pos();

        D3DXMESH_PTR d3d_mesh = m_scene_mesh.get_root_mesh_info()->m_d3d_mesh;

        
// check for collision in movement (4 points).
        //
        // I hard-coded the bounding size of the object (25 radius) and added ability to climb up at mose 32 units.
        for(long i = 0; i < 4; i++)
        {
            
float x_add[4] = {  0.0f, 25.0f,    0.0f,  -25.0f };
            
float z_add[4] = { 25.0f,  0.0f,  -25.0f,    0.0f };

            
float dist;            

            
if(is_ray_intersect_mesh(d3d_mesh,
                                     x_pos + x_add[i],           y_pos + m_above_floor, z_pos + z_add[i],
                                     x_pos + x_add[i] + x_move,  y_pos + m_above_floor, z_pos + z_add[i] + z_move,
                                     &dist))
            {
                
// clear out movement and break
                x_move = z_move = 0.0f;
                
break;
            }
        }
       
        
// fix height of monster
        y_pos = closest_height_below_object(d3d_mesh, x_pos, y_pos + m_above_floor, z_pos);

        
// move monster and set new animations as needed
        m_monster.move(x_pos + x_move, y_pos, z_pos + z_move);        

        
static short last_anim = ANIM_NONE;

        
if(!float_equal(x_move, 0.0f) || !float_equal(z_move, 0.0f))
        {
            
if(last_anim != ANIM_WALK)
            {
                last_anim = ANIM_WALK;
                m_monster.set_anim_info_set(&m_monster_anim, "Walk", time_end / 20);
            }
        }
        
else
        {
            
if(last_anim != ANIM_IDLE)
            {
                last_anim = ANIM_IDLE;
                m_monster.set_anim_info_set(&m_monster_anim, "Idle", time_end / 20);
            }
        }

        
// update monster animations
        m_monster.update_anim_info_set(time_end / 20, TRUE);       
        
        
// render everything
        clear_display_zbuffer(1.0f);                       

        
// begin render now
        if(SUCCEEDED(g_d3d_device->BeginScene()))        
        {
            
// render the level mesh
            g_d3d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
            m_scene.render();

            
// draw the backdrop (composed of six textures)

            g_d3d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);

            g_d3d_sprite->Begin(0);

            
for(int row = 0; row < 2; row++)
            {
                
for(int col = 0; col < 3; col++)
                    m_background[row * 3 + col].draw(col * 256, row * 256, 0, 0, 0, 0, 1.0f, 1.0f, 0xFFFFFFFF);
            }

            g_d3d_sprite->End();

            
// draw the 3D monster
            
            g_d3d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

            m_monster.render();

            g_d3d_device->EndScene();            
        }

        present_display();

        
return TRUE;
    }

    BOOL shutdown()
    {
        destroy_display();

        
return TRUE;
    }
};

const float APP::m_above_floor = 32.0f;

int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    DWORD client_width  = 640;
    DWORD client_height = 480;
    DWORD x_pos = (get_screen_width()  - client_width) / 2;
    DWORD y_pos = (get_screen_height() - client_height) / 4;

    DWORD window_style = WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU;

    
if(! build_window(inst, "3d_in_2d_class", "3D in 2D Test", window_style,
                      x_pos, y_pos, client_width, client_height))
    {
        
return -1;
    }

    APP app;
    app.run();

    
return 0;
}

 

截图:


posted on 2007-10-28 01:09 lovedday 阅读(1411) 评论(5)  编辑 收藏 引用

评论

# re: 2D和3D图形引擎的混合(2) 2007-10-28 23:28 neoragex2002

有点意思。不过你这个场景选取得不太好,带粒子效果的,很容易看出来是烘培出来的背景。  回复  更多评论   

# re: 2D和3D图形引擎的混合(2) 2007-10-28 23:32 neoragex2002

至于简化场景用的导航网格这种技法,至少在2000年前就有人用了,heh  回复  更多评论   

# re: 2D和3D图形引擎的混合(2) 2007-10-29 00:11 lovedday

谢谢,你的博客写的不错,有空我会多去看看,顺便请教请教。 ^_^  回复  更多评论   

# re: 2D和3D图形引擎的混合(2) 2007-11-01 20:30 风在奔跑

本来就是烘培出来的背景吧?  回复  更多评论   

# re: 2D和3D图形引擎的混合(2) 2010-06-02 15:45 guest

@风在奔跑
下载的代码中data目录没有文件啊  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论