音乐就是一系列的音符,这些音符在不同的时间用不同的幅度被播放或者停止。有非常多的指令被用来播放音乐,但是这些指令的操作基本相同,都在使用各种各样不同的音符。在计算机上进行作曲,实际上是存储了很多组音乐,回放时由音频硬件将这些音符播放出来。

Midi格式(文件扩展名是.MID)是存储数字音乐的标准格式。

DirectMusic 音乐片段(music segments)使用.SGT文件扩展名,其他的相关文件包括乐队文件(band file .BND),这种文件里面包含乐器信息;弦映射表文件(chordmaps file .CDM)包含在回放时修改音乐的和弦指令;样式文件(styles file .STY)包含回放样式信息;模板文件(templates file .TPL)包含创造音乐片段的模板。

Midi是一种非常强大的音乐格式,惟一的不利因素是音乐品质依赖于音乐合成器的性能,因为Midi 仅仅记录了音符,其播放的品质由播放音乐的软硬件决定。MP3文件(文件后缀为.MP3)是一种类似于波表文件的文件格式,但是MP3文件和WAV文件最大的区别在于MP3文件将声音压缩到了最小的程度,但是音质却基本不变。可以用DirectShow组件播放MP3文件,DirectShow组件是一个非常强大的多媒体组件,用DirectShow几乎可以播放任何媒体文件,包括声音和音频文件,部分声音文件我们只能用DirectShow播放。

Direct Audio是一个复合组件,它由DirectSound和DirectMusic两个组件组成,DirectMusic在DirectX8中得到了巨大的增强,但是DirectSound基本保持原有的状态。DirectSound是主要的数字声音回放组件。DirectMusic处理所有的乐曲格式,包括MIDI、DirectMusic本地格式文件和波表文件。DirectMusic处理完之后将它们送入DirectSound中做其他处理,这意味着回放MIDI的时候可以使用数字化的乐器。

使用DirectSound

使用时需要创建一个和声卡通讯的COM对象,用这个COM对象再创造一些独立的声音数据缓冲区(被称之为辅助音频缓冲区 secondary sound buffers)来存储音频数据。缓冲区中的这些数据在主混音缓存(称之为主音频缓存 primary sound buffer)中被混合,然后可以用指定的任何格式播放出来。回放格式通过采样频率、声道数、采样精度排列,可能的采样频率有8000HZ, 11025HZ,22050HZ和44100HZ(CD音质)。

对于声道数可以有两个选择:单通道的单声道声音和双通道的立体声声音。采样精度被限制在两种选择上:8位的低质量声音和16位的高保真声音。在没有修改的情况下,DirectSound主缓冲区的默认设置是22025HZ采样率、8位精度、立体声。在DirectSound中可以调整声音的播放速度(这同样会改变声音的音调),调整音量。循环播放等。甚至还可以在一个虚拟的3D环境中播放,以模拟一个实际环绕在周围的声音。

需要做的是将声音数据充满缓冲区,如果声音数据太大的话,必须创建流播放方法,加载声音数据中的一小块,当这一小块播放完毕以后,再加载另外的小块数据进缓冲区,一直持续这个过程,直到声音被处理完毕。在缓冲区中调整播放位置可以实现流式音频,当播放完成通知应用程序更新音频数据。这个通知更新的过程我们称之为“通告”。在同一时间被播放的缓存数目虽然没有限制,但是仍然需要保证缓冲区数目不要太多,因为每增加一个缓冲区,就要消耗很多内存和CPU资源。

在项目中使用DirectSound和DirectMusic,需要添加头文件dsound.h和dmsuic.h,并且需要链接DSound.lib到包含库中,添加DXGuid.lib库可以让DirectSound更容易使用。

以下是DirectSound COM接口:

IDirectSound8:DirectSound接口。
IDirectSoundBuffer8:主缓冲区和辅助缓冲区接口,保存数据并控制回放。
IDirectSoundNotify8:通知对象,通知应用程序指定播放位置已经达到。

IDirectSound8是主接口,用它来创建缓冲区(IDirectSoundBuffer8),然后用缓冲区接口创建通告接口(IDirectSoundNotify8),通告接口告诉应用程序指定的位置已经到达,通告接口在流化音频文件时非常有用。

初始化DirectSound

使用 DirectSound的第一步是创建IDirectSound8对象,IDirectSound8起到控制音频硬件设备的作用,可以通过 DirectSoundCreate8函数来创建。

The DirectSoundCreate8 function creates and initializes an object that supports the IDirectSound8 interface.
HRESULT DirectSoundCreate8(
LPCGUID lpcGuidDevice,
LPDIRECTSOUND8 * ppDS8,
LPUNKNOWN pUnkOuter
);

Parameters

lpcGuidDevice
Address of the GUID that identifies the sound device. The value of this parameter must be one of the GUIDs returned by DirectSoundEnumerate, or NULL for the default device, or one of the following values.

 

Value Description
DSDEVID_DefaultPlayback System-wide default audio playback device. Equivalent to NULL.
DSDEVID_DefaultVoicePlayback Default voice playback device.

ppDS8
Address of a variable to receive an IDirectSound8 interface pointer.
pUnkOuter
Address of the controlling object's IUnknown interface for COM aggregation. Must be NULL, because aggregation is not supported.

Return Values

If the function succeeds, it returns DS_OK. If it fails, the return value may be one of the following.


Return Code
DSERR_ALLOCATED
DSERR_INVALIDPARAM
DSERR_NOAGGREGATION
DSERR_NODRIVER
DSERR_OUTOFMEMORY

Remarks

The application must call the IDirectSound8::SetCooperativeLevel method immediately after creating a device object.

创建主音频缓冲区

用 IDirectSoundBuffer对象控制主音频缓冲区,创建主缓冲区不需要DirectX8的接口,因为这个接口从来没有改变。用来创建音频缓冲区的函数是IDirectSound8::CreateSoundBuffer。

The CreateSoundBuffer method creates a sound buffer object to manage audio samples.

HRESULT CreateSoundBuffer(
LPCDSBUFFERDESC pcDSBufferDesc,
LPDIRECTSOUNDBUFFER * ppDSBuffer,
LPUNKNOWN pUnkOuter
);

Parameters

pcDSBufferDesc
Address of a DSBUFFERDESC structure that describes the sound buffer to create.
ppDSBuffer
Address of a variable that receives the IDirectSoundBuffer interface of the new buffer object. Use QueryInterface to obtain IDirectSoundBuffer8. IDirectSoundBuffer8 is not available for the primary buffer.
pUnkOuter
Address of the controlling object's IUnknown interface for COM aggregation. Must be NULL.

Return Values

If the method succeeds, the return value is DS_OK, or DS_NO_VIRTUALIZATION if a requested 3D algorithm was not available and stereo panning was substituted. See the description of the guid3DAlgorithm member of DSBUFFERDESC. If the method fails, the return value may be one of the error values shown in the following table.


Return code
DSERR_ALLOCATED
DSERR_BADFORMAT
DSERR_BUFFERTOOSMALL
DSERR_CONTROLUNAVAIL
DSERR_DS8_REQUIRED
DSERR_INVALIDCALL
DSERR_INVALIDPARAM
DSERR_NOAGGREGATION
DSERR_OUTOFMEMORY
DSERR_UNINITIALIZED
DSERR_UNSUPPORTED

Remarks

DirectSound does not initialize the contents of the buffer, and the application cannot assume that it contains silence.

If an attempt is made to create a buffer with the DSBCAPS_LOCHARDWARE flag on a system where hardware acceleration is not available, the method fails with either DSERR_CONTROLUNAVAIL or DSERR_INVALIDCALL, depending on the operating system.


pcDSBufferDesc是一个指向DSBUFFERDESC结构的指针,保存所创建的缓冲区的信息。

The DSBUFFERDESC structure describes the characteristics of a new buffer object. It is used by the IDirectSound8::CreateSoundBuffer method and by the DirectSoundFullDuplexCreate8 function.

An earlier version of this structure, DSBUFFERDESC1, is maintained in Dsound.h for compatibility with DirectX 7 and earlier.

typedef struct DSBUFFERDESC {
DWORD dwSize;
DWORD dwFlags;
DWORD dwBufferBytes;
DWORD dwReserved;
LPWAVEFORMATEX lpwfxFormat;
GUID guid3DAlgorithm;
} DSBUFFERDESC;

Members

dwSize
Size of the structure, in bytes. This member must be initialized before the structure is used.
dwFlags
Flags specifying the capabilities of the buffer. See the dwFlags member of the DSBCAPS structure for a detailed listing of valid flags.
dwBufferBytes
Size of the new buffer, in bytes. This value must be 0 when creating a buffer with the DSBCAPS_PRIMARYBUFFER flag. For secondary buffers, the minimum and maximum sizes allowed are specified by DSBSIZE_MIN and DSBSIZE_MAX, defined in Dsound.h.
dwReserved
Reserved. Must be 0.
lpwfxFormat
Address of a WAVEFORMATEX or WAVEFORMATEXTENSIBLE structure specifying the waveform format for the buffer. This value must be NULL for primary buffers.
guid3DAlgorithm
Unique identifier of the two-speaker virtualization algorithm to be used by DirectSound3D hardware emulation. If DSBCAPS_CTRL3D is not set in dwFlags, this member must be GUID_NULL (DS3DALG_DEFAULT). The following algorithm identifiers are defined.

 

Value Description Availability
DS3DALG_DEFAULT DirectSound uses the default algorithm. In most cases this is DS3DALG_NO_VIRTUALIZATION. On WDM drivers, if the user has selected a surround sound speaker configuration in Control Panel, the sound is panned among the available directional speakers. Applies to software mixing only. Available on WDM or Vxd Drivers.
DS3DALG_NO_VIRTUALIZATION 3D output is mapped onto normal left and right stereo panning. At 90 degrees to the left, the sound is coming out of only the left speaker; at 90 degrees to the right, sound is coming out of only the right speaker. The vertical axis is ignored except for scaling of volume due to distance. Doppler shift and volume scaling are still applied, but the 3D filtering is not performed on this buffer. This is the most efficient software implementation, but provides no virtual 3D audio effect. When the DS3DALG_NO_VIRTUALIZATION algorithm is specified, HRTF processing will not be done. Because DS3DALG_NO_VIRTUALIZATION uses only normal stereo panning, a buffer created with this algorithm may be accelerated by a 2D hardware voice if no free 3D hardware voices are available. Applies to software mixing only. Available on WDM or Vxd Drivers.
DS3DALG_HRTF_FULL The 3D API is processed with the high quality 3D audio algorithm. This algorithm gives the highest quality 3D audio effect, but uses more CPU cycles. See Remarks. Applies to software mixing only. Available on Microsoft Windows 98 Second Edition and later operating systems when using WDM drivers.
DS3DALG_HRTF_LIGHT The 3D API is processed with the efficient 3D audio algorithm. This algorithm gives a good 3D audio effect, but uses fewer CPU cycles than DS3DALG_HRTF_FULL. Applies to software mixing only. Available on Windows 98 Second Edition and later operating systems when using WDM drivers.

需要设置的惟一一个值是dwFlags,这是一系列标志,用于决定缓冲区性能。
dwFlags
Flags that specify buffer-object capabilities. Use one or more of the values shown in the following table.

 

Value Description
DSBCAPS_CTRL3D The buffer has 3D control capability.
DSBCAPS_CTRLFREQUENCY The buffer has frequency control capability.
DSBCAPS_CTRLFX The buffer supports effects processing.
DSBCAPS_CTRLPAN The buffer has pan control capability.
DSBCAPS_CTRLVOLUME The buffer has volume control capability.
DSBCAPS_CTRLPOSITIONNOTIFY The buffer has position notification capability. See the Remarks for DSCBUFFERDESC.
DSBCAPS_GETCURRENTPOSITION2 The buffer uses the new behavior of the play cursor when IDirectSoundBuffer8::GetCurrentPosition is called. In the first version of DirectSound, the play cursor was significantly ahead of the actual playing sound on emulated sound cards; it was directly behind the write cursor. Now, if the DSBCAPS_GETCURRENTPOSITION2 flag is specified, the application can get a more accurate play cursor. If this flag is not specified, the old behavior is preserved for compatibility. This flag affects only emulated devices; if a DirectSound driver is present, the play cursor is accurate for DirectSound in all versions of DirectX.
DSBCAPS_GLOBALFOCUS The buffer is a global sound buffer. With this flag set, an application using DirectSound can continue to play its buffers if the user switches focus to another application, even if the new application uses DirectSound. The one exception is if you switch focus to a DirectSound application that uses the DSSCL_WRITEPRIMARY flag for its cooperative level. In this case, the global sounds from other applications will not be audible.
DSBCAPS_LOCDEFER The buffer can be assigned to a hardware or software resource at play time, or when IDirectSoundBuffer8::AcquireResources is called.
DSBCAPS_LOCHARDWARE The buffer uses hardware mixing.
DSBCAPS_LOCSOFTWARE The buffer is in software memory and uses software mixing.
DSBCAPS_MUTE3DATMAXDISTANCE The sound is reduced to silence at the maximum distance. The buffer will stop playing when the maximum distance is exceeded, so that processor time is not wasted. Applies only to software buffers.
DSBCAPS_PRIMARYBUFFER The buffer is a primary buffer.
DSBCAPS_STATIC The buffer is in on-board hardware memory.
DSBCAPS_STICKYFOCUS The buffer has sticky focus. If the user switches to another application not using DirectSound, the buffer is still audible. However, if the user switches to another DirectSound application, the buffer is muted.
DSBCAPS_TRUEPLAYPOSITION Force IDirectSoundBuffer8::GetCurrentPosition to return the buffer's true play position. This flag is only valid in Windows Vista.
以下是创建声音缓冲区的代码:

    // setup the DSBUFFERDESC structure
    DSBUFFERDESC ds_buffer_desc;

    
// zero out strcutre
    ZeroMemory(&ds_buffer_desc, sizeof(DSBUFFERDESC));

    ds_buffer_desc.dwSize        
= sizeof(DSBUFFERDESC); 
    ds_buffer_desc.dwFlags       
= DSBCAPS_CTRLVOLUME;
    ds_buffer_desc.dwBufferBytes 
= wave_format.nAvgBytesPerSec * 2;  // 2 seconds
    ds_buffer_desc.lpwfxFormat   = &wave_format;

    
// create the fist version object
    if(FAILED(g_ds->CreateSoundBuffer(&ds_buffer_desc, &ds, NULL)))
    {
        
// error ocuurred
        MessageBox(NULL, "Unable to create sound buffer""Error", MB_OK);
    }

设置格式


对于格式,有一系列的选择,但是建议在11025HZ、16位、单通道;22050HZ、16位、单通道中选择。选择格式的时候,不要尝试使用立体声,立体声浪费处理时间,而且效果很难评估。同样也不要使用16位以外的采样精度,因为这会导致音质的大幅下降。对于采样频率来说,越高越好,但是也不要设置超过22050HZ,在这个采样频率下,也能表现出CD音质的水准而没有太多的损失。

设置回放格式需要通过调用 IDirectSoundBuffer::SetFormat。

The SetFormat method sets the format of the primary buffer. Whenever this application has the input focus, DirectSound will set the primary buffer to the specified format.

HRESULT SetFormat(
LPCWAVEFORMATEX pcfxFormat
);

Parameters

pcfxFormat
Address of a WAVEFORMATEX structure that describes the new format for the primary sound buffer.

Return Values

If the method succeeds, the return value is DS_OK. If the method fails, the return value may be one of the following error values:


Return code
DSERR_BADFORMAT
DSERR_INVALIDCALL
DSERR_INVALIDPARAM
DSERR_OUTOFMEMORY
DSERR_PRIOLEVELNEEDED
DSERR_UNSUPPORTED

Remarks

The format of the primary buffer should be set before secondary buffers are created.

The method fails if the application has the DSSCL_NORMAL cooperative level.

If the application is using DirectSound at the DSSCL_WRITEPRIMARY cooperative level, and the format is not supported, the method fails.

If the cooperative level is DSSCL_PRIORITY, DirectSound stops the primary buffer, changes the format, and restarts the buffer. The method succeeds even if the hardware does not support the requested format; DirectSound sets the buffer to the closest supported format. To determine whether this has happened, an application can call the GetFormat method for the primary buffer and compare the result with the format that was requested with the SetFormat method.

This method is not available for secondary sound buffers. If a new format is required, the application must create a new DirectSoundBuffer object.


这个函数惟一的参数是指向WAVEFORMATEX结构的指针,该结构保存已设置的格式信息。

The WAVEFORMATEX structure defines the format of waveform-audio data. Only format information common to all waveform-audio data formats is included in this structure. For formats that require additional information, this structure is included as the first member in another structure, along with the additional information.

This structure is part of the Platform SDK and is not declared in Dsound.h. It is documented here for convenience.

typedef struct WAVEFORMATEX {
WORD wFormatTag;
WORD nChannels;
DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec;
WORD nBlockAlign;
WORD wBitsPerSample;
WORD cbSize;
} WAVEFORMATEX;

Members

wFormatTag
Waveform-audio format type. Format tags are registered with Microsoft Corporation for many compression algorithms. A complete list of format tags can be found in the Mmreg.h header file. For one- or two-channel PCM data, this value should be WAVE_FORMAT_PCM.
nChannels
Number of channels in the waveform-audio data. Monaural data uses one channel and stereo data uses two channels.
nSamplesPerSec
Sample rate, in samples per second (hertz). If wFormatTag is WAVE_FORMAT_PCM, then common values for nSamplesPerSec are 8.0 kHz, 11.025 kHz, 22.05 kHz, and 44.1 kHz. For non-PCM formats, this member must be computed according to the manufacturer's specification of the format tag.
nAvgBytesPerSec
Required average data-transfer rate, in bytes per second, for the format tag. If wFormatTag is WAVE_FORMAT_PCM, nAvgBytesPerSec should be equal to the product of nSamplesPerSec and nBlockAlign. For non-PCM formats, this member must be computed according to the manufacturer's specification of the format tag.
nBlockAlign
Block alignment, in bytes. The block alignment is the minimum atomic unit of data for the wFormatTag format type. If wFormatTag is WAVE_FORMAT_PCM or WAVE_FORMAT_EXTENSIBLE, nBlockAlign must be equal to the product of nChannels and wBitsPerSample divided by 8 (bits per byte). For non-PCM formats, this member must be computed according to the manufacturer's specification of the format tag.

Software must process a multiple of nBlockAlign bytes of data at a time. Data written to and read from a device must always start at the beginning of a block. For example, it is illegal to start playback of PCM data in the middle of a sample (that is, on a non-block-aligned boundary).

wBitsPerSample
Bits per sample for the wFormatTag format type. If wFormatTag is WAVE_FORMAT_PCM, then wBitsPerSample should be equal to 8 or 16. For non-PCM formats, this member must be set according to the manufacturer's specification of the format tag. If wFormatTag is WAVE_FORMAT_EXTENSIBLE, this value can be any integer multiple of 8. Some compression schemes cannot define a value for wBitsPerSample, so this member can be zero.
cbSize
Size, in bytes, of extra format information appended to the end of the WAVEFORMATEX structure. This information can be used by non-PCM formats to store extra attributes for the wFormatTag. If no extra information is required by the wFormatTag, this member must be set to zero. For WAVE_FORMAT_PCM formats (and only WAVE_FORMAT_PCM formats), this member is ignored.
以下设置音频格式为:11025HZ、单通道、16位。

// setup the WAVEFORMATEX structure
    WAVEFORMATEX wave_format;

    ZeroMemory(
&wave_format, sizeof(WAVEFORMATEX));

    wave_format.wFormatTag      
= WAVE_FORMAT_PCM;
    wave_format.nChannels       
= 1;        // mono
    wave_format.nSamplesPerSec  = 11025;    
    wave_format.wBitsPerSample  
= 16;
    wave_format.nBlockAlign     
= (wave_format.wBitsPerSample / 8* wave_format.nChannels;
    wave_format.nAvgBytesPerSec 
= wave_format.nSamplesPerSec * wave_format.nBlockAlign;

原著:Radu Privantu
  翻译:pAnic
  2005年5月11日
  
  原文出处:http://www.devmaster.net/articles/building-mmorpg
  
  ——————————————————————-
  
  译者序:这是一篇讲解如何开发一款MMORPG的入门文章,作者本人也是一款游戏的开发者,文中的内容源于实践,有很高的参考价值。很多人都想拥有自己的游戏,这篇文章对那些想自己开发游戏的人来说可能是一纸福音,也可能是一盆冷水。无论如何,开发游戏都不是一件简单的事情。以下是翻译正文:
  
  
  ——————————————————————-
  
  文章的中心是如何起步开发你自己的大型多人在线角色扮演游戏( 原文:Massive Multiplayer Online Role Playing Games) (MMORPG)(译者注:俗称:网络游戏,网游)。针对的读者是经验和资源有限的开发者。读完文章之后,你应该懂得如何起步,还有一些关于什么是应该做的和不应该做的忠告。第一步是评估你的能力和资源。你必须对自己诚实,因为做你力不从心的事情会浪费你的时间并让你心灰意冷。
  
  第一步:评估你的能力
  
  必须的技能:
  
  懂至少一种编程语言。 迄今为止, C++因为性能和效率的优越性成为游戏开发者的首选。 Visual Basic, Java 或者 C# 可能也是不错的选择;熟悉一种图形库。通常的选择是SDL, OpenGL, 或者DX/D3D。(译者注:网上也有很多免费/付费引擎下载和出售);
  
  选择一种网络通讯库。 你可以从WinSock, SDL_net, 或DirectPlay中选择。(译者注:很多人喜欢开发自己独特的网络库,这并不复杂,似乎ACE也是一种选择);
  对游戏开发有大体的经验。例如,事件循环,多线程,GUI 设计,等等。
  
  强烈推荐的技能:
  
  C/S结构通讯;
  
  多平台开发。 你可能希望设计一个MMORPG, 尤其是服务器能运行在多种操作系统。为此,我推荐使用SDL, OpenGL 和SDL_net;网站开发。如果你想让用户通过网站查看玩家统计,服务器信息和其他信息,这是必须的。(译者注:其实网站可以交给其他人开发,如果有必要的话);

  安全管理。你当然不想因为有人攻击你的服务器而浪费时间!

  团队组织能力。 你需要一个你能成功领导和管理的团队;
  
  第二步:初步规划
  
  我注意到很多人在不同的论坛发帖子寻找团队开发MMORPG。他们中的大部分是这样:“我们成立了一个公司/游戏工作室,需要3个美工,两个程序,1个音乐制作,等等。为了创新,不要看过去的MMORPG,你有全部的自由用来创造你想要的世界,等等。我们会在项目完成并赚到钱的时候付给你酬劳,等等”。不幸的是,以现有的技术和带宽,你无法拥有一个动态的世界。朝向无法到达的目标前进只会导致失败。正确的做法是拿出一些小规模的,功能性强的,可扩展的设计和构架。,
  
  基本软件构架
  
  首先,尝试创建一个简单的C/S模型,有如下功能:
  
  创建一个新角色;
  保存那个角色(服务器端);
  用那个角色登陆;
  能够和其他人交谈;
  能在3D空间游览;
  
  保存角色看起来简单,其实不然。例如,有两种方式保存角色:使用数据库服务或者使用文件。两者有各自的优缺点:

现在你决定了如何存储角色,你还得选择C/S通讯的网络协议:TCP 还是 UDP?,我们都知道TCP速度慢,但是更准确,并且需要额外带宽。我实际使用TCP并没有遇到什么问题。如果你有充足的带宽,TCP是个好选择,至少对初学者是这样。 UDP 会很麻烦,尤其是对新手。记住,游戏或引擎的初步测试会在你的局域网进行,所有的包都会按顺序依次抵达。在Inte
et上无法保证这一点。虽然包会按顺序到达,但是有时候会丢包,这通常是个麻烦事。当然,你可以设计你的协议使得C/S能够从丢包中恢复。但这对初学者来说很痛苦,不值得推荐。
  
  第三步:选择数据传输协议
  
  又是看起来很简单,其实不然。你不能只是发送’’\0’’结尾的串。因为你需要一个通用的协议,能同时适用字符串和二进制数据。用0(或其他字符)做结束符是不明智的,因为那个结束符可能是你要发送的数据的一部分。此外,如果你发送20字节,然后再20字节,服务器极有可能收不到两个20字节的包。取而代之的是,它会一次性收到40字节,为了避免浪费带宽在不必要的头上。而且,你可以发送1KB的包,但服务器会以两个小包的形式收到它。所以你必须知道哪里是一个包的开始,哪里是结束。在 “永恒大陆”(译者注:原文: Ete
al Lands,本文的作者正在开发的一款MMORPG)中,我们用如下的方法:
  
  Offset 0: 1 字节 表示传输的命令;
  Offset 1: 2 字节,传输的数据长度;
  Offset 3: 变长,消息内容;

  这种方法有一致的优点:所有的数据传输有统一的标准。缺点是有些命令有固定已知的长度,浪费了一些带宽。以后我们会改成混合的方法。
  
  下一件事是决定服务器模型: “非阻塞soket,不使用线程”,或者“阻塞soket,使用线程”。两种方法(使用线程 vs 不使用线程)各有优缺点。
  
  线程:
  
  服务器响应会更加平滑,因为如果一个玩家需要大量时间(例如从数据库中读取数据),这会在它自己的线程中完成,不会影响其他人。(译者注:也许作者的意思是每个玩家都有独立的线程,但这对MMORPG不太现实);

  难以恰当的实现和调试:你可能需要大量同步,并且一个小疏忽就会导致灾难性的后果( 服务器瘫痪,物品复制,等等);

  可以利用多处理器;

  无线程:
  
  实现和调试更简单;

  响应速度慢;

  在我的公司,我们使用无线程的方法,因为我没有足够的资源和人力处理线程模式。
  
  第四步:客户端
  
  你打算做2D还是3D游戏?有些人认为2D游戏做起来简单。我两者都做过,并且我倾向于3D游戏更简单。容我解释。

  2D下,你通常有一个帧缓冲,也就是一个巨大的象素点数组。象素点的格式会因显卡的不同而不同。有些是RGB模式,另一些是BGR模式,等等。每种颜色的bit数也会不同。只有在16bpp模式才有这个问题。8-bit和24-bit模式简单一些,但有他们各自的问题(8-bit颜色数太少(256),而24-bit速度更慢)。同时,你需要制作你的精灵动画程序,不得不自己排序所有对象,以便他们以正确的顺序绘制。当然,你可以用OpenGL或者D3D制作2D游戏,但通常这并不值得。并不是所有人都有3D加速卡,所以使用3D库开发2D游戏一般会带给你两者的缺点:不是所有人都能玩,你也不能旋转摄像机,拥有漂亮的阴影,和3D游戏炫目的效果。

  (译者注,目前绝大部分显卡都支持565的16bpp格式,这个也成为目前16位色的业界通用格式,有不少文章和代码都是讲述这一格式下图像处理的,尤其是使用MMX技术)

  3D的途径,正如我所说,更简单。但是需要一些数学(尤其是三角)的知识。现代的图形库很强大,免费提供了基本的操作(你不需要从后到前排列对象,改变物体的色彩和/或帖图都十分简单,对象的光照会按照光源和它的位置计算(只要你为它们计算了法向量),还有更多)。并且。3D给了你的创作和运动更多的自由度,缺点就是不是所有人都能玩你的游戏(没有3D卡的人数可能会让你大吃一惊的),并且,预渲染的图片总是比实时渲染的更漂亮。

  (译者注:市面上想买不支持3D的显卡目前很困难,只是高性能的3D卡价格也不低)
  
  第五步:安全
  
  显然,不能相信用户。任何时候都不能假设用户无法破解你精巧的加密算法(如果你使用了的话)或者协议,用户发送的任何信息都要通过验证。极有可能,在你的服务器上,你有固定的缓冲区。例如,通常有一个小(可能是4k)缓冲区用来接收数据(从soket)。恶意用户会发送超长数据。如果不检查,这会导致缓冲区溢出,引起服务器瘫痪,或者更坏的,这个用户可以hack你的服务器,执行非法代码。每个单独的消息都必须检查:缓冲区是否溢出,数据是否合法(例如用户发送“进入那扇门”,即使门在地图的另一端,或者“使用治疗药水”尽管用户没有那种药水,等等)。我再次强调,验证所有数据非常重要。一旦有非法数据,把它和用户名,IP,时间和日期,和非法的原因记录下来。偶尔检查一下那个记录。如果你发现少量的非法数据,并且来自于大量用户,这通常是客户端的bug或者网络问题。然而,如果你发现从一个用户或者IP发现大量非法数据,这是明显的迹象表明有人正在欺骗服务器,试图hack服务器,或者运行宏/脚本。同时,决不要在客户端存储数据。客户端应该从服务器接收数据。换句话说,不能发送这样的消息“OK,这是我得物品列表”或者“我的力量是10,魔法是200,生命值是2000/2000”。而且,客户端不应收到它不需要的数据。例如:客户端不应该知道其他玩家的位置,除非他们在附近。这是常识,给每个人发送所有玩家会占用大量带宽,并且有些玩家会破解客户端从中获取不公平的利益(像在地图上显示特定玩家的位置)
  
  (译者注:就像传奇的免蜡烛外挂)。所有这些似乎都是常识,但,再次,你会惊奇的发现有多少人不知道这些我们认为的常识。
    
  另一个要考虑的问题,当涉及到安全:玩家走动的速度必须在服务器计算,而不是客户端。
  
  (译者注:这是重要的原则,但是会耗费大量服务器资源。魔兽世界没有这样做,它采用类似其他玩家揭发的形式掩盖这个事实,导致加速外挂可以用,但是在有其他玩家的时候会暴露)。
  
  服务器应该跟踪时间(以ms为单位)当客户最后一次移动的时候,并且,移动的请求如果比通常的极限更快到来,这个请求应该被抛弃。不要记录这类虚假请求,因为这可能是因为网络延迟(也就是玩家延迟,过去的10秒内发送的数据同时到达了)。
    
  检查距离。如果一个玩家试图和100亿公里以外的玩家交易(或者甚至在另一张地图上),记录下来。如果一个玩家试图查看,或者使用一个遥远的地图对象,记录它。小心假的ID。例如,正常情况下每个玩家都会分配一个ID(ID在登陆的时候分配,可以是持久的(唯一ID)。如果ID在玩家登陆的时候赋予9或怪物被创建的时候),显然可以用玩家数组(保存玩家)的位置(索引)作为ID。
    
  所以第一个登陆的玩家ID是0,第二个是1,依此类推。现在,通常你会有一个限制,比如说2000个索引在玩家列表里。所以如果一个客户端发送一条命令类似:“查看 ID200000的角色”,这会使服务器当机,如果没有防备的话,因为服务器会访问非法的内存区域。所以,一定要检查,就像这样: "if actor id<0 or if actor id> max players 然后记录非法操作并且断开玩家。如果你使用C或者C++,注意或者定义索引为’’unsigned int’’ 并且检查上限,或因为某些原因定义为int(int,默认是有符号的),记得检查 <0 and >max 。没有做这些会严重挫伤你和其他用户。类似的,要检查超出地图坐标。如果你的服务器有某种寻路算法,并且客户端通过点击地面来移动,确保他们不要点击在地图外部。
  
  第六步:获得一个团队
  
  制作游戏需要大量的工作(除非是个Pong and Tetris游戏)。尤其是MMORPG。你无法单靠自己。理论上,一个完整的团队组成是这样:
  
  至少3 个程序员: 1 个做服务器,两个客户端(或者一个客户端,一个负责工具,例如美术插件,世界编辑器,等等)。有6个程序员是最好的,更多就没必要了。这取决于你的领导能力。最少一个美工,2到3个更合适。如果这是个3D游戏,你需要一个3D美工,一个2D美工(制作帖图,界面,等等),一个动画师,和一个美术部负责人。美术部应该由有经验的人组织和安排,除非你就是个艺术家。
  
  少数世界构建者:创建所有地图是个漫长的过程,并且直接关系到游戏的成败。再次,你需要一个世界构建部的负责人。你的世界需要协调一致,所以不能只有一个意气用事的人。
  
  一个网站管理员是必须的,除非你精通网站设计,并且愿意花时间做网站。音效和音乐不是必须的,但是有音效和音乐的游戏比没有的会更吸引人。
  
  一个游戏经济系统设计师.。你也许觉得那很简单,可以自己来做,但事实上那是最复杂的工作之一。如果经济系统设计不良(比如物品没有平衡,资源在地图上随意放置,等等。) 玩家会觉得无聊并且退出游戏。我们早期的进展存在很大的问题,尤其是因为经济系统主要是由我(一个程序员)设计的,它没有被恰当的计划。于是,我们花费了两个月来重新思考和建立一整个新的经济系统。这需要一次完全的物品清除。我告诉你,玩家会很不乐意你删除他们的物品。幸运的是,大部分玩家赞同这个想法,但是这么多小时的争论,妥协,解释和时间的浪费还是让我们丧气。以后会更多。
  
  如前所说,你需要一个10~15人的团队,不包括协调员和管理者。这10~15人必须是有经验的。如果都是新手就不值得,因为你需要花大量时间解释要做什么,怎样做,为什么他现在的做法不好,等等。
  
  一开始就凑齐10~15人几乎是不可能的。不管你在不同的论坛发多少帖,你也无法找到合适的团队成员。毕竟,如果一个人熟练于他/她的领域,为什么在你无法拿出任何东西的时候他/她要加入你的团队?很多人有远大的想法,但是实现它们需要大量时间和努力,所以他们宁可从事自己的工作也不会加入你。那如果你需要10~15人,但是无法让他们加入你的团队,你如何才能制作一款MMORPG呢?好,事实上,你一开始不需要所有人都到位。你真正需要的是一个程序员和一个美工。如果你是个程序员,只要找个美工就可以了。请求懂美术的朋友帮忙,花钱请大学生/朋友做一些美术或者其他工作。
  
  现在你有了一个美工,你期待的游戏的样子,现在可以开始实现了。一旦你有了可以运行的 C/S引擎,一些用来展示的截图(或者更好,玩家可以登陆你的世界,四处走动,聊天),更多的人会愿意加入你的团队。更恰当的是,除非你使用独有的技术,否则你的客户端可以开源。许多程序员会加入(作为志愿者)一个开源工程而不是非开源项目。而服务器不应该开源(除非你打算做一款完全开源的 MMORPG)。
    
  其他一些忠告:在有东西可展示之前,不要夸大你的游戏。最惹人烦的事情之一就是一个新手发一个“需要帮助” 的请求,要求一个巨大的团队加入他的游戏制作,解释这个游戏到底有多酷。一旦你拥有了网站广告(通常是在一个免费主机),你会看到一个吸引人的导航条,包含“下载”,“截图”,“ 原画”(译者注,原文:Concept art,概念艺术,在游戏应该指美工的原始设计),“论坛”。你点击下载链接,然后看到美妙的“建设中”页面(或者更糟糕,一个404错误)。然后你点击截图,得到同样的结果。如果你没有东西给人下载,就不要放下载链接。如果没有截图展示,不要放截图链接。然而更好的是,在工程进展10%(程序和美工)之前,不要浪费时间在网站上。
  
  第七步:打破某些神话
  
  你无法制作MMORPG, 只有大公司才可以。
  
  我不同意。虽然制作一款像魔兽世界(World of Warcraft),无尽任务2(Ever Quest 2),亚瑟王的召唤2(Asheron’’s Call 2),血统2(Lineage 2),和其他一些游戏对一个小的自发团队是不可能的,但是做一款像样的游戏还是可以的,只要你有经验,动机,和时间。,你需要1000小时的编程来制作一个可运行的测试版,大概10~15k小时完成几乎完整的客户端和服务器。。但是作为团队领导者,你不能只编程。保持团队团结,解决争执,维护公共关系 (PR),技术支持,架设服务器,惩罚捣乱分子,自由讨论,等等都是你的职责。你可能会被非编程的任务淹没。你很可能需要上班/上学,这减少了你花费在项目上的时间。我们很幸运,没有成员离开团队,但是如果这种事情发生,那的确是大问题。假设你的美工半途离开。或者更糟糕,他/她没有给你使用他/她作品的许可。当然这可以通过和他们签订合同来解决,但找另外一个美工仍然很麻烦。一个工程中有两种不同的美术风格也是问题。
  
  需要大笔金钱(通常 4-6 位数) 用来架设一个 MMORPG 服务器.
  
  当然,这不是真的。我见过专业服务器,1000GB/月,不到100美元/月(2~300美元的初装费)。除非你的数据传输协议设计非常不合理, 1000GB/月对一个1000玩家在线(平均)的服务器来说足够了。当然,你还需要另一个服务器做网站和客户端下载(客户端下载会占用大量流量,当游戏变得流行的时候)。我们的客户端有22MB,有时候会有400GB/月的传输量。而我们还没有很流行(仍然)。另一件事,我们不需要另一台专用服务器开启这个工程。ADSL/cable服务器可以胜任,直到你的同时在线人数达到20~30。然后要么找一个友好的主机公司,用广告交换免费主机,要么就只能自己掏腰包了。
  
  制作一个MMORPG很有趣。
  
  这不是真的。你可能认为每个人都会赏识你,玩家会支持你,你标新立异,并且,当然,很多玩家都玩你的游戏。玩家可能让人讨厌。即使是完全免费的游戏,他们也能找到理由抱怨。更糟糕的是人们经常会抱怨矛盾的事。战士会抱怨升级太难,商人会对战士掠夺大量钱财很失望。如果你减少怪物掉落物品,有些玩家就会威胁说要退出游戏。如果你增加,同样的一群人会不满新手能更简单赚钱的事实。真是左右为难。改革和改进是必须的。如果你决定改变某些东西,例如给加工物品增加挑战性,有些人会说太难了。如果你不做,他们又会说太简单无味。你会发现满意的玩家通常不会说什么并且感到满意,同时破坏者会怨声载道。
    
  MMORPG的经济比单机版难以平衡的多。在单机游戏,你可以逐渐改良武器,只要玩家进展,他/她可以使用更好的装备,丢弃(或者卖掉)旧的。另一方面,在多人游戏里,这种观点不成立,因为每个人都试图得到最好的武器,而跳过低等级武器。大部分玩家宁可空手省钱,直到他们能买游戏中最好的武器。经济系统设计要参考相关的文章。
    
  迄今为止我列举的所有事情,加上额外的工作和挑战,足以让你在决定涉足这个工程之前三思而行。你必须知道你的决定意味着什么。
  
  总结
  
  希望这篇文章能给你足够的知识。我的下一篇文章将介绍如何建立一个经济系统(更明确的,要避免哪些错误),还有一些调试服务器和客户端的信息。
  
  关于作者
  
  这篇文章作者是 Radu Privantu, 永恒大陆(Ete
al Lands) www.ete
al-lands.com的主程序和项目规划, 永恒大陆是一款免费,客户端开源的MMORPG。作者可以通过 chaos_rift@yahoo.com 联系

除了指定的是鼠标标识符以及鼠标数据格式外,初始化鼠标就和初始化键盘几乎完全相同。

//--------------------------------------------------------------------------------
// Initialize mouse interface, return a mouse interface pointer.
//--------------------------------------------------------------------------------
IDirectInputDevice8* Init_Mouse(HWND hwnd, IDirectInput8* directinput)
{
    IDirectInputDevice8
* directinput_device;

    
// create the device object
    if(FAILED(directinput->CreateDevice(GUID_SysMouse, &directinput_device, NULL)))
        
return NULL;

    
// set the data format
    if(FAILED(directinput_device->SetDataFormat(&c_dfDIMouse)))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// set the coooperative mode
    if(FAILED(directinput_device->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE)))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// acquire the device for use
    if(FAILED(directinput_device->Acquire()))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// everything well, so return a vaild pointer.
    return directinput_device;
}

要调用DirectInputDevice8::GetDeviceState,使用诸如相对移动和按键状态等鼠标的相关信息来填充 DIMOUSESTATE结构体。

DIMOUSESTATE结构体的定义如下:

Describes the state of a mouse device that has up to four buttons, or another device that is being accessed as if it were a mouse device. This structure is used with the IDirectInputDevice8::GetDeviceState method.

Syntax

typedef struct DIMOUSESTATE {
    LONG lX;
    LONG lY;
    LONG lZ;
    BYTE rgbButtons[4];
} DIMOUSESTATE, *LPDIMOUSESTATE;

Members

lX
X-axis.

lY
Y-axis.

lZ
Z-axis, typically a wheel. If the mouse does not have a z-axis, the value is 0.

rgbButtons
Array of buttons. The high-order bit of the byte is set if the corresponding button is down.

Remarks

You must prepare the device for mouse-style access by calling the IDirectInputDevice8::SetDataFormat method, passing the c_dfDIMouse global data format variable.

The mouse is a relative-axis device, so the absolute axis positions for mouse axes are accumulated relative motion. Therefore, the value of the absolute axis position is not meaningful except in comparison with other absolute axis positions.

If an axis is in relative mode, the appropriate member contains the change in position. If it is in absolute mode, the member contains the absolute axis position.

点击下载源码和工程

完整源码示例:

/***************************************************************************************
PURPOSE:
    Mouse device Demo
 **************************************************************************************
*/

#define DIRECTINPUT_VERSION 0x0800

#include 
<windows.h>
#include 
<stdio.h>
#include 
<dinput.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"dinput8.lib")

#pragma warning(disable : 
4996)

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class and caption text.
HWND g_hwnd;
char g_class_name[] = "MouseClass";

IDirectInput8
* g_directinput;               // directinput component
IDirectInputDevice8* g_directinput_device;  // mouse device

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Initialize mouse interface, return a mouse interface pointer.
//--------------------------------------------------------------------------------
IDirectInputDevice8* Init_Mouse(HWND hwnd, IDirectInput8* directinput)
{
    IDirectInputDevice8
* directinput_device;

    
// create the device object
    if(FAILED(directinput->CreateDevice(GUID_SysMouse, &directinput_device, NULL)))
        
return NULL;

    
// set the data format
    if(FAILED(directinput_device->SetDataFormat(&c_dfDIMouse)))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// set the coooperative mode
    if(FAILED(directinput_device->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE)))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// acquire the device for use
    if(FAILED(directinput_device->Acquire()))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// everything well, so return a vaild pointer.
    return directinput_device;
}

//--------------------------------------------------------------------------------
// Read mouse buffer.
//--------------------------------------------------------------------------------
BOOL Read_Device(IDirectInputDevice8* directinput_device, void* buffer, long buffer_size)
{
    HRESULT rv;

    
while(1)
    {
        
// poll device
        g_directinput_device->Poll();

        
// read in state
        if(SUCCEEDED(rv = g_directinput_device->GetDeviceState(buffer_size, buffer)))
            
break;

        
// return when an unknown error
        if(rv != DIERR_INPUTLOST || rv != DIERR_NOTACQUIRED)
            
return FALSE;

        
// re-acquire and try again
        if(FAILED(g_directinput_device->Acquire()))
            
return FALSE;
    }

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS        win_class;
    MSG             msg;
    DIMOUSESTATE    mouse_state 
= {0};
    
char            text[256];
    
long            x_pos = 0, y_pos = 0;

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_MOUSE), 0, NULL);

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// initialize directinput and get keyboard device
    DirectInput8Create(inst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void **&g_directinput, NULL);

    
// initialize mouse
    g_directinput_device = Init_Mouse(g_hwnd, g_directinput);

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }
        
        
// read in mouse and display coordinates
        Read_Device(g_directinput_device, &mouse_state, sizeof(DIMOUSESTATE));

        x_pos 
+= mouse_state.lX;
        y_pos 
+= mouse_state.lY;

        
if(mouse_state.lX != 0 || mouse_state.lY != 0)
        {
            sprintf(text, 
"%ld, %ld", x_pos, y_pos);
            SetWindowText(GetDlgItem(g_hwnd, IDC_COORDINATES), text);
        }
    }

    
// release directinput objects
    g_directinput_device->Unacquire();
    g_directinput_device
->Release();
    g_directinput
->Release();

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}

运行截图:


设置数据格式

每种设备都有一种用于读取数据的特定数据格式,需要考虑的东西也很多,包括键、鼠标按键、轴等。因此要使程序从设备读取数据,首先必须告诉DirectInput读取这种数据所采用的格式。通过 IDirectInputDevice8::SetDataFormat函数即可满足上述要求。

Sets the data format for the Microsoft DirectInput device.

Syntax

HRESULT SetDataFormat(LPCDIDATAFORMAT lpdf);

Parameters

lpdf
Address of a structure that describes the format of the data that the DirectInputDevice should return. An application can define its own DIDATAFORMAT structure or use one of the following predefined global variables:
 
c_dfDIKeyboard
c_dfDIMouse
c_dfDIMouse2
c_dfDIJoystick
c_dfDIJoystick2

Return Value

If the method succeeds, the return value is DI_OK.

If the method fails, the return value can be one of the following error values:

DIERR_ACQUIRED The operation cannot be performed while the device is acquired.
DIERR_INVALIDPARAM An invalid parameter was passed to the returning function, or the object was not in a state that permitted the function to be called. This value is equal to the E_INVALIDARG standard Component Object Model (COM) return value.
DIERR_NOTINITIALIZED The object has not been initialized.

Remarks

The data format must be set before the device can be acquired by using the IDirectInputDevice8::Acquire method. It is necessary to set the data format only once. The data format cannot be changed while the device is acquired.

If the application is using action mapping, the data format is set instead by the call to IDirectInputDevice8::SetActionMap.

SetDataFormat函数只有一个参数,这个参数是一个指向DIDATAFORMAT结构体的指针,此结构体的定义如下:

Describes a device’s data format. This structure is used with the IDirectInputDevice8::SetDataFormat method.

Syntax

typedef struct DIDATAFORMAT {
    DWORD dwSize;
    DWORD dwObjSize;
    DWORD dwFlags;
    DWORD dwDataSize;
    DWORD dwNumObjs;
    LPDIOBJECTDATAFORMAT rgodf;
} DIDATAFORMAT, *LPDIDATAFORMAT;
 
typedef const DIDATAFORMAT *LPCDIDATAFORMAT;

Members

dwSize
Size of this structure, in bytes.

dwObjSize
Size of the DIOBJECTDATAFORMAT structure, in bytes.

dwFlags
Flags describing other attributes of the data format. This value can be one of the following:

DIDF_ABSAXIS
The axes are in absolute mode. Setting this flag in the data format is equivalent to manually setting the axis mode property, using the IDirectInputDevice8::SetProperty method. This cannot be combined with DIDF_RELAXIS flag.

DIDF_RELAXIS
The axes are in relative mode. Setting this flag in the data format is equivalent to manually setting the axis mode property using the IDirectInputDevice8::SetProperty method. This cannot be combined with the DIDF_ABSAXIS flag.

dwDataSize
Size of a data packet returned by the device, in bytes. This value must be a multiple of 4 and must exceed the largest offset value for an object’s data within the data packet.

dwNumObjs
Number of objects in the rgodf array.

rgodf
Address to an array of DIOBJECTDATAFORMAT structures. Each structure describes how one object’s data should be reported in the device data. Typical errors include placing two pieces of information in the same location and placing one piece of information in more than one location.

Remarks

Applications do not typically need to create a DIDATAFORMAT structure. An application can use one of the predefined global data format variables, c_dfDIMouse, c_dfDIMouse2, c_dfDIKeyboard, c_dfDIJoystick, or c_dfDIJoystick2.

Applications that need to create a DIDATAFORMAT structure must first call IDirectInputDevice8::EnumObjects to determine the available objects for the current device. This is because the IDirectInputDevice8::SetDataFormat method will return DIERR_INVALIDPARAM if an an object is described in the DIDATAFORMAT structure but does not exist on the current device.

设置协作级别

首先必须面对一个事实,那就是每个程序都会使用许多输入设备。几乎每个程序都会使用键盘和鼠标,同时某些程序还会使用游戏杆。在处理的时候,必须同其他可能仍在运行的应用程序共享对这些设备的访问,或者应用程序独占对设备的所有访问,这样除非该应用程序结束了对设备的访问,否则就不允许其他应用程序控制这些设备。

设置协作级别的函数是IDirectInputDevice8::SetCooperativeLevel。

Establishes the cooperative level for this instance of the device. The cooperative level determines how this instance of the device interacts with other instances of the device and the rest of the system.

Syntax

HRESULT SetCooperativeLevel(HWND hwnd,
    DWORD dwFlags
);

Parameters

hwnd
Window handle to be associated with the device. This parameter must be a valid top-level window handle that belongs to the process. The window associated with the device must not be destroyed while it is still active in a Microsoft DirectInput device.

dwFlags
Flags that describe the cooperative level associated with the device. The following flags are defined:

DISCL_BACKGROUND
The application requires background access. If background access is granted, the device can be acquired at any time, even when the associated window is not the active window.

DISCL_EXCLUSIVE
The application requires exclusive access. If exclusive access is granted, no other instance of the device can obtain exclusive access to the device while it is acquired. However, nonexclusive access to the device is always permitted, even if another application has obtained exclusive access.

An application that acquires the mouse or keyboard device in exclusive mode should always unacquire the devices when it receives WM_ENTERSIZEMOVE and WM_ENTERMENULOOP messages. Otherwise, the user cannot manipulate the menu or move and resize the window.

DISCL_FOREGROUND
The application requires foreground access. If foreground access is granted, the device is automatically unacquired when the associated window moves to the background.

DISCL_NONEXCLUSIVE
The application requires nonexclusive access. Access to the device does not interfere with other applications that are accessing the same device.

DISCL_NOWINKEY
Disable the Microsoft Windows logo key. Setting this flag ensures that the user cannot inadvertently break out of the application. Note, however, that DISCL_NOWINKEY has no effect when the default action mapping user interface (UI) is displayed, and the Windows logo key will operate normally as long as that UI is present.

Return Value

If the method succeeds, the return value is DI_OK.

If the method fails, the return value can be one of the following error values:

DIERR_INVALIDPARAM An invalid parameter was passed to the returning function, or the object was not in a state that permitted the function to be called. This value is equal to the E_INVALIDARG standard Component Object Model (COM) return value.
DIERR_NOTINITIALIZED The object has not been initialized.
E_HANDLE The HWND parameter is not a valid top-level window that belongs to the process.

Remarks

Applications must specify either DISCL_FOREGROUND or DISCL_BACKGROUND; it is an error to specify both or neither. Similarly, applications must specify either DISCL_EXCLUSIVE or DISCL_NONEXCLUSIVE.

If the system mouse is acquired in exclusive mode, the pointer is removed from the screen until the device is unacquired. This applies only to a mouse created by passing GUID_SysMouse to IDirectInput8::CreateDevice.

Applications that select the background exclusive mode cooperative level are not guaranteed to retain access to the device if another application requests exclusive access. When a background exclusive mode application loses access, calls to DirectInput device methods will fail and return DIERR_NOTACQUIRED. The application can regain access to the device by manually unacquiring the device and reaquiring it.

Applications must call this method before acquiring the device by using the IDirectInputDevice8::Acquire method.

设置特殊属性

设置设备的特殊属性,包括轴模式(axis mode),缓冲区(buffering)以及最大最小范围。对于轴模式,有两个选择:相对和绝对。绝对模式基于一个中心坐标来报告坐标。位于这个点左边或上面的值都报告成负值,同时位于这个点右边或下面的值都报告成正值。相对坐标就是当前位置同上一个位置之间的距离差。

最后一个特殊性属性是设备的最小和最大范围设置。举例来说,将游戏杆推动到最左边就会产生最小值,而推动到最右边就会产生最大值。这两边产生何种值取决于具体的设置,只有游戏杆才会涉及到最小和最大范围设置。

用于设置特殊属性的函数是 IDirectInputDevice8::SetProperty。

Sets properties that define the device behavior. These properties include input buffer size and axis mode.

Syntax

HRESULT SetProperty(REFGUID rguidProp,
    LPCDIPROPHEADER pdiph
);

Parameters

rguidProp
Reference to (C++) or address of (C) the globally unique identifier (GUID) identifying the property to be set. This can be one of the predefined values, or a pointer to a GUID that identifies the property. The following property values are predefined for an input device:

DIPROP_APPDATA
Sets the application-defined value associated with an in-game action, as a DIPROPPOINTER.

DIPROP_AUTOCENTER
Specifies whether device objects are self centering. This setting applies to the entire device, rather than to any particular object, so the dwHow member of the associated DIPROPDWORD structure must be DIPH_DEVICE.
The dwData member can be one of the following values.

DIPROPAUTOCENTER_OFF: The device should not automatically center when the user releases the device. An application that uses force feedback should disable autocentering before playing effects.

DIPROPAUTOCENTER_ON: The device should automatically center when the user releases the device.

Not all devices support the autocenter property.

DIPROP_AXISMODE
Sets the axis mode. The value being set (DIPROPAXISMODE_ABS or DIPROPAXISMODE_REL) must be specified in the dwData member of the associated DIPROPDWORD structure. See the description of the pdiph parameter for more information.

This setting applies to the entire device, so the dwHow member of the associated DIPROPDWORD structure must be set to DIPH_DEVICE.

DIPROP_BUFFERSIZE
Sets the input buffer size. The value being set must be specified in the dwData member of the associated DIPROPDWORD structure. See Remarks. This setting applies to the entire device, so the dwHow member of the associated DIPROPDWORD structure must be set to DIPH_DEVICE.

DIPROP_CALIBRATION
Predefined property that allows the application to access the information that Microsoft DirectInput uses to manipulate axes that require calibration. This property exists primarily for applications of the control panel type. Normal applications should not need to deal with calibration information.
You can access the calibration mode property for an axis by setting the dwHow member of the DIPROPHEADER structure to DIPH_BYID or to DIPH_BYOFFSET and setting the dwObj member to the object identifier (ID) or offset, respectively.

Control panel applications that set new calibration data must also invoke the IDirectInputJoyConfig::SendNotify method to notify other applications of the change in calibration. For more information about IDirectInputJoyConfig::SendNotify, see the Microsoft DirectX Driver Development Kit (DDK).

DIPROP_CALIBRATIONMODE
Enables the application to specify whether DirectInput should retrieve calibrated or uncalibrated data from an axis. By default, DirectInput retrieves calibrated data.
Setting the calibration mode for the entire device is equivalent to setting it for each axis individually.

The dwData member of the DIPROPDWORD structure can be one of the following values:

DIPROPCALIBRATIONMODE_COOKED: DirectInput should return data after applying calibration information. This is the default mode.
DIPROPCALIBRATIONMODE_RAW: DirectInput should return raw, uncalibrated data. This mode is typically used only by applications of the control panel type. Note that raw data might include negative values.

Setting a device into raw mode causes the dead zone, saturation, and range settings to be ignored.

DIPROP_CPOINTS
Sets calibration points used for the adjustment of incoming raw data. The values being set must be specified as CPOINT types in the cp array of the associated DIPROPCPOINTS structure. This setting applies to individual device objects, so the dwHow member of the associated DIPROPHEADER structure must be set to either DIPH_BYID or DIPH_BYOFFSET.

DIPROP_DEADZONE
Sets the value for the dead zone of a joystick, in the range from 0 through 10,000, where 0 indicates that there is no dead zone, 5,000 indicates that the dead zone extends over 50 percent of the physical range of the axis on both sides of center, and 10,000 indicates that the entire physical range of the axis is dead. When the axis is within the dead zone, it is reported as being at the center of its range. This setting can be applied to either the entire device or to a specific axis.

DIPROP_FFGAIN
Sets the gain for the device. This setting applies to the entire device, rather than to any particular object, so the dwHow member of the associated DIPROPDWORD structure must be DIPH_DEVICE.
The dwData member contains a gain value that is applied to all effects created on the device. The value is an integer in the range from 0 through 10,000, specifying the amount by which effect magnitudes should be scaled for the device. For example, a value of 10,000 indicates that all effect magnitudes are to be taken at face value. A value of 9,000 indicates that all effect magnitudes are to be reduced to 90 percent of their nominal magnitudes.

DirectInput always checks the gain value before setting the gain property. If the gain is outside of the range (less than zero or greater than 10,000), IDirectInputDevice8::SetProperty will return DIERR_INVALIDPARAM. Otherwise, if successful, it will return DI_OK, even if the device does not support force feedback.

Setting a gain value is useful when an application wants to scale down the strength of all force-feedback effects uniformly, based on user preferences.

Unlike other properties, the gain can be set when the device is in an acquired state.

DIPROP_INSTANCENAME
This property exists for advanced applications that want to change the friendly instance name of a device (as returned in the tszInstanceName member of the DIDEVICEINSTANCE structure) to distinguish it from similar devices that are plugged in simultaneously. Most applications should have no need to change the friendly name.
This setting applies to the entire device, so the dwHow member of the associated DIPROPDWORD structure must be set to DIPH_DEVICE.

The pdiph parameter must be a pointer to the diph member of a DIPROPSTRING structure.

DIPROP_PRODUCTNAME
This property exists for advanced applications that want to change the friendly product name of a device (as returned in the tszProductName member of the DIDEVICEINSTANCE structure) to distinguish it from similar devices which are plugged in simultaneously. Most applications should have no need to change the friendly name.
This setting applies to the entire device, so the dwHow member of the associated DIPROPDWORD structure must be set to DIPH_DEVICE.

The pdiph parameter must be a pointer to the diph member of a DIPROPSTRING structure.

Setting the product name is only useful for changing the user-defined name of an analog joystick on Microsoft Windows 98,Windows 2000, and Windows Millennium Edition (Windows Me) computers. In other cases, attempting to set this property will still return DI_OK. However, the name is not stored in a location used by IDirectInputDevice8::GetProperty.

DIPROP_RANGE
Sets the range of values an object can possibly report. The minimum and maximum values are taken from the lMin and lMax members of the associated DIPROPRANGE structure.
For some devices, this is a read-only property.

You cannot set a reverse range; lMax must be greater than lMin.

DIPROP_SATURATION
Sets the value for the saturation zones of a joystick, in the range from 0 through 10,000. The saturation level is the point at which the axis is considered to be at its most extreme position. For example, if the saturation level is set to 9,500, the axis reaches the extreme of its range when it has moved 95 percent of the physical distance from its center position (or from the dead zone). This setting can be applied to either the entire device or a specific axis.

pdiph
Address of the DIPROPHEADER structure contained within the type-specific property structure.

Return Value

If the method succeeds, the return value is DI_OK or DI_PROPNOEFFECT.

If the method fails, the return value can be one of the following error values.

DIERR_INVALIDPARAM An invalid parameter was passed to the returning function, or the object was not in a state that permitted the function to be called. This value is equal to the E_INVALIDARG standard Component Object Model (COM) return value.
DIERR_NOTINITIALIZED The object has not been initialized.
DIERR_OBJECTNOTFOUND The requested object does not exist.
DIERR_UNSUPPORTED The function called is not supported at this time. This value is equal to the E_NOTIMPL standard COM return value.

Remarks

The buffer size determines the amount of data that the buffer can hold between calls to the IDirectInputDevice8::GetDeviceData method before data is lost. This value may be set to 0 to indicate that the application does not read buffered data from the device. If the buffer size in the dwData member of the DIPROPDWORD structure is too large for the device to support it, then the largest possible buffer size is set.

在调用SetProperty函数时始终要使用的DIPROPHEADER结构体定义如下:

Serves as a header for all property structures.

Syntax

typedef struct DIPROPHEADER {
    DWORD    dwSize;
    DWORD    dwHeaderSize;
    DWORD    dwObj;
    DWORD    dwHow;
} DIPROPHEADER, *LPDIPROPHEADER;
 
typedef const DIPROPHEADER *LPCDIPROPHEADER;

Members

dwSize
Size of the enclosing structure. This member must be initialized before the structure is used.

dwHeaderSize
Size of the DIPROPHEADER structure.

dwObj
Object for which the property is to be accessed. The value set for this member depends on the value specified in the dwHow member.

dwHow
Value that specifies how the dwObj member should be interpreted. This value can be one of the following:

DIPH_DEVICE
The dwObj member must be 0.

DIPH_BYOFFSET
The dwObj member is the offset into the current data format of the object whose property is being accessed.

DIPH_BYUSAGE
The dwObj member is the HID usage page and usage values in packed form.

DIPH_BYID
The dwObj member is the object type/instance identifier. This identifier is returned in the dwType member of the DIDEVICEOBJECTINSTANCE structure returned from a previous call to the IDirectInputDevice8::EnumObjects member.

获得设备

在使用任何设备之前,首先必须获得设备。只有获得了设备才能确保程序能够访问设备,以及同其他程序共享访问还是完全控制设备。要注意的是其他程序可能会争夺并抢走对设备的控制权,要补救这种情况就必须重新获得设备。何时必须获得设备?第一种情况就是在创建接口的时候,原因在于使用设备之前必须首先获得它。另一种情况就是当另一个程序抢走了对设备的控制,同时 DirectInput通知了程序时。

调用IDirectInputDevice8::Acquire函数即可获得设备。

Obtains access to the input device.

Syntax

HRESULT Acquire(VOID);

Return Value

If the method succeeds, the return value is DI_OK, or S_FALSE if the device was already acquired.

If the method fails, the return value can be one of the following error values.

DIERR_INVALIDPARAM An invalid parameter was passed to the returning function, or the object was not in a state that permitted the function to be called. This value is equal to the E_INVALIDARG standard Component Object Model (COM) return value.
DIERR_NOTINITIALIZED The object has not been initialized.
DIERR_OTHERAPPHASPRIO Another application has a higher priority level, preventing this call from succeeding. This value is equal to the E_ACCESSDENIED standard COM return value. This error can be returned when an application has only foreground access to a device but is attempting to acquire the device while in the background.

Remarks

Before a device can be acquired, a data format must be set by using the IDirectInputDevice8::SetDataFormat method or IDirectInputDevice8::SetActionMap method. If the data format has not been set, IDirectInputDevice8::Acquire returns DIERR_INVALIDPARAM.

Devices must be acquired before calling the IDirectInputDevice8::GetDeviceState or IDirectInputDevice8::GetDeviceData methods for that device.

Device acquisition does not use a reference count. Therefore, if an application calls the IDirectInputDevice8::Acquire method twice, then calls the IDirectInputDevice8::Unacquire method once, the device is unacquired.

If IDirectInputDevice8::BuildActionMap succeeds but no actions have been mapped, a subsequent call to IDirectInputDevice8::SetActionMap will return DI_OK but a call to IDirectInputDevice8::Acquire will fail with DIERR_INVALIDPARAM.

轮询设备

轮询可以准备设备并在合适的情况下读取设备数据,因为数据可能具有临界时间。游戏杆就是这样的设备,要从设备读取数据,计算机需要发送一个电子脉冲给它。虽然轮询对于游戏杆输入而言是必须的,但是对键盘或鼠标来说却并不需要轮询。

轮询设备需要使用IDirectInputDevice8::Poll函数。

Retrieves data from polled objects on a Microsoft DirectInput device. If the device does not require polling, calling this method has no effect. If a device that requires polling is not polled periodically, no new data is received from the device. Calling this method causes DirectInput to update the device state, generate input events (if buffered data is enabled), and set notification events (if notification is enabled).

Syntax

HRESULT Poll(VOID);

Return Value

If the method succeeds, the return value is DI_OK, or DI_NOEFFECT if the device does not require polling.

If the method fails, the return value can be one of the following error values:

DIERR_INPUTLOST Access to the input device has been lost. It must be reacquired.
DIERR_NOTACQUIRED The operation cannot be performed unless the device is acquired.
DIERR_NOTINITIALIZED The object has not been initialized.

Remarks

Before a device data can be polled, the data format must be set by using the IDirectInputDevice8::SetDataFormat or IDirectInputDevice8::SetActionMap method, and the device must be acquired by using the IDirectInputDevice8::Acquire method.

读取数据

经过以上的步骤,就剩下最后一步了,那就是使用 IDirectInputDevice8::GetDeviceState函数读取设备数据。为了存储设备的信息,必须传递一个数据缓冲区给此函数,这样程序才能使用设备的信息,每种设备的数据各不相同。

Retrieves immediate data from the device.

Syntax

HRESULT GetDeviceState(DWORD cbData,
    LPVOID lpvData
);

Parameters

cbData
Size of the buffer in the lpvData parameter, in bytes.

lpvData
Address of a structure that receives the current state of the device. The format of the data is established by a prior call to the IDirectInputDevice8::SetDataFormat method.

Return Value

If the method succeeds, the return value is DI_OK.

If the method fails, the return value can be one of the following error values:

DIERR_INPUTLOST Access to the input device has been lost. It must be reacquired.

DIERR_INVALIDPARAM An invalid parameter was passed to the returning function, or the object was not in a state that permitted the function to be called. This value is equal to the E_INVALIDARG standard Component Object Model (COM) return value.

DIERR_NOTACQUIRED The operation cannot be performed unless the device is acquired.

DIERR_NOTINITIALIZED The object has not been initialized.

E_PENDING Data is not yet available.

Remarks

Before device data can be obtained, set the cooperative level by using the IDirectInputDevice8::SetCooperativeLevel method, then set the data format by using IDirectInputDevice8::SetDataFormat, and acquire the device by using the IDirectInputDevice8::Acquire method.

The five predefined data formats require corresponding device state structures according to the following table:

Data format State structure
c_dfDIMouse DIMOUSESTATE
c_dfDIMouse2 DIMOUSESTATE2
c_dfDIKeyboard array of 256 bytes
c_dfDIJoystick DIJOYSTATE
c_dfDIJoystick2 DIJOYSTATE2

For example, if you passed the c_dfDIMouse format to the IDirectInputDevice8::SetDataFormat method, you must pass a DIMOUSESTATE structure to the IDirectInputDevice8::GetDeviceState method.

无论是哪种设备,下面的代码都能读取其数据。它们考虑了丢失设备并在适当的时刻重新获得设备的情况,必须传递一个指向缓冲区的指针给下面这个函数,缓冲区的大小要能够保存设备信息以及读取的数据量。

BOOL Read_Device(IDirectInputDevice8* directinput_device, void* buffer, long buffer_size)
{
    HRESULT rv;

    
while(1)
    {
        
// poll device
        g_directinput_device->Poll();

        
// read in state
        if(SUCCEEDED(rv = g_directinput_device->GetDeviceState(buffer_size, buffer)))
            
break;

        
// return when an unknown error
        if(rv != DIERR_INPUTLOST || rv != DIERR_NOTACQUIRED)
            
return FALSE;

        
// re-acquire and try again
        if(FAILED(g_directinput_device->Acquire()))
            
return FALSE;
    }

    
return TRUE;
}

使用DirectInput处理键盘

如果调用成功,下面的初始化函数则返回一个指向新创建的IDirectInputDevice8对象的指针;反之当函数调用失败时,返回NULL。只需给此函数传递父窗口的句柄和与定义的DirectInput对象即可。

//--------------------------------------------------------------------------------
// Initialize keyboard interface, return a keyboad interface pointer.
//--------------------------------------------------------------------------------
IDirectInputDevice8* Init_Keyboard(HWND hwnd, IDirectInput8* directinput)
{
    IDirectInputDevice8
* directinput_device;

    
// create the device object
    if(FAILED(directinput->CreateDevice(GUID_SysKeyboard, &directinput_device, NULL)))
        
return NULL;

    
// set the data format
    if(FAILED(directinput_device->SetDataFormat(&c_dfDIKeyboard)))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// set the coooperative mode
    if(FAILED(directinput_device->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE)))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// acquire the device for use
    if(FAILED(directinput_device->Acquire()))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// everything well, so return a vaild pointer.
    return directinput_device;
}

要存储键盘数据,必须使用一个大小为256字节的数组,每个字节存储一个键的状态,这样就给出了保存256个键的空间。每个字节存储键当前的状态信息,即键是否被按下。要得到键的状态,就要检查高位(字节),如果置位,键就被按下;如果清零,键就未被按下。在DirectInput 中,每个键都有一个属于自己的宏,这些宏都以DIK_为前缀。A键定义为DIK_A,ESC为DIK_ESCAPE等。查阅DX SDK或DInput.h可以得到其他键的宏。

点击下载源码和工程

完整源码如下:

/***************************************************************************************
PURPOSE:
    Keyboard device Demo
 **************************************************************************************
*/

#define DIRECTINPUT_VERSION 0x0800

#include 
<windows.h>
#include 
<stdio.h>
#include 
<dinput.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"dinput8.lib")

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class and caption text.
HWND g_hwnd;
static char g_class_name[] = "KeyboardClass";

IDirectInput8
* g_directinput;               // directinput component
IDirectInputDevice8* g_directinput_device;  // keyboard device

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Initialize keyboard interface, return a keyboad interface pointer.
//--------------------------------------------------------------------------------
IDirectInputDevice8* Init_Keyboard(HWND hwnd, IDirectInput8* directinput)
{
    IDirectInputDevice8
* directinput_device;

    
// create the device object
    if(FAILED(directinput->CreateDevice(GUID_SysKeyboard, &directinput_device, NULL)))
        
return NULL;

    
// set the data format
    if(FAILED(directinput_device->SetDataFormat(&c_dfDIKeyboard)))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// set the coooperative mode
    if(FAILED(directinput_device->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE)))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// acquire the device for use
    if(FAILED(directinput_device->Acquire()))
    {
        directinput_device
->Release();
        
return NULL;
    }

    
// everything well, so return a vaild pointer.
    return directinput_device;
}

//--------------------------------------------------------------------------------
// Read keyboard buffer.
//--------------------------------------------------------------------------------
BOOL Read_Device(IDirectInputDevice8* directinput_device, void* buffer, long buffer_size)
{
    HRESULT rv;

    
while(1)
    {
        
// poll device
        g_directinput_device->Poll();

        
// read in state
        if(SUCCEEDED(rv = g_directinput_device->GetDeviceState(buffer_size, buffer)))
            
break;

        
// return when an unknown error
        if(rv != DIERR_INPUTLOST || rv != DIERR_NOTACQUIRED)
            
return FALSE;

        
// re-acquire and try again
        if(FAILED(g_directinput_device->Acquire()))
            
return FALSE;
    }

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS    win_class;
    MSG         msg;
    
char        key_state_buffer[256];

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_KEYBOARD), 0, NULL);

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// initialize directinput and get keyboard device
    DirectInput8Create(inst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void **&g_directinput, NULL);

    
// initialize keyboard
    g_directinput_device = Init_Keyboard(g_hwnd, g_directinput);

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }
        
        
// read in keyboard and break if ESCAPE pressed
        Read_Device(g_directinput_device, key_state_buffer, sizeof(key_state_buffer));

        
if(key_state_buffer[DIK_ESCAPE] & 0x80)
            
break;
    }

    
// release directinput objects
    g_directinput_device->Unacquire();
    g_directinput_device
->Release();
    g_directinput
->Release();

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}


运行效果:


  很多中国人对中药充满了自信、中药也给他们带来很多“安全感”。这有些互惠互利的感觉:你相信我,我就给你安全感,而且让你担心一旦没有中医中药,中国一定会亡国亡种。这已经不是药了,也不是医学了,仿佛是中国人的寄主和守护神,而中国人则是中医中药的寄生虫。我又要批评那些支持中医的人数典忘祖了。在神农之前,生活在这片神奇土地上的原始人难道不是人么,怎么可以抹去中国没有中医的那段历史,原始中国人在山顶洞河姆渡男的狩猎女的采集的历史,你们怎么可以数典忘祖,轻松抹去中国原始人类没有中医的100多万年的历史?咦,原来没有中医,中国人居然也可以活下来,不但没亡种,而且还可以一直进化呢。中国人是突然蹦出来的么,是先有中医还是先有中国人?这道理就跟上帝造人说一样简单。先有了人类,人类再意淫出了上帝造人说。先有了中国人才有中医,中医是为了中国人活得更好而存在,当中医不再适应时代要求,则被残酷的淘汰。

  不过这仅仅是安全感而已,跟“国人幸福感”一样,都是幻觉而已,而且是病态的幻觉。病态到什么程度:即使汞超标11.7万倍,中医支持者还能觉得中医中药安全感。中华民族果然是勇敢勤劳善良的民族。

  中医支持者认为中医理论代表了未来医学发展的方向,未来的概念多长,地球的寿命是按亿来计算的。若是如此,中医要在未来产生多大的功绩,无法形容,这就很让人心里痒痒的,但又不能穿越到未来去验证一下。中医既然被很多中国人认为是未来医学的发展方向,于是目前的中医在中国产生的任何代价、任何镇痛、任何不安全、任何副作用,都被允许了;任何理性批评、任何科学探讨,都被拒绝了,这就是最恐怖的事情。我想到一句话:先污染,但也不治理。

  事实是中医连现代医学发展方向都没代表。

  中医支持者获取了这样病态的安全感,需要不断地靠意淫来强化“安全感”,但不会因为脑子吃不消就不意淫了,因为他们根本就没动过大脑。他们为了达到最大限度的意淫,他们有很好的办法以减少脑细胞的活动,就是根本不看方舟子何祚庥等人的文章在说些什么,也不管他们说得有道理还是没道理,只看标题和作者,就开始挥舞意淫的大棒,扣莫名其妙的帽子,如此滔滔不绝,然后觉得很自己爱国,于是很满意。若是遇到不知名的普通人,居然也批评中医,支持中医的人不会反思一下自己为什么支持中医,而会反问:你为什么反对中医?反问也是节省脑细胞运动量的方式。

  如果是把中医中药推上PK台,通过投票来决定中医中药的去留,我想中医肯定是要留的。但是中医理论号称是地球上人类未来医学发展的方向(如果有外星人医学的话,也是要靠中医理论来指引发展方向的,因为中医理论的理论基础——阴阳五行已然包含了宇宙全部奥秘,我不禁为外星人的医学状况而暗暗担忧起来,不得不建议每次外太空飞行器都搭载中医善本书,以拯救全宇宙),我觉得如果要投票的话,有必要让全地球的人来参加一下,支持中医的人肯定就没自信了,瘪了,不是“地球人都知道”中医。残存的清醒的理智以男低音的方式小声告诉他们中医在外国并不吃香并不被认同,当然这个“外国”不包括意淫大国——大韩民国。当然科学是不能靠投票来取胜的,科学也是不能仗着人多来取胜的。这样简单的道理,支持中医的人恰恰揣着明白装糊涂。

  鸡同鸭讲的感觉,鸡同鸭讲的事实,很期待中医支持者用科学的实验来证明中医可靠、中药安全、望闻问切的诊断有效。这比扣帽子、吹牛放炮更管用。

  很多或者绝大部分(99.99999%以上)的中医生一辈子都成不了神医、大师,但是一定可以成为“书法家”,而且是“爱国书法家”。支持中医的人其实生病时候很少看中医,但是口头上一定要说支持中医生的话:老先生,你的字写得真好,透出5000年的中国传统文化底蕴。

  5000年的传统文化能治病么?

  悠久历史的中医中药就是意大利面,让中医支持者不但能意淫,而且觉得有面子,它的安全性如何那是不重要的;让中医生,既可以用意念开药方又可以做受人爱戴的“爱国书法家”,这也是很有面子的事情。

作者:净凡皙/文责自负

DirectInput是一些COM对象的集合(和所有DirectX组件相同),这些COM对象描绘了输入系统和各个输入设备。最主要的对象是DirectInput8,它用于初始化系统以及创建输入设备接口。

DirectInput COM对象:

IDirectInput8:主要的DirectInput8 COM接口,其他所有接口都通过这个接口进行查询。
DirectInputDevice8:用于输入设备的COM接口,每个设备都有自己单独的接口可供使用。
DirectInputEffect:用于力反馈效果的 COM接口,比如某些游戏杆和某些鼠标上的力反馈效果。

各种输入设备(比如键盘、鼠标和游戏杆)都使用相同的接口对象IDirectInputDevice8。某些设备,比如游戏杆和鼠标,能够通过查询各自的IDirectInputDevice8对象以得到另外一个接口IDirectInputEffect,这个接口用于控制设备的力反馈效果。IDirectInput8组件对象包含了很多用于初始化输入系统以及获得设备接口的函数,在这些函数中,常用的只有两个,它们是IDirectInput8::EnumDevices和IDirectInput8::CreateDevice。

初始化DirectInput

要使用DirectInput,需要确保包含了DInput.h和在工程中链接了DInput8.lib,一个IDirectInput8对象就代表了主要DirectInput对象。
DirectInput提供了帮助函数DirectInput8Create用于初始化IDirectInput8接口。

Creates a Microsoft DirectInput object and returns an IDirectInput8 or later interface.

Syntax

HRESULT WINAPI DirectInput8Create(          HINSTANCE hinst,
    DWORD dwVersion,
    REFIID riidltf,
    LPVOID ppvOut,
    LPUNKNOWN punkOuter
);

Parameters

hinst
Instance handle to the application or dynamic-link library (DLL) that is creating the DirectInput object. DirectInput uses this value to determine whether the application or DLL has been certified and to establish any special behaviors that might be necessary for backward compatibility.

It is an error for a DLL to pass the handle to the parent application. For example, an Microsoft ActiveX control embedded in a Web page that uses DirectInput must pass its own instance handle, and not the handle to the browser. This ensures that DirectInput recognizes the control and can enable any special behaviors that might be necessary.

dwVersion
Version number of DirectInput for which the application is designed. This value is normally DIRECTINPUT_VERSION. If the application defines DIRECTINPUT_VERSION before including Dinput.h, the value must be greater than 0x0800. For earlier versions, use DirectInputCreateEx, which is in Dinput.lib.

riidltf
Unique identifier of the desired interface. This value is IID_IDirectInput8A or IID_IDirectInput8W. Passing the IID_IDirectInput8 define selects the ANSI or Unicode version of the interface, depending on whether UNICODE is defined during compilation.

ppvOut
Address of a pointer to a variable to receive the interface pointer if the call succeeds.

punkOuter
Pointer to the address of the controlling object’s IUnknown interface for Component Object Model (COM) aggregation, or NULL if the interface is not aggregated. Most calling applications pass NULL. If aggregation is requested, the object returned in ppvOut is a pointer to IUnknown, as required by COM aggregation.

使用DirectInput设备的步骤

1、获取设备GUID,调用IDirectInput8::EnumDevice来实现。
2、创建设备COM对象,调用IDirectInput8::CreateDevice来实现。
3、设置数据格式,调用IDirectInputDevice8::SetDataFormat来实现。
4、设置协作级别,调用IDirectInputDevice8::SetCooperativeLevel来实现。
5、设置任何特殊属性,调用IDirectInputDevice8::SetProperty来实现。
6、获得设备,调用IDirectInputDevice8::Acquire来实现。
7、轮询设备,调用IDirectInputDevice8::Poll来实现。
8、读取数据,调用IDirectInputDevice8::GetDeviceState来实现。

在进行这些步骤前,要确保声明了一个IDirectInput设备对象,即IDirectInputDevice8对象。

获取设备GUID

每个安装的设备都有一个系统分配的全局惟一标识符(global unique identification, GUID)数字。要使用一个设备,首先必须知道它的GUID。对于连接到系统上的鼠标和键盘,得到他们的GUID非常容易,DirectInput分别为鼠标和键盘的GUID定义成GUID_SysKeyboard和GUID_SysMouse。要使用GUID_SysKeyboard或GUID_SysMouse,必须在所有其他的预处理程序指令前定义INITGUID,或者将DXGuid.lib库链接到项目中。至于其他设备,必须枚举出这些设备,才能得到需要的那些设备的GUID。枚举就是遍历一个含有数据项的列表的过程,数据项就是诸如游戏杆之类的输入设备。假设有5个游戏杆连接到了系统上,那么在枚举的过程中,DirectInput就会传递各个游戏杆的相关信息,而且每次只传递一个游戏杆的信息,直到所有的游戏杆都已经被列出来或者列举被强行终止。

用于枚举设备的函数是IDirectInput8::EnumDevice。

Enumerates available devices.

Syntax

HRESULT EnumDevices(DWORD dwDevType,
    LPDIENUMDEVICESCALLBACK lpCallback,
    LPVOID pvRef,
    DWORD dwFlags
);

Parameters

dwDevType
Device type filter.

To restrict the enumeration to a particular type of device, set this parameter to a DI8DEVTYPE_
value. See DIDEVICEINSTANCE.

To enumerate a class of devices, use one of the following values.

DI8DEVCLASS_ALL
All devices.

DI8DEVCLASS_DEVICE
All devices that do not fall into another class.

DI8DEVCLASS_GAMECTRL
All game controllers.

DI8DEVCLASS_KEYBOARD
All keyboards. Equivalent to DI8DEVTYPE_KEYBOARD.

DI8DEVCLASS_POINTER
All devices of type DI8DEVTYPE_MOUSE and DI8DEVTYPE_SCREENPOINTER.

lpCallback
Address of a callback function to be called once for each device enumerated. See DIEnumDevicesCallback.

pvRef
Application-defined 32-bit value to be passed to the enumeration callback each time it is called.

dwFlags
Flag value that specifies the scope of the enumeration. This parameter can be one or more of the following values:

DIEDFL_ALLDEVICES
All installed devices are enumerated. This is the default behavior.

DIEDFL_ATTACHEDONLY
Only attached and installed devices.

DIEDFL_FORCEFEEDBACK
Only devices that support force feedback.

DIEDFL_INCLUDEALIASES
Include devices that are aliases for other devices.

DIEDFL_INCLUDEHIDDEN
Include hidden devices. For more information about hidden devices, see DIDEVCAPS.

DIEDFL_INCLUDEPHANTOMS
Include phantom (placeholder) devices.

Return Value

If the method succeeds, the return value is DI_OK.

If the method fails, the return value can be one of the following error values.

DIERR_INVALIDPARAM An invalid parameter was passed to the returning function, or the object was not in a state that permitted the function to be called. This value is equal to the E_INVALIDARG standard Component Object Model (COM) return value.
DIERR_NOTINITIALIZED The object has not been initialized.

Remarks

All installed devices can be enumerated, even if they are not present. For example, a flight stick might be installed on the system but not currently plugged into the computer. Set the dwFlags parameter to indicate whether only attached or all installed devices should be enumerated. If the DIEDFL_ATTACHEDONLY flag is not present, all installed devices are enumerated.

A preferred device type can be passed as a dwDevType filter so that only the devices of that type are enumerated.

On Microsoft Windows XP, Microsoft DirectInput enumerates only one mouse and one keyboard device, referred to as the system mouse and the system keyboard. These devices represent the combined output of all mice and keyboards respectively on a system. For information about how to read from multiple mice or keyboards individually on Windows XP, see the WM_INPUT documentation.

Note  The order in which devices are enumerated by DirectInput is not guaranteed.

lpCallback是一个指向枚举函数的指针,在系统上每找到一个匹配的设备时,就会调用该函数。

Application-defined callback function that receives Microsoft DirectInput devices as a result of a call to the IDirectInput8::EnumDevices method.

Syntax

BOOL CALLBACK DIEnumDevicesCallback(LPCDIDEVICEINSTANCE lpddi,
    LPVOID pvRef
);

Parameters

lpddi
Address of a DIDEVICEINSTANCE structure that describes the device instance.

pvRef
The application-defined value passed to IDirectInput8::EnumDevices or IDirectInput8::EnumDevicesBySemantics as the pvRef parameter.

Return Value

Returns DIENUM_CONTINUE to continue the enumeration or DIENUM_STOP to stop the enumeration.

Remarks

If a single hardware device can function as more than one DirectInput device type, it is enumerated as each device type that it supports. For example, a keyboard with a built-in mouse is enumerated twice: once as a keyboard and once as a mouse. The product globally unique identifier (GUID) is the same for each device, however.

lpddi是一个指向DIDEVICEINSTANCE结构体的指针,此结构体包含了此次调用时当前枚举设备上的信息。

Describes an instance of a Microsoft DirectInput device. This structure is used with the IDirectInput8::EnumDevices, IDirectInput8::EnumDevicesBySemantics, and IDirectInputDevice8::GetDeviceInfo methods.

Syntax

typedef struct DIDEVICEINSTANCE {
    DWORD dwSize;
    GUID  guidInstance;
    GUID  guidProduct;
    DWORD dwDevType;
    TCHAR tszInstanceName[MAX_PATH];
    TCHAR tszProductName[MAX_PATH];
    GUID  guidFFDriver;
    WORD  wUsagePage;
    WORD  wUsage;
} DIDEVICEINSTANCE, *LPDIDEVICEINSTANCE;
 
typedef const DIDEVICEINSTANCE  *LPCDIDEVICEINSTANCE;

Members

dwSize
Size of this structure, in bytes. This member must be initialized before the structure is used.

guidInstance
Unique identifier for the instance of the device. An application can save the instance globally unique identifier (GUID) into a configuration file and use it at a later time. Instance GUIDs are specific to a particular computer. An instance GUID obtained from one computer is unrelated to instance GUIDs on another.

guidProduct
Unique identifier for the product. This identifier is established by the manufacturer of the device.

dwDevType
Device type specifier. The least-significant byte of the device type description code specifies the device type. The next-significant byte specifies the device subtype. This value can also be combined with DIDEVTYPE_HID, which specifies a Human Interface Device (HID).

tszInstanceName
Friendly name for the instance. For example, “Joystick 1.”

tszProductName
Friendly name for the product.

guidFFDriver
Unique identifier for the driver being used for force feedback. The driver’s manufacturer establishes this identifier.

wUsagePage
If the device is a Human Interface Device (HID), this member contains the HID usage page code.

wUsage
If the device is a Human Interface Device (HID), this member contains the HID usage code.

创建设备COM对象

有了设备GUID,就能创建实际的IDirectInputDevice8 COM对象了,用于创建此COM对象的函数是IDirectInput8::CreateDevice。

Creates and initializes an instance of a device based on a given globally unique identifier (GUID), and obtains an IDirectInputDevice8 interface.

Syntax

HRESULT CreateDevice(REFGUID rguid,
    LPDIRECTINPUTDEVICE *lplpDirectInputDevice,
    LPUNKNOWN pUnkOuter
);

Parameters

rguid
Reference to (C++) or address of (C) the instance GUID for the desired input device (see Remarks). The GUID is retrieved through the IDirectInput8::EnumDevices method, or it can be one of the predefined GUIDs listed below. For the following GUID values to be valid, your application must define INITGUID before all other preprocessor directives at the beginning of the source file, or link to Dxguid.lib.

GUID_SysKeyboard
The default system keyboard.

GUID_SysMouse
The default system mouse.

lplpDirectInputDevice
Address of a variable to receive the IDirectInputDevice8 interface pointer if successful.

pUnkOuter
Address of the controlling object’s IUnknown interface for Component Object Model (COM) aggregation, or NULL if the interface is not aggregated. Most calling applications pass NULL.

Return Value

If the method succeeds, the return value is DI_OK.

If the method fails, the return value can be one of the following:

DIERR_DEVICENOTREG: The device or device instance is not registered with Microsoft DirectInput. This value is equal to the REGDB_E_CLASSNOTREG standard COM return value.

DIERR_INVALIDPARAM: An invalid parameter was passed to the returning function, or the object was not in a state that permitted the function to be called. This value is equal to the E_INVALIDARG standard COM return value.

DIERR_NOINTERFACE: The specified interface is not supported by the object. This value is equal to the E_NOINTERFACE standard COM return value.

DIERR_NOTINITIALIZED: The object has not been initialized.

DIERR_OUTOFMEMORY: The DirectInput subsystem couldn’t allocate sufficient memory to complete the call. This value is equal to the E_OUTOFMEMORY standard COM return value.

Remarks

Calling this method with pUnkOuter = NULL is equivalent to creating the object by CoCreateInstance (&CLSID_DirectInputDevice, NULL, CLSCTX_INPROC_SERVER, riid, lplpDirectInputDevice) and then initializing it with Initialize.

Calling this method with pUnkOuter != NULL is equivalent to creating the object by CoCreateInstance (&CLSID_DirectInputDevice, punkOuter, CLSCTX_INPROC_SERVER, &IID_IUnknown, lplpDirectInputDevice). The aggregated object must be initialized manually.

点击下载源码和资源

在最底层的层次中,Direct3D并不使用网格模型,而只是使用多边形。D3DX增强了 Direct3D系统的功能性,添加了一系列负责处理网格模型的容器和进行渲染的对象。.X文件是微软公司所开发的,高度通用的三维模型存储格式。它是模板驱动并完全可扩展,这就意味着可以使用它来满足文件存储的所有需求。一个.X文件,正如它的文件扩展名所表明的,是非常通用的。它可以是基于文本的,以便更容易进行编辑;或者是基于二进制的,这样可以使文件更小,并且更容易地进行保护以便不被窥视。整个.X文件格式是基于模板的,非常类似于C语言结构。

为了读取并处理一个.X文件,可以利用COM对象的一个小集合来解析从头到尾在.X文件中所遇到的每个数据对象。将数据对象作为一个字节的数组进行处理;仅仅是将数组转换为一种可使用的结构,以便能够容易地访问到包含在对象里的数据。

根据存储在.X文件里的内容,这些对象可以改变。在这里,对象代表了网格模型以及网格模型相关的数据(例如骨骼结构,所谓的框架层次和动画数据)。程序员的工作就是去解析这些对象,加载网格模型数据,创建动画表格,并构造框架的层次。

.X文件格式的详细介绍请参阅XFile网格的应用(1)

框架层次的运用

使用框架模板(frame template)将一个或多个数据对象(通常为网格模型)进行分组,以便能够更容易地进行处理。也可以创建一个网格模型,并使用多个框架去包含网格模型引用,这样就能够对一个网格模型使用许多次。一个框架层次(frame hierarchy)定义了一个场景的结构,或者网格模型的分组,每当一个框架移动时,所有嵌入其中的框架也同样产生移动。根没有父框架,意味着他就是层次的顶端,且不属于任何别的框架。被连接到其他框架上的框架称之为子框架(child frames)(也可以称之为节点(node))。

当一个框架移动时,它的所有子框架也随之移动。例如如果移动上臂,前臂和手也跟着移动,而另一方面,如果移动人的手,只有手会改变位置,因为它没有子框架(前臂是手的父框架)。每个框架都有它自己的方位,在.X文件的术语中称之为框架变换(frame transformation)。将这种变换运用到较高层次的对象上,每个变换将被传递下去,从层次的顶端以各种方式到达每个子框架。

框架层次是使用高级网格模型和动画技术的要点,事实上,它们是某些运用所必需的,诸如蒙皮网格模型。使用框架层次,另外一个原因是隔离场景其中一部分,以这种方式,可以通过移动特定的框架来修改场景中的一小块区域,而保留场景的其余部分。

解析.X文件

为了解析一个.X文件,需要使用 ID3DXFile对象,它的工作就是打开一个.X文件并枚举文件中的数据对象,再将他们以一种易于访问的方式展现给用户。为了使用ID3DXFile组件,应该包含dxfile.h,rmfxguid.h,rmfxtmpl.h,同时还必须将dxguid.lib和d3ddxof.lib库链接到工程中。解析一个.X文件并不像开始时所看到的那样困难,窍门就是搜索整个对象的层次,查找想要使用的数据对象,也就是网格模型和框架的对象。

代码示例如下所示:

//------------------------------------------------------------------
// A Mesh definition structure
//------------------------------------------------------------------
typedef struct MESH
{
    
char*               m_name;             // name of mesh

    ID3DXMesh
*          m_mesh;             // mesh object
    ID3DXMesh*          m_skinmesh;         // skin mesh object
    ID3DXSkinInfo*      m_skininfo;         // skin information

    DWORD               m_num_materials;    
// number of materails in mesh
    D3DMATERIAL9*       m_materials;        // array of materials
    IDirect3DTexture9** m_textures;         // array of textures    

    
// clear all structure data
    MESH()
    {
        m_name  
= NULL;     

        m_mesh      
= NULL;
        m_skinmesh  
= NULL;
        m_skininfo  
= NULL;

        m_num_materials 
= 0;
        m_materials     
= NULL;
        m_textures      
= NULL;               
    }

    
// free all used resources
    ~MESH()
    {        
        delete[] m_name;
        m_name 
= NULL;

        Release_Com(m_mesh);
        Release_Com(m_skinmesh);
        Release_Com(m_skininfo);

        delete[] m_materials;
        m_materials 
= NULL;
        
        
// release all textures resource
        if(m_textures != NULL)
        {
            
for(DWORD i = 0; i < m_num_materials; i++)
                Release_Com(m_textures[i]);

            delete[] m_textures;
            m_textures 
= NULL;
        }
    }

} MESH;

//------------------------------------------------------------------
// Structure to contain frame information
//------------------------------------------------------------------
typedef struct FRAME
{
    
char*   m_name;     // frame's name
    MESH*   m_mesh;     // linked list of meshes    
    FRAME*  m_child;    // child frame

    FRAME()
    {
        
// clear all data
        m_name = NULL;
        m_mesh 
= NULL;
        m_child 
= NULL;
    }

    
~FRAME()
    {
        
// delete all used resources, including linked list of frames.
        delete[] m_name;    m_name    = NULL;
        delete m_mesh;      m_mesh    
= NULL;
        delete m_child;     m_child   
= NULL;        
    }

} FRAME;

// parent frame for .X file
FRAME* g_parent_frame = NULL;

//--------------------------------------------------------------------------------
// Parse specified xfiel data, recursive function.
//--------------------------------------------------------------------------------
void Parse_XFile_Data(ID3DXFileData* xfile_data, FRAME* parent_frame)
{
    ID3DXFileData
*  sub_xfile_data = NULL;
    ID3DXBuffer
*    adjacency = NULL;

    GUID  type;
    
char* name = NULL;
    DWORD size;

    MESH
* mesh = NULL;
    ID3DXBuffer
* material_buffer = NULL;
    D3DXMATERIAL
* materials = NULL;

    
// get the template type
    
// retrieves the globally unique identifier (GUID) of the object's template
    if(FAILED(xfile_data->GetType(&type)))
        
return;

    
// get the template name (if any)
    
// retrieves a pointer to a microsoft directX file object's name
    if(FAILED(xfile_data->GetName(NULL, &size)))
        
return;

    
if(size != 0)
    {
        
if((name = new char[size]) != NULL)
            xfile_data
->GetName(name, &size);
    }

    
// give template a default name if none found
    if(name == NULL)
    {
        
if((name = new char[9]) == NULL)
            
return;

        strcpy(name, 
"Template");
    }

    
// set sub frame
    FRAME* sub_frame = parent_frame;

    
// process the templates
    FRAME* frame = NULL;

    
if(type == TID_D3DRMFrame)  // it is a frame
    {
        
// create a new frame structure
        frame = new FRAME();

        
// store the name
        frame->m_name = name;
        name 
= NULL;

        
// add to parent frame
        parent_frame->m_child = frame;

        
// set sub frame parent
        sub_frame = frame;
    }
    
else if(type == TID_D3DRMMesh)  // it is a mesh
    {
        
// create a new mesh structure
        mesh = new MESH();

        
// store the name
        mesh->m_name = name;
        name 
= NULL;

        
// load mesh data (as a skinned mesh)
        
// loads a skin mesh from microsoft directX .x file data object
        if(FAILED(D3DXLoadSkinMeshFromXof(xfile_data, 0, g_d3d_device, &adjacency, &material_buffer, NULL,
            
&mesh->m_num_materials, &mesh->m_skininfo, &mesh->m_mesh)))
        {
            delete[] name;
            delete mesh;
            
return;
        }

        Release_Com(adjacency);

        
// clone skin mesh if bones exist
        if(mesh->m_skininfo != NULL && mesh->m_skininfo->GetNumBones() != 0)
        {
            
// clones a mesh using a flexible vertex format (FVF) code
            if(FAILED(mesh->m_mesh->CloneMeshFVF(0, mesh->m_mesh->GetFVF(), g_d3d_device, &mesh->m_skinmesh)))
            {
                mesh
->m_skininfo->Release();
                mesh
->m_skininfo = NULL;
            }
        }

        
// load materials or create a default one if none
        if(mesh->m_num_materials == 0)
        {
            
// create a default one
            mesh->m_materials = new D3DMATERIAL9[1];
            mesh
->m_textures  = new LPDIRECT3DTEXTURE9[1];

            ZeroMemory(mesh
->m_materials, sizeof(D3DMATERIAL9));

            mesh
->m_materials[0].Diffuse.r = 1.0;
            mesh
->m_materials[0].Diffuse.g = 1.0;
            mesh
->m_materials[0].Diffuse.b = 1.0;
            mesh
->m_materials[0].Diffuse.a = 1.0;
            mesh
->m_materials[0].Ambient   = mesh->m_materials[0].Diffuse;
            mesh
->m_materials[0].Specular  = mesh->m_materials[0].Diffuse;

            mesh
->m_textures[0= NULL;

            mesh
->m_num_materials = 1;
        }
        
else
        {
            
// load the materials
            materials = (D3DXMATERIAL*) material_buffer->GetBufferPointer();

            mesh
->m_materials = new D3DMATERIAL9[mesh->m_num_materials];
            mesh
->m_textures  = new LPDIRECT3DTEXTURE9[mesh->m_num_materials];

            
// set materials and textures for mesh
            for(DWORD i = 0; i < mesh->m_num_materials; i++)
            {
                mesh
->m_materials[i] = materials[i].MatD3D;
                mesh
->m_materials[i].Ambient = mesh->m_materials[i].Diffuse;

                
// build a texture path and load it
                if(FAILED(D3DXCreateTextureFromFile(g_d3d_device, materials[i].pTextureFilename, &mesh->m_textures[i])))
                    mesh
->m_textures[i] = NULL;
            }
        }

        Release_Com(material_buffer);

        
// set mesh to parent frame
        parent_frame->m_mesh = mesh;
    }   
// end if(type == TID_D3DRMMesh)
    else if(type == TID_D3DRMAnimationSet || type == TID_D3DRMAnimation || type == TID_D3DRMAnimationKey)
    {
        
// skip animation sets and animations
        delete[] name;
        
return;
    }

    
// release name buffer
    delete[] name;

    SIZE_T num_child;
    xfile_data
->GetChildren(&num_child);

    
// scan for embedded templates
    for(SIZE_T i = 0; i < num_child; i++)
    {
        xfile_data
->GetChild(i, &sub_xfile_data);

        
// process embedded xfile data, recursive call.
        Parse_XFile_Data(sub_xfile_data, sub_frame);

        Release_Com(sub_xfile_data);
    }
}

//--------------------------------------------------------------------------------
// Parse x file, and return root frame.
//--------------------------------------------------------------------------------
FRAME* Parse_XFile(char* filename)
{
    ID3DXFile
* xfile = NULL;
    ID3DXFileEnumObject
* xfile_enum = NULL;
    ID3DXFileData
* xfile_data = NULL;

    
// create the file object
    if(FAILED(D3DXFileCreate(&xfile)))
        
return NULL;

    
// register the templates
    if(FAILED(xfile->RegisterTemplates((LPVOID) D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES)))
    {
        xfile
->Release();
        
return NULL;
    }

    
// create an enumerator object that will read a .x file
    if(FAILED(xfile->CreateEnumObject((LPVOID) filename, DXFILELOAD_FROMFILE, &xfile_enum)))
    {
        xfile
->Release();
        
return NULL;
    }

    
// allocate a frame that becomes root
    FRAME* frame = new FRAME();

    SIZE_T num_child;

    
// retrieve the number of children in this file data object
    xfile_enum->GetChildren(&num_child);

    
// loop through all objects looking for the frames and meshes
    for(SIZE_T i = 0; i < num_child; i++)
    {
        
// retrieves a child object in this file data object
        if(FAILED(xfile_enum->GetChild(i, &xfile_data)))
            
return NULL;

        
// parse xfile data
        Parse_XFile_Data(xfile_data, frame);

        Release_Com(xfile_data);
    }

    
// release xfile enumerator object and xfile object
    Release_Com(xfile_enum);
    Release_Com(xfile);

    
// return root frame
    return frame;
}

Parse_XFile和Parse_XFile_Data协同工作以解析.X文件里的每个数据对象,Parse_XFile功能函数打开.X文件并枚举它,以查找层次结构中最顶层的对象,每个被找到的对象都将被传递给Parse_XFile_Data功能函数。 Parse_XFile_Data功能函数负责处理数据对象,它从获取对象的类型以及对象的实例名(如果有的话)开始进行处理。从那里,可以处理对象数据,然后递归地调用功能函数以便枚举所有的子对象,这个处理过程持续不断,直到所有的数据对象都被处理。

网格模型和D3DX

基本上,在Direct3D中使用到两种类型的网格模型,标准网格模型(standard mesh)和蒙皮网格模型(skinned mesh)。所谓的标准网格模型,除了可以使用纹理映射以增强外观外,它没有什么花哨的功能。蒙皮网格模型的独特之处在于它们是可变形的 (deformable),也就是说,网格模型可以在运行当中动态地改变它的形状。为网格模型的变形做准备,必须在三维建模程序中将网格模型的顶点附属到一个虚构的骨骼集中,在任何骨骼移动的时候,附属其上的顶点也将跟着移动。

ID3DXBuffer对象

ID3DXBuffer对象用于存储并检索数据的缓冲区,D3DX使用ID3DXBuffer对象去存储网格模型所涉及的信息,例如材质和纹理映射的列表。ID3DXBuffer仅有两个功能函数。第1个功能函数是ID3DXBuffer::GetBufferPointer,可以使用它去获取一个只想包含在对象缓冲区里的数据的指针。 GetBufferPointer功能函数的调用将返回一个可以强制转换成任何数据类型的指针:

Retrieves a pointer to the data in the buffer.

LPVOID GetBufferPointer(VOID);

第2个功能函数是ID3DXBuffer:: GetBufferSize,它返回存储数据所需的字节数。

Retrieves the total size of the data in the buffer.

DWORD GetBufferSize(VOID);

标准网格模型

一个标准的网格模型十分简单,它仅包含一个单一网格模型的定义,使用D3DX来处理标准网格模型尤其容易,因为D3DX仅要求使用一个简短的代码序列去装载并显示标准网格模型,标准网格模型有一个 ID3DXMESH对象表示,它的任务就是存储并绘制一个单一的网格模型。

在实例化一个ID3DXMESH对象后,使用下面的功能函数从.X文件中加载网格模型的对象。

HRESULT WINAPI D3DXLoadMeshFromXof(LPD3DXFILEDATA pxofMesh,
    DWORD Options,
    LPDIRECT3DDEVICE9 pDevice,
    LPD3DXBUFFER *ppAdjacency,
    LPD3DXBUFFER *ppMaterials,
    LPD3DXBUFFER *ppEffectInstances,
    DWORD *pNumMaterials,
    LPD3DXMESH *ppMesh
);

Parameters

pxofMesh
[in] Pointer to an ID3DXFileData interface, representing the file data object to load.

Options
[out] Combination of one or more flags from the D3DXMESH enumeration, specifying creation options for the mesh.

pDevice
[in] Pointer to an IDirect3DDevice9 interface, the device object associated with the mesh.

ppAdjacency
[out] Pointer to a buffer that contains adjacency data. The adjacency data contains an array of three DWORDs per face that specify the three neighbors for each face in the mesh. For more information about accessing the buffer, see ID3DXBuffer.

ppMaterials
[in, out] Address of a pointer to an ID3DXBuffer interface. When the method returns, this parameter is filled with an array of D3DXMATERIAL structures.

ppEffectInstances
[out] Pointer to a buffer containing an array of effect instances, one per attribute group in the returned mesh. An effect instance is a particular instance of state information used to initialize an effect. See D3DXEFFECTINSTANCE. For more information about accessing the buffer, see ID3DXBuffer.

pNumMaterials
[in, out] Pointer to the number of D3DXMATERIAL structures in the ppMaterials array, when the method returns.

ppMesh
[out, retval] Address of a pointer to an ID3DXMesh interface, representing the loaded mesh.

D3DXLoadMeshFromXof函数里的大多数参数是在执行期间由D3DX来填入的,用户只需提供所加载的.X文件名,一个预初始化的ID3DXBuffer和ID3DXMesh对象,以及一个DWORD变量去存储在网格模型中所使用的材质的数量。

渲染网格模型

ID3DXMesh对象的核心是一个称之为 DrawSubset的渲染功能函数,它的工作是渲染网格模型的一个子集。一个子集(subset)就是一个网格模型的一个部分,它根据渲染的不同情况被分隔开来,例如不同材质或纹理的子集。可以将一个网格模型分割成许多子集,程序员的工作就是去领会每个子集所代表的是什么,并渲染它。

在加载好一个.X文件后,便获得了一个网格模型对象以及它的材质。网格模型的子集与那些材质相关联,所以如果在一个网格模型里有5种材质,那么网格模型就包含了5个要绘制的子集。子集的布置允许用户能够容易地渲染一个网格模型,只需要简单地搜索每种材质,设置它,然后渲染子集,重复这些步骤直到绘制完整个网格实例。为了定位世界中的网格模型,需要在进行绘制之前设置世界变换矩阵。

示例代码如下:

       // render the mesh
        for(DWORD i = 0; i < mesh->m_num_materials; i++)
        {
            
// set the materials properties for the device
            g_d3d_device->SetMaterial(&mesh->m_materials[i]);

            
// assigns a texture to a stage for a device
            g_d3d_device->SetTexture(0, mesh->m_textures[i]);

            
// draw a subset of a mesh
            mesh_to_draw->DrawSubset(i);
        }


蒙皮网格模型

Direct3D中一个最令人兴奋的就是蒙皮网格模型,一个蒙皮网格模型可以动态地改变形状。通过连接那些构造“骨骼”底层结构中网格模型的各自顶点,或框架层次来实现它。一个蒙皮网格模型使用骨骼去定义它的轮廓,当骨骼移动时,网格模型改变形状以便与之相匹配。骨骼在一个.X文件里被表示为一个框架层次,当构造网格模型时,以一种父与子的形式来连接框架。当一个父框架被重新定向时,所有附属其上的子框架继承父框架的变换,并兼有它们自身的变换,这使得动画的实现更加容易,只需移动一个框架,其所有附属框架也将相应地移动。

要加载并使用一个蒙皮网格模型,可以直接处理.X文件的数据对象。枚举包含在一个.X文件里的网格模型的数据对象时,需要调用各种不同的D3DX网格模型加载功能函数去处理那些对象数据。当加载蒙皮网格模型时,要用到的一个功能函数就是 D3DXLoadSkinMeshFromXof。这个功能函数的作用就是从.X文件里读取网格模型的数据对象,创建一个包含网格模型的 ID3DXMesh对象,同时创建一个ID3DXSkinInfo对象,该对象描述了网格模型变形所需要的骨骼到顶点的连接数据。

要使用蒙皮网格模型,需要创建两个网格模型容器。第一个网格模型负责从.X文件进行加载的工作,它确实是一个标准的网格模型,使用了一个ID3DXMesh对象去包含网格模型的信息。在这里要做的就是去克隆(复制)该网格模型,以便渲染变形后的蒙皮网格模型。

为了创建这个克隆的网格模型,调用网格模型的CloneMeshFVF功能函数:

Clones a mesh using a flexible vertex format (FVF) code.

Syntax

HRESULT CloneMeshFVF(DWORD Options,
    DWORD FVF,
    LPDIRECT3DDEVICE9 pDevice,
    LPD3DXMESH *ppCloneMesh
);

Parameters

Options
[in] A combination of one or more D3DXMESH flags specifying creation options for the mesh.

FVF
[in] Combination of FVF codes, which specifies the vertex format for the vertices in the output mesh. For the values of the codes, see D3DFVF.

pDevice
[in] Pointer to an IDirect3DDevice9 interface representing the device object associated with the mesh.

ppCloneMesh
[out, retval] Address of a pointer to an ID3DXMesh interface, representing the cloned mesh.

可以这么使用这个函数:

        // clone skin mesh if bones exist
        if(mesh->m_skininfo != NULL && mesh->m_skininfo->GetNumBones() != 0)
        {
            
// clones a mesh using a flexible vertex format (FVF) code
            if(FAILED(mesh->m_mesh->CloneMeshFVF(0, mesh->m_mesh->GetFVF(), g_d3d_device, &mesh->m_skinmesh)))
            {
                mesh
->m_skininfo->Release();
                mesh
->m_skininfo = NULL;
            }
        }

要修改骨骼的变换,必须构造一个D3DXMATRIX对象的数组(每个骨骼都有一个矩阵)。因此,如果蒙皮网格模型使用了10块骨骼,那么就需要10个D3DXMATRIX对象去包含每块骨骼的变换。现在开始存储每块骨骼的各种各样的变换,需要记住的重要事情就是,每块骨骼都继承了它的父框架的变换,因此,改变一个骨骼的定位时,存在某种程度的连锁效应。

一旦使用了变换,就能更新蒙皮网格模型的顶点并渲染网格模型,为了更新蒙皮网格模型,需要锁定初始网格模型和蒙皮网格模型的(所克隆的辅助网格模型)顶点缓冲区:

void* source = NULL;
void* dest = NULL;

// locks a vertex buffer and obtains a pointer to the vertex buffer memory
mesh->m_mesh->LockVertexBuffer(0&source);
mesh
->m_skinmesh->LockVertexBuffer(0&dest);

一旦锁定,调用ID3DXSkinInfo::UpdateSkinnedMesh功能函数去变换蒙皮网格模型里的所有顶点,以便匹配在D3DXMATRIX数组里所设置的骨骼的方向。

// update skinned mesh, applies software skinning to the target vertices based on the current matrices.
 mesh->m_skininfo->UpdateSkinnedMesh(matrices, NULL, source, dest);

最后,解锁顶点缓冲区:

// unlock buffers
mesh->m_skinmesh->UnlockVertexBuffer();
mesh
->m_mesh->UnlockVertexBuffer();


完整的示例代码如下所示:

/***************************************************************************************
PURPOSE:
    XFile/Skinned Mesh Demo
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
"d3d9.h"
#include 
"d3dx9.h"
#include 
"DxFile.h"
#include 
"RmxfGuid.h"
#include 
"RmxfTmpl.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"winmm.lib")
#pragma comment(lib, 
"d3d9.lib")
#pragma comment(lib, 
"d3dx9.lib")
#pragma comment(lib, 
"d3dxof.lib")

#pragma warning(disable : 
4305 4996)

#define WINDOW_WIDTH    400
#define WINDOW_HEIGHT   400

#define Safe_Release(p) if(p) (p)->Release();
#define Release_Com(p)  if(p)  { (p)->Release(); (p) = NULL; }

// window handles, class and caption text.
HWND g_hwnd;
HINSTANCE g_inst;
static char g_class_name[] = "XFileClass";
static char g_caption[]    = "XFile Demo";

// the Direct3D and device object
IDirect3D9* g_d3d = NULL;
IDirect3DDevice9
* g_d3d_device = NULL;

//------------------------------------------------------------------
// A Mesh definition structure
//------------------------------------------------------------------
typedef struct MESH
{
    
char*               m_name;             // name of mesh

    ID3DXMesh
*          m_mesh;             // mesh object
    ID3DXMesh*          m_skinmesh;         // skin mesh object
    ID3DXSkinInfo*      m_skininfo;         // skin information

    DWORD               m_num_materials;    
// number of materails in mesh
    D3DMATERIAL9*       m_materials;        // array of materials
    IDirect3DTexture9** m_textures;         // array of textures    

    
// clear all structure data
    MESH()
    {
        m_name  
= NULL;     

        m_mesh      
= NULL;
        m_skinmesh  
= NULL;
        m_skininfo  
= NULL;

        m_num_materials 
= 0;
        m_materials     
= NULL;
        m_textures      
= NULL;               
    }

    
// free all used resources
    ~MESH()
    {        
        delete[] m_name;
        m_name 
= NULL;

        Release_Com(m_mesh);
        Release_Com(m_skinmesh);
        Release_Com(m_skininfo);

        delete[] m_materials;
        m_materials 
= NULL;
        
        
// release all textures resource
        if(m_textures != NULL)
        {
            
for(DWORD i = 0; i < m_num_materials; i++)
                Release_Com(m_textures[i]);

            delete[] m_textures;
            m_textures 
= NULL;
        }
    }

} MESH;

//------------------------------------------------------------------
// Structure to contain frame information
//------------------------------------------------------------------
typedef struct FRAME
{
    
char*   m_name;     // frame's name
    MESH*   m_mesh;     // linked list of meshes    
    FRAME*  m_child;    // child frame

    FRAME()
    {
        
// clear all data
        m_name = NULL;
        m_mesh 
= NULL;
        m_child 
= NULL;
    }

    
~FRAME()
    {
        
// delete all used resources, including linked list of frames.
        delete[] m_name;    m_name    = NULL;
        delete m_mesh;      m_mesh    
= NULL;
        delete m_child;     m_child   
= NULL;        
    }

} FRAME;

// parent frame for .X file
FRAME* g_parent_frame = NULL;

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Parse specified xfiel data, recursive function.
//--------------------------------------------------------------------------------
void Parse_XFile_Data(ID3DXFileData* xfile_data, FRAME* parent_frame)
{
    ID3DXFileData
*  sub_xfile_data = NULL;
    ID3DXBuffer
*    adjacency = NULL;

    GUID  type;
    
char* name = NULL;
    DWORD size;

    MESH
* mesh = NULL;
    ID3DXBuffer
* material_buffer = NULL;
    D3DXMATERIAL
* materials = NULL;

    
// get the template type
    
// retrieves the globally unique identifier (GUID) of the object's template
    if(FAILED(xfile_data->GetType(&type)))
        
return;

    
// get the template name (if any)
    
// retrieves a pointer to a microsoft directX file object's name
    if(FAILED(xfile_data->GetName(NULL, &size)))
        
return;

    
if(size != 0)
    {
        
if((name = new char[size]) != NULL)
            xfile_data
->GetName(name, &size);
    }

    
// give template a default name if none found
    if(name == NULL)
    {
        
if((name = new char[9]) == NULL)
            
return;

        strcpy(name, 
"Template");
    }

    
// set sub frame
    FRAME* sub_frame = parent_frame;

    
// process the templates
    FRAME* frame = NULL;

    
if(type == TID_D3DRMFrame)  // it is a frame
    {
        
// create a new frame structure
        frame = new FRAME();

        
// store the name
        frame->m_name = name;
        name 
= NULL;

        
// add to parent frame
        parent_frame->m_child = frame;

        
// set sub frame parent
        sub_frame = frame;
    }
    
else if(type == TID_D3DRMMesh)  // it is a mesh
    {
        
// create a new mesh structure
        mesh = new MESH();

        
// store the name
        mesh->m_name = name;
        name 
= NULL;

        
// load mesh data (as a skinned mesh)
        
// loads a skin mesh from microsoft directX .x file data object
        if(FAILED(D3DXLoadSkinMeshFromXof(xfile_data, 0, g_d3d_device, &adjacency, &material_buffer, NULL,
            
&mesh->m_num_materials, &mesh->m_skininfo, &mesh->m_mesh)))
        {
            delete[] name;
            delete mesh;
            
return;
        }

        Release_Com(adjacency);

        
// clone skin mesh if bones exist
        if(mesh->m_skininfo != NULL && mesh->m_skininfo->GetNumBones() != 0)
        {
            
// clones a mesh using a flexible vertex format (FVF) code
            if(FAILED(mesh->m_mesh->CloneMeshFVF(0, mesh->m_mesh->GetFVF(), g_d3d_device, &mesh->m_skinmesh)))
            {
                mesh
->m_skininfo->Release();
                mesh
->m_skininfo = NULL;
            }
        }

        
// load materials or create a default one if none
        if(mesh->m_num_materials == 0)
        {
            
// create a default one
            mesh->m_materials = new D3DMATERIAL9[1];
            mesh
->m_textures  = new LPDIRECT3DTEXTURE9[1];

            ZeroMemory(mesh
->m_materials, sizeof(D3DMATERIAL9));

            mesh
->m_materials[0].Diffuse.r = 1.0;
            mesh
->m_materials[0].Diffuse.g = 1.0;
            mesh
->m_materials[0].Diffuse.b = 1.0;
            mesh
->m_materials[0].Diffuse.a = 1.0;
            mesh
->m_materials[0].Ambient   = mesh->m_materials[0].Diffuse;
            mesh
->m_materials[0].Specular  = mesh->m_materials[0].Diffuse;

            mesh
->m_textures[0= NULL;

            mesh
->m_num_materials = 1;
        }
        
else
        {
            
// load the materials
            materials = (D3DXMATERIAL*) material_buffer->GetBufferPointer();

            mesh
->m_materials = new D3DMATERIAL9[mesh->m_num_materials];
            mesh
->m_textures  = new LPDIRECT3DTEXTURE9[mesh->m_num_materials];

            
// set materials and textures for mesh
            for(DWORD i = 0; i < mesh->m_num_materials; i++)
            {
                mesh
->m_materials[i] = materials[i].MatD3D;
                mesh
->m_materials[i].Ambient = mesh->m_materials[i].Diffuse;

                
// build a texture path and load it
                if(FAILED(D3DXCreateTextureFromFile(g_d3d_device, materials[i].pTextureFilename, &mesh->m_textures[i])))
                    mesh
->m_textures[i] = NULL;
            }
        }

        Release_Com(material_buffer);

        
// set mesh to parent frame
        parent_frame->m_mesh = mesh;
    }   
// end if(type == TID_D3DRMMesh)
    else if(type == TID_D3DRMAnimationSet || type == TID_D3DRMAnimation || type == TID_D3DRMAnimationKey)
    {
        
// skip animation sets and animations
        delete[] name;
        
return;
    }

    
// release name buffer
    delete[] name;

    SIZE_T num_child;
    xfile_data
->GetChildren(&num_child);

    
// scan for embedded templates
    for(SIZE_T i = 0; i < num_child; i++)
    {
        xfile_data
->GetChild(i, &sub_xfile_data);

        
// process embedded xfile data, recursive call.
        Parse_XFile_Data(sub_xfile_data, sub_frame);

        Release_Com(sub_xfile_data);
    }
}

//--------------------------------------------------------------------------------
// Parse x file, and return root frame.
//--------------------------------------------------------------------------------
FRAME* Parse_XFile(char* filename)
{
    ID3DXFile
* xfile = NULL;
    ID3DXFileEnumObject
* xfile_enum = NULL;
    ID3DXFileData
* xfile_data = NULL;

    
// create the file object
    if(FAILED(D3DXFileCreate(&xfile)))
        
return NULL;

    
// register the templates
    if(FAILED(xfile->RegisterTemplates((LPVOID) D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES)))
    {
        xfile
->Release();
        
return NULL;
    }

    
// create an enumerator object that will read a .x file
    if(FAILED(xfile->CreateEnumObject((LPVOID) filename, DXFILELOAD_FROMFILE, &xfile_enum)))
    {
        xfile
->Release();
        
return NULL;
    }

    
// allocate a frame that becomes root
    FRAME* frame = new FRAME();

    SIZE_T num_child;

    
// retrieve the number of children in this file data object
    xfile_enum->GetChildren(&num_child);

    
// loop through all objects looking for the frames and meshes
    for(SIZE_T i = 0; i < num_child; i++)
    {
        
// retrieves a child object in this file data object
        if(FAILED(xfile_enum->GetChild(i, &xfile_data)))
            
return NULL;

        
// parse xfile data
        Parse_XFile_Data(xfile_data, frame);

        Release_Com(xfile_data);
    }

    
// release xfile enumerator object and xfile object
    Release_Com(xfile_enum);
    Release_Com(xfile);

    
// return root frame
    return frame;
}

//--------------------------------------------------------------------------------
// Initialize d3d, d3d device, vertex buffer; set render state for d3d;
// set perspective matrix and view matrix, load xfile.
//--------------------------------------------------------------------------------
BOOL Do_Init()
{
    D3DPRESENT_PARAMETERS present_param;
    D3DDISPLAYMODE  display_mode;
    D3DXMATRIX mat_proj, mat_view;

    
// do a windowed mode initialization of Direct3D
    if((g_d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
        
return FALSE;

    
// retrieves the current display mode of the adapter
    if(FAILED(g_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
        
return FALSE;

    ZeroMemory(
&present_param, sizeof(present_param));

    
// initialize d3d presentation parameter
    present_param.Windowed               = TRUE;
    present_param.SwapEffect             
= D3DSWAPEFFECT_DISCARD;
    present_param.BackBufferFormat       
= display_mode.Format;
    present_param.EnableAutoDepthStencil 
= TRUE;
    present_param.AutoDepthStencilFormat 
= D3DFMT_D16;

    
// creates a device to represent the display adapter
    if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
&present_param, &g_d3d_device)))
        
return FALSE;     

    
// set render state

    
// disable d3d lighting
    g_d3d_device->SetRenderState(D3DRS_LIGHTING, FALSE);
    
// enable z-buffer
    g_d3d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

    
// create and set the projection matrix

    
// builds a left-handed perspective projection matrix based on a field of view
    D3DXMatrixPerspectiveFovLH(&mat_proj, D3DX_PI/4.01.333331.01000.0);

    
// sets a single device transformation-related state
    g_d3d_device->SetTransform(D3DTS_PROJECTION, &mat_proj);

    
// create and set the view matrix
    D3DXMatrixLookAtLH(&mat_view, 
                       
&D3DXVECTOR3(0.050.0-150.0),
                       
&D3DXVECTOR3(0.050.0,  0.0), 
                       
&D3DXVECTOR3(0.01.0,   0.0));

    g_d3d_device
->SetTransform(D3DTS_VIEW, &mat_view);

    
// load a skinned mesh from an .X file
    g_parent_frame = Parse_XFile("warrior.x");

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Release all d3d resource.
//--------------------------------------------------------------------------------
BOOL Do_Shutdown()
{
    delete g_parent_frame;

    Safe_Release(g_d3d_device);
    Safe_Release(g_d3d);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Draw current frame, recursive function.
//--------------------------------------------------------------------------------
void Draw_Frame(FRAME* frame)
{
    MESH
* mesh;
    D3DXMATRIX
* matrices = NULL;
    ID3DXMesh
* mesh_to_draw;

    
// return if no frame
    if(frame == NULL)
        
return;

    
// draw meshes if any in frame
    if((mesh = frame->m_mesh) != NULL)
    {
        
// setup pointer to mesh to draw
        mesh_to_draw = mesh->m_mesh;

        
// generate mesh from skinned mesh to draw with
        if(mesh->m_skinmesh != NULL && mesh->m_skininfo != NULL)
        {
            DWORD num_bones 
= mesh->m_skininfo->GetNumBones();

            
// allocate an array of matrices to orient bones
            matrices = new D3DXMATRIX[num_bones];

            
// set all bones orientation to identity
            for(DWORD i = 0; i < num_bones; i++)
                D3DXMatrixIdentity(
&matrices[i]);

            
// lock source and destination vertex buffers

            
void* source = NULL;
            
void* dest = NULL;

            
// locks a vertex buffer and obtains a pointer to the vertex buffer memory
            mesh->m_mesh->LockVertexBuffer(0&source);
            mesh
->m_skinmesh->LockVertexBuffer(0&dest);

            
// update skinned mesh, applies software skinning to the target vertices based on the current matrices.
            mesh->m_skininfo->UpdateSkinnedMesh(matrices, NULL, source, dest);

            
// unlock buffers
            mesh->m_skinmesh->UnlockVertexBuffer();
            mesh
->m_mesh->UnlockVertexBuffer();

            
// point to skin mesh to draw
            mesh_to_draw = mesh->m_skinmesh;
        }

        
// render the mesh
        for(DWORD i = 0; i < mesh->m_num_materials; i++)
        {
            
// set the materials properties for the device
            g_d3d_device->SetMaterial(&mesh->m_materials[i]);

            
// assigns a texture to a stage for a device
            g_d3d_device->SetTexture(0, mesh->m_textures[i]);

            
// draw a subset of a mesh
            mesh_to_draw->DrawSubset(i);
        }

        
// free array of matrices
        delete[] matrices;
        matrices 
= NULL;
    }

    
// draw child frames, recursively call.
    Draw_Frame(frame->m_child);
}

//--------------------------------------------------------------------------------
// Render a frame.
//--------------------------------------------------------------------------------
BOOL Do_Frame()
{
    D3DXMATRIX mat_world;

    
// clear device back buffer
    g_d3d_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(064128255), 1.0f0);

    
// Begin scene
    if(SUCCEEDED(g_d3d_device->BeginScene()))
    {
        
// create and set the world transformation matrix
        
// rotate object along y-axis
        D3DXMatrixRotationY(&mat_world, (float) (timeGetTime() / 1000.0));
        
        g_d3d_device
->SetTransform(D3DTS_WORLD, &mat_world);              

        
// draw frames
        Draw_Frame(g_parent_frame);

        
// end the scene
        g_d3d_device->EndScene();
    }

    
// present the contents of the next buffer in the sequence of back buffers owned by the device
    g_d3d_device->Present(NULL, NULL, NULL, NULL);

    
// release texture
    g_d3d_device->SetTexture(0, NULL);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASSEX  win_class;
    MSG         msg;

    g_inst 
= inst;

    
// create window class and register it
    win_class.cbSize        = sizeof(win_class);
    win_class.style         
= CS_CLASSDC;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= 0;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(NULL, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= NULL;
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;
    win_class.hIconSm       
= LoadIcon(NULL, IDI_APPLICATION);

    
if(! RegisterClassEx(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateWindow(g_class_name, g_caption, WS_CAPTION | WS_SYSMENU, 00,
                          WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, inst, NULL);

    
if(g_hwnd == NULL)
        
return FALSE;

    ShowWindow(g_hwnd, SW_NORMAL);
    UpdateWindow(g_hwnd);

    
// initialize game
    if(Do_Init() == FALSE)
        
return FALSE;

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }
        
        
// draw a frame
        if(Do_Frame() == FALSE)
            
break;
    }

    
// run shutdown function
    Do_Shutdown();

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}


运行效果:


一篇很久的文章了,看看现在的游戏圈还是这样子的,在发表出来希望对做游戏的同志们有所帮助.

1、无休止的抄袭

  回想起上个世纪末,华人游戏圈还处于原始阶段,那时候随便竖几条枪占个山头就敢说自己是做游戏的,拿出来的东西勉强有个模样就不错了,Bug少点那得是国货精品。真正的国货精品又怎么样?一些玩家说:“玩过FF6我才知道我们的精品(《仙剑奇侠传》)跟人家的差得那么远。”那还是跟FF6比,现在的仙剑奇侠传3跟FF7又是什么差距呢?天晓得。但在中国,人们就认为《仙剑奇侠传》是最好的RPG。总结这种现象的时候,很多人不约而同地把“中国语言和地道的民族题材”当作最重要的一条,这就是他们所认为的“中文游戏优势”。中文游戏是什么呢?在大部分中国游戏制作人眼里,它等同外国游戏套中国皮。很多人叫嚷的“中国要有自己的AD&D”就是这么回事——把圣灵与黑暗改成“阴阳”,魔法师唤作“道士”,属性相克略加修改成了“五行变化”。整个游戏只有命名是自己的,除了抄你还做了些什么呢?

  没错,用本土语言和本土题材创作是一种优势,但这种优势只在特定的文化、地域界限之内起作用。随便哪个挂名“勇者斗恶龙”的游戏在日本都能热卖,但同样的东西拿到欧美去则只能惨淡收场。土星版的《仙剑奇侠传》在日本卖不好,正好从另一个侧面说明了这一点。更为关键的是,所谓的本土优势纯粹是市场判定,它体现出来的是游戏产品在语言文化上的优势而不是游戏本身的优势。所以本土优势与游戏作品的素质其实并无关系,跟游戏是否原创也没有关系。用这个标准来看,自中国大陆游戏业早期推出的《官渡》、《中国球王》以降,又有几个游戏称得上是原创的?冒昧地说,“中国人自己的垃圾游戏”和“垃圾游戏”大抵没有本质上的区别,垃圾游戏就是垃圾游戏。

  中国游戏行业每年都在抄,而且每年都有人跳出来说,中国游戏不抄不行。为什么不抄不行,是因为没钱吗?可不是。中国游戏缺钱的年代已经过去了,网络游戏让世人看到原来在中国做游戏也能赚钱。靠游戏赚钱并不是什么稀奇现象,但对接触过游戏行业的中国人来说,这简直就是黑色幽默——在中国做游戏也能赚钱,太不可思议了。不仅能赚钱,丁磊为此还作过一个贴切的形容:“网络游戏是每天睡觉都可以有成千上万收入的行当。” 现在这个行业每年有亿万资金往里面涌,正说明梦想着“轻松赚大钱”的投资者不在少数。

  钱永远不是万能的。有了钱,这个行业还是只能拿出那么点皮毛。这个年头,谁有了钱谁就使出吃奶的力气赶紧圈地,广告费能砸多少是多少,什么费用都不能省,除了制作经费。没办法,现在这个市场明摆着就是你扔多少广告费你就有多少回报——要是在运营之初没有足够的在线人数撑着,什么事情都不好办。陈天桥有一句名言:“我们要让大家知道,《传奇》是个烂游戏,但盛大是个好公司。”这话放哪都绝对是语不惊人死不休——甭管《传奇》是不是你作的,作为一家游戏公司你拿出来的东西都不是什么好货,你还有什么脸面说自己是好公司?但在中国,“好公司”做烂游戏,这就是铁一样的事实。《传奇世界》制作六个月就开始测试,从设计者的角度来看,六个月意味着这个游戏差不多没有前期策划,这种情况下全部代码能写完都已经是奇迹了,游戏本身当然是惨不忍睹——但这样的东西在中国还能挺得住,这就说明问题了。

  本来我还以为网游大潮能够给一些思路不够开阔的同仁醒醒脑,但现在连一些本来头脑还算清醒的家伙都开始晕头转向了。很多人见到我就大谈在线人数、资本操作,但他们明明是一线制作人员——这几年你们都干嘛去了?一个欣欣向荣的行业不但要有充足的资金,还要有务实的技术人员,但在中国这就产生了一个怪现象:在大家都朝不保夕的年代,讨论技术的气氛很是浓厚,但行业越阔,谈游戏就谈得越少。是啊,现在“好公司”都靠烂游戏挣钱,谁还会去关心游戏做得怎么样。


  2、荒唐的自信

  你哪里来的自信?

  这句话我很想拿出来问问每一个游戏从业人员。凭什么靠烂游戏发家的公司能自称是“好公司”?凭什么对游戏行业的发展和现状的接近一无所知的人能够站在这侃侃而谈?凭什么,中国这么多人在网游的大道上充满自信地昂首阔步,一会声称自己是“好公司”,一会宣布自己的目标是“网上迪斯尼”?他们究竟对这个行业的现状了解有多少?

  中国网游在销售渠道上已经显现出了过度营销的迹象。点卡搭售和炒货现象状况非常普遍,特别是点卡搭售——运营商用热门游戏的点卡硬性搭售冷门游戏的点卡,这意味着很多网游的实际销售额有一部分并不是通过正常销售完成的,搭售出去的点卡往往就烂在渠道的手里,或者通过一些别的手段处理掉。这表明现在市场上已经存在大量劣质游戏,而且运营商还用尽一切手段把这些游戏推出市场。这种情形让人不禁想起ATARI Shock:就在1984年的圣诞节,在美国处于家用机垄断地位的ATARI平台上因为充斥着大量虚有其表,内容雷同的游戏,导致整个美国的绝大部分玩家对游戏产业产生不信任感,销售渠道和消费者同时拒绝游戏软件,使得大量的游戏厂商破产,无数从业人员失业,整个美国游戏市场濒临崩溃。

  中国的游戏本身的问题比它在渠道上的表现还要严重得多。像《传奇世界》这样做了几个月就开始测试的游戏遍地都是,而同样是做游戏,暴雪光是测试就会用几个月时间。这还是看表面,看看游戏内容——不看不知道,一看吓一跳,所谓的中国原创网游几乎等同MMORPG,题材搞来搞去就那几套,表现手法几乎一摸一样,全是Diablo或者UO的劣质仿制品。这还算是原创吗?但是中国人还是有办法把这些东西卖出去的,其中一种手段就是五花八门的广告词:什么完美、高度逼真的全3D游戏世界,什么多种族,多职业,灵活的角色设定,什么严谨的政治、经济、权力结构,什么独特的XX系统,同类型产品的共同点被吹成“特色”,与同类型产品稍有不同的特点就成了天大的优点。

  如果说中国游戏行业领先于世界,那么以上这些问题可能都不是问题——但事情是这样的吗?正当我们这么些厂商正在用雷同的产品上作重复劳动的时候,无论是日本,还是欧美都在用我们难以想象的加速度继续赶超我们。回到开篇时候我的那句话,当年的仙剑奇侠传跟FF6有距离,但这个距离还是可见的;就现在,即使同样是火热滚烫的网络游戏,眼下国内搞的这么些东西,跟CAPCOM将要推出的《铁骑大战》、已经推出的《Biohazard Outbreak》,还有全民高尔夫Online等PS2 BB Network上的游戏又怎么能比呢?业内有个执行企划甚至这样告诉我:“我不敢多玩这些游戏,因为每玩一个我就感到深深的绝望。”为什么绝望?因为即使是在中国市场大红大紫、叫中国人无可奈何的韩国网游,也远不能够和这些日本游戏相提并论。

  除了圈地,中国游戏行业就显得非常不会花钱。网游大潮带来的投资往往大规模流入媒体和渠道,对制作的投入一般就停留在“买引擎”、“挖角”、“投资散兵游勇”(也就是那些做出个游戏毛坯四处兜售等着吃饭的行业新兵)的层次上面,这对一线制作水平的提高是非常有限的——总之不管有钱也好,没钱也罢,中国游戏行业总是在无休止地抄袭,即使现在的大好时光也不过是搭建在对几个里程碑式游戏的抄袭之上。所以,这个行业无论对外如何使用广告枪文,对内如何宣传各种远称不上合理的说法,都掩盖不住以下的事实:放眼世界,中国游戏行业不但技不如人,而且在萌芽阶段就开始了各种恶性竞争。游戏厂商普遍不以游戏为本,倒是绞尽脑汁用游戏制作以外的手段抢夺市场——如果不是这样,还能怎么解释某个没有背景音乐的游戏获得“年度最佳游戏音乐奖”?怎么解释某个网游还没公开测试(更别说正式运营)就得了奖?

  在这种环境之下,技术人员不肯脚踏实地做事,明眼人不去揭穿皇帝的新衣,倒是一个接一个的外行敢于口出妄言。打个比方说,有篇叫做《酷经济》的文章,大谈中国游戏为何落后于别人,开篇就来一句“中国的网络游戏市场,让韩国、日本游戏占领了,是游戏开发技术不如人吗?是我们的游戏企业能力不如人吗?其实都不是。”天啊,连技术落后都不肯承认,接下来恐怕什么实事都用不着讲了,尽管放卫星就是。业外人胡说八道并不可怕,令人担忧的是很多小有名气的业内新兵也在胡言乱语,刚刚一脚踏进盛大的唐骏就属此类。在搜狐聊天室举办的一次活动里面,一个网友曾问他:“盛大网络游戏一个“传奇世界”、一个“热血传奇”,您玩不玩网络游戏?对网络游戏了解吗?”唐骏说:“我对网络游戏了解,但不是高手。”笔者在游戏方面的有着多年钻研,深知要对电子游戏达到“了解”的程度,进而对网络游戏达到“了解”的程度要花多少的精力,要耗费多少的资源。试问你唐骏搞IT搞了那么多年,要是说你也同样花了那么多的精力和资源在游戏上面,可能吗?果然,再接下来的问答中他露出了马脚:

  主持人陈拥军说:因为传奇和传奇世界在中国是非常有代表性的,您对这两块游戏怎么看?包括前景。

  唐骏说:传奇是带动整个中国网络游戏的产业,传奇是中国第一款真正具有实力的自主开发的游戏(笔者注:天啊,这句话用的都是什么语法?传奇怎么又变成你盛大自主研发的东西了?)。这都是非常具有代表意义的,也是未来有非常大发展前景的。加大更多的自主开发,来满足更多玩家的需求。

  唐骏说:我们讲到迪斯尼,如果你在美国,我们看到迪斯尼是什么?第一,它拥有品牌,这个品牌是所有娱乐也好,包括电视、电影、主题公园里面、相关的产品包括媒体等等都是迪斯尼品牌推出来的。这是我们现实生活当中的品牌,我们希望网上迪斯尼就是让人们在网上也同时能够感受到娱乐带来的快乐,比如网上电影、网上电视、网上主题公园等等,这都是我们对未来网上迪斯尼的构想。

  拜托,请不要拿盛大和Disney相提并论好不好?Disney品牌是靠像蓝猫那种水平的烂动画支撑起来的吗?不是的,人家靠的是米老鼠,白雪公主,小飞侠,阿拉丁这种实实在在的好动画支撑起来的。盛大的研发实力怎么样,产品放到世界属于第几流,这是路人皆知的事情,用这样的烂游戏、烂公司来做品牌,又能有什么前景?试想想,要是任天堂推出一个传奇一样的垃圾(把任天堂和传奇放在同一个句子里面的时候我不得不用这个字眼),它可能明天就要倒闭了!同样是这个盛大,有这大笔充裕的流动资金也还要流血上市,一上市包括陈天桥在内的内部人士就狂抛45%股票,这像是在做“网上Disney”吗?难道不是投机逃跑吗?

  现在似乎只有两种说法能够解释中国游戏行业发展的现状:

  第一种说法是大家都在争先恐后地抓紧时间捞一票,最后看看谁能够在彻底搞垮这个市场之前收手走人。为什么搞烂游戏的反而是“好公司”?因为现在好公司的标准是在短时间内不顾后果、不择手段地暴发。什么原创游戏,什么民族工业,扯淡。

  第二种说法是很多人每天都在做着“网上迪斯尼”的美梦,至于游戏行业在国外发展得怎么样,在中国的现状又是怎么样,他们非但一无所知而且还根本没有去看。有的人瞄了两眼,说:是啊,这些东西挺好的,但是在中国行不通。我真没发现这些东西有什么行不通的,前提是足够便宜——例如跟CS似的,只卖三十八块钱(嗯,我承认这是天方夜谈)。所谓的行不通还有一种可能,就是借助中国固有的排外观念和政府保护加以抵制——我们的东西不好没关系,把门一关,不准洋货进来,我大清的煤油灯再不亮,我也不用你的电灯,甚至凡是比煤油灯亮的都不许点,反对的就是汉奸。只可惜现在说这个已经太晚了,任天堂投下了神游机,SONY将引进了PS2,眼下一个已经汉化的《命运传说2》也能把中国所有的RPG打得人仰马翻(BTW:然而很多中国人还以为自己很懂RPG!)。

  与国外一流游戏厂商的较量已经迫在眉睫,人家骑在你头上只是早晚的事情,但国内的游戏制作仍然活在“你再强也奈我不何”的鸵鸟政策之下。网游的兴起让许多人开始关注游戏行业,也催生了不少张口就是运营,闭口就是资本操作的“专家”,他们胸有成竹地对一个他么完全不了解的行业指指点点。而我看到的,只是几个暴发户在前面领跑,后面一大群人陆续跟上。日本人中村彰宪曾指出中国游戏行业是“完全的资本主义”,参照现在的现状,我们不难理解他在说什么:从单机时代到网游时代,上到业界红人下至网吧老板,绝大部分投身游戏行业的中国人一直把游戏行业视作是一个纯粹的投机项目,倒是极少见有谁把它当作一个既有现在亦有将来的产业来对待。人人都奉行机会主义而妄顾行业的生死存亡,这正是电子游戏登陆中国以来所以出现这么多莫名其妙的争端,引发诸多社会问题,行业成长如此缓慢的根本原因

3、成本问题

  中国游戏行业所以成这样子,跟盗版的泛滥也不无关系。面对中国市场,所有的世界一流厂商都只是在谨慎的投石问路,其中最大的原因就是恐惧盗版。像FC,在中国从来没有过行货主机和行货软件,但其兼容机销售是上亿台,盗版卡带销售更是不计其数,这样的市场谁能不怕?所以说,是盗版——不如说,是中国人的消费观念问题过早的把这个行业逼上了绝路。绝大多数的中国玩家认为买盗版是理所当然的,因为正版贵,他们买不起——尽管在中国发售的正版游戏都不过是在几十块钱上下,他们仍然宁愿买盗版。同样是这些人,他们可以一年换几个手机,可以花几百块钱去看演唱会,但偏偏就在游戏问题上,他们说他们消费不起。他们就是觉得买不起正版游戏所以用盗版是理所当然的,因为他们要玩——这听上去像是农民因为没饭吃所以要造反。用Adobe的CEO皮卓丁的话来说,有的人就是以为Photoshop就值几块钱,你有什么办法?

  但玩家就是这样认为的,他们一边买着盗版,一边整天嚷着正版软件定价不合理,非要到每个游戏才几十块钱的时候才能接受。可是等游戏卖到几十块钱的时候这又是怎样一种景况?Warcraft 3卖几十块钱,这合理吗?像那种规模的游戏,几十块钱里面仅仅包含了中国代理上贡给暴雪的一点版费,这点版费跟研发费用想比只能是九牛一毛。换句话说,那些定价让中国人觉得负担得起的正版外国游戏在中国几乎是以倾销的形式推出:Warcraft 3卖几十块钱,FIFA2004卖几十块钱,CS卖几十块钱,文明3也是卖几十块钱,但中国人即使有能力做这样的游戏,制作成本加推广成本还不止这几十块钱。到了中国人真的做出些卖几十块钱的游戏,又怎么可能跟“几十块钱游戏”相提并论?就目前来看,除了韩国网游以外的外国游戏根本就没办法在中国市场收回成本,所以在中国卖几十块钱的收益不过够他们报销茶水费。所以,外国厂商在正版定价问题上照顾了没有尊严的中国玩家,但与此同时也有意无意地压缩了中国游戏行业的原创空间——技术含量低,游戏理念落后的中国游戏只能靠在中国卖几十块钱获利,但是EA等厂商的游戏有在外国卖几百块钱的获利作后盾。事实就是,中国游戏生下来便要和这种“几十块钱游戏”在中国市场上同台竞争。

  然而,我们经常说的单机游戏跟唱片、电影一样是靠卖拷贝赚钱的,它的制作成本绝大部分属于可摊成本,通俗的说就是拷贝数卖得越多每张拷贝数的成本就越低,钱就赚得越多。但是不说不知道,其实整个中国大陆地区的单机游戏消费能力,乐观估计也就是跟台湾一个省持平,兴许还要低一些。游戏的销售成本也是个问题。如果考虑到中国物流系统效率较低,中国地域跨度广造成的软件销售覆盖面广,覆盖到某些地区的边际成本高,这个市场就更为有限了。盗版猖獗更使得某些地区常产生盗版比正版先到,盗版到了正版没到的现象。所以,新游戏卖个几十万就已经算是商业奇迹,在这种环境下谁都知道老老实实做游戏赚钱是行不通的,结果就是大家一轮哄抢,从血狮到仙剑2,最后只剩下如今的一潭死水。

  分析一下单机游戏在中国的失败,我们就不难理解网络游戏今日的风行:1.网络游戏的销售对中国现有的物流系统依赖较少,点卡销售可轻易普及到便利店,网上销售点卡和网吧销售点卡也极大的提高了资金回收效率,同时避免了盗版问题。2.网络游戏依存的平台发展赶上中国发展互联网的顺风车,宽带上网的普及、网吧雨后春笋般地出现极大方便了服务器端的架设和推动了客户端的普及。虽然网络游戏制作成本并不比单机游戏要低,而且它还多出了架设服务器的成本和养活一大堆维护人员和客服人员所产生的成本,但是上述的便利使得网络游戏发展每个客户的边际成本很低并且忽略了地域跨度——相比起在制作成本这简直可以忽略不计。

  中国人还太穷了,所以在市场上我们常见到层出不穷的价格战,成本问题几乎决定了一切。就我数年的观察,中国游戏行业确实有一些决心要为梦想,要为这个行业努力的热血青年,他们总想以牺牲自己的方式来跨越成本问题。但问题是一个行业不可能只由热血青年来支撑,再说热血青年自己也没有喝风屙烟的本领——资本家可不会跟着你一起跳河。资本家只会考虑两点:假定你所要做的游戏真的是好游戏,首先你能不能真的做出来?等你做出来了,它能不能带来相当的利润?我要为之冒多大的风险?不得不承认,当前的中国资本家果真解决了成本问题,并且给了热血青年一个响亮的耳光,“《传奇》是个烂游戏,但盛大是个好公司。”。

  人家说中国游戏行业是“完全的资本主义”大抵是没有错的,资本的力量在游戏行业里面膨胀得太厉害,起了的作用太过大了,制作力量完全望其项背。整个中国社会听到的那些来自游戏行业的声音,关于游戏行业利好的消息事实上都是资本家的声音,游戏制作者或因为专业水准低下、或因为在行业地位低下完全被冷落在一旁。说句粗俗的话,现在唐骏这个游戏行业新兵便是放个屁都比游戏制作人说话响亮,这正常么?这跟游戏名制作人一呼百应的日本恰好完全相反那,比尔盖茨在日本首发XBOX发售的时候就没敢声称自己“了解电子游戏”。你以为人家是傻子吗?人家就知道,解决了成本问题不等于解决了一切问题。

  4、平台玩人人

  国家标准化管理委员会今年曾委托某大学做了一份关于游戏产业的调研报告,报告中提到不支持PS2之类的主机进入中国的若干理由,其中有两点:1、这类主机的硬件体系是封闭的、专用的、非通用的;2、这类主机的软件体系是封闭的,采用了非市场化的授权体系。

  看到这段文字的同时,我还看到一位业界研究专家模样的人落了这样的旁注:

  “绝大多数电视游戏玩家可能都无法接受这两点理由,的确,厂商有权保护自己的知识产权,有权利用自己的知识产权去牟取利益,即使这种利益是建立在垄断的基础上。但我还是要举双手赞同该报告的结论,在对待高技术产业上,政府最应该关心的是能否形成自己的知识产权开发功能,能否通过自身的创新能力,形成产业化规模。”

  假如除了电脑以外的游戏平台仅仅是能玩游戏的简化版电脑,我也举双手赞成以上的说法,但问题是游戏机远远不是“简化版电脑”那么简单。对于一个能够被称之为“产业”的游戏行业来说,作为游戏主机的PS、XBOX等不过是实体化的前端硬件(家用机的英文“Console”的原意),而同样名为PS、XBOX的游戏平台(Platform)的含义则超出“游戏主机”一词所包括的内容。

  从街机时代开始,日本的游戏硬件制作已经有了20多年的积累,不但拥有大量专利还拥有大批人才:从如何应用电子技术制作越来越小巧、性能越来越强的家用机,到如何利用机械技术打造各种巨型的体感游戏机台;在整体方面思考怎么样的家用机手柄才能满足现在的各种游戏、未来的各种游戏需要的,在细微之处考虑一台街机应该用多厚的木板,应该剪裁为何种形状才能在节省材料和美化外形之间找到平衡。中国勉强算得上有游戏软件研发,但中国人对游戏硬件研发则几近一无所知。纯粹对应软件的游戏研发尚停留在程序开发层面,很多制作人甚至连鼠标+键盘的操作方式都没吃得透,这就是不重视游戏硬件的恶果。“一个软件开发人员的成熟期大概需要3年,但是一个硬件开发人员则需要花上大约5~6年的时间来熟悉那些调试错误和积累开发经验。”SEGA AM2的铃木久司社长如是说。用这个标准来衡量,日本已经有了好几代这方面的人才,而中国在这方面的数字约等于零。

  在游戏机通过非正式渠道进入中国以来,中国社会对游戏硬件不但没有投入应有关注,还肆意把许多社会问题推到某些游戏硬件上面来(例如街机、家用游戏机):曾经有一段时间,同样是学生玩游戏,在电脑上玩不算是什么坏事,但放到了游戏机上似乎就是十恶不赦的罪过;街机曾经被人人喊打。政府迫于公众压力,曾在2000年6月授权文化部等7部委执行的《关于电子游戏场所管理的通知》,在这份通知的第四部分内容明确规定,除了来料加工以供出口的游戏机生产外,凡是面向国内的游戏机零配件的生产经营销售和进口活动一律停止。具有讽刺意味的是,等到DDR在中国变成流行的时候,有记者就说:“这么简单的东西,为什么中国人就想不出来?”他怎么不想想,难道有识之士会投身到一个被渲染成像造假药、酿假酒那样害人不浅的行业来吗?曾几何时我们有过仿冒8位游戏机、16位游戏机的历史。姑且不论其合法性,那段历史至少还证明当时我们在某方面的硬件制造技术跟外国不是相差很远。但从32位机开始,中国玩家只能享受从非法渠道过来的“原装进口”外国主机,国内的仿冒生产仅限于手柄、光枪、电源等科技含量较低外设(研发能力还是没有的)。

  在游戏平台的构成里面,硬件制造处于最底层,硬件授权体系和软件发行体系作在其之上。诚如前文的那些专家所言,游戏平台的硬件体系和软件体系都是封闭的,但是他们没有去看到封闭意味着什么。封闭的硬件体系首先改善了游戏运行环境,像游戏机,至少玩家不必担心它的操作系统稳定性以及硬件兼容性,制作者也无需为硬件的频繁变动而增加DEBUG流程。更重要的是,硬件授权体系的封闭才能够像任天堂似的应用诸如mini-DVD这样的非主流数据储存载体,从根本上杜绝盗版的存在。据最新消息称,连微软也正在寻求一种管理下一代Xbox游戏机光盘设计和开发的工程师。对于这个工作职位的说明进一步提到,在新的游戏磁盘规格的关键因素列表中,反盗版是排在第一位的。不错,在完全开放的PC平台上做游戏无疑是很轻松的,但轻松的代价就是从开始就要与盗版作战。中国人在这方面积累了很多“宝贵经验”:把一张光盘的数据用非压缩格式做成两张甚至三张;使用对盘片和光驱要求甚高的加密格式。不得不说,这些努力很大程度上是瞎忙:盗版者“帮”制作人压缩数据放在一张盗版光盘上;加密格式也只能“争取几个星期的发行时间”。至于盗版的危害,恐怕就无需我多言了——本都保不住,怎么原创?

  很少人意识到我们在游戏硬件上的距离跟外国越拉越远,也只有更少的人意识到发展游戏硬件制造和硬件体系的必要。网络游戏夺去了所有人的眼球,连一些政府官员也不例外。在不久前的第二届中国互联网大会上,文化部市场司庹祖海副司长也着重强调,最近有一些游戏机想进入国内市场,而国家2000年的“通知”并未变更,在规定变更前相关部门会继续按照这个政策执行,这些游戏机存在合法性问题。庹祖海副司长还谈到,就整个游戏产业来讲,国家认为应该发展。但是游戏产业分很多种产品,发展的先后主次,都需要研究。他表示,是否发展其他游戏产业,文化部和有关部委将进行调研,待国家批准后进行。观察一下现在舆论的导向,我们不难推测出副司长说这番话的原因:网络游戏绕开了盗版问题,而且中国人有能力做网络游戏(且不说中国人还有能力让次品赚钱),所以为它就是当今中国游戏行业的救命稻草,像PS2等游戏硬件则可以推迟发展。对于大部分人来说,这已经是一个很完美的答案,但在我看来它还不够完美。尽管现在许多调查机构出台了各种各样的数据,政府也出台了各种各样的政策,表面上看它们预示着前途一切光明,但却从来没有人针对“网络作为游戏平台的前景”做一个贯穿游戏软件、游戏硬件的发展历史、发展现状的技术分析,然而这恰是不可缺少的一环——日本游戏行业每年都会就游戏的技术发展开会,不开会的时候也有不少像山内溥、岩田聪这样的行业大腕就游戏技术发展说很多危言耸听的话。笔者自知学识浅薄,却也要开一个先例,在这里也要试一下做这样的技术分析:

  Namco的岩谷徹(闻名于世的Pac-Man创造者)在接受记者采访时曾说,现在看来网络只是游戏的扩展手段,还不能说网络时代已经到来了。这话八九不离十地道出网络游戏的现状:网络作为游戏平台至今无法脱离其他平台的影响,而且网络游戏在很大程度上是PC游戏应用网络技术之后的成果。我们以公认为网络游戏里程碑的《网络创世纪》(大家所熟知的UO)为例,最原始的《创世纪》是一个文字游戏,连图像都是用文字“拼”出来的,而它若干后续作不断的引进了不同的图像技术之后,变成现在玩家广泛接触到的《创世纪》系列,应用网络技术的《创世纪》就是《网络创世纪》。我相信有些人会对这个说法有异议。的确,作为网络游戏里程碑的《网络创世纪》在游戏系统上奠定了网络游戏的虚拟社会组织、虚拟经济体系,但是在直接面向玩家的部分,例如界面、基本操作、人物设定和世界观等等却是《创世纪》系列的延续。用非专业的方式来看待这个问题,则可以这样表达:如果说《网络创世纪》是MMO RPG(即大型多人在线角色扮演游戏),作为RPG(Role-Playing Game)的《创世纪》就是《网络创世纪》的游戏原型,MMO(Massively Multiplayer Online)则是这个原型的网络扩展形态。纵观现在所有的网络游戏,我们可无一例外地用类似的方法找到它的原型,而且这个原型不是在网络游戏平台上原创出来的。然而,发展多年的欧美和日本游戏行业拥有不同的发展方向,在不同的发展方向影响下拥有各自不同的游戏原型,这些可以说是它们各自的积累,不但有知识经验的积累还有人才的积累。中国游戏行业在单机时代是抄,在网游时代也是抄,并没有自己的游戏原型,更没有什么积累。游戏原型不可能在一种“好公司做烂游戏”的业界氛围中诞生,实际上也不可能在网络游戏开发中诞生——网络游戏更多的制作投入是在前面所说的“MMO”,即多人在线环境的扩展形态部分,在游戏原型方面能够有相当程度的引入已经不错了,游戏原型的原创是一个网络游戏项目在人力和物力上所不能承受的。

  必须承认,现在我们看到的游戏原型引入还很有限,也就固定在RPG等几种,这是因为现有的网络无法提供很多游戏原型引入所需要的带宽和反应速度。随着网络技术的发展,游戏原型的引入将在未来为网络游戏发展的一条必经之路。在这条必经之路上,无论是欧美还是日本都有它们的筹码,因为他们有在非网络游戏平台上发展下来的游戏原型基础,而中国本来在游戏原型方面就是纯粹靠照抄,如果现在就放弃发展一切非网络游戏平台(也就是等于放弃非网络游戏平台游戏),到时候将败得不可翻身。除了RPG等少数游戏原型就不能做(应该说是不能模仿、照抄)的中国游戏制作者看到《铁骑大战》这样引入了顶尖3D技术,最前卫的游戏原型以及最过硬的游戏硬件制造技术的网络游戏之后,试问又有谁能不绝望?

  以上仅仅谈及游戏平台的软件发展,要是从游戏硬件的发展来讲,网络游戏平台更不是可供整个游戏行业立足的地方。街机平台的硬件核心在于大型框架机体,家用机平台的硬件核心在于游戏主机,而网络游戏平台的硬件核心在于互联网。然而,大型框架机体和游戏主机的主要功能是玩游戏,因此它的设计、制造或者售卖很大程度上在游戏行业自身操控之下。相反,互联网不是娱乐专用设备,它属于一个国家或地区的基础设施,游戏行业完全不能干预它的运作。这会引发两个严重的问题:

  A. 互联网的稳定性与网络游戏平台运转直接挂钩,平台运行风险大增。

  非网络游戏平台只受电力供应影响,而且即使电力供应出问题也不至于酿成严重后果。网络游戏平台在依赖电力供应的同时,更依赖互联网的稳定。一旦互联网的运作出现重大问题(病毒泛滥、设施损坏等等),整个网络游戏平台的状况就会变得极坏。这种情景在中国已经出现过了。

  B. 因为完全在其控制范围之外,游戏业界无法控制网络游戏平台的发展。

  无论是“MGS改变世界”,还是“N64将改变游戏”,证明的都是同一条道理:游戏研发不可能脱离硬件支持或者软件开发而存在,硬件技术支持和软件开发技术必须同步发展。历史上。在家用机发展发展史上,我们可看到某项重要机能的缺失会导致整个主机缺乏软件支持(想想SS和PS之争);又或是尽管有比对手优秀的机能,但是不符合实际应用的高性能带来的高成本妨碍主机普及,进一步妨碍第三方厂商支持,导致高性能主机成为高性能废铁。各种封闭的硬件体系引发开放的竞争,进而互相淘汰,这样才有了游戏业界自身引发的一轮轮技术革命和游戏理念革命,游戏平台才得以如此迅速的发展。这种竞争的背后肯定涉及厂商的利益,但最终受惠的将是消费者。

  反观被视为将是中国游戏行业主流平台的PC和手机,这些平台实际上是计算机、计算机网络的延续,游戏行业不但无法掌握它的发展脉络,更无法影响其发展。如果说真正的游戏产业是“人人玩平台”,游戏厂商有机会建立属于自己的封闭体系互相竞争,那么依赖游戏行业无法改变的游戏平台最终导致游戏行业成为其他行业的附庸,属于“平台玩人人”。我们可以用更实际的例子说明这个问题:为了让赛车游戏有进一步的发展,在街机上我们可以用全新的理念为赛车游戏做一个专门的框架,但PC平台就做不到——PC游戏平台的标配外设就是键盘鼠标,如果你还想你的游戏要有销路,你就不能要求每个人为了你的游戏买特殊外设。一个 “人人玩平台”的游戏行业能够主宰自己的发展,主机性能不满足新游戏的发展可以淘汰一代主机,机能过剩可以通过价格竞争予以削平避免引起机能滥用。游戏制作人亦可随时考虑加入新的外设实现新的设计,即使是GBA这样的掌机,也可以用如《我的太阳》(KONAMI)似的加入太阳感应器实现新的设计。我们更可以看到,那些所谓封闭式游戏平台的更新换代已经开始威胁电脑作为网络游戏终端的存在,支持WI-FI的NDS和PSP就要摆上货价了。但是游戏行业可以为了原创淘汰一代电子计算机,淘汰一代互联网技术吗?显然是不可能的。

  比起其他的平台, PC、手机都不是稳固的游戏平台,更不是最佳游戏平台。现在它们之所以变成了中国的主流游戏平台,其实与中国的现状有关。近日NVIDIA的CEO王仁勋在中国媒体的采访中发表了这样的见解:“在中国,由于居住空间有限,PC经常被用来玩游戏、看电视或是DVD。在美国,PC就是PC,游戏机就是游戏机。两个地方的文化背景差异很大,这也导致了消费者对产品的诉求存在差异。”我完全赞同以上的观察,不过我还要补充一点就是,除了消费能力、生活空间、文化差异方面,它跟当前整个中国社会状息息相关。中国开放得非常快,人们很快就用上了用各种各样的新产品,但他们的意识特别是消费观念完全没跟上,价值取向稀奇古怪——如果是算起经济帐来,同样是玩盗版,买1000多块钱的PS2无疑比买同样是1000多块钱的电脑显卡划算,但就是很多人没法接受用1000多块钱买一个提供单纯娱乐功能的游戏机,他们就是宁愿买同样价值的显卡凑合那些日益粗制滥造的PC游戏。同样荒谬的事情还发生在移动电话领域,不过我也不必多言,毕竟不比别人富的中国人三天两头换一个手机已经不是什么新鲜事了。现在的中国社会还是一个高效率的趋同放大器(不过也不止是中国),从文凭到手机,人们热衷于追求那些“你有我也有”的东西,这些东西本身价值几何倒是其次。所以,依靠那些高品质游戏、那些外国人所说的“Must Have”Software去推游戏平台根本是不可行的,即使是Mario、Zelda这些瞬间能带动几十万平台销量的游戏也不例外。更何况一个游戏折合人民币几百块钱在中国人眼里就是天文数字,同样一个月花销几百、一千多块钱玩网络游戏他们却觉得毫不稀奇。

  长期从事PC和网络游戏研发的中国人既不懂游戏平台的发展,也不懂站在游戏平台之上的游戏行业运作。“平台玩人人”的游戏行业注定被其他行业决定的“流行配置”、“网络环境”牵着鼻子走,在这些“不得不考虑的问题”面前中国人有再好的原创游戏理念也只能无条件屈从。这样下去电子游戏根本无法在中国发展为产业。中国人要在世界游戏行业扎根就必须要有自己的东西,但游戏平台是原创的立足点——如果连立足点都不是自己可以影响的,那么这个丝毫不能主宰自己命运的行业还谈什么原创?还谈什么“自身的创新能力,形成产业化规模”?中国政府正在与其他国家一同推动全球化进程,这是非常明智的,但一些民间人士看待行业(不止游戏行业)成长的时候往往就不那么明智了:我中华大国就是什么都要有,比你好的要有,不比你的好我也要有——不比你好我也一定要用自己的,没有电灯我点蜡烛,反对的就是汉奸。狭隘的民族主义情绪早就让这些人失去了分析问题的能力,他们就是坚持中国就是什么都要有,也不管这些东西以后中国人实际上有没有得到好处。我在这里提醒他们一下:须知游戏行业在中国的行业结构上处于边缘地位,这点它远不如汽车工业。汽车工业都可以开放到现在这种地步,游戏行业为什么不可以?退一步说,中国人没有自己掌控之下的游戏平台也不是什么有损国体的事。欧洲人也没有自己制定的游戏平台,就没听说过谁出来大喊大叫说,欧洲没有自己的游戏平台,欧盟就要不行了。鄙人极爱游戏,也极希望看到中国游戏行业真正兴旺起来,但我还是要说,既然承认了游戏行业是娱乐行业,就得同时承认游戏这东西不能当饭吃,在中国人的生活质量还远比不上欧洲人的情况下,真有余力就应该投入到其他能改善国计民生的行业去。而且,一个娱乐行业的发展要在中国生根发芽,第一步就是要打造一个形成良性循环的生存环境。像现在似的,沸沸扬扬地搞出一大堆靠低质游戏,对这个行业根本有百害而无一利。中国人在游戏行业里面还是个小学生,现在搞出这么个所谓的游戏产业其实大半是泡沫,小半是从别人那抄过来的“非自主知识产权”——“有自主知识产权的游戏产业”这玩意儿在中国就从来没有存在过——没有游戏硬件制造专利、没有出过世界级里程碑式游戏、到现在还有不少人把游戏机视作洪水猛兽的国家又有什么资格声称自己拥有“有自主知识产权”的“游戏产业”?

  很多人认为,我们现阶段应该保护某些从事开放性平台游戏开发厂商的利益,拒绝其他平台在中国落地生根。但开放性平台的“平台玩人人”性质决定这些厂商无法成为游戏行业的中流砥柱。我们不可能闭门造车自己搞出一个跟得上世界的游戏平台。为了今后游戏行业的发展,就应该让代表当今世界游戏行业发展的游戏平台进入中国市场,培养中国游戏行业成为在世界游戏行业格局中举足轻重的第三方厂商群体,借此参与到世界游戏产业的共同发展,以实力而非地方保护主义取得在世界游戏产业中的话语权。以前关起国门来保护的还有汽车行业,结果呢?中国人因此得到什么好处了吗?把全部精力放在所谓大有前途的网络游戏平台或者其他开放性平台实际上是一次没有胜算的豪赌,它葬送的将是中国游戏行业的全部未来

5、行业自律

  如果说封闭的硬件授权体系仅仅是为游戏质量和厂商的盈利提供了保障,封闭的软件授权体系则属于行业自律,它是直接影响到市场活跃程度的根本。前面提到的ATARI Shock几乎摧毁了美国游戏行业,表面上看是因为低质量软件的泛滥,但如果从经营的角度去看,不难发现ATARI作为第三方软件授权模式的开创者并没有运用这种模式保证发行软件的质量和档期,从而直接引发ATARI Shock这样的恶果。其后,任天堂实行了“任天堂官方质量封条”(Official Nintendo Seal of Quality)政策:在收取授权费(权利金)的同时,对所有的授权软件实行品质上的、数量上的严格监控,所有的游戏发行档期由任天堂一手安排。如此一来,内容、形式雷同的游戏可以通过档期安排错开发行避免互相影响,质量低劣的垃圾游戏则一定无法通过审查。“任天堂官方质量封条”代表着在ATARI和开放平台上游戏制作和发行方被忽略的,游戏行业应有的自律。事实证明采用这样的授权体系是一个相当英明的决定:任天堂因此站稳了脚跟,开始了长达10年的任天堂时代。不论是在游戏行业的次时代争霸,还是现在的三国鼎立时代,各硬件厂商一直沿用着这种软件授权体系,曾击败任天堂的SCE也只是将其软件授权政策制定得略为松绑而已。

  封闭不等于是非市场化的,由ATARI时代冒出水面、SCE用以打败任天堂的第三方模式就是软件开发授权市场化的最佳证据。在NGC、PS2、XBOX三国鼎立的今天,争取第三方厂商支持早已成为除了主机普及率以外最重要的事情,过去任天堂靠第一方软件和第二方模式打天下的日子已经不再了:任天堂曾经凭借自社开发的《Zelda64》把N64的销量从每周3000台以下的冰点急速跃升到15万台以上,但最后仍旧因为缺乏第三方支持而无法突破PS的第三方软件潮水式防御。事后任天堂名制作人宫本茂(Mario、Zelda之父)惋惜地说:“如果那时我们能得到哪怕一点点协助,可能未来的局势就会完全不同。”同年,ENIX宣布加盟PS并为其推出《DQVII》(勇者斗恶龙7)的时候,SCE副社长德中晖久激动地喃喃说道:“有了《DQ》的保障,我们的事业有如头上的青天那样安如磐石。” “磐石”指的是什么?就是作为第三方厂商游戏的《DQ》这个日本国民游戏对于PS2而言的市场价值。第三方厂商的支持已经成为一个游戏主机生存状况的决定性因素,各硬件厂商为了争取第三方的支持在软件授权和主机设计上拼个你死我活,为此竞相开出比对方更加优惠的授权条件,例如授权金的优惠,技术支持,黄金发售档期——还有什么授权体系比这更市场化的?“这种利益是建立在垄断的基础上”云云更是睁眼说瞎话。把PS2的影响力说得再夸张都好,NGC、XBOX还是它当前实实在在的竞争者。跟操作系统领域的微软、芯片制造领域的Intel相比,游戏行业的SONY不过算是一马当先。

  回过头来看看那些根本没有软件授权体系的平台又怎么样呢?回忆一下近年来世界范围内出现的FPS浪潮、RTS浪潮,国内的中文RPG浪潮吧,不管是国外还是国内,正因为PC平台、网络游戏平台完全没有软件授权体系约束游戏软件的制作发行,所以每当这些平台上有一个类型的经典只作问世,不久之后无数同类型游戏将同时出现。游戏公司把制作那些看上去一摸一样的游戏当作票房保证,并且有意通过各种手段(包括媒体宣传,各种的所谓世界级大赛)引导消费者来保证这种票房,游戏厂商则在这几种类型上重复劳动,坐享其成。在这种重复劳动的竞争里面,创造力得不到分数,获胜的总是获投资更多者,结果就是大者恒大,坐大者靠继续做同一类型游戏来吸引投资,继续坐大。ID Software就是典型例子:这家公司堪称FPS的创始者,但是他们一直以来只做FPS——不但如此,这些年他们每次拿出的新作品的时候从来不宣传新游戏加入了什么设计让游戏更加有趣,而是宣传这个游戏加进了这个特效还是那个特效,让你的显卡大显神威。可是,近年来最火红的FPS就不是Quake3,而是由游戏爱好者做的,采用的不过是Half Life引擎(技术水平介于Quake2和Quake3)的——不用我说,大家都知道这东西就是CS(Counter Strike)。造就CS成功的其中一个重要因素正是CS在同类型游戏的游戏模式上有着了不起的创新(著名的“反恐行动”),ID Software这样可谓财大气粗的公司反而在这方面原地踏步。毫无节制的重复投资把PC游戏制作拖进了死循环,不少当初锐意创新的开拓者在如此资本冲击之下成为年复一年,日复一日制作雷同游戏的赚钱机器。不少好的游戏原案被称为是“冒太大风险”予以否决,像《大刀》这样虚有其表的项目反而能骗来大笔投资,PC游戏制作被这样拖入了死气沉沉的境地:暴雪的Diablo主要制作人员出走,牛蛙、Westwood、黑岛等著名小组纷纷解散,CS的出现也不过是拜游戏爱好者所赐。这就解释了为什么我们看到的大多数PC游戏比起家用机游戏不但缺乏新意而且品质低劣——即使是同类型游戏的面对面较量,在PC平台上热卖的FIFA、NFS之流也不能与家用机上的WE(实况足球/胜利十一人)、GT(现在的官方中文翻译是《跑车浪漫旅》)相提并论,所以前者的市场停滞不前,后者的市场却在扩大。

  中国游戏从业人员还不懂得什么是行业自律,他们借助开放游戏平台的开放性低价引进外国游戏冲击本国市场,生产低质量游戏搞恶性竞争,任由各方胡乱炒作制造更多的泡沫。这些人还普遍把行业发展寄托于国家对网络游戏平台这样的开放性平台的支持,其中一点就是提议国家牵头搞类似韩国那样的软件通用引擎模式。引用一些媒体的话就是:韩国游戏过去买不起游戏引擎,就由韩国政府出资,韩国科技振兴院出面负责购买国外的优秀的引擎,然后再把这些游戏引擎改良制作成可通用化的程序,再以相对低廉的价格卖给制作小组,名曰“支持韩国游戏行业发展”。前阵子报道说,国家真的就为此时搞了一个“863计划”。国家支持游戏行业发展我们当然欢迎,可这种做法实际上就是搞所谓的“非市场化的授权体系”,而且此类授权的作用就是用行政手段把游戏制作的门槛放得更低。问题是,低级产品的大量出现会在市场和投资方两头堵塞原创空间,再说中国游戏行业现在根本不缺低级产品,更用不着再引入更多的人造出更多低级产品。有人说这是学韩国,但其实韩国游戏即使比中国强,它的发展方向已经注定了只有本国市场、中国市场和东南亚市场能够消化它的产品,而且它的热销很大程度上是因为其他非开放游戏平台在这些国家发展程度低(例如一度视游戏机为洪水猛兽的中国)。在高品质的日本、欧美游戏面前,韩国游戏根本无竞争力可言。韩国人自己的本土市场没等行业发展起来就已经饱和了,于是他们只能在中国和背水一战,学他们肯定是死路一条。

  封闭的授权体系背后实际上是行业的高度自律。在过去它重新激活了美国市场,保证了电子游戏行业的健康发展。有了它的间接支持,CERO(日本的“电脑娱乐评级机构”)、ESRP(美国的“娱乐软件评级部”)才得以顺利运行,在行业内部解决了“游戏软件分级”这种让政府觉得左右为难的问题。在中国网游发展得轰轰烈烈的今天,业内抄袭、投机风潮正盛,以往由其他游戏平台间接造成的社会问题显然已经通通转嫁给了网络游戏平台。冒昧问一句,现在又有哪位中国游戏界名人能够少说几句“投资运营”,提及一下关乎业界未来存亡的“行业自律”?很可惜,如今在游戏圈内游弋的资本家们并不会在乎这些,他们还沉浸在“游戏行业能赚钱”的欢乐之中。

  6、游戏行业是娱乐行业

  网络游戏赚钱了——区别于以前的街机房和盗版水货游戏机制造贩卖,这次是很体面地以“互联网增值服务”之名赚钱了。所以,中国大部分人开始认同游戏行业是娱乐行业,认为应该给予其发展的空间。但大家别忘了,娱乐行业其实也分三六九等,像歌剧、相声、小品,地位就是比摇滚、蹦D高得多。游戏无疑属于下等娱乐:街机房、网吧向来是重点稽查对象之一,理由是三教九流混迹常混迹于此;游戏是廉价的,应该说,要是你敢不廉价我就去买盗版;游戏还是值得被怀疑的,它曾经是电子海洛因——现在虽然“不是了”,但家长们仍然有足够的理由认为它有损未成年人的身心健康;玩家群体是弱势的,主流媒体上玩家群体总是集体失语,由得那些不玩游戏的人爱怎么说就怎么说——按他们的说法,凡是做游戏的都应该以“制造、贩卖海洛因”论处。有人说,网络游戏的时代来了,我们终于正名了,行业终于有发展的希望了。我想,事情没那么简单。

  任天堂社长岩田聪在今年的东京游戏展强调:“所有的娱乐都在争夺用户有限的时间,并为此展开了激烈的竞争。”此言道出了日本游戏软件市场自1997年达到颠峰之后不断萎缩的重要原因之一:近年移动电话的普及以及移动电话性能的大幅度提升造就了新的游戏平台(最新的DoCoMo手机已经可以运行Final Fantasy、Dragon Quest 复刻版)和新的娱乐咨询平台,这使得个人可支配资金减少,且人们的部分注意力不免会从游戏机转移至移动电话。与此同时,宽带网络在日本国内的逐渐普及催生了许多免费小游戏网站(以Flash游戏为主),这样的游戏方式便利而且不花钱,也无疑会对现有的游戏软件市场产生影响。参考这样的情况仔细想想,难道网络游戏真的能幸免于盗版的影响吗?真的可以因为幸免于盗版而幸免于其他平台的冲击吗?

  中国经济在增长,借助游戏媒体的发展和互联网的普及,人们的眼界也日渐开阔,现在玩游戏机的人已经比起FC时代是有过之而无不及。假设我们为封闭体系的游戏平台进入中国设置障碍,那些循非正式渠道流入中国的游戏主机势必持续扩散,而这些主机的软件主要来源就是几块钱一张的盗版。在这种模式下,玩游戏只需要游戏主机这个一次性投资,软件几乎是不花钱的。也就是说,中国游戏行业乃至各行各业在这个过程中非但没有占到任何的便宜,反而会被影响力日益扩大的家用机游戏占去用户娱乐时间——也就是中国游戏行业的生存空间。这种情况会比某些人所说的“中国拥有自主知识产权的游戏产业将受到抑制,导致中国信息产业遭受重创”更坏,因为你的对手根本就不是对手本人,而是对手的一个极廉价倾销版本,所有的行业保护手段都将不适用,这意味着你不会有任何与对手讨价还价的机会。介时你有多少本土优势,多少宣传优势也是白搭,因为通过中国的一个极为严重的漏洞——盗版搞出来的东西有两个无法超越的优势:第一是价格,即使是每小时几毛钱的网游,也不可能跟5块钱玩几个星期、一个月的游戏抗衡;第二是软件品质的差距,这种差距在前文已经说到过——完全是石器时代和后工业时代的差距。要彻底解决这个问题的办法就是灭绝中国的一切盗版,但这在很长时间内都是不可能的——且不讨论灭绝盗版的可行性,就以中国现在的状况,灭绝盗版绝对会影响计算机的普及。当然,还有一种办法就是政府立法彻底禁止中国人谈论游戏主机,不过我想这会是社会大退步。

  我曾经有一个理想,虽然说现在我已经放弃它了,但是我还是想带着几分羞耻把它说出来:我要用自己的努力和天赋为中国电子游戏行业打造一个稳固的根基,因为电子游戏是娱乐行业,它在中国不但有过去,现在,还有未来——它是中国从来没有过的新事物,它代表了新世纪的那种崭新的、上进的、充满了交流的娱乐观念,能够让一度闭关自守的中国人通过一个新的途径把自己与世界连接起来。对于我来说这是一个伟大的理想,但在有些人看来,它更像是一个夜总会老板的豪言壮语。在这里,我也不敢坚持我是对的,因为一个自己认为是好的事物竟然在中国屡屡成了什么性交易、杀父弑母的导火索,我也不好意思坚持自己在人格上是不可被怀疑的。但在外国,游戏却没什么不好,围绕它出现的社会问题并不比电影多——我觉得游戏没有错,错的是玩游戏的人和做游戏的人,是他们出了问题。

7、什么人在做游戏

  做游戏的人问题本来就多了,刮了“自主研发风”以后问题就更多了,多得不禁让人倒抽一口凉气——游戏圈内正流行着这么一句话:现在自称能当企划的人能绕地球两圈半。一个知名游戏企业在上海公开招聘的时候只要求应征者写十个游戏名称,但即使如此,上千个前来应征的人也多的是写不完的。其他行业会拿十个书名,十个品牌名来做题目吗?考验难度如此之低,也还是有不少人无法通过。这还仅仅是应征,如果在看这篇文章的诸位曾目睹过现在网络游戏都是由一些怎么样的人,用怎么样的方法来做,你们一定会觉得问题比这严重得多。

  在我们的常识里面,开飞机要专业的,要经过训练的;做会计也是要专业的,要拿到证书的。我想说,做游戏也是要专业的,难道不是吗?好比说,你自称比较擅长RPG设计,或者说你想在这方面有所发展,那你对FF(最终幻想系列)要有个基本了解吧,对DQ(勇者斗恶龙系列)要有基本了解吧,对MM(魔法门)系列等世界公认的经典有所了解吧。这种要求在我看来一点都不过分——好比你自称很懂白话文就应该看过《阿Q正传》,自称精通中国文化总要看过四书五经,连这都没看过算什么懂了?但在那段人人踊跃做RPG的时期(现在只有圈内老人还记得了),还有现在MMRPG的自主研发时代,有多少年轻人光玩过一个仙剑奇侠传就燃起胸中无比热情跑进游戏圈来做游戏啊?有多少年轻人光玩过一个传奇就自以为很懂网络游戏跑来做游戏啊?虽然这里缺乏切实的数据,但是我凭我那么多年的观察可以判断,在这两个自主研发沸沸扬扬的年代,圈内一线制作的搞游戏设计、游戏策划的人里面大部分都是这种货色,而且一代不如一代。

  自中国人开始做游戏起,年年都有人问“中国游戏怎么了”、“中国游戏路在何方”,同时年年都有尚不知游戏为何物、对游戏行业缺乏基本了解的人挤进游戏圈来。现在不要求你已经懂得了游戏是什么,但如果说连个约莫认识都没有是说不过去的。其实无知并不重要,重要的是要承认自己无知。但现在有谁承认自己无知了?有谁认真去学习了?别的我不敢说,就整个圈子搞设计策划的这批人来说,大部分都以为自己懂得的已经够够了,甚至可以开班收徒弟了。其实在日本同行面前,大家都是小学生,顶多就是一年级跟二年级的区别。现在圈内的资本家、领导者所以表现得如此无知,搞设计搞策划的这些人也有责任:无知而且自满,告诉人家自己什么都能做,其实除了抄袭就什么都不能做,甚至连抄都抄不过来。等资本家知道自己当了冤大头,项目多半已经完蛋了,这种事情在业内难道还少吗?日本游戏行业也不见得公司领导层里都是游戏方面的行家,人家所以还能搞得蒸蒸日上,因为底下搞制作的这些人很有专业水平,很靠得住。现在做游戏的人所以普遍给人印象是职业水平低下的、靠不住的,说到底是因为这帮人里面混杂了许多外行,他们根本是来混饭的。

  我不是在苛求中国游戏制作者,而是,虽然有些事情你在中国确实做不到,要么要做到就要付出极高的代价。其他的还罢,首先要玩懂一些在游戏发展历史上占有重要地位的游戏,对于很多人来说就不是容易的事情:这些游戏通常都没有中文版本,要不就是翻译得极其糟糕(在我看来)。想要真正的玩懂,在玩的过程中领会许多游戏设计技巧,就必须扔开一切可供参考的所谓游戏攻略,实实在在看懂游戏中的每一段对白,老老实实地把游戏玩一遍而不是依据什么指南“过”一遍。这首先需要时间,其次需要至少一门外语的一定的语文水平(这里是指语文水平——也就是说,仅仅有哑巴外语、应试外语的水平是不够的)。更有某些特殊的平台上特殊的游戏让人不得不买正版,就对财力有所要求了。再说,在中国玩游戏还一度跟吸食海洛因是同等性质的,如何能避免家里人要死要活地劝说,还得看个人的造化修为。

  坦白的说,我基本具备了上述条件,但正因为我有这样的条件,我才知道要达到这种程度有多不容易。有些事情他们做不到也不怪他们,因为这些事情是根本没办法单靠游戏业内的改变去解决的。

  李敖先生曾说,其实没有台湾问题,根本是个美国问题,中国跟美国的问题。我在此也要说,其实没有中国游戏行业发展问题,只有中国教育问题。教育跟游戏制作有关系吗?有的。所谓“幸福源于参差多态”(罗素语),电子游戏的乐趣也是源于参差多态的。如果所有的游戏都是一种思路做出来的,就没有现在的电子游戏行业了。游戏行业还很年轻,不存在这样或者那样的登山宝训或者权威理论,只有着思想与思想之间碰撞产生的火花,巨大的成功与惨痛的失败。即使是游戏行业已经高度发展的日本,游戏制作的职业培训也尚未成熟,特别是很多游戏理念、游戏理论的认同仍然需要靠山内溥、三上真司这样的业界精神领袖来维系。真因为如此,在游戏行业从事一线研发有着许多不确定性,但同时这也是一种没有各种思维上的条条框框限制的工作。经验靠自己积累,现象靠自己观察,问题要自己解决,在游戏行业要做好事情就必须保持良好的理解能力,严谨的分析能力和不拘一格的创造能力。可惜,这些内容并不为中国应试教育所关注。

  存在就是合理,应试教育在多少人的非议下还能纹丝不动是有道理的:中国国土面积大,人口众多,地区贫富悬殊,地方对教育的投入也极其悬殊。面对这种情况,教育制度的定制就只能就低不就高,力求用最小的投入产出最多的效果。应试教育就完全符合以上所有条件:它可以高效的,批量的产出人才(典型的填鸭式教育),又可以用比较低的成本检验成果(分数代表一切)。这是一种无差异化的教育,产出的人才会有同样的优势,例如世界一直公认中国学生算术做得好(但大数学家却屈指可数),也会有同样的缺陷,其中一点就是现在人们经常提起的群体性知识缺陷,群体性能力缺陷。例如,老一辈人常说,现在的人语文真是差劲。的确,应试教育能够让几岁的孩子背完唐诗三百首,能够让高考学生大篇大篇的背诵范文,但这显然丝毫不能促进他们对祖国语言有多少了解。原因很简单,因为理解能力的提高不是靠死记硬背别人的解释(包括段落大意、中心思想等等),而是要经过各种关于活学活用的锻炼。的确,我不怀疑他们背的东西都是经典,但是学习经典的最终目的不是要变成重复古人话语的留声机,变成自己思想中的条条框框,而是通过熟悉经典而达到引经据典、为我所用的境界。说到这里,我禁不住要提出一个奇怪的现象:游戏行业本身并没有产生出什么理论权威,没有产生出什么条条框框,但居于世界游戏行业底层的、玩游戏或者做游戏的中国人总是企图为游戏设立什么条条框框。《电子游戏软件》上出现的“天师现象”算是一个典型:对某一种、某一类的游戏总是有着近乎天生的、莫名其妙的仇恨,声称非某某游戏不玩,非某某公司出品不玩等,声称只有某些游戏才是经典,其他游戏一律是不入流的货色。这里姑且不怀疑这些游戏究竟是不是经典——本来玩玩经典游戏不是什么坏事,但某些人玩经典玩中毒了,他就是认为游戏就是要做成某一个游戏那样才叫做好玩、有趣,乃至到了不是经典不玩,不像经典就不玩,唯经典是瞻的地步。等这样的人进了游戏公司,事情就麻烦了,你会发现你他们对游戏设计根本没有任何理解能力,跟他们谈许多设计问题简直就是对牛弹琴,因为他们根本不曾接触经典以外的游戏,哪怕是一些尽管不够经典但是很有设计参考价值的游戏。在学生时代习惯于从经典里面不加理解地找绝对真理的他们,把这种习惯完完全全地搬到游戏行业里面——此处已经排除了他们借鉴的经典不够“经典”的可能,那种由于借鉴了一个坏的设计导致整个项目乱七八糟的情况我是见过的。

  没有理解就更没有分析。可以被称之为“玩家”的中国人在游戏杂志里,平日的言谈里总是离不开这三个字:可玩性。游戏好玩他们就说有“可玩性”,游戏不好玩就是没有“可玩性”,甚至说一个游戏好玩是因为它有“可玩性”,不好玩是因为它没有——“可玩性”之于游戏就仿佛如那玩意儿之于人——要判断游戏好玩不好玩跟判别男女同样简单,只需往裤裆下面伸手一摸就知道了。请问事情有那么简单吗?“可玩性”是一种已经成为共识的概念吗?我从未见过有人对这个词作过相关的论证。而且,爱用“可玩性”说事的人都忽略了一个问题,就是游戏受众问题。有的游戏你觉得好玩,我觉得不好玩,这时候你跟我谈“可玩性”就没有意义了。我在拙文《谈谈游戏作品分析》里写道过,“无论他们举出多少游戏设计去支撑他们的“可玩性”,都无外乎是证明了“可玩性”这样的出身:在他们有精力、有时间、有机会玩到的游戏里面,挑选出他们可以理解的、认为好玩的,再使用这样或那样的方法归纳出他们认为这些游戏好玩的地方。说到底,‘可玩’即是说话人‘可以玩’,‘可玩性’对说话人是没要求的。”请问这种模棱两可的概念拿得出来分析论证问题吗?应试教育经常给出“诚信”、“民族大义”这样的题目让学生们“自由发挥”,但却丝毫没有培养他们的逻辑思维,更没有给予他们最基本的哲学素养。没有逻辑思维的脑袋就是不觉得“好玩的游戏就有可玩性,有可玩性的游戏就好玩”有什么问题;没有哲学素养的结果就是搞了半天都不知道“可玩性”只是一个他们自己搞出来蒙自己的概念——我却见过不知多少做了数年游戏的人张嘴闭嘴就是“可玩性”!

  很久以前,有个游戏圈的前辈跟我说过,他觉得做游戏最重要就是从别人的游戏中学习。这话大抵没错——游戏行业太新了,如果不能从别人做的游戏里面学你根本就什么都不知道。但看看做游戏的这些人,多数一没有理解能力二没有分析能力,你让他们怎么去学。如果说他们什么都不用学就能创造,这就更扯淡了——脱离有效率的学习和借鉴之创造纯属异想天开,更何况中国学生创造能力全球倒数已经不是什么新闻了。不明白行业状况的读者可能会说,我这是在抹黑同行。但我哪里是在抹黑同行,我刚才说的这些人已经是真的为游戏行业动过脑筋的人了,他们已经算是真正做游戏的人了。刮“自主研发风”以后,因为找不到工作跑到游戏行业来混饭的大学生们问题更多,我根本不敢放到文章里面谈。

  发达国家的教育重视培养人对人的理解,中国现在的应试教育强调的是培养人对死物的理解。中国的官样文章里面有句话形容现在的教育现状,叫做“片面追求升学率”,意思是说追求升学率是件好事,但片面就不好了。我却以为,不以人为本的教育完全就是误人子弟。诸位,你们可能跟我一样都看到以下状况——很多的游戏制作人,他们把自己做的东西吹得天花乱坠,但绝口不提一点,就是他们的东西是做给人玩的。玩过国产游戏的都应该懂我的意思,有些国产游戏第一个场景就叫人如入地狱,看上去这种游戏更像是做给制作人自己玩的。游戏是做给人玩的,这是最显浅的道理,但很多人就是不这样认为,他们就是觉得游戏是做给猪玩的。持这样的理解做游戏只好做出一些不知道给谁玩的东西出来——相比起来什么理解能力、创造能力的缺失反而是末节。我们的应试教育只承认人的一种天赋,就是吸收知识然后把它写在纸上的天赋。但游戏行业要发展,就需要有着各种各样不同天赋的人,去做各种不同的事情,各样不同的游戏。有着大概一摸一样能力、大致一摸一样的想法的人注定只能做本质上一摸一样的东西,就像工厂流水线里做出来的东西一样——在游戏行业,同质化加上粗制滥造的产品是经不起考验的,一旦和别人的好东西面对面较量,立马就要摧枯拉朽。今日中国游戏行业过度营销背后的游戏产品正是这样的东西,但很多人觉得这还不够,还要加把劲“自主研发”。

  可能你们会以为我扯得太远了,游戏行业和教育大概没有这么多直接关系。但事实上,游戏制作的进行极为依靠集中脑力劳动的人力资源,教育对于游戏制作来说实在是太重要了,我国教育的现状限制了我国游戏行业的发展。最近《南方日报》里面的一篇叫做《不能让一个大学生因贫失学》的文章谈到了一些很有说服力的事实:

  “人是一个国家最宝贵的财富。日本、德国、瑞士等自然资源贫乏的国家之所以繁荣富强,高素质的国民是最为关键的原因,而这又源于政府对教育的重视。一年前热播的电视剧《走向共和》又一个经典场面,甲午战争后日本获得巨额赔款,首相要求用一部分改善国民生活,天皇坚决拒绝,说:‘全部用来办教育。’二次大战后,日本国民一度食不果腹,但政府对教育拨款却从不吝啬。”

  “反观我国,重视教育年年都说,可一到编制政府预算的时候就总要打折扣。‘九五’计划规定教育支出占GDP的比重到20世纪末要达到4%(世界平均水平是5.1%),可是现在要编制“十一五”计划了,这个目标还是没有实现。与此同时,贫困大学生的比例越来越高,因贫失学的压力越来越大。”

  有了这样的数目的、这样持续的教育投入去支持日本游戏行业的人力资源,日本游戏行业从一无是处到今日的如日中天。我们一度有“大力提倡素质教育”那么一说,最后又怎么样呢?我国就这样的教育投入,要等着低成本的应试教育模式垮台可以说是遥遥无期。然而,这种教育产出的人力资源根本不适合游戏行业,无法有效转化为游戏行业的人力资本。在以研发制作为核心的游戏行业,没有人力资本方面的优势就等同没有核心竞争力,这才是中国游戏行业的发展问题所在。

  游戏行业不行,游戏产品有责任;游戏产品不行,做游戏的有责任。做游戏的人太差劲,教育有责任。别看中国人多,几千万文盲摆在那里,拿了大学文凭的人素质可能还不如别人高中生,全国适合做游戏的人加起来大概还没有日本一个省适合做游戏的人多。人力资源贬乏直接造成了游戏行业人力资本的贬乏,此时大力发展游戏产业必定会加速产业空洞化。我承认,自己不但给文章起了个“没有明天”的题目,还通篇用描写“大炼钢铁”的方式去写现在吹得沸沸扬扬的“自主研发”,大有诋毁现在游戏行业发展形势一片大好的险恶用心。然而,我有很多朋友在年轻的时候也是头脑发热跑进游戏圈做事,到了现在已过而立之年,弄得不上不下,唯有在游戏圈撕混下去。现在有很多同样头脑发热的大学生纷纷涌进游戏行业做事,他们比起上一代的这些“愤青”对游戏了解得更少。同样的事情眼见再次发生,我就觉得,如果现在中国游戏行业能萧条下来,让这些至少受过教育的年轻人致力于其他行业,对中国,对中国游戏行业在数十年后的发展,都会是好事一件。


  8、什么人在玩游戏

  根据现在中国大众传媒的定义,在FC时代甚至更早时候就开始玩游戏的人已经完全被排除在“游戏玩家”这个群体以外——要被称为游戏玩家,电子游戏你要玩实况足球,电脑游戏你玩CS、星际争霸,网络游戏你要玩什么《传奇》、《RO》……按照某种说法,这是前网游时代玩家与网游时代玩家的区别。笔者作为前者中的一员,万万不敢证明自己就有什么优越性,甚至我认为这两个群体在本质上是非常相似的,那就是他们其实不懂得什么才是娱乐,游戏的吸引力又太大——还没等他们对什么是娱乐,什么是游戏有一个基本了解,就掉进游戏里面去了。在以前,街机房简直是一个令父母家长闻之色变的地方,现在的网吧则直接取替了它的地位,仅此而已。

  作为过来人,我也有玩游戏玩得天昏地暗的时候,也有偷遛进街机房被学校抓到通报批评的时候。当年我在学校大会上挨批,面红耳赤地站着听台上的学校领导训话,心里就暗想:十年以后,我们一定可以光明正大地玩游戏。十年过去了,我已不再年轻,才明白这种想法有多么幼稚。

  王小波曾言,“正常的性心理是把性当作生活中一件重要的事,但不是全部。不正常则要么不承认有这回事,要么除此之外什么都不想。假如一个社会的性心理不正常,那就会两样都占全。”在这里,我们可以顺理成章地把“性”替换成“游戏”,因为中国人的游戏心理同样不正常。早在许多年前,胡适就曾写文章批评中国人玩麻将没日没夜,浪费了时间耽误了中国人。时至今日,中国人对待娱乐方面的态度仍然没有多大改善,整个中国社会仍未从舆论、教育、学术方面对此作出反思,每次发现问题就是迫不及待地去查,去堵,没有人真真正正地像胡适这样去观察、去发现问题,更没有谁实实在在地去解决问题。问题长期不解决的恶果就是久治不愈:主角换了,由麻将换成了家用机、街机游戏再到今天的网络游戏,以前有欠下赌债卖房子卖儿女,今日有为了个把“武器”出卖肉体、自杀、甚至杀父弑母。问题重重复复地出现,政府每次又重复地花费大量的社会资源去查堵。我不打算在这篇文章里谈社会问题——我更想以同为前游戏时代的玩家的身份,向前游戏时代的玩家们说一些我们正在亲身经历的事情。

  我们是中国接触电子游戏最早的一批人,但我们中的许多人还是那样的不够水准。网游时代的玩家也是多数很不够水准,但是摆在他们面前的游戏跟我们玩过的游戏完全不一样。当年我们玩的都是什么?电子游戏是Super Mario,是Pacman,是FF,DQ,FE(火炎之纹章),是魂斗罗,Battle City(坦克大战);PC上是三国志,大富翁,大航海时代1/2,信长之野望;街机上是SF2,恶狼传说系列、侍魂系列还有吞食天地、1942。就连那些为许多人所不齿的18禁游戏,例如当时红极一时的同级生系列也是世界级品质的东西,其中同级生更是恋爱游戏的“始祖鸟”,现在ILLUSION的那些丫丫乌出品绝不可以与之相提并论。我们玩了那么多一流的游戏。有言道,一流的读者不是天生的,他是培养出来的。我们赶上了时候,玩到了那么多第一流的,名声如雷贯耳的游戏,我们本来应该是第一流的玩家。可事实并不如此。

  现在我们中的绝大多数人正在用一种莫名其妙的态度去对待游戏。很多玩家对某些公司,例如暴雪、SQUARE有着狂热的忠诚,“不许说它们坏话”。然而他们绝大部分人的支持仅仅是“在精神上”的,自称是FANS但是从来没有买过这些公司的一个正版。买盗版本身是偷窃行为,是对创作人利益的漠视——我看不出做游戏的人因为盗版得了什么FANS的支持——唯一的解释就是,在盗版问题上他们得了不同程度的精神分裂:声称热爱游戏,喜欢某个游戏制作人,私底下却在做着损人利己的勾当。他们还认为,活在中国最幸福的事情之一就是可以无拘无束地用盗版。日本和欧美的游戏定价都很高,新游戏平均50美元的定价对于平民百姓来说也不算低,每个玩家每年平均下来也就是买几个游戏。但人家就把这样的地方当作是必争之地。中国玩家买盗版每年动辄就是几十张上百张游戏(全是盗版),涉猎的范围是世界级的,对厂商名、系列名如数家珍——可是人家根本不相信你会真的为这些游戏掏钱,这也是我们老是等不到一流游戏出汉化版的原因之一。

  玩游戏玩得多却不等于玩得好。中国人玩游戏,囫囵吞枣恐怕也是世界之最。以RPG游戏为例,中国玩家普遍的“玩过”标准是,匆匆看过对白甚至不看对白,对照着攻略过一遍就算是玩过。值得指出的是,与一般人的了解不同,他们所以不怎么认真对待对白不是因为语言问题,我就见过不少人放着英文版不买跑去买日文版,只因为一个很小的理由:日文版的字看上去更好看。对白?看着太累了,他们宁愿在通关以后买本游戏杂志看看所谓“小说攻略”。这是游戏态度问题。游戏对于中国人来说太廉价了,平均起来也就是五块钱一张,所以他们也用着仅价值5块钱的态度对待游戏。体验游戏是如此,思考任何关于游戏的问题也是如此。养成了这种恶劣的游戏习惯,游戏玩得越多,玩得越滥,就越容易造成游戏品味方面的“阳痿”,觉得个个游戏都差不多,也不想想其实是他自己的游戏体验出问题了。

  谈到这里,诸位大概也明白了我的意思:要构成一个够水准的游戏行业,除了游戏制作者要够水准,玩家也要够水准。山内溥一度豪情万丈地说:“消费者追求的是从游戏中体会道独创的乐趣,因此才会买下游戏主机。这个道理,我们从过去一直到将来都不能有片刻的忘怀。”说归说,如果玩家不卖帐,任天堂也就只好倒闭了,很多好游戏我们就玩不到了。我们的水准还很不够,本应是第一流玩家的我们没有能力像日本玩家那样积极投身游戏行业,左右游戏行业的发展——我们甚至没有左右其他中国人对游戏的看法,游戏被打压的时候我们总是保持集体沉默,背后或唉声叹气,或暴跳如雷。

  最近我在游戏店碰到一个玩游戏的漂亮女孩,她被人问到了一个古怪的问题:你为什么不买正版?她笑着说,游戏是短暂的。而我,多年以来坚持,游戏虽然短暂,可它带来了感动——而且是游戏制作人带来了感动。感动是永恒的,游戏制作人理应得到尊重。好好玩一个游戏,掏钱买自己认为值得玩的游戏,这才是尊重别人的表现,才是成年人应该有的游戏态度。忽然感觉自己老了。过了一个十年,我们已经不在年轻,中国人对待游戏的态度却依然是那样的幼稚。我们是不是应该问问自己,甚至问问每一个玩游戏的人:游戏对于我们来说究竟是什么?

  说到这里离题有点远了——思考这个问题不仅仅是为了游戏行业,更是因为我们已经是成年人了,已经玩了很多很多的游戏,所以我们要明白我们之前做了些什么,还有以后怎么做。如果连我们也不能保有正常的游戏心理,如果连我们继续玩游戏玩得那么不够水准,很难象游戏在我们成为社会主流的明日会有什么好名声。我再重申,只因为我们是成年人了,为自己以前做过的事情承担责任,为以后要做的事情承担义务,对于我们来说是理所当然的事情——假如我们还想我们和我们的子孙在这方面有不被干涉的自由,不会再有一个以“救救孩子”为口号的愚蠢声音朝着我们乱吼。

  断言中国游戏行业没有明天可能还为之过早,但这个明天也决不会是什么都不做、什么都不想就能够等来的明天。

前三年,阿蒙曾有一段时间喜欢上了佛教,只因觉得心力交瘁心乱如麻,唯有佛教思想能沁人心脾、明心显性,以寻求心灵深处片刻的宁静……实际上,佛教说来说去,无非就是要我们真正能领悟到“苦海无边,回头是岸”的道理,通常我们在面对苦难时总会害怕与担忧,但一旦看破红尘,将人生看成一大苦难场,那么我们会变得坦然与安祥,真正地享受生命的快乐。

 
作为一个有失败经历而且目前离成功还很远的创业者,阿蒙是有一些心得与体会的,人们常说“没有无缘无故的爱,也没有无缘无故的恨”,这个同样适用于创业,即“没有无缘无故的成功,也没有无缘无故的失败”,市场经济在一定的范围内是一个好东西,它让部分的竞争走向公开、公平、公正,因此面对客户面对市场,我们需要有成本意识,需要有质量意识,还需要有服务意识,我们不能稀里糊涂,也不能马马虎虎,更不能反应迟钝,这就对创业者有了更高的要求,创业者要功成名就,需要付出更多的心血与汗水。
 
佛教总结人的一生有八苦(生、老、病、死、爱离别、怨憎会、求不得、五阴炽盛),阿蒙也来总结一下这些年所受的苦,归纳来说,主要有以下的三苦:
 
1.身苦;
 
对于初创企业来说,这个生理方面的苦是非常地明显的,由于要控制成本,我们不可能招聘太多的人才,因此创业者本身有时要担当多个角色,很多事情要亲力亲为,这种忙碌日复一日年复一年,每一天风里来雨里去,没有双周日,没有节假日,你无怨无悔,因为你要创业,你有成功的梦想,这个梦想支撑着你不断地奋斗与前进。
 
2.心苦;
 
创业者劳心甚于劳力,员工的表现、公司的前景、资金链现金流、市场的开拓等等都让创业者每天寝食不安如坐针毡,合同签署了固然高兴一下,但回款的问题又开始让你心烦意乱,昨晚与王总又吃了个饭,讨论关于新项目的事项,但还是没有有明确的答复,这饭不知吃到何时才能了结?创业者在思想上起伏不定,情绪上担惊受怕,心理上心力交瘁,这是常有的事情。
 
当然也有非常NB的创业者,他们有能力空手套白狼,一下子几千万就来了,然后他们高起高做,住的是洋房,坐的是名车,N个保镖N个助理N个美女围着他转个不停,那简真太像个老板了,这种人可能命好,象阿蒙这种人是苦命的,晚上吃完饭打个包回来给老婆,叫新来的保安开大门,该死的保安还以为偶是个送饭的,回到家后仔细照镜子,还真象,不能怪人家。
 
3.境苦。
中国部分行业的环境并不好,比如足球,整个就是大染缸,什么四小天鹅啊,从国外飞回来后,不到一年就成了废铜烂铁。IT行业也好不到哪里去,盗版猖獗,管理混乱,部分政府好大喜功,部分企业急功近利,部分IT人士好高骛远,一年换一个地,看似逍遥,实则内心空虚,这年头要找一个好的主人也不容易啊,没有完美的企业,也没有完美的个人,因此大体上过得去就行了。
 
市场环境千变万化,创业者需要提高注意力,随着市场环境的变化而变化。商业机会稍纵即逝,创业者需要提高警惕性,一点都不得马虎。竞争对手如狼似虎,创业者需要时时刻刻防范,并采取合理的策略与对手火拼到底。这些都是创业者要面对的环境问题,最糟糕的是遇到天灾人祸,比如当年的SARS,不知害死了多少的企业。
 
面对这些苦,创业者需要坚强不屈,不要害怕与躲避,只要对自已、对员工负责,总有一种思想、一种安慰、一种信仰可以让创业者的心得到依靠,并成为创业者不断努力奋斗的精神支柱。无论什么艰难险阻,只要获得了激情与动力,我们都积极进取共同度过。
 
人性的某些特质,只有通过苦难才能得到考验和提高,一个人、一个企业,乃至一个民族、一个国家,通过承受苦难而获得的精神价值是一笔特殊的财富。比如突如其来的“非典”,它实际上增强了中华民族的团结、乐观、自信等优秀品质。
 
创业者对苦难感悟越深,就越能从中体会到人存在的意义,也就可以超然于物外,胸怀坦荡且自在逍遥,这才是对精神磨难的彻底解脱。
 
创业者需要直面现实社会,勇敢地面对现实中的三苦,力争做到“我不下地狱,谁下地狱”。
 
 
本文部分章节参照《读禅学管理》。


[转载至阿蒙专栏]

在这种关键的时刻,那些平时慢慢悠悠顺序发生和并列发生的事,都压缩在这样一个决定一切的短暂时刻表现出来。这一时刻对世世代代作出不可改变的决定,它决定着一个人的生死、一个民族的存亡甚至整个人类的命运。
命运总是迎着强有力的人物和不可一世者走去。多少年来,命运总是使自己屈从于这样的个人:凯撒、亚历山大、拿破仑,因为命运喜欢这些像自己那样不可捉摸的强权人物。

但是有时候,当然,这在任何时代都是极为罕见的,命运也会出于一种奇怪的心情,把自己抛到一个平庸之辈的手中。有时候——这是世界历史上最令人惊奇的时刻——命运之线在瞬息时间内是掌握在一个窝囊废手中。英雄们的世界游戏像一阵风暴似的也把那些平庸之辈卷了进来。但是当重任突然降临到他们身上时,与其说他们感到庆幸,毋宁说他们更感到骇怕。他们几乎都是把抛过来的命运又哆哆嗦嗦地从自己手里失落。一个平庸之辈能抓住机缘使自己平步青云,这是很难得的。因为伟大的事业降临到渺小人物的身上,仅仅是短暂的瞬间。谁错过了这一瞬间,它绝不会再恩赐第二遍。

格鲁希

维也纳会议正在举行。在玩弄权术和互相争吵之中,像一枚嗖嗖的炮弹飞来这样的消息:拿破仑这头被困的雄狮自己从厄尔巴岛的牢笼中闯出来了;拿破仑赶走了国王;军队又都狂热地举着旗帜投奔到他那一边……好像被一只利爪攫住,那些刚刚还在互相抱怨的大臣们又都聚集在一起,他们再次联合起来,彻底击败这个篡权者。威灵顿开始从北边向法国进军,一支由布吕歇尔布统率的普鲁士军,作为他的增援部队从另一方向前进。施瓦尔岑贝格在莱茵河畔整装待发;而作为后备军的俄国军团,正带着全部辎重,缓慢地穿过德国。

拿破仑看清了这种致命的危险。他必须在普鲁士人、英国人、奥地利人联合成为一支欧洲盟军前就将他们分而攻之,各个击破。于是他匆忙把赌注押在欧洲流血最多的战场——比利时。(1815年)6月16日拿破仑大军的先头部队在林尼与普鲁士军遭遇,并将普军击败。这是这头雄狮的第一次猛击,这一击非常厉害,然而却不致命。被击败而并未被消灭的普军向布鲁塞尔撤退。

拿破仑准备向威灵顿的部队进攻。他不允许自己喘息,也不允许对方喘息,因为每拖延一天,就意味着给对方增添力量。17日,拿破仑率领全军到达四臂村高地前,威灵顿这个对手已在高地上筑好工事,严阵以待。拿破仑充分估计到自己面临的各种危险,即布吕歇尔的军队仅仅是被击败,而并未被消灭。这支军队随时可能与威灵顿的军队会合。为了防止这种可能性,他抽调出一部分部队去跟踪追击普鲁士军,以阻止他们与英军会合。

他把这支追击部队交给了格鲁希元帅指挥。格鲁希,一个气度中庸的男子,老实可靠,兢兢业业。他既没有缪拉那样的胆识魄力,也没有圣西尔那样的足智多谋,更缺乏内伊那样的英雄气概。关于他,没有神话般的传说,也没有谁把他描绘成威风凛凛的勇士。他从戎20年,他是缓慢地、一级一级地升到元帅的军衔。拿破仑大概也知道,格鲁希既不是气吞山河的英雄,也不是运筹帷幄的谋士。但是他自己的元帅,一半已在黄泉之下,而其余几位已对这种没完没了的风餐露宿的戎马生活十分厌倦,正怏怏不乐地呆在自己的庄园里呢。所以,拿破仑是出于无奈才对这个中庸的男子委以重任的。

17日上午11时,拿破仑第一次把独立指挥权交给格鲁希元帅。就在这一天,在这短暂的瞬间,唯唯诺诺的格鲁希跳出一味服从的军人习气,自己走进世界历史的行列。拿破仑的命令是清楚的:当他自己向英军进攻时,格鲁希务必率领交给他的三分之一兵力去追击普鲁士军,而且他必须始终和主力部队保持联系。

格鲁希元帅踌躇地接受了这项命令。他不习惯独立行事。只是当他看到皇帝的天才目光,才感到心里踏实,应承下来。格鲁希的部队在瓢泼大雨中出发。

决定世界历史的一瞬间

18日上午11点,炮手们接到命令:用榴弹炮轰击山头上的身穿红衣的英国士兵。接着,内伊——这位“雄中之杰”,率领步兵发起冲锋。从上午11点至下午1点,法军师团向高地进攻,一度占领了村庄和阵地,但又被击退下来,继而又发起进攻。在空旷、泥泞的山坡上已覆盖着1万具尸体。可是除了大量消耗以外,什么也没有达到。双方的军队都已疲惫不堪,双方的统帅都焦虑不安。双方都知道,谁先得到增援,谁就是胜利者。威灵顿等待着布吕歇尔;拿破仑盼望着格鲁希。

但是,格鲁希并未意识到拿破仑的命运掌握在他自己手中,他只是遵照命令于17日晚间出发,按预计方向去追击普鲁士军。因为敌人始终没有出现,被击溃的普军撤退的踪迹也始终没有找到。

正当格鲁希元帅在一户农民家里急急忙忙进早餐时,他脚底下的地面突然微微震动起来。所有的人都悉心细听。从远处一再传来沉闷的、渐渐消失的声音:这是大炮的声音,是远处炮兵正在开炮的声音,不过并不太远,至多只有三小时的路程。这是圣让山上的炮火声,是滑铁卢战役开始的声音。副司令热拉尔急切地要求:“立即向开炮的方向前进!”所有的人都毫不怀疑:皇帝已经向英军发起攻击了,一次重大的战役已经开始。可是格鲁希却拿不定主意。他习惯于唯命是从,他胆小怕事地死抱着写在纸上的条文——皇帝的命令:追击撤退的普军。热拉尔看到他如此犹豫不决,便恳切地请求:至少能让他率领自己的一师部队和若干骑兵到那战场上去。格鲁希考虑了一下。他只考虑了一秒钟。

然而格鲁希考虑的这一秒钟却决定了他自己的命运、拿破仑的命运和世界的命运。格鲁希使劲地摇了摇手说,把这样一支小部队再分散兵力是不负责任的,他的任务是追击普军,而不是其他。就这样,他拒绝了这一违背皇帝命令的行动。而决定性的一秒钟就在这一片静默之中消逝了,它一去不复返,以后,无论用怎样的言词和行动都无法弥补这一秒钟——威灵顿胜利了。

格鲁希的部队继续往前走。随着一小时一小时的过去,格鲁希越来越没有把握,因为令人奇怪的是,普军始终没有出现。显然,他们离开了退往布鲁塞尔去的方向。接着,情报人员报告了种种可疑的迹象,说明普军在撤退过程中已分几路转移到了正在激战的战场。如果这时候格鲁希赶紧率领队伍去增援皇帝,还是来得及的。但他只是怀着愈来愈不安的心情,继续等待着消息,等待着皇帝要他返回的命令。可是没有消息来。只有低沉的隆隆炮声震颤着大地,炮声却愈来愈远。孤注一掷的滑铁卢搏斗正在进行,炮弹便是投下来的铁骰子。

滑铁卢的下午

时间已经到了下午一点钟。拿破仑的四次进攻虽然被击退下来,但威灵顿主阵地的防线显然也出现了空隙。拿破仑正准备发起一次决定性的攻击。这时,他发现东北方向有一股黑的人群迎面奔来。一支新的部队!

所有的望远镜都立刻对准着这个方向。难道是格鲁希大胆地违背命令,奇迹般地及时赶到了?可是不!一个带上来的俘虏报告说,这是布吕歇尔将军的前卫部队,是普鲁士军队。此刻,皇帝第一次预感到,那支被击溃的普军为了抢先与英军会合,已摆脱了追击;而他——拿破仑自己却用了三分之一的兵力在空地上作毫无用处、失去目标的运动。他立即给格鲁希写了一封信,命令他不惜一切代价赶紧与自己靠拢,并阻止普军向威灵顿的战场集结。

与此同时,内伊元帅又接到了进攻的命令。必须在普军到达以前歼灭威灵顿部队。整个下午,向威灵顿的高地发起了一次又一次的冲锋。战斗一次比一次残酷,投入的步兵一次比一次多。但是威灵顿依旧岿然不动。而格鲁希那边却始终没有消息来。内伊元帅已决定把全部队伍都拉上去,决一死战。于是,1万名殊死一战的盔甲骑兵和步骑兵踩烂了英军的方阵,砍死了英军的炮手,冲破了英军的最初几道防线。虽然他们自己再次被迫撤退,但英军的战斗力已濒于殆尽。山头上像箍桶似的严密防线开始松散了。当受到重大伤亡的法军骑兵被炮火击退下来时,拿破仑的最后预备队——老近卫军正步履艰难地向山头进攻。欧洲的命运全系在能否攻占这一山头上。

决战

自上午以来,双方的400门大炮不停地轰击着。前线响彻骑兵队向开火的方阵冲杀的铁蹄声。从四面八方传来的冬冬战鼓声,震耳欲聋,整个平原都在颤动!但是在双方的山头上,双方的统帅似乎都听不见这嘈杂的人声。他们只是倾听着更为微弱的声音。

两只表在双方的统帅手中,像小鸟的心脏似的在嘀嗒嘀嗒地响。这轻轻的钟表声超过所有震天的吼叫声。拿破仑和威灵顿各自拿着自己的计时器,数着每一小时,每一分钟,计算着还有多少时间,最后的决定性的增援部队就该到达了。威灵顿知道布吕歇尔就在附近。而拿破仑则希望格鲁希也在附近。现在双方都已没有后备部队了。谁的增援部队先到,谁就赢得这次战役的胜利。

普军的侧翼终于响起了枪击声。拿破仑深深地吸了一口气:“格鲁希终于来了!”他以为自己的侧翼现在已有了保护,于是集中了最后剩下的全部兵力,向威灵顿的主阵地再次发起攻击。这主阵地就是布鲁塞尔的门闩,必须将它摧毁,这主阵地就是欧洲的大门,必须将它冲破。

然而刚才那一阵枪声仅仅是一场误会。由于汉诺威兵团穿着别样的军装,前来的普军向汉诺威士兵开了枪。但这场误会的遭遇战很快就停止了。现在,普军的大批人马毫无阻挡地、浩浩荡荡地从树林里穿出来。厄运就此降临了。这一消息飞快地在拿破仑的部队中传开。部队开始退却。所有剩下的英军一下子全都跃身而起,向着溃退的敌人冲去。与此同时,普鲁士骑兵也从侧面向仓皇逃窜、疲于奔命的法军冲杀过去。

仅仅几分钟的工夫,这支赫赫军威的部队变成了一股被人驱赶的抱头鼠窜、惊慌失措的人流。在一片惊恐的混乱叫喊声中,他们轻而易举地捕获了拿破仑的御用马车和全军的贵重财物,俘虏了全部炮兵。只是由于黑夜的降临,才拯救了拿破仑的性命和自由。一直到半夜,满身污垢、头昏目眩的拿破仑才在一家低矮的乡村客店里,疲倦地躺坐在扶手软椅上,这时,他已不再是个皇帝了。他的帝国、他的皇朝、他的命运全完了。一个微不足道的小人物的怯懦毁坏了他这个最有胆识、最有远见的人物在20年里所建立起来的全部英雄业绩。

那关键的一秒钟就是这样进行了可怕的报复。在尘世生活中,这样的一瞬间是很少降临的。当它无意之中降临到一个人身上时,他却不知如何利用它。在命运降临的伟大瞬间,市民的一切美德——小心、顺从、勤勉、谨慎,都无济于事,它始终只要求天才人物,并且将他造就成不朽的形象。命运鄙视地把畏首畏尾的人拒之门外。命运——这世上的另一位神,只愿意用热烈的双臂把勇敢者高高举起,送上英雄们的天堂。

  中医支持者说:中医日渐萎缩的现状,是现在好中医生太少,“原则上的中
医”绝对是英明而伟大的,而且疗效比西医好,副作用比西医小。

  然后他们又开始忽悠了:只要西医没有承诺100%的治愈率,就不要相信西医,
谁能保证你不是那少数呢?

  然后他们还说:目前中国经济、科学、文化太落后了,如果中国是世界第一
大强国,肯定全世界都来学中医了。

  既然现在的中医如此不堪,还忽悠大家生病了去看中医,这不是煽动是什么?
法·轮·功的教义里有“生病了不吃药”,如果李·洪·志大师身体力行地彻底
执行这教义,我们还真佩服他是条汉子,至少在言而有信方面。但是他和他家人
生病了,灰溜溜地偷偷地跑去看医生的时候,我们就知道他真不是个东西。同样
如果中医支持者自己生病都不去看中医,利用中国人的民族感情煽动大家生病了
去都看中医,这不是赤裸裸的煽动,是什么!

  再说 “好中医”既然那么少,全国人都来看那么几个“好中医”,那场面
一定很壮观。这些传说中的“好中医”也很奇怪,明明中医处境已经十分危险了,
他们几个人横空出世,再使出杀手锏,正好证明中医现在还有“神医”存在,证
明中医比科学还科学,力挽中医于不倒,这将是多么大的功德,他们将比扁鹊还
扁鹊,比华佗还华佗。但是我们都知道这是不可能的,就好比基督徒不能让上帝
现身一样。“好中医”是见光死,中医理论产生的好疗效也是见光死。

  支持中医的人,请为自己的言论负责,说出来的话要算话,不要让人有错觉,
中医支持者除了反问就只会出尔反尔了,如果自己都没身体力行地执行“生病了
就去看中医”这条“中医教义”,却忽悠别人去执行这条“中医教义”,这就是
煽动的言论了。

  其实“林妹妹”是你们学习的榜样,她才是真正的中医卫道士,如此奇异的
一番景象,很多须眉让了巾帼了。但是我不太希望中医支持者蜕变成中医卫道士,
人的生命毕竟是宝贵的。我想中医支持者思想里有或轻或重的精神分裂症而却不
自知,或许中医支持根本没意识到自己言论属于煽动性质,因为我们社会本身就
很荒谬。比如说我们很容易发现一些时尚、奢侈、败家类杂志的主编、编辑、记
者本身收入就比中国平均收入高不了多少,却忽悠大家去买几千几万甚至价格更
高的奢侈物品。只要正常一点的中国人大概都不会被他们忽悠,即使买了那种内
容巨空洞、文字巨绚丽、图片巨精美的杂志来看,来装点门面证明自己很有品位,
也不代表就会买上面推荐的东西,当然那种一天不花个几千几万就不爽的人和巨
傻的人除外。

  这些主编、编辑、记者很聪明,他们自己肯定不会去买那些她们推荐的东西,
他们也许会很崇尚“奢侈、贵族”的生活,但这只是精神上和原则上的崇尚,用
华美的诱人的文字引诱别人也来崇尚它,用精美的图片来崇尚它,总而言之,属
于意淫。他们该干嘛还是干嘛,下班还是买菜洗衣做饭,哄孩子,照顾老人等等,
他们做饭时候切到手了,就用创可贴,不会去找中医的“金创药”,也不会因此
去花几个小时去炖中药喝。那他们为什么推荐那些东西,很简单,奢侈品厂家给
了他们杂志社、报社广告费,说明白点,他们就是职业的托。

  还有那些算命先生、神棍、神婆,自己活得比较悲惨,却干着给别人算命的
营生,其实一看就是骗子。我们可以质问他:你自己都这样了,怎么还给别人算
命呢。他会说:我命里注定给别人算命啊,我命里注定活成这样。或者他给你一
句话:天机不可泄露。这是狡辩,但是却无法当场揭穿他。想揭穿很简单,你问
他今晚七点的中奖彩票号码,他就慌了。骗子最怕的是当场揭穿。所以算命先生
一般都是给你算一年以上的运程,甚至动不动算你一辈子的命,过了一年以上的
时间我们也不会去追究他什么。中医骗子的逻辑也是如此,西医能轻松治好的病,
中医不能轻松治好的或者一治就死的,中医骗子绝对不治这类病,防止被当场揭
穿。所以变着戏法骗,专治西医治不好的病,中医骗子治好了那是运气,治不好
也没人责怪,只能怪命里该死——荒谬吧,但这居然还有人信。

  中医支持者,也是中医有意无意的托。如果是有意的托,他们就是在欺诈。
若是无意的托,赶着支持中医传统文化的时髦的托,他们并没有意识到自己正干
着谋财害命的事。这的确是谋财害命的事,因为中医传统文化本身是不能治病的,
文化不能治病,民族精神也是不能治病的,不能因为中医姓中,就乡愿起来。当
然中医支持者有看西医的自由,但是既然他们看西医,却说着要大家去看中医的
鬼话,这不是思想的精神分裂么,这不是煽动么?

  很多人在这个荒谬的环境感觉不到荒谬了。那些发表耸人听闻的“煽动言论”
的人,正常生病该用西医还是西医。绝对不会因为自己发表了支持中医的言论,
就为自己言论负责了。他们知道生命就是真正最奢侈的奢侈品,用看上去“完美
的中医理论、璀璨的历史文化”是不能用来救命的。这些东西都只是中医支持者
的意淫,都是只能放进博物馆的假奢侈,就如同时尚杂志的精美照片和绚丽的文
字一样,都只能用来意淫而已。

  中医支持者相信中国人祖先创造出了伟大的完美的终极的中医理论,后人只
需舒心地躺在或骄傲地站在中医理论上自如地运用起中医理论就可以充当现代医
学、未来医学、超未来医学的老师,中医理论代表了医学的发展和前进方向,并
且注明中医理论的最终解释权归相信中医理论的中国人,所以直到人类毁灭,西
医应该谦虚地心甘情愿地匍匐在中医的石榴裙下,并聆听中医理论的教诲,俺们
中医蒙你吽,中医阿门。——幻觉和意淫,中国古人没那么超前,如果他们穿越
过到现在,会一个巴掌拍死所有的中医支持者:中医理论不是万能的神。即使是
最顽固的遗老辜鸿铭,看到这样的情况也会大骂:中医之亡,实亡于中医生与中
医支持者好吹牛屄也,欲救中医之亡必先从中医生及中医支持者不吹牛屄作起。
(见辜鸿铭《不吹牛屄》:“中国之亡,不亡于实业,不亡于外交,而实亡于中
国督抚之好吹牛屄也”;“今日欲救中国之亡,必从督抚不吹牛屄作起”。)

  西医(现代医学)公布治愈率是西医老实,跟西药标明详细的副作用道理是
一样的。中医不统计治愈率,中药不标明副作用,不能证明中医中药可靠,如果
你觉得中医中药可靠,那就是你的混乱幻觉。

  随着中国的强大(包括中国不“强大”的岁月里),外国人都在向中国学习
一些东西,比如中国的优秀文化,但肯定不会是中医理论。30年改革开放的历史
就足以证明,国学热、儒学热,孔子热,汉服热、汉语热……其他的传统文化都
在回归,但唯独不见中医热,倒是废医验药的呼声越来越高,赞同“中医是有意
无意的骗子”的人越来越多。随着科学的进步和传播,中医理论能走的道路越来
越窄。趁着现在中医尚未彻底地身败名裂,趁早把中医理论送进博物馆供人参观,
否则悔之晚矣。

作者:净凡皙/文责自负

“数典忘祖”,每逢提起批评中医的话题,总是有人扣这样的帽子。那就来
谈谈数典忘祖的中医们吧。

  青蒿和青蒿素

  中医几千年来,就是没治好过疟疾。上世纪70年代,有一个中医院的人从黄
花蒿里提取“青蒿素”,注意是提取,不是通过传统中医的洗晒炮制等工序,
“青蒿素”对疟疾有奇效。通过中医的理论指导得到的药就是中药,以现代医学
理论为指导,得到的药物就是现代医药。

  但是中医人士不这么想,这下中医来劲了。悍然宣称青蒿是自古以来中医治
疗疟疾的药物。并从古代药典找出证据来了。“中药青蒿治疗疟疾最早见于西元
340年间的东晋《肘后备急方》,作者是东晋医学家和炼丹化学家葛洪(281—
341AD)。青蒿素的发明就是得益于传统中医药学。1972年从中药青蒿中分离得到
抗疟有效单体,命名为青蒿素,对鼠疟、猴疟的原虫抑制率达到l00%。”

  既然中国人用青蒿治疗了几千年疟疾,而且疗效显著,还需要提炼什么青蒿
素么,直接吃中药青蒿就好了么?吃中药青蒿根本治不好疟疾,新鲜青蒿通过晒
干、烘培、炒、酒制……都会改变青蒿的药性,早就没有青蒿素在里面了。历史
上根本没有一例记载中医治好了疟疾的病例。有一个例子还是一个传教士用金鸡
纳治好了康熙的疟疾。

  而且中医们还似模似样的规定是用量来了:“青蒿一握,以水二升渍,绞取
汁,尽服之。”疟疾病人敢喝这样的中药么,不喝死才怪。

  中医们实在是可爱的紧。即使中药青蒿可以治病,甚至还和鳖一起炖服,那
就完全失去了药用价值。(见《青蒿素提取条件研究》作者:赵兵 王玉春 吴江
等 杂志名:中草药 总页数:958 年代:2000 期号:第1-12期提取工艺中,当温度
超过摄氏50度,分解就开始加速,提取效率大大降低,到60度,分子结构破坏,
就基本失去药效了。因此现在的提取工艺大多控制在50度以下。)而且青蒿素在
水中溶解度低,口服剂在胃肠里易被分解,根本没有疗效。无论是汤法,或是浸
法都没有疗效。

  最后青蒿素又叫黄花蒿素,根本不是从青蒿里提取的……这下中医们慌了,
然后他们又祭出“同物异名说”,怎么知道古代青蒿不是黄花蒿呢,古代又没植
物学。那我就祭出“无病说”,中医用中药青蒿治疗的根本不是疟疾,是不是
“虐鸡”啊,杀鸡后紧张过度,或者干脆声称是青蒿治疗艾滋病算了,古代又没
有疾病分类。

  后来中医们在1972年以后的典籍上都这样改写:青蒿、黄花蒿都可以治疗疟
疾。(这一过程叫修改历史、粉饰典籍——数典忘祖)

  辨症和辩证

  上世纪50年代中医们为了迎合统治者的治国哲学擅自将“辨症”改成“辩
证”,就这样“辩证”成了中医的终极理论。中医们擅自修改中医立足的基本理
论,这样毫无严谨治学的原则,毫无学术道德的行为实在让人不齿。

  中医人士看不起的西方野蛮人,为了坚持学说都可以毅然接受火刑。退一万
步讲,既然中医理论十分优秀,那中医们怎么连基本核心理论都不一直坚持,这
是发扬伟大医学的态度吗?正是这样主动自宫的行为,看以看出赤裸裸的两个字
“奴才”。要靠这样的奴才治学,治病救人,就是不可想象的。他们本着一切为
了统治者,一切为了苟延残喘,谋财害命,而不是为了发扬医学,不是发展科学。
可惜,中医就像年老色衰的老妇人,无论怎么改头换面,涂脂抹粉,,千方百计
迎合统治者,都无法挽救败亡的命运,不求真务实,光靠吹牛放炮,10年内中医
必亡。

  好在统治者并不糊涂,他们用奴才理论去拉政治选票政治支持,但是他不用
中医治病,人家用“白求恩”牌西医。因为他看不起奴才,也不相信奴才,更不
会相信口里骂着别人“数典忘祖”,自己暗地里羞答答数典忘祖真正背弃传统的
虚伪奴才。

作者:净凡皙/文责自负

我们这些总有一死的人的命运多么奇特!我们每个人在这个世界上都只作一个短暂的逗留;目的何在,却无从知道,尽管有时自以为对此若有所感。但是,不必深思,只要从日常生活就可以明白:人是为别人而生存的──首先是为那样一些人,我们的幸福全部依赖于他们的喜悦和健康;其次是为许多我们所不认识的人,他们的命运通过同情的纽带同我们密切结合在一起。我每天上百次的提醒自己:我的精神生活和物质生活都是以别人(包括生者和死者)的劳动为基础的,我必须尽力以同样的分量来报偿我所领受了的和至今还在领受着的东西。我强烈地向往着俭朴的生活。并且时常发觉自己占用了同胞的过多劳动而难以忍受。我认为阶级的区分是不合理的,它最后所凭借的是以暴力为根据。我也相信,简单淳朴的生活,无论在身体上还是在精神上,对每个人都是有益的。

  我完全不相信人类会有那种在哲学意义上的自由。每一个人的行为不仅受着外界的强制,而且要适应内在的必然。叔本华说:“人虽然能够做他所想做的,但不能要他所想要的。”这句格言从我青年时代起就给了我真正的启示;在我自己和别人的生活面临困难的时候,它总是使我们得到安慰,并且是宽容的持续不断的源泉。这种体会可以宽大为怀地减轻那种容易使人气馁的责任感,也可以防止我们过于严肃地对待自己和别人;它导致一种特别给幽默以应有地位的人生观。

  要追究一个人自己或一切生物生存的意义或目的,从客观的观点看来,我总觉得是愚蠢可笑的。可是每个人都有一些理想,这些理想决定着他的努力和判断的方向。就在这个意义上,我从来不把安逸和享乐看作生活目的本身──我把这种伦理基础叫做猪栏的理想。照亮我的道路,是善、美和真。要是没有志同道合者之间的亲切感情,要不是全神贯注于客观世界──那个在艺术和科学工作领域里永远达不到的对象,那么在我看来,生活就会是空虚的。我总觉得,人们所努力追求的庸俗目标──财产、虚荣、奢侈的生活──都是可鄙的。

  我有强烈的社会正义感和社会责任感,但我又明显地缺乏与别人和社会直接接触的要求,这两者总是形成古怪的对照。我实在是一个“孤独的旅客”,我未曾全心全意地属于我的国家、我的家庭、我的朋友,甚至我最为接近的亲人;在所有这些关系面前,我总是感觉到一定距离而且需要保持孤独──而这种感受正与年俱增。人们会清楚地发觉,同别人的相互了解和协调一致是有限度的,但这不值得惋惜。无疑,这样的人在某种程度上会失去他的天真无邪和无忧无虑的心境;但另一方面,他却能够在很大程度上不为别人的意见、习惯和判断所左右,并且能够避免那种把他的内心平衡建立在这样一些不可靠的基础之上的诱惑。

  我的政治理想是民主政体。让每一个人都作为个人而受到尊重,而不让任何人成为被崇拜的偶像。我自己一直受到同代人的过分的赞扬和尊敬,这不是由于我自己的过错,也不是由于我自己的功劳,而实在是一种命运的嘲弄。其原因大概在于人们有一种愿望,想理解我以自已微薄的绵力,通过不断的斗争所获得的少数几个观念,而这种愿望有很多人却未能实现。我完全明白,一个组织要实现它的目的,就必须有一个人去思考,去指挥、并且全面担负起责任来。但是被领导的人不应当受到强迫,他们必须能够选择自己的领袖。在我看来,强迫的专制制度很快就会腐化堕落。因为暴力所招引来的总是一些品德低劣的人,而且我相信,天才的暴君总是由无赖来继承的,这是一条千古不易的规律。就是由于这个缘故,我总强烈地反对今天在意大利和俄国所见到的那种制度。像欧洲今天所存在的情况,已使得民主形式受到怀疑,这不能归咎于民主原则本身,而是由于政府的不稳定和选举制度中与个人无关的特征。我相信美国在这方面已经找到了正确的道路。他们选出了一个任期足够长的总统,他有充分的权力来真正履行他的职责。另一方面,在德国政治制度中,为我所看重的是它为救济患病或贫困的人作出了可贵的广泛的规定。在人生的丰富多彩的表演中,我觉得真正可贵的,不是政治上的国家,而是有创造性的、有感情的个人,是人格;只有个人才能创造出高尚的和卓越的东西,而群众本身在思想上总是迟钝的,在感觉上也总是迟钝的。

  讲到这里,我想起了群众生活中最坏的一种表现,那就是使我厌恶的军事制度。一个人能够洋洋得意的随着军乐队在四列纵队里行进,单凭这一点就足以使我对他鄙夷不屑。他所以长了一个大脑,只是出于误会;光是骨髓就可满足他的全部需要了。文明的这种罪恶的渊薮,应当尽快加以消灭。任人支配的英雄主义、冷酷无情的暴行,以及在爱国主义名义下的一切可恶的胡闹,所有这些都使我深恶痛绝!在我看来,战争是多么卑鄙、下流!我宁愿被千刀万剐,也不愿参与这种可憎的勾当。尽管如此,我对人类的评价还是十分高的,我相信,要是人民的健康感情没有遭到那些通过学校和报纸而起作用的商业利益和政治利益的蓄意败坏,那么战争这个妖魔早就该绝迹了。

  我们所能有的最美好的经验是奥秘的经验。它是坚守在真正艺术和真正科学发源地上的基本感情。谁要体验不到它,谁要是不再有好奇心,也不再有惊讶的感觉,谁就无异于行尸走肉,他的眼睛便是模糊不清的。就是这样奥秘的经验──虽然掺杂着恐惧──产生了宗教。我们认识到有某种为我们所不能洞察的东西存在,感觉到那种只能以其最原始的形式接近我们的心灵的最深奥的理性和最灿烂的美──正是这种认识和这种情感构成了真正的宗教感情;在这个意义上,而且也只是在这个意义上,我才是一个具有深挚的宗教感情的人。我无法想象存在这样一个上帝,它会对自己的创造物加以赏罚,会具有我们在自己身上所体验到的那种意志。我不能也不愿去想象一个人在肉体死亡以后还会继续活着;让那些脆弱的灵魂,由于恐惧或者由于可笑的唯我论,去拿这种思想当宝贝吧!我自己只求满足于生命永恒的奥秘,满足于觉察现存世界的神奇结构,窥见它的一鳞半爪,并且以诚挚的努力去领悟在自然界中显示出来的那个理性的一部分,倘若真能如此,即使只领悟其极小的一部分,我也就心满意足了。

当我们在做一些管理平台类的程序(比如Windows的任务管理器)时,往往需要限制程序只能打开一个实例。解决这个问题的大致思路很简单,无非是在程序打开的时候判断一下是否有与自己相同的进程开着,如果有,则关闭自身,否则正常运行。
  但是,问题就出在如何判别是否有一个与自己相同的进程开着上面。我在网上搜索了一下相关的文章,发现对于这个问题的解决不外乎以下几种方式:
  1、在进程初始化时使用::CreateMutex创建一个互斥对象,通过检测互斥对象是否已存在来确定该程序是否已经运行。
  该方式的确可以很容易的实现判别程序实例是否已存在,只需要在InitInstance方法开头添加以下语句:
m_hUnique = ::CreateMutex(NULL, FALSE, UNIQUE_ID);
if (GetLastError() == ERROR_ALREADY_EXISTS) return FALSE;

  UNIQUE_ID为具有唯一性的字符串,一般可以用VC++为主程序头文件自动生成的包含标识宏(就是.h文件顶上的那一长串宏定义),当然,也可以用工具自己手动生成,随君所好了^^。要注意的是别忘了在ExitInstance方法中用 CloseHandle(m_hUnique) 将该互斥对象关闭。但这种方式存在一个很大的问题,就是很难获取已打开程序实例的主窗口句柄。而我们绝大多数时候,都需要将那个程序实例的主窗口激活。为了获取主窗口句柄,就需要再用到后面提到的其他方法。
  2、遍历所有已经打开的进程主窗口,比较窗口标题,如果找到满足条件的标题,则表示程序已经运行,并激活该窗口。
  这种方式虽然可以找到程序的主窗口,但问题明显:A.如果窗口标题经常变化怎么办(比如标题中会带有打开文档的文件名)?B.如果其他程序的主窗口标题恰好与该程序的相同怎么办?
  第一个问题可以通过写注册表或者写INI文件的方式来解决。即当主窗口标题改变时,将新标题写入注册表或者INI文件。不过这种解决方式也忒麻烦了吧-_-||   第二个问题就麻烦了,至少我还没有找到好的解决方案。如果非要说一个,那我提议你“想尽办法”“不择手段”的将窗口标题设的和别的程序绝对不同。不过估计搞定了这步,你半条命也快没了。
  3、用::SetProp给主窗口添加一个具有唯一性的属性值,以便在进程初始化的时候可以通过遍历所有窗口的该属性来判断。
  添加属性值的代码一般可以放在InitInstance方法的最后,如下:
::SetProp(m_pMainWnd->m_hWnd, “UNIQUE_ID”, (HANDLE)UNIQUE_ID);

  UNIQUE_ID是一个具有唯一性的整数值(为什么不能用字符串?因为字符串的比较需要将字符串读取出来,而这儿只能记录字符串地址,在别的程序里这个地址无意义,所以无法读出这个字符串)。这种方式仅有的问题就出在如何确定该整数值是具有唯一性的。我们后面提出的解决方法,就是在这种方法的基础上发展出来的。
  在总结了上述几种方式的利弊之后,我发现,只需要为程序建立一个具有唯一性的整数值,一方面可以通过这个值是否存在来判断程序是否已经运行(::CreateMutex其实也是类似的概念),另一方面可以通过将这个值赋给主窗口,以便能够找到已打开的程序实例的主窗口句柄。于是,ATOM量便派上用场了(ATOM变量类型等同于WORD,因而是一个整数值)。
  ATOM量本质上就是散列表的键标识符,其对应键值为一个字符串。每个程序都有自己的ATOM量表,同时Windows也有一个全局的ATOM表。我们要用的方法就是,为程序创建一个全局的ATOM量,通过这个量是否存在来判断程序是否已经运行,并通过将这个量作为属性值添加到主窗口来标识这个主窗口。具体过程如下:
  1、给主程序App类添加一个ATOM类型的成员变量:m_aAppId,作为程序ID。
  2、在InitInstance方法开头添加以下代码(UNIQUE_ID是具有唯一性的字符串宏):
m_aAppId = ::GlobalFindAtom(UNIQUE_ID); //查找程序ID是否存在
if (m_aAppId) //程序ID存在,激活已打开的程序实例的主窗口
{
HWND hWnd = ::GetWindow(::GetForegroundWindow(), GW_HWNDFIRST);
for (; hWnd; hWnd = ::GetWindow(hWnd, GW_HWNDNEXT))
{
if ((ATOM)::GetProp(hWnd, “APP_ID”) == m_aAppId)
{
if (::IsIconic(hWnd)) ::ShowWindow(hWnd, SW_RESTORE); //还原最小化的窗口
::SetForegroundWindow(hWnd); //激活窗口
m_aAppId = 0; //赋值0是为了防止ExitInstance中将找到的ATOM量删除
break;
}
}
return FALSE;
}
else //程序ID不存在,创建程序ID
{
m_aAppId = ::GlobalAddAtom(APP_ID);
}

  3、在InitInstance方法最后为主窗口添加标识属性:
::SetProp(m_pMainWnd->m_hWnd, “APP_ID”, (HANDLE)m_aAppId);

  4、在ExitInstance方法中添加下面代码以删除程序ID:
if (m_aAppId) ::GlobalDeleteAtom(m_aAppId);

心得:该方法所用到的ATOM量是一个应用广泛的技术,如::CreateMutex、::SetProp等API函数都间接用到了ATOM量。利用它,我们可以做很多需要用到唯一性验证的事情。

在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C ++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。
其格式一般为: #Pragma Para
其中Para 为参数,下面来看一些常用的参数。

(1)message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗
口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
#Pragma message(“消息文本”)
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法
#ifdef X86
#Pragma message(“_X86 macro activated!”)
#endif
当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“

X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了


(2)另一个使用得比较多的pragma参数是code_seg。格式如:
#pragma code_seg( [“section-name”[,”section-class”] ] )
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。

(3)#pragma once (比较常用)
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。

(4)#pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。
有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。

(5)#pragma resource “.dfm”表示把.dfm文件中的资源加入工程。*.dfm中包括窗体
外观的定义。

(6)#pragma warning( disable : 4507 34; once : 4385; error : 164 )
等价于:
#pragma warning(disable:4507 34) // 不显示4507和34号警告信息
#pragma warning(once:4385) // 4385号警告信息仅报告一次
#pragma warning(error:164) // 把164号警告信息作为一个错误。
同时这个pragma warning 也支持如下格式:
#pragma warning( push [ ,n ] )
#pragma warning( pop )
这里n代表一个警告等级(1—4)。
#pragma warning( push )保存所有警告信息的现有的警告状态。
#pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告
等级设定为n。
#pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的
一切改动取消。例如:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
//…….
#pragma warning( pop )
在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。
(7)pragma comment(…)
该指令将一个注释记录放入一个对象文件或可执行文件中。
常用的lib关键字,可以帮我们连入一个库文件。
(8)用pragma导出dll中的函数


传统的到出 DLL 函数的方法是使用模块定义文件 (.def),Visual C++ 提供了更简洁方便的方法,那就是“__declspec()”关键字后面跟“dllexport”,告诉连接去要导出这个函数,例如:

__declspec(dllexport) int __stdcall MyExportFunction(int iTest);
把“__declspec(dllexport)”放在函数声明的最前面,连接生成的 DLL 就会导出函数“_MyExportFunction@4”。

上面的导出函数的名称也许不是我的希望的,我们希望导出的是原版的“MyExportFunction”。还好,VC 提供了一个预处理指示符“#pragma”来指定连接选项 (不仅仅是这一个功能,还有很多指示功能) ,如下:

#pragma comment(linker,”/EXPORT:MyExportFunction=_MyExportFunction@4”)
这下就天如人愿了:)。如果你想指定导出的顺序,或者只将函数导出为序号,没有 Entryname,这个预处理指示符 (确切地说是连接器) 都能够实现,看看 MSDN 的语法说明:

/EXPORT:entryname[,@ordinal[,NONAME]][,DATA]
@ordinal 指定顺序;NONAME 指定只将函数导出为序号;DATA 关键字指定导出项为数据项。



每个编译程序可以用#pragma指令激活或终止该编译程序支持的一些编译功能。例如,对循环优化功能:
#pragma loop_opt(on) // 激活
#pragma loop_opt(off) // 终止
有时,程序中会有些函数会使编译器发出你熟知而想忽略的警告,如“Parameter xxx is never used in function xxx”,可以这样:
#pragma warn —100 // Turn off the warning message for warning #100
int insert_record(REC r)
{ /
function body */ }
#pragma warn +100 // Turn the warning message for warning #100 back on
函数会产生一条有唯一特征码100的警告信息,如此可暂时终止该警告。
每个编译器对#pragma的实现不同,在一个编译器中有效在别的编译器中几乎无效。可从编译器的文档中查看。

变脸是川剧艺术中一朵瑰丽的奇葩,更在上世纪60年代被周恩来总理钦点为文化机密.
然而,这项国家二级机密如今却在互联网上廉价出售,目前变脸绝迹已经流散到日本,德国的一些低俗舞台上去,不能不令人扼腕叹息。
   某些国人的机密意识远远不及商品意识,或者说,经济意识。无论是国宝还是古玩,情报还是机密,只要对方出银子,立马拱手将国家的利益和威严殷勤相送,可笑,那敦煌的道士,在被批判了近一个世纪,依然后继有人,将出卖艺术的尊严和民族的遗产发扬光大。
无论是国家机密还是商业机密,流失的原因不外乎两个,首先,是国人的“好客”,外国“友人”一旦到来,马上拉着拽着去参观自己的生产线,当“友人”们提出疑问,迅速知无不言,言无不尽,生怕对方不能掌握自己的独家绝技。所以,很多的“友人”们大摇大摆得揣着我们的机密,回到自己的国家献宝.

    我不想提景泰蓝的泄露,我不只想问,某些国人,可不可以稍微提高一下自我保护意识,不要让心血和资产一起流失,文化侵略,文化盗窃,对于文化强盗,我们要学会翻脸无情!
还有一种更为可耻的行为,就是用乞丐的嘴脸来出卖一切可以出卖的情报和机密。变脸事件或许对某些人来说,是无关痛痒的,但是由小看大,当国家一定将某项文化列为机密,受到相关法律保护时,一旦有人蓄意出卖,则是无视法律的尊严,同时也破坏了法律系统的完善。

    在呼吁保护机密的同时,我也在质疑我们的导向,为什么民族艺术家们的收入越来越少,为什么民族艺术的市场越来越小?为什么一个普通的流行歌手可以拿到百万的出场费,而我们的京剧艺术家,高音歌唱家,民间手工艺人门还在苦苦艰守着清规戒律,甚至为吃饭发愁?
谁主导了媒体,谁主导了潮流,谁在为文化的低俗化而痛心疾首,谁还会为了推倒的牌楼而振臂高呼,以人为本,以民族利益为最高利益,这在商品社会里致关重要。

   我盼望着,艺术界,文化界的变脸,亡羊补牢未为晚矣!否则宏扬民族传统文化必将沦落为空泛的口号,国粹不可沦陷,我们任重道远。

中医典籍,浩如烟海,穷毕生之精力,亦难尽阅。故学习中医,当有所为,有所不为。若欲面面俱到,必然浅尝辄止,杂而不精!

  如何学习中医?有人云:先学四大经典,然后了解《周易》,并熟读《医理传真》、《医学衷中参西录》等名家著作。个人以为,此方案更适合立志成为中医大师者学习,而对于初学自学者来说,有些不切实际,且不切实用。

  民国之初至新中国成立之际,中国恰逢乱世,民不聊生。民众文盲率极高,能读懂一封信或看懂一篇报纸的人,在当时的很多地方,似乎就算得上是个文化人了。而且,当时的民众普遍贫困,当时的出版业也远不及如今发达,出一本书或买一本书都是一件很重大的事。然而,那时所诞生的一批中医学者,其素质及疗效却远胜过今天的中医业内人士。

  如今,书多得成了望不见顶的山,看不到边的海。书越多越容易迷失方向,越容易抓不住重点,学习起来不得要领,事倍而功半。

  《内经》、《难经》言词艰涩,义理深邃,且有很多篇幅讲的是天人相应的自然法则及针灸技能,自学中医者若只欲掌握辨证论治,所需重点钻研的内容只是原著的1/5。至于《易经》则是另外一门独立的学问,非极有机缘悟性者,不宜习之;否则,耗尽一生却一无所获。

  我们尊崇经典,但更要注重实效,切不可在理论的海洋中游得太远而回不了岸。纸上谈兵者,既误人,也误已!

  《三国演义----舌战群儒》中,有人问:“且请问孔明治何经典”?孔明曰:“寻章摘句,世之腐儒也,何能兴邦立事?且古耕莘伊尹,钓渭子牙,张良、陈平之流。邓禹、耿弇之辈,皆有匡扶宇宙之才,未审其生平治何经典。岂亦效书生,区区于笔砚之间,数黑论黄,舞文弄墨而已乎?”
  “若夫小人之儒,惟务雕虫,专工翰墨,青春作赋,皓首穷经;笔下虽有千言,胸中实无一策,亦何取哉”!
  “非比夸辩之徒,虚誉欺人:坐议立谈,无人可及;临机应变,百无一能。诚为天下笑耳!”

   大道至简。

  中医入门,只需学好四门功课即可:《中医基础理论》、《中医诊断学》、《中药学》、《伤寒论》。

  实际上,《中医基础理论》、《中医诊断学》所讲的内容皆源于《黄帝内经》,它们可谓是通俗版、精华浓缩版的《黄帝内经》。临床上千变万化,说来道去的,都是那些最基本的理论的反复运用。

  宋代一位禅宗大师曾说:三十年前,老纳参禅之初,看山是山,看水是水;后来参禅有悟时,看山不是山,看水不是水;而今禅中彻悟,看山仍然山,看水仍然是水。

  同样的中医基础理论,初学时与大成时的区别在于:随着时间的推移,阅历的丰富,理解的层度大不相同而已!

现代中医是指由中医院校培养出来的中医。传统中医是指通过师带徒的形式培养出来的中医。现代中医与传统中医的区别表现在如下几个方面:


    1、教育方式的不同。

    院校培养的中医本科要经过5年的学习,前4年时全部学习理论知识,第五年是到医院实习和写论文。这样学习的弊病是学理论与实习操作割裂,前面学习的理论,到实习时都淡忘,理论不能很好地与实践联系在一起。中医本科生毕业时,连自己家人的头痛脑热的病都看不了。

    师带徒培养的中医一般是老师与徒弟每天吃住在一起,老师与徒弟一起看病人,老师看病人的经验马上可以告诉徒弟,没有病人时徒弟与老师一起研究中医理论,徒弟有疑惑时可以随时向老师请教。这样的优点是学习中医理论可以与医疗实践很好地结合在一起,老师的经验徒弟能够很好地传承,徒弟出师时已经可以独立行医。

    2、思维方式的不同。

    现代中医眼里更多的是病人的化验单,很多医生是根据病人的化验单来开中药,以病人化验单来判断治疗的效果。现代中医教育有一半的课时是西医的课时,西医教育的侵蚀已经严重干扰了现代中医按照中医古法四诊八纲的诊疗系统为患者诊病。

    传统中医是以遵循古法四诊八纲的诊疗系统为患者诊病,西医的化验最多只能当作辅助参考。传统中医眼里只有病人的阴阳、表里、寒热与虚实,只有病人经络于五脏六腑与致病因子的关系,传统中医考虑的是天气、季节、地理环境、病人的体质以及周围的人对病人的影响,传统中医注重的是将病人调理好,达到“正气存于内,邪不可干。”的效果。

    3、治疗手段不同

    现代中医一般只用单一的手段为病人治疗,如开中药的不用针灸,扎针灸的不开汤药。

    传统中医必须掌握药、针灸、推拿和导引等综合的治疗手段,在根据病人的需要给予综合的治疗。

在渲染多边形网格对象到场景中的时候,离观察者越远的对象应该越模糊,同时离观察者越近的物体应该越清楚,这就是深度排序(depth sorting)。深度排序有两种常用的方法。

第一种方法称为画家算法(painter’s algorithm)。这种方法将对象划分成不同的多边形,由后往前对这些多边形进行排序,再按照排好的顺序绘制出这些多边形。采用这种方法绘制多边形,能够确保前面的多边形总是在其后多边形之前进行绘制。

深度排序的第二种方法称为z缓冲方法(z- buffer),它是图形硬件设备使用最多的方法。这种方法依赖于像素,每个像素都有一个z值(z值是像素距离观察者的距离)。当每个像素被写入时,渲染器首先检查是否已经存在一个z值更小的像素,如果不存在,这个像素就被绘制出来;如果存在,就跳过该像素。

许多 3D图形加速卡都有一个内置的z缓冲,这也是深度排序选择z缓冲方法的原因。在应用程序中使用z缓冲,最容易的方法就是在创建设备对象以及设置显示方式的时候初始化z缓冲,如下所示:

   D3DPRESENT_PARAMETERS d3dpp;

   ZeroMemory(
&d3dpp, sizeof(d3dpp));

  d3dpp.Windowed                
= TRUE;
  d3dpp.SwapEffect              
= D3DSWAPEFFECT_DISCARD;
  d3dpp.BackBufferFormat        
= d3ddm.Format;
  d3dpp.EnableAutoDepthStencil  
= TRUE;
  d3dpp.AutoDepthStencilFormat  
= D3DFMT_D16;

  
if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,
                                 D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
&d3dpp, &g_pD3DDevice)))
    
return FALSE;

  
// Set the rendering states
  g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
  g_pD3DDevice
->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

同时在绘制每帧之前应该用IDirect3DDevice9::Clear来清除z缓存。

// clear device back buffer
    g_d3d_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(000255), 1.0f0);

完整代码如下所示:

/***************************************************************************************
PURPOSE:
    ZBuffer Demo

Required libraries:
  WINMM.LIB, D3D9.LIB, D3DX9.LIB.
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
"d3d9.h"
#include 
"d3dx9.h"

#pragma comment(lib, 
"winmm.lib")
#pragma comment(lib, 
"d3d9.lib")
#pragma comment(lib, 
"d3dx9.lib")

#pragma warning(disable : 
4305)

#define WINDOW_WIDTH    400
#define WINDOW_HEIGHT   400

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class and caption text.
HWND g_hwnd;
HINSTANCE g_inst;
static char g_class_name[] = "ZBufferClass";
static char g_caption[]    = "ZBuffer Demo";

// the Direct3D and device object
IDirect3D9* g_d3d = NULL;
IDirect3DDevice9
* g_d3d_device = NULL;

// The 3D vertex format and descriptor
typedef struct
{
    
float x, y, z;  // 3D coordinates    
    D3DCOLOR color; // diffuse color
} VERTEX;

#define VERTEX_FVF   (D3DFVF_XYZ | D3DFVF_DIFFUSE)

IDirect3DVertexBuffer9
* g_vertex_buffer = NULL;

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Initialize d3d, d3d device, vertex buffer; set render state for d3d;
// set perspective matrix and view matrix.
//--------------------------------------------------------------------------------
BOOL Do_Init()
{
    D3DPRESENT_PARAMETERS present_param;
    D3DDISPLAYMODE  display_mode;
    D3DXMATRIX mat_proj, mat_view;
    BYTE
* vertex_ptr;

    
// initialize vertex data
    VERTEX verts[] = {
      { 
-100.0f-100.0f0.0f, D3DCOLOR_RGBA(255,0,0,255) },
      { 
-100.0f,  100.0f0.0f, D3DCOLOR_RGBA(255,0,0,255) },
      {  
100.0f-100.0f0.0f, D3DCOLOR_RGBA(255,0,0,255) },
      {  
100.0f,  100.0f0.0f, D3DCOLOR_RGBA(255,0,0,255) },
      { 
-100.0f-100.0f0.0f, D3DCOLOR_RGBA(255,0,0,255) },
      { 
-100.0f,  100.0f0.0f, D3DCOLOR_RGBA(255,0,0,255) },

      { 
-100.0f-100.0f0.0f, D3DCOLOR_RGBA(0,0,255,255) },
      { 
-100.0f,  100.0f0.0f, D3DCOLOR_RGBA(0,0,255,255) },
      {  
100.0f-100.0f0.0f, D3DCOLOR_RGBA(0,0,255,255) },
      {  
100.0f,  100.0f0.0f, D3DCOLOR_RGBA(0,0,255,255) },
    }; 

    
// do a windowed mode initialization of Direct3D
    if((g_d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
        
return FALSE;

    
// retrieves the current display mode of the adapter
    if(FAILED(g_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
        
return FALSE;

    ZeroMemory(
&present_param, sizeof(present_param));

    
// initialize d3d presentation parameter
    present_param.Windowed               = TRUE;
    present_param.SwapEffect             
= D3DSWAPEFFECT_DISCARD;
    present_param.BackBufferFormat       
= display_mode.Format;
    present_param.EnableAutoDepthStencil 
= TRUE;
    present_param.AutoDepthStencilFormat 
= D3DFMT_D16;

    
// creates a device to represent the display adapter
    if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
&present_param, &g_d3d_device)))
        
return FALSE;     

    
// set render state

    
// disable d3d lighting
    g_d3d_device->SetRenderState(D3DRS_LIGHTING, FALSE);
    
// enable z-buffer
    g_d3d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

    
// create and set the projection matrix

    
// builds a left-handed perspective projection matrix based on a field of view
    D3DXMatrixPerspectiveFovLH(&mat_proj, D3DX_PI/4.01.333331.01000.0);

    
// sets a single device transformation-related state
    g_d3d_device->SetTransform(D3DTS_PROJECTION, &mat_proj);

    
// create and set the view matrix
    D3DXMatrixLookAtLH(&mat_view, 
                       
&D3DXVECTOR3(0.00.0-500.0),
                       
&D3DXVECTOR3(0.0f0.0f0.0f), 
                       
&D3DXVECTOR3(0.0f1.0f0.0f));

    g_d3d_device
->SetTransform(D3DTS_VIEW, &mat_view);

    
// create the vertex buffer and set data
    g_d3d_device->CreateVertexBuffer(sizeof(verts), 0, VERTEX_FVF, D3DPOOL_DEFAULT, &g_vertex_buffer, NULL);

    
// locks a range of vertex data and obtains a pointer to the vertex buffer memory
    g_vertex_buffer->Lock(00, (void**)&vertex_ptr, 0);

    memcpy(vertex_ptr, verts, 
sizeof(verts));

    
// unlocks vertex data
    g_vertex_buffer->Unlock();

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Release all d3d resource.
//--------------------------------------------------------------------------------
BOOL Do_Shutdown()
{
    Safe_Release(g_vertex_buffer);
    Safe_Release(g_d3d_device);
    Safe_Release(g_d3d);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Render a frame.
//--------------------------------------------------------------------------------
BOOL Do_Frame()
{
    D3DXMATRIX mat_world;

    
// clear device back buffer
    g_d3d_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(000255), 1.0f0);

    
// Begin scene
    if(SUCCEEDED(g_d3d_device->BeginScene()))
    {
        
// set the vertex stream, shader.

        
// binds a vertex buffer to a device data stream
        g_d3d_device->SetStreamSource(0, g_vertex_buffer, 0sizeof(VERTEX));

        
// set the current vertex stream declation
        g_d3d_device->SetFVF(VERTEX_FVF);

        
// create and set the world transformation matrix
        
// rotate object along y-axis
        D3DXMatrixRotationY(&mat_world, (float) (timeGetTime() / 1000.0));
        
        g_d3d_device
->SetTransform(D3DTS_WORLD, &mat_world);              

        
// renders a sequence of noindexed, geometric primitives of the specified type from the current set
        
// of data input stream.
        g_d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 04);

        
// draw next four polygons, but rotate on z-axis.
        D3DXMatrixRotationZ(&mat_world, -(float)(timeGetTime() / 1000.0));
        g_d3d_device
->SetTransform(D3DTS_WORLD, &mat_world);
        g_d3d_device
->DrawPrimitive(D3DPT_TRIANGLESTRIP, 62);

        
// end the scene
        g_d3d_device->EndScene();
    }

    
// present the contents of the next buffer in the sequence of back buffers owned by the device
    g_d3d_device->Present(NULL, NULL, NULL, NULL);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASSEX  win_class;
    MSG         msg;

    g_inst 
= inst;

    
// create window class and register it
    win_class.cbSize        = sizeof(win_class);
    win_class.style         
= CS_CLASSDC;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= 0;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(NULL, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= NULL;
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;
    win_class.hIconSm       
= LoadIcon(NULL, IDI_APPLICATION);

    
if(! RegisterClassEx(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateWindow(g_class_name, g_caption, WS_CAPTION | WS_SYSMENU, 00,
                          WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, inst, NULL);

    
if(g_hwnd == NULL)
        
return FALSE;

    ShowWindow(g_hwnd, SW_NORMAL);
    UpdateWindow(g_hwnd);

    
// initialize game
    if(Do_Init() == FALSE)
        
return FALSE;

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }
        
        
// draw a frame
        if(Do_Frame() == FALSE)
            
break;
    }

    
// run shutdown function
    Do_Shutdown();

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}

效果图:


源码及素材下载

大爆炸,烟雾痕迹甚至魔术飞弹尾部发出的微小火花,都是粒子(particle)所制造出来的特殊效果。在适当的时机,启用alpha混合并绘制粒子,这样粒子就能朝向观察点(使用公告板),得到的结果就是混合对象的抽象拼贴,他们可以用于创建一些奇妙的效果。

粒子奇妙的地方就在于粒子的大小实际上是任意的,原因在于可以创建一个缩放矩阵,使其同粒子多边形的世界变换矩阵结合起来。也就是说,除非粒子纹理不同,否则只需要使用一个多边形来绘制所有的粒子,无论如何,多边形的数目都必须同纹理的数目保持一致。

还需要创建粒子图像,图像中心为一个实心(不透明)圆形,向图像的边缘延伸,图像逐渐变透明,如下图所示:



接着,需要设置4个顶点,这4个顶点使用了2个多边形(可以使用三角形带进行优化)。顶点的坐标表示粒子的缺省大小,稍后需要将粒子进行缩放,以适合这个大小。每个粒子都可以拥有独特的属性,包括粒子颜色(通过使用材质来实现)。

接下来,将这个结构体同一个含有两个多边形(创建一个正方形)的顶点缓冲结合起来,以便将多边形渲染到3D设备上。在被绘制出来之前,每个粒子都需要通过它自己的世界矩阵进行定向(当然使用公告板)。然后将世界变换矩阵同每个粒子的缩放变换矩阵组合起来,再设置一个材质(使用 IDirect3DDevice::SetMaterial函数),用来改变粒子的颜色。最后,绘制粒子。

完整源码如下所示:

/***************************************************************************************
PURPOSE:
    Particle Demo

Required libraries:
  WINMM.lib, D3D9.LIB, D3DX9.LIB.
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
"d3d9.h"
#include 
"d3dx9.h"

#pragma comment(lib, 
"winmm.lib")
#pragma comment(lib, 
"d3d9.lib")
#pragma comment(lib, 
"d3dx9.lib")

#pragma warning(disable : 
4305 4244)

#define WINDOW_WIDTH    400
#define WINDOW_HEIGHT   400

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class and caption text.
HWND g_hwnd;
HINSTANCE g_inst;
static char g_class_name[] = "ParticleClass";
static char g_caption[]    = "Particle Demo";

// the Direct3D and device object
IDirect3D9* g_d3d = NULL;
IDirect3DDevice9
* g_d3d_device = NULL;

// the particle vertex buffer and texture
IDirect3DVertexBuffer9* g_particle_vb = NULL;
IDirect3DTexture9
*      g_particle_texture = NULL;

// The particle vertex format and descriptor
typedef struct
{
    
float x, y, z;      // 3D coordinates    
    D3DCOLOR diffuse;   // color
    float u, v;         // texture coordinates
} VERTEX;

#define VERTEX_FVF   (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

// create a structure for tracking particles
struct PARTICLE
{
    
float x_pos, y_pos, z_pos;  // coordinate
    float x_add, y_add, z_add;  // movement values
    float red, green, blue;     // colors
    long  timer, counter;       // current and update counter

    PARTICLE()
    {
        
// position particle at origin
        x_pos = y_pos = z_pos = 0.0;

        
// get a random update counter
        counter = rand() % 50 + 10;
        timer 
= 0;

        
// get a random speed
        x_add = (float)(rand() % 11- 5.0;
        y_add 
= (float)(rand() % 11- 5.0;
        z_add 
= (float)(rand() % 11- 5.0;

        
// get a random color
        red   = (float)(rand() % 101/ 100.0;
        green 
= (float)(rand() % 101/ 100.0;
        blue  
= (float)(rand() % 101/ 100.0;
    }
};

PARTICLE
* g_particles = NULL;

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Copy vertex data into vertex buffer, create texture from file.
//--------------------------------------------------------------------------------
BOOL Setup_Particles()
{
    BYTE
* vertex_ptr;

    VERTEX verts[] 
= {
        { 
-50.0f50.0f0.0f0xFFFFFFFF0.0f0.0f },
        {  
50.0f50.0f0.0f0xFFFFFFFF1.0f0.0f },
        { 
-50.0f,  0.0f0.0f0xFFFFFFFF0.0f1.0f },
        {  
50.0f,  0.0f0.0f0xFFFFFFFF1.0f1.0f }
    };    

    
// create vertex buffers and stuff in data       
    if(FAILED(g_d3d_device->CreateVertexBuffer(sizeof(verts), 0, VERTEX_FVF, D3DPOOL_DEFAULT, &g_particle_vb, NULL)))   
        
return FALSE;   

    
// locks a range of vertex data and obtains a pointer to the vertex buffer memory
    if(FAILED(g_particle_vb->Lock(00, (void**)&vertex_ptr, 0)))
        
return FALSE;

    memcpy(vertex_ptr, verts, 
sizeof(verts));

    
// unlocks vertex data
    g_particle_vb->Unlock();    

    
// get textures    
    D3DXCreateTextureFromFile(g_d3d_device, "Particle.bmp"&g_particle_texture);    
    
    
// create some particles
    g_particles = new PARTICLE[512];

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Initialize d3d, d3d device, vertex buffer, texutre; set render state for d3d;
// set perspective matrix.
//--------------------------------------------------------------------------------
BOOL Do_Init()
{
    D3DPRESENT_PARAMETERS present_param;
    D3DDISPLAYMODE  display_mode;
    D3DXMATRIX mat_proj, mat_view;    

    
// do a windowed mode initialization of Direct3D
    if((g_d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
        
return FALSE;

    
// retrieves the current display mode of the adapter
    if(FAILED(g_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
        
return FALSE;

    ZeroMemory(
&present_param, sizeof(present_param));

    
// initialize d3d presentation parameter
    present_param.Windowed               = TRUE;
    present_param.SwapEffect             
= D3DSWAPEFFECT_DISCARD;
    present_param.BackBufferFormat       
= display_mode.Format;
    present_param.EnableAutoDepthStencil 
= TRUE;
    present_param.AutoDepthStencilFormat 
= D3DFMT_D16;

    
// creates a device to represent the display adapter
    if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
&present_param, &g_d3d_device)))
        
return FALSE;     

    
// set render state

    
// enable d3d lighting
    g_d3d_device->SetRenderState(D3DRS_LIGHTING, TRUE);
    
// enable z-buffer
    g_d3d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
    
// set ambient light to highest level (to see particles)
    g_d3d_device->SetRenderState(D3DRS_AMBIENT, 0xFFFFFFFF);

    
// create and set the projection matrix

    
// builds a left-handed perspective projection matrix based on a field of view
    D3DXMatrixPerspectiveFovLH(&mat_proj, D3DX_PI/4.01.01.01000.0);

    
// sets a single device transformation-related state
    g_d3d_device->SetTransform(D3DTS_PROJECTION, &mat_proj);

    
// create and set the view transformation
    D3DXMatrixLookAtLH(&mat_view, &D3DXVECTOR3(0.0f0.0f-500.0f), &D3DXVECTOR3(0.0f0.0f0.0f), 
                       
&D3DXVECTOR3(0.0f1.0f0.0f));

    g_d3d_device
->SetTransform(D3DTS_VIEW, &mat_view);

    
// create the meshes
    Setup_Particles();    

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Release all d3d resource.
//--------------------------------------------------------------------------------
BOOL Do_Shutdown()
{
    delete[] g_particles;

    Safe_Release(g_particle_vb);
    Safe_Release(g_particle_texture);    
    Safe_Release(g_d3d_device);
    Safe_Release(g_d3d);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Render a frame.
//--------------------------------------------------------------------------------
BOOL Do_Frame()
{
    D3DXMATRIX mat_view, mat_world, mat_transposed, mat_transform;
    
static D3DMATERIAL9 s_material;
    
static BOOL  s_is_mat_init = TRUE;
    
static DWORD s_counter = timeGetTime();

    
// limit to 30fps
    if(timeGetTime() < s_counter+33)
        
return TRUE;

    s_counter 
= timeGetTime();

    
// configure the material if first time called
    if(s_is_mat_init = TRUE)
    {
        s_is_mat_init 
= FALSE;
        ZeroMemory(
&s_material, sizeof(s_material));
        s_material.Diffuse.a 
= s_material.Ambient.a = 0.5f;
    }

    
// clear device back buffer
    g_d3d_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(064128255), 1.0f0);    

    
// Begin scene
    if(SUCCEEDED(g_d3d_device->BeginScene()))
    {
        
// set the particle source, shader, texture.            
        g_d3d_device->SetStreamSource(0, g_particle_vb, 0sizeof(VERTEX));
        g_d3d_device
->SetFVF(VERTEX_FVF);
        g_d3d_device
->SetTexture(0, g_particle_texture);

        
// get and set the transposed view matrix (billboard technique)
        g_d3d_device->GetTransform(D3DTS_VIEW, &mat_view);
        D3DXMatrixTranspose(
&mat_transposed, &mat_view);

        
// enable alpha blending
        g_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
        g_d3d_device
->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
        g_d3d_device
->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);    

        
// loop through all particles and draw them
        for(short i = 0; i < 512; i++)
        {
            
// move particle first
            g_particles[i].x_pos += g_particles[i].x_add;
            g_particles[i].y_pos 
+= g_particles[i].y_add;
            g_particles[i].z_pos 
+= g_particles[i].z_add;

            
// reverse movements if past counter
            if((g_particles[i].timer += 1>= g_particles[i].counter)
            {
                g_particles[i].timer 
= 0;
                g_particles[i].x_add 
*= -1.0f;
                g_particles[i].y_add 
*= -1.0f;
                g_particles[i].z_add 
*= -1.0f;
            }

            
// setup the particle's world transformation
            D3DXMatrixTranslation(&mat_transform, g_particles[i].x_pos, g_particles[i].y_pos, g_particles[i].z_pos);
            D3DXMatrixMultiply(
&mat_world, &mat_transform, &mat_transposed);
            g_d3d_device
->SetTransform(D3DTS_WORLD, &mat_world);

            
// set the particle's material
            s_material.Diffuse.r = s_material.Ambient.r = g_particles[i].red;
            s_material.Diffuse.g 
= s_material.Ambient.g = g_particles[i].green;
            s_material.Diffuse.b 
= s_material.Ambient.b = g_particles[i].blue;
                
            
// Sets the material properties for the device
            g_d3d_device->SetMaterial(&s_material);

            
// draw the particle
            g_d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 02);
        }        

        
// release texture
        g_d3d_device->SetTexture(0, NULL);

        
// end the scene
        g_d3d_device->EndScene();
    }

    
// present the contents of the next buffer in the sequence of back buffers owned by the device
    g_d3d_device->Present(NULL, NULL, NULL, NULL);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASSEX  win_class;
    MSG         msg;

    g_inst 
= inst;

    
// create window class and register it
    win_class.cbSize        = sizeof(win_class);
    win_class.style         
= CS_CLASSDC;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= 0;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(NULL, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= NULL;
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;
    win_class.hIconSm       
= LoadIcon(NULL, IDI_APPLICATION);

    
if(! RegisterClassEx(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateWindow(g_class_name, g_caption, WS_CAPTION | WS_SYSMENU, 00,
                          WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, inst, NULL);

    
if(g_hwnd == NULL)
        
return FALSE;

    ShowWindow(g_hwnd, SW_NORMAL);
    UpdateWindow(g_hwnd);

    
// initialize game
    if(Do_Init() == FALSE)
        
return FALSE;

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }
        
        
// draw a frame
        if(Do_Frame() == FALSE)
            
break;
    }

    
// run shutdown function
    Do_Shutdown();

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}

效果图:


点击下载源码和素材

公告板(billboard)是一种允许在2D对象出现在3D中的很酷的技术,公告板的原理就是通过使用世界矩阵,根据观察点来排列多边形,因为观察的角度已知(或能够获得一个观察变换矩阵),就只需要使用相反的观察角来构造矩阵。创建公告板世界矩阵的方法是从Direct3D获取当前的观察矩阵并将此矩阵转置。这个转置矩阵会将所有的东西进行恰当的定位,以朝向观察点。接着就只需应用网格的平移矩阵,在世界中正确地确定网格的位置。

源码中的Setup_Mesh函数用来创建顶点缓冲和从文件取得纹理数据,其中用到了D3DXCreateTextureFromFileEx函数,来看看它的使用信息:

Creates a texture from a file. This is a more advanced function than D3DXCreateTextureFromFile.

Syntax

HRESULT WINAPI D3DXCreateTextureFromFileEx(LPDIRECT3DDEVICE9 pDevice,
    LPCTSTR pSrcFile,
    UINT Width,
    UINT Height,
    UINT MipLevels,
    DWORD Usage,
    D3DFORMAT Format,
    D3DPOOL Pool,
    DWORD Filter,
    DWORD MipFilter,
    D3DCOLOR ColorKey,
    D3DXIMAGE_INFO *pSrcInfo,
    PALETTEENTRY *pPalette,
    LPDIRECT3DTEXTURE9 *ppTexture
);

Parameters

pDevice
[in] Pointer to an IDirect3DDevice9 interface, representing the device to be associated with the texture.

pSrcFile
[in] Pointer to a string that specifies the filename. If the compiler settings require Unicode, the data type LPCTSTR resolves to LPCWSTR. Otherwise, the string data type resolves to LPCSTR. See Remarks.

Width
[in] Width in pixels. If this value is zero or D3DX_DEFAULT, the dimensions are taken from the file and rounded up to a power of two. If the device supports non-power of 2 textures and D3DX_DEFAULT_NONPOW2 is specified, the size will not be rounded.

Height
[in] Height, in pixels. If this value is zero or D3DX_DEFAULT, the dimensions are taken from the file and rounded up to a power of two. If the device supports non-power of 2 textures and D3DX_DEFAULT_NONPOW2 is sepcified, the size will not be rounded.

MipLevels
[in] Number of mip levels requested. If this value is zero or D3DX_DEFAULT, a complete mipmap chain is created. If D3DX_FROM_FILE, the size will be taken exactly as it is in the file, and the call will fail if this violates device capabilities.

Usage
[in] 0, D3DUSAGE_RENDERTARGET, or D3DUSAGE_DYNAMIC. Setting this flag to D3DUSAGE_RENDERTARGET indicates that the surface is to be used as a render target. The resource can then be passed to the pNewRenderTarget parameter of the IDirect3DDevice9::SetRenderTarget method. If either D3DUSAGE_RENDERTARGET or D3DUSAGE_DYNAMIC is specified, Pool must be set to D3DPOOL_DEFAULT, and the application should check that the device supports this operation by calling IDirect3D9::CheckDeviceFormat. D3DUSAGE_DYNAMIC indicates that the surface should be handled dynamically. See Using Dynamic Textures.

Format
[in] Member of the D3DFORMAT enumerated type, describing the requested pixel format for the texture. The returned texture might have a different format from that specified by Format. Applications should check the format of the returned texture. If D3DFMT_UNKNOWN, the format is taken from the file. If D3DFMT_FROM_FILE, the format is taken exactly as it is in the file, and the call will fail if this violates device capabilities.

Pool
[in] Member of the D3DPOOL enumerated type, describing the memory class into which the texture should be placed.

Filter
[in] A combination of one or more D3DX_FILTER controlling how the image is filtered. Specifying D3DX_DEFAULT for this parameter is the equivalent of specifying D3DX_FILTER_TRIANGLE | D3DX_FILTER_DITHER.

MipFilter
[in] A combination of one or more D3DX_FILTER controlling how the image is filtered. Specifying D3DX_DEFAULT for this parameter is the equivalent of specifying D3DX_FILTER_BOX.

ColorKey
[in] D3DCOLOR value to replace with transparent black, or 0 to disable the color key. This is always a 32-bit ARGB color, independent of the source image format. Alpha is significant and should usually be set to FF for opaque color keys. Thus, for opaque black, the value would be equal to 0xFF000000.

pSrcInfo
[in, out] Pointer to a D3DXIMAGE_INFO structure to be filled in with a description of the data in the source image file, or NULL.

pPalette
[out] Pointer to a PALETTEENTRY structure, representing a 256-color palette to fill in, or NULL.

ppTexture
[out] Address of a pointer to an IDirect3DTexture9 interface, representing the created texture object.

Return Value

If the function succeeds, the return value is D3D_OK.

If the function fails, the return value can be one of the following:

D3DERR_INVALIDCALL The method call is invalid. For example, a method’s parameter may have an invalid value.
D3DERR_NOTAVAILABLE This device does not support the queried technique.
D3DERR_OUTOFVIDEOMEMORY Direct3D does not have enough display memory to perform the operation.
D3DXERR_INVALIDDATA The data is invalid.
E_OUTOFMEMORY Direct3D could not allocate sufficient memory to complete the call.

Remarks

The compiler setting also determines the function version. If Unicode is defined, the function call resolves to D3DXCreateTextureFromFileExW. Otherwise, the function call resolves to D3DXCreateTextureFromFileExA because ANSI strings are being used.

Use D3DXCheckTextureRequirements to determine if your device can support the texture given the current state.

This function supports the following file formats: .bmp, .dds, .dib, .hdr, .jpg, .pfm, .png, .ppm, and .tga. See D3DXIMAGE_FILEFORMAT.

Mipmapped textures automatically have each level filled with the loaded texture. When loading images into mipmapped textures, some devices are unable to go to a 1x1 image and this function will fail. If this happens, then the images need to be loaded manually.

For the best performance when using D3DXCreateTextureFromFileEx:

Doing image scaling and format conversion at load time can be slow. Store images in the format and resolution they will be used. If the target hardware requires power of 2 dimensions, then create and store images using power of 2 dimensions.
For mipmap image creation at load time, filter using D3DX_FILTER_BOX. A box filter is much faster than other filter types such as D3DX_FILTER_TRIANGLE.
Consider using DDS files. Since DDS files can be used to represent any Microsoft DirectX 9.0 texture format, they are very easy for D3DX to read. Also, they can store mipmaps, so any mipmap-generation algorithms can be used to author the images.

我们来看看Do_Frame是如何进行公告板的绘制的,以下是关键代码:

    // build view matrix
    D3DXMatrixLookAtLH(&mat_view, &D3DXVECTOR3(cos(angle) * 200.0200.0, sin(angle) * 200.0),
        
&D3DXVECTOR3(0.00.00.0), &D3DXVECTOR3(0.01.00.0));

    
// set view matrix
    g_d3d_device->SetTransform(D3DTS_VIEW, &mat_view);

    
// Begin scene
    if(SUCCEEDED(g_d3d_device->BeginScene()))
    {
        
// 1) draw the floor
        
        
// binds a vertex buffer to a device data stream
        g_d3d_device->SetStreamSource(0, g_floor_vb, 0sizeof(VERTEX));

        
// set the current vertex stream declation
        g_d3d_device->SetFVF(VERTEX_FVF);

        
// assigns a texture to a stage for a device
        g_d3d_device->SetTexture(0, g_floor_texture);

        
// build world matrix, we only need identity matrix, because we do not need to change original floor position.
        D3DXMatrixIdentity(&mat_world);

        
// set world matrix
        g_d3d_device->SetTransform(D3DTS_WORLD, &mat_world);        

        
// renders a sequence of noindexed, geometric primitives of the specified type from the current set
        
// of data input stream.
        g_d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 02);

        
// 2) draw the billboards

        g_d3d_device
->SetStreamSource(0, g_billboard_vb, 0sizeof(VERTEX));
        g_d3d_device
->SetTexture(0, g_billboard_texture);

        
// enable alpha test
        g_d3d_device->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
        
// get world matrix, just get it from view matrix's transpose.
        D3DXMatrixTranspose(&mat_world, &mat_view);
        
        
// draw all billboard images
        for(short i = 0; i < 3; i++)
        {
            
for(short j = 0; j < 3; j++)
            {
                mat_world._41 
= i * 80.0 - 80.0;
                mat_world._42 
= 0.0;
                mat_world._43 
= j * 80.0 - 80.0;

                
// set world matrix
                g_d3d_device->SetTransform(D3DTS_WORLD, &mat_world);
                
// draw polygon
                g_d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 02);
            }
        }

        
// disable alpha test
        g_d3d_device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);

        
// release texture
        g_d3d_device->SetTexture(0, NULL);

        
// end the scene
        g_d3d_device->EndScene();
    }

1)地板的绘制:首先调用D3DXMatrixLookAtLH取得视口矩阵,接着调用SetTransform设置视口矩阵。绘制地板时先调用SetStreamSource和SetFVF设置顶点信息,再调用SetTexture设置纹理,接着调用D3DXMatrixIdentity单位化世界矩阵并设置世界矩阵,因为我们不需要改变地板的位置,所以世界矩阵直接设置为单位矩阵就可以了;再接着调用DrawPrimitive绘制图形。

2)公告板的绘制:首先设置顶点数据格式,接着启用alpha测试,再接着将视口矩阵转置得到世界矩阵,使用两层for循环来平移世界矩阵并设置世界矩阵,最后绘制这些公告板。

       // draw all billboard images
        for(short i = 0; i < 3; i++)
        {
            
for(short j = 0; j < 3; j++)
            {
                mat_world._41 
= i * 80.0 - 80.0;
                mat_world._42 
= 0.0;
                mat_world._43 
= j * 80.0 - 80.0;

                
// set world matrix
                g_d3d_device->SetTransform(D3DTS_WORLD, &mat_world);
                
// draw polygon
                g_d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 02);
            }
        }

完整源码如下:

/***************************************************************************************
PURPOSE:
    Billboard Demo

Required libraries:
  WINMM.lib, D3D9.LIB, D3DX9.LIB.
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
"d3d9.h"
#include 
"d3dx9.h"

#pragma comment(lib, 
"winmm.lib")
#pragma comment(lib, 
"d3d9.lib")
#pragma comment(lib, 
"d3dx9.lib")

#pragma warning(disable : 
4305 4244)

#define WINDOW_WIDTH    400
#define WINDOW_HEIGHT   400

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class and caption text.
HWND g_hwnd;
HINSTANCE g_inst;
static char g_class_name[] = "BillboardClass";
static char g_caption[]    = "Billboard Demo";

// the Direct3D and device object
IDirect3D9* g_d3d = NULL;
IDirect3DDevice9
* g_d3d_device = NULL;

// The 3D vertex format and descriptor
typedef struct
{
    
float x, y, z;  // 3D coordinates    
    float u, v;     // texture coordinates
} VERTEX;

#define VERTEX_FVF   (D3DFVF_XYZ | D3DFVF_TEX1)

// the billboard vertex buffer and texture
IDirect3DVertexBuffer9* g_billboard_vb = NULL;
IDirect3DTexture9
*      g_billboard_texture = NULL;

// the floor vertex buffer and texture
IDirect3DVertexBuffer9* g_floor_vb = NULL;
IDirect3DTexture9
*      g_floor_texture = NULL;

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Copy vertex data into vertex buffer, create texture from file.
//--------------------------------------------------------------------------------
BOOL Setup_Mesh()
{
    BYTE
* vertex_ptr;

    VERTEX billboard_verts[] 
= {
        { 
-42.0f80.0f0.0f0.0f0.0f },
        {  
40.0f80.0f0.0f1.0f0.0f },
        { 
-40.0f,  0.0f0.0f0.0f1.0f },
        {  
40.0f,  0.0f0.0f1.0f1.0f }
    };

    VERTEX floor_verts[] 
= {
        { 
-100.0f0.0f,  100.0f0.0f0.0f },
        {  
100.0f0.0f,  100.0f1.0f0.0f },
        { 
-100.0f0.0f-100.0f0.0f1.0f },
        { 
100.0f0.0f-100.0f1.0f1.0f }
    };

    
// create vertex buffers and stuff in data
    
    
// for billboard
    if(FAILED(g_d3d_device->CreateVertexBuffer(sizeof(billboard_verts), 0, VERTEX_FVF, D3DPOOL_DEFAULT, 
                                               
&g_billboard_vb, NULL)))   
        
return FALSE;   

    
// locks a range of vertex data and obtains a pointer to the vertex buffer memory
    if(FAILED(g_billboard_vb->Lock(00, (void**)&vertex_ptr, 0)))
        
return FALSE;

    memcpy(vertex_ptr, billboard_verts, 
sizeof(billboard_verts));

    
// unlocks vertex data
    g_billboard_vb->Unlock();

    
// for floor
    if(FAILED(g_d3d_device->CreateVertexBuffer(sizeof(floor_verts), 0, VERTEX_FVF, D3DPOOL_DEFAULT, &g_floor_vb, NULL)))    
        
return FALSE;    

    
// locks a range of vertex data and obtains a pointer to the vertex buffer memory
    if(FAILED(g_floor_vb->Lock(00, (void**)&vertex_ptr, 0)))
        
return FALSE;

    memcpy(vertex_ptr, floor_verts, 
sizeof(floor_verts));

    
// unlocks vertex data
    g_floor_vb->Unlock();

    
// get textures    
    D3DXCreateTextureFromFile(g_d3d_device, "Floor.bmp"&g_floor_texture);

    
// Creates a texture from a file. 
    D3DXCreateTextureFromFileEx(g_d3d_device, "Billboard.bmp", D3DX_DEFAULT, D3DX_DEFAULT,
        D3DX_DEFAULT, 
0, D3DFMT_A1R5G5B5, D3DPOOL_MANAGED, D3DX_FILTER_TRIANGLE, D3DX_FILTER_TRIANGLE,
        D3DCOLOR_RGBA(
0,0,0,255), NULL, NULL, &g_billboard_texture);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Initialize d3d, d3d device, vertex buffer, texutre; set render state for d3d;
// set perspective matrix.
//--------------------------------------------------------------------------------
BOOL Do_Init()
{
    D3DPRESENT_PARAMETERS present_param;
    D3DDISPLAYMODE  display_mode;
    D3DXMATRIX mat_proj, mat_view;    

    
// do a windowed mode initialization of Direct3D
    if((g_d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
        
return FALSE;

    
// retrieves the current display mode of the adapter
    if(FAILED(g_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
        
return FALSE;

    ZeroMemory(
&present_param, sizeof(present_param));

    
// initialize d3d presentation parameter
    present_param.Windowed               = TRUE;
    present_param.SwapEffect             
= D3DSWAPEFFECT_DISCARD;
    present_param.BackBufferFormat       
= display_mode.Format;
    present_param.EnableAutoDepthStencil 
= TRUE;
    present_param.AutoDepthStencilFormat 
= D3DFMT_D16;

    
// creates a device to represent the display adapter
    if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
&present_param, &g_d3d_device)))
        
return FALSE;     

    
// set render state

    
// disable d3d lighting
    g_d3d_device->SetRenderState(D3DRS_LIGHTING, FALSE);
    
// enable z-buffer
    g_d3d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
    
// set alpha reference value and function
    g_d3d_device->SetRenderState(D3DRS_ALPHAREF, 0x01);
    g_d3d_device
->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);

    
// create and set the projection matrix

    
// builds a left-handed perspective projection matrix based on a field of view
    D3DXMatrixPerspectiveFovLH(&mat_proj, D3DX_PI/4.01.01.01000.0);

    
// sets a single device transformation-related state
    g_d3d_device->SetTransform(D3DTS_PROJECTION, &mat_proj);

    
// create the meshes
    Setup_Mesh();    

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Release all d3d resource.
//--------------------------------------------------------------------------------
BOOL Do_Shutdown()
{
    Safe_Release(g_billboard_vb);
    Safe_Release(g_billboard_texture);
    Safe_Release(g_floor_vb);
    Safe_Release(g_floor_texture);
    Safe_Release(g_d3d_device);
    Safe_Release(g_d3d);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Render a frame.
//--------------------------------------------------------------------------------
BOOL Do_Frame()
{
    D3DXMATRIX mat_view, mat_world;

    
// clear device back buffer
    g_d3d_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(064128255), 1.0f0);

    
// update the view position
    float angle = (float) timeGetTime() / 2000.0;

    
// build view matrix
    D3DXMatrixLookAtLH(&mat_view, &D3DXVECTOR3(cos(angle) * 200.0200.0, sin(angle) * 200.0),
        
&D3DXVECTOR3(0.00.00.0), &D3DXVECTOR3(0.01.00.0));

    
// set view matrix
    g_d3d_device->SetTransform(D3DTS_VIEW, &mat_view);

    
// Begin scene
    if(SUCCEEDED(g_d3d_device->BeginScene()))
    {
        
// 1) draw the floor
        
        
// binds a vertex buffer to a device data stream
        g_d3d_device->SetStreamSource(0, g_floor_vb, 0sizeof(VERTEX));

        
// set the current vertex stream declation
        g_d3d_device->SetFVF(VERTEX_FVF);

        
// assigns a texture to a stage for a device
        g_d3d_device->SetTexture(0, g_floor_texture);

        
// build world matrix, we only need identity matrix, because we do not need to change original floor position.
        D3DXMatrixIdentity(&mat_world);

        
// set world matrix
        g_d3d_device->SetTransform(D3DTS_WORLD, &mat_world);        

        
// renders a sequence of noindexed, geometric primitives of the specified type from the current set
        
// of data input stream.
        g_d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 02);

        
// 2) draw the billboards

        g_d3d_device
->SetStreamSource(0, g_billboard_vb, 0sizeof(VERTEX));
        g_d3d_device
->SetTexture(0, g_billboard_texture);

        
// enable alpha test
        g_d3d_device->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
        
// get world matrix, just get it from view matrix's transpose.
        D3DXMatrixTranspose(&mat_world, &mat_view);
        
        
// draw all billboard images
        for(short i = 0; i < 3; i++)
        {
            
for(short j = 0; j < 3; j++)
            {
                mat_world._41 
= i * 80.0 - 80.0;
                mat_world._42 
= 0.0;
                mat_world._43 
= j * 80.0 - 80.0;

                
// set world matrix
                g_d3d_device->SetTransform(D3DTS_WORLD, &mat_world);
                
// draw polygon
                g_d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 02);
            }
        }

        
// disable alpha test
        g_d3d_device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);

        
// release texture
        g_d3d_device->SetTexture(0, NULL);

        
// end the scene
        g_d3d_device->EndScene();
    }

    
// present the contents of the next buffer in the sequence of back buffers owned by the device
    g_d3d_device->Present(NULL, NULL, NULL, NULL);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASSEX  win_class;
    MSG         msg;

    g_inst 
= inst;

    
// create window class and register it
    win_class.cbSize        = sizeof(win_class);
    win_class.style         
= CS_CLASSDC;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= 0;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(NULL, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= NULL;
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;
    win_class.hIconSm       
= LoadIcon(NULL, IDI_APPLICATION);

    
if(! RegisterClassEx(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateWindow(g_class_name, g_caption, WS_CAPTION | WS_SYSMENU, 00,
                          WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, inst, NULL);

    
if(g_hwnd == NULL)
        
return FALSE;

    ShowWindow(g_hwnd, SW_NORMAL);
    UpdateWindow(g_hwnd);

    
// initialize game
    if(Do_Init() == FALSE)
        
return FALSE;

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }
        
        
// draw a frame
        if(Do_Frame() == FALSE)
            
break;
    }

    
// run shutdown function
    Do_Shutdown();

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}

效果图:


技巧如下:

字体的绘制需要使用ID3DXFont对象和 D3DXCreateFontIndirect函数。

DirectX SDK文档对ID3DXFont做了简要的说明:

The ID3DXFont interface encapsulates the textures and resources needed to render a specific font on a specific device.

The ID3DXFont interface is obtained by calling D3DXCreateFont or D3DXCreateFontIndirect.

我们来看看SDK文档提供的关于 D3DXCreateFontIndirect的使用说明:

Creates a font object indirectly for both a device and a font.

Syntax

HRESULT WINAPI D3DXCreateFontIndirect(LPDIRECT3DDEVICE9 pDevice,
    CONST D3DXFONT_DESC *pDesc,
    LPD3DXFONT *ppFont
);

Parameters

pDevice
[in] Pointer to an IDirect3DDevice9 interface, the device to be associated with the font object.

pDesc
[in] Pointer to a D3DXFONT_DESC structure, describing the attributes of the font object to create. If the compiler settings require Unicode, the data type D3DXFONT_DESC resolves to D3DXFONT_DESCW; otherwise, the data type resolves to D3DXFONT_DESCA. See Remarks.

ppFont
[out] Returns a pointer to an ID3DXFont interface, representing the created font object.

Return Value

If the function succeeds, the return value is D3D_OK.

If the function fails, the return value can be one of the following:

D3DERR_INVALIDCALL The method call is invalid. For example, a method’s parameter may have an invalid value.
E_OUTOFMEMORY Microsoft Direct3D could not allocate sufficient memory to complete the call.

Remarks

The compiler setting also determines the function version. If Unicode is defined, the function call resolves to D3DXCreateFontIndirectW. Otherwise, the function call resolves to D3DXCreateFontIndirectA because ANSI strings are being used.

该函数的第二个参数使用了一个结构体D3DXFONT_DESC,来看看它的具体定义:

Defines the attributes of a font.

Syntax

typedef struct D3DXFONT_DESC {
    INT Height;
    UINT Width;
    UINT Weight;
    UINT MipLevels;
    BOOL Italic;
    BYTE CharSet;
    BYTE OutputPrecision;
    BYTE Quality;
    BYTE PitchAndFamily;
    TCHAR FaceName[LF_FACESIZE];
} D3DXFONT_DESC;

Members

Height
Height, in logical units, of the font’s character cell or character.

Width
Width, in logical units, of characters in the font.

Weight
Weight of the font in the range from 0 through 1000.

MipLevels
Number of mip levels requested. If this value is zero or D3DX_DEFAULT, a complete mipmap chain is created. If the value is 1, the texture space is mapped identically to the screen space.

Italic
Set to TRUE for an Italic font.

CharSet
Character set.

OutputPrecision
Output precision. The output precision defines how closely the output must match the requested font height, width, character orientation, escapement, pitch, and font type.

Quality
Output quality.

PitchAndFamily
Pitch and family of the font.

FaceName
A null-terminated string or characters that specifies the typeface name of the font. The length of the string must not exceed 32 characters, including the terminating null character. If FaceName is an empty string, the first font that matches the other specified attributes will be used.

If the compiler settings require Unicode, the data type TCHAR resolves to WCHAR; otherwise, the data type resolves to CHAR. See Remarks.

Remarks

The compiler setting also determines the structure type. If Unicode is defined, the D3DXFONT_DESC structure type resolves to a D3DXFONT_DESCW; otherwise the structure type resolves to a D3DXFONT_DESCA.

Possible values of the above members are given in the Microsoft Windows Graphics Device Interface (GDI) LOGFONT  structure.

当然,在设置的时候我们不需要设置所有的属性,只要设置其中的几个主要属性就可以了。

我们可以这样设置字体的属性并创建字体:

   D3DXFONT_DESC           font_desc;

   // create the font
    ZeroMemory(&font_desc, sizeof(font_desc));
    
    
// set font descripter
    strcpy(font_desc.FaceName, "Arial");
    font_desc.Height 
= 32;

    
// Creates a font object indirectly for both a device and a font
    D3DXCreateFontIndirect(g_d3d_device, &font_desc, &g_font);

通过ID3DXFont::DrawText方法我们可以绘制字体了,来看看它的具体使用信息:

Draws formatted text. This method supports ANSI and Unicode strings.

Syntax

INT DrawText(LPD3DXSPRITE pSprite,
    LPCTSTR pString,
    INT Count,
    LPRECT pRect,
    DWORD Format,
    D3DCOLOR Color
);

Parameters

pSprite
[in] Pointer to an ID3DXSprite object that contains the string. Can be NULL, in which case Microsoft Direct3D will render the string with its own sprite object.

To improve efficiency, a sprite object should be specified if ID3DXFont::DrawText is to be called more than once in a row.

pString
[in] Pointer to a string to draw.

If the Count parameter is -1, the string must be null-terminated.

Count
[in] Specifies the number of characters in the string. If Count is -1, then the pString parameter is assumed to be a pointer to a null-terminated string and ID3DXFont::DrawText computes the character count automatically.

pRect
[in] Pointer to a RECT  structure that contains the rectangle, in logical coordinates, in which the text is to be formatted. As with any RECT object, the coordinate value of the rectangle's right side must be greater than that of its left side. Likewise, the coordinate value of the bottom must be greater than that of the top.

Format
[in] Specifies the method of formatting the text. It can be any combination of the following values:

DT_BOTTOM
Justifies the text to the bottom of the rectangle. This value must be combined with DT_SINGLELINE.

DT_CALCRECT
Determines the width and height of the rectangle. If there are multiple lines of text, ID3DXFont::DrawText uses the width of the rectangle pointed to by the pRect parameter and extends the base of the rectangle to bound the last line of text. If there is only one line of text, ID3DXFont::DrawText modifies the right side of the rectangle so that it bounds the last character in the line. In either case, ID3DXFont::DrawText returns the height of the formatted text but does not draw the text.

DT_CENTER
Centers text horizontally in the rectangle.

DT_EXPANDTABS
Expands tab characters. The default number of characters per tab is eight.

DT_LEFT
Aligns text to the left.

DT_NOCLIP
Draws without clipping. ID3DXFont::DrawText is somewhat faster when DT_NOCLIP is used.

DT_RIGHT
Aligns text to the right.

DT_RTLREADING
Displays text in right-to-left reading order for bidirectional text when a Hebrew or Arabic font is selected. The default reading order for all text is left-to-right.

DT_SINGLELINE
Displays text on a single line only. Carriage returns and line feeds do not break the line.

DT_TOP
Top-justifies text.

DT_VCENTER
Centers text vertically (single line only).

DT_WORDBREAK
Breaks words. Lines are automatically broken between words if a word would extend past the edge of the rectangle specified by the pRect parameter. A carriage return/line feed sequence also breaks the line.

Color
[in] Color of the text. For more information, see D3DCOLOR.

Return Value

If the function succeeds, the return value is the height of the text in logical units. If DT_VCENTER or DT_BOTTOM is specified, the return value is the offset from pRect->top to the bottom of the drawn text.

If the function fails, the return value is zero.

Remarks

The parameters of this method are very similar to those of the Microsoft Windows Graphics Device Interface (GDI) DrawText  function.

This method supports both ANSI and Unicode strings.

This method must be called inside a IDirect3DDevice9::BeginScene ... IDirect3DDevice9::EndScene block. The only exception is when an application calls ID3DXFont::DrawText with DT_CALCRECT to calculate the size of a given block of text.

Unless the DT_NOCLIP format is used, this method clips the text so that it does not appear outside the specified rectangle. All formatting is assumed to have multiple lines unless the DT_SINGLELINE format is specified.

If the selected font is too large for the rectangle, this method does not attempt to substitute a smaller font.

This method supports only fonts whose escapement and orientation are both zero.

我们可以这样使用该函数:

    RECT rect = { 00, WINDOW_WIDTH, WINDOW_HEIGHT };

    // Begin scene
    if(SUCCEEDED(g_d3d_device->BeginScene()))
    {
        
// draw some text
        g_font->DrawTextA(NULL, "Programming is fun!"-1&rect, DT_CENTER | DT_VCENTER, 0xFFFFFFFF);

        
// end the scene
        g_d3d_device->EndScene();
    }

完整源码如下:

/***************************************************************************************
PURPOSE:
    Font Demo

Required libraries:
  D3D9.LIB and D3DX9.LIB
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
"d3d9.h"
#include 
"d3dx9.h"

#pragma comment(lib, 
"d3d9.lib")
#pragma comment(lib, 
"d3dx9.lib")

#pragma warning(disable : 
4996)

#define WINDOW_WIDTH    400
#define WINDOW_HEIGHT   400

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class and caption text.
HWND g_hwnd;
HINSTANCE g_inst;
static char g_class_name[] = "FontClass";
static char g_caption[]    = "Font Demo";

// the Direct3D and device object
IDirect3D9* g_d3d = NULL;
IDirect3DDevice9
* g_d3d_device = NULL;

// the font object
ID3DXFont* g_font = NULL;

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Initialize d3d, d3d device, vertex buffer, texutre. 
//--------------------------------------------------------------------------------
BOOL Do_Init()
{
    D3DPRESENT_PARAMETERS   present_param;
    D3DDISPLAYMODE          display_mode;
    D3DXFONT_DESC           font_desc;

    
// do a windowed mode initialization of Direct3D
    if((g_d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
        
return FALSE;

    
// retrieves the current display mode of the adapter
    if(FAILED(g_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
        
return FALSE;

    ZeroMemory(
&present_param, sizeof(present_param));

    
// initialize d3d presentation parameter
    present_param.Windowed          = TRUE;
    present_param.SwapEffect        
= D3DSWAPEFFECT_DISCARD;
    present_param.BackBufferFormat  
= display_mode.Format;    

    
// creates a device to represent the display adapter
    if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
&present_param, &g_d3d_device)))
        
return FALSE;     

    
// create the font
    ZeroMemory(&font_desc, sizeof(font_desc));
    
    
// set font descripter
    strcpy(font_desc.FaceName, "Arial");
    font_desc.Height 
= 32;

    
// Creates a font object indirectly for both a device and a font
    D3DXCreateFontIndirect(g_d3d_device, &font_desc, &g_font);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Release all d3d resource.
//--------------------------------------------------------------------------------
BOOL Do_Shutdown()
{
    Safe_Release(g_font);
    Safe_Release(g_d3d_device);
    Safe_Release(g_d3d);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Render a frame.
//--------------------------------------------------------------------------------
BOOL Do_Frame()
{
    RECT rect 
= { 00, WINDOW_WIDTH, WINDOW_HEIGHT };

    
// clear device back buffer
    g_d3d_device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_RGBA(064128255), 1.0f0);

    
// Begin scene
    if(SUCCEEDED(g_d3d_device->BeginScene()))
    {
        
// draw some text
        g_font->DrawTextA(NULL, "Programming is fun!"-1&rect, DT_CENTER | DT_VCENTER, 0xFFFFFFFF);

        
// end the scene
        g_d3d_device->EndScene();
    }

    
// present the contents of the next buffer in the sequence of back buffers owned by the device
    g_d3d_device->Present(NULL, NULL, NULL, NULL);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASSEX  win_class;
    MSG         msg;

    g_inst 
= inst;

    
// create window class and register it
    win_class.cbSize        = sizeof(win_class);
    win_class.style         
= CS_CLASSDC;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= 0;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(NULL, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= NULL;
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;
    win_class.hIconSm       
= LoadIcon(NULL, IDI_APPLICATION);

    
if(! RegisterClassEx(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateWindow(g_class_name, g_caption, WS_CAPTION | WS_SYSMENU, 00,
                          WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, inst, NULL);

    
if(g_hwnd == NULL)
        
return FALSE;

    ShowWindow(g_hwnd, SW_NORMAL);
    UpdateWindow(g_hwnd);

    
// initialize game
    if(Do_Init() == FALSE)
        
return FALSE;

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }
        
        
// draw a frame
        if(Do_Frame() == FALSE)
            
break;
    }

    
// run shutdown function
    Do_Shutdown();

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}

效果图如下:


技巧如下:

在设置可变顶点格式时加入法线和漫反色,如下所示:

// The 3D vertex format and descriptor
typedef struct
{
    
float x, y, z;      // 3D coordinates    
    float nx, ny, nz;   // normals
    D3DCOLOR diffuse;   // color
} VERTEX;

#define VERTEX_FVF   (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE)

开启光照和Z缓存:

    // enable d3d lighting
    g_d3d_device->SetRenderState(D3DRS_LIGHTING, TRUE);
    
// enable z-buffer
    g_d3d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

设置光源属性并打开光源:

    D3DLIGHT9 light;

    // set light data, color, position, range.
    ZeroMemory(&light, sizeof(light));

    light.Type      
= D3DLIGHT_POINT;
    light.Diffuse.r 
= light.Ambient.r = 0.5;
    light.Diffuse.g 
= light.Ambient.g = 0.5;
    light.Diffuse.b 
= light.Ambient.b = 0.0;
    light.Diffuse.a 
= light.Ambient.a = 1.0;
    light.Range         
= 1000.0;
    light.Attenuation0  
= 0.5;
    light.Position.x    
= 300.0;
    light.Position.y    
= 0.0;
    light.Position.z    
= -600.0;

    
// set and enable the light
    g_d3d_device->SetLight(0&light);
    g_d3d_device
->LightEnable(0, TRUE);

完整源码如下:

/***************************************************************************************
PURPOSE:
    Light Demo

Required libraries:
  WINMM.LIB, D3D9.LIB, D3DX9.LIB.
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
"d3d9.h"
#include 
"d3dx9.h"

#pragma comment(lib, 
"winmm.lib")
#pragma comment(lib, 
"d3d9.lib")
#pragma comment(lib, 
"d3dx9.lib")

#pragma warning(disable : 
4305)

#define WINDOW_WIDTH    400
#define WINDOW_HEIGHT   400

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class and caption text.
HWND g_hwnd;
HINSTANCE g_inst;
static char g_class_name[] = "LightClass";
static char g_caption[]    = "Light Demo";

// the Direct3D and device object
IDirect3D9* g_d3d = NULL;
IDirect3DDevice9
* g_d3d_device = NULL;

// The 3D vertex format and descriptor
typedef struct
{
    
float x, y, z;      // 3D coordinates    
    float nx, ny, nz;   // normals
    D3DCOLOR diffuse;   // color
} VERTEX;

#define VERTEX_FVF   (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE)

IDirect3DVertexBuffer9
* g_vertex_buffer = NULL;

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Initialize d3d, d3d device, vertex buffer, texutre; set render state for d3d;
// set perspective matrix and view matrix.
//--------------------------------------------------------------------------------
BOOL Do_Init()
{
    D3DPRESENT_PARAMETERS present_param;
    D3DDISPLAYMODE  display_mode;
    D3DXMATRIX mat_proj, mat_view;
    D3DLIGHT9 light;
    BYTE
* vertex_ptr;

    
// initialize vertex data
    VERTEX verts[] = {
        { 
-100.0f,  100.0f-100.0f0.0f,0.0f,-1.0f, D3DCOLOR_RGBA(255,255,255,255) },
        {  
100.0f,  100.0f-100.0f0.0f,0.0f,-1.0f, D3DCOLOR_RGBA(255,255,255,255) },
        { 
-100.0f-100.0f-100.0f0.0f,0.0f,-1.0f, D3DCOLOR_RGBA(255,255,255,255) },
        {  
100.0f-100.0f-100.0f0.0f,0.0f,-1.0f, D3DCOLOR_RGBA(255,255,255,255) },

        {  
100.0f,  100.0f-100.0f1.0f,0.0f,0.0f, D3DCOLOR_RGBA(255,255,255,255) },
        {  
100.0f,  100.0f,  100.0f1.0f,0.0f,0.0f, D3DCOLOR_RGBA(255,255,255,255) },
        {  
100.0f-100.0f-100.0f1.0f,0.0f,0.0f, D3DCOLOR_RGBA(255,255,255,255) },
        {  
100.0f-100.0f,  100.0f1.0f,0.0f,0.0f, D3DCOLOR_RGBA(255,255,255,255) },

        {  
100.0f,  100.0f,  100.0f0.0f,0.0f,1.0f, D3DCOLOR_RGBA(255,255,255,255) },
        { 
-100.0f,  100.0f,  100.0f0.0f,0.0f,1.0f, D3DCOLOR_RGBA(255,255,255,255) },
        {  
100.0f-100.0f,  100.0f0.0f,0.0f,1.0f, D3DCOLOR_RGBA(255,255,255,255) },
        { 
-100.0f-100.0f,  100.0f0.0f,0.0f,1.0f, D3DCOLOR_RGBA(255,255,255,255) },

        { 
-100.0f,  100.0f,  100.0f-1.0f,0.0f,0.0f, D3DCOLOR_RGBA(255,255,255,255) },
        { 
-100.0f,  100.0f-100.0f-1.0f,0.0f,0.0f, D3DCOLOR_RGBA(255,255,255,255) },
        { 
-100.0f-100.0f,  100.0f-1.0f,0.0f,0.0f, D3DCOLOR_RGBA(255,255,255,255) },
        { 
-100.0f-100.0f-100.0f-1.0f,0.0f,0.0f, D3DCOLOR_RGBA(255,255,255,255) }
    }; 

    
// do a windowed mode initialization of Direct3D
    if((g_d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
        
return FALSE;

    
// retrieves the current display mode of the adapter
    if(FAILED(g_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
        
return FALSE;

    ZeroMemory(
&present_param, sizeof(present_param));

    
// initialize d3d presentation parameter
    present_param.Windowed               = TRUE;
    present_param.SwapEffect             
= D3DSWAPEFFECT_DISCARD;
    present_param.BackBufferFormat       
= display_mode.Format;
    present_param.EnableAutoDepthStencil 
= TRUE;
    present_param.AutoDepthStencilFormat 
= D3DFMT_D16;

    
// creates a device to represent the display adapter
    if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
&present_param, &g_d3d_device)))
        
return FALSE;     

    
// set render state

    
// enable d3d lighting
    g_d3d_device->SetRenderState(D3DRS_LIGHTING, TRUE);
    
// enable z-buffer
    g_d3d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

    
// create and set the projection matrix

    
// builds a left-handed perspective projection matrix based on a field of view
    D3DXMatrixPerspectiveFovLH(&mat_proj, D3DX_PI/4.01.333331.01000.0);

    
// sets a single device transformation-related state
    g_d3d_device->SetTransform(D3DTS_PROJECTION, &mat_proj);

    
// create and set the view matrix
    D3DXMatrixLookAtLH(&mat_view, 
                       
&D3DXVECTOR3(0.00.0-500.0),
                       
&D3DXVECTOR3(0.0f0.0f0.0f), 
                       
&D3DXVECTOR3(0.0f1.0f0.0f));

    g_d3d_device
->SetTransform(D3DTS_VIEW, &mat_view);

    
// create the vertex buffer and set data
    g_d3d_device->CreateVertexBuffer(sizeof(VERTEX) * 160, VERTEX_FVF, D3DPOOL_DEFAULT, &g_vertex_buffer, NULL);

    
// locks a range of vertex data and obtains a pointer to the vertex buffer memory
    g_vertex_buffer->Lock(00, (void**)&vertex_ptr, 0);

    memcpy(vertex_ptr, verts, 
sizeof(verts));

    
// unlocks vertex data
    g_vertex_buffer->Unlock();

    
// set light data, color, position, range.
    ZeroMemory(&light, sizeof(light));

    light.Type      
= D3DLIGHT_POINT;
    light.Diffuse.r 
= light.Ambient.r = 0.5;
    light.Diffuse.g 
= light.Ambient.g = 0.5;
    light.Diffuse.b 
= light.Ambient.b = 0.0;
    light.Diffuse.a 
= light.Ambient.a = 1.0;
    light.Range         
= 1000.0;
    light.Attenuation0  
= 0.5;
    light.Position.x    
= 300.0;
    light.Position.y    
= 0.0;
    light.Position.z    
= -600.0;

    
// set and enable the light
    g_d3d_device->SetLight(0&light);
    g_d3d_device
->LightEnable(0, TRUE);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Release all d3d resource.
//--------------------------------------------------------------------------------
BOOL Do_Shutdown()
{
    Safe_Release(g_vertex_buffer);
    Safe_Release(g_d3d_device);
    Safe_Release(g_d3d);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Render a frame.
//--------------------------------------------------------------------------------
BOOL Do_Frame()
{
    D3DXMATRIX mat_world;

    
// clear device back buffer
    g_d3d_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(000255), 1.0f0);

    
// Begin scene
    if(SUCCEEDED(g_d3d_device->BeginScene()))
    {
        
// create and set the world transformation matrix
        
// rotate object along y-axis
        D3DXMatrixRotationY(&mat_world, (float) (timeGetTime() / 1000.0));
        
        g_d3d_device
->SetTransform(D3DTS_WORLD, &mat_world);

        
// set the vertex stream, shader.

        
// binds a vertex buffer to a device data stream
        g_d3d_device->SetStreamSource(0, g_vertex_buffer, 0sizeof(VERTEX));

        
// set the current vertex stream declation
        g_d3d_device->SetFVF(VERTEX_FVF);

        
// draw the vertex buffer
        for(short i = 0; i < 4; i++)
            g_d3d_device
->DrawPrimitive(D3DPT_TRIANGLESTRIP, i * 42);

        
// end the scene
        g_d3d_device->EndScene();
    }

    
// present the contents of the next buffer in the sequence of back buffers owned by the device
    g_d3d_device->Present(NULL, NULL, NULL, NULL);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASSEX  win_class;
    MSG         msg;

    g_inst 
= inst;

    
// create window class and register it
    win_class.cbSize        = sizeof(win_class);
    win_class.style         
= CS_CLASSDC;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= 0;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(NULL, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= NULL;
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;
    win_class.hIconSm       
= LoadIcon(NULL, IDI_APPLICATION);

    
if(! RegisterClassEx(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateWindow(g_class_name, g_caption, WS_CAPTION | WS_SYSMENU, 00,
                          WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, inst, NULL);

    
if(g_hwnd == NULL)
        
return FALSE;

    ShowWindow(g_hwnd, SW_NORMAL);
    UpdateWindow(g_hwnd);

    
// initialize game
    if(Do_Init() == FALSE)
        
return FALSE;

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }
        
        
// draw a frame
        if(Do_Frame() == FALSE)
            
break;
    }

    
// run shutdown function
    Do_Shutdown();

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}

效果图:


       文/本报记者 邓艳玲
  摄影/本报记者 吕家佐
  2007年06月28日青年周末

  “抓住陈晓旭一说,大家都登了,反而我的观点就出来了,说些不痛不痒的
话,谁理你!”尽管上遭卫生部副部长、国家中医药管理局局长王国强怒斥“这
是借人们喜爱的影视演员攻击中医药的言论,很不严肃、很不道德、很不科
学!”,下遇普通网民拍砖“信口雌黄,无聊至极”,何祚庥反而认为自己借陈
晓旭一事来说中医,是“非常讲策略的”,也达到了让人们注意中医存在严重问
题的目的。

  5月底,当多数国人还在为“林妹妹”陈晓旭的红颜薄命扼腕叹息时,一个
非常“不和谐”的声音传出:是中医害死了陈晓旭。发出这个声音的正是中国科
学院院士、著名反伪斗士何祚庥。他声称自己就是根据报纸上对陈晓旭去世的报
道,得出这个结论的。

  “他这不是瞎胡闹吗?”在网上,他被强烈地质疑和谩骂着。就在他几乎被
唾沫淹没之时,6月14日,卫生部副部长王国强在接受采访时,对他进行了严苛
的评价。第二日,几乎所有媒体都报道和转载了这一说法,何祚庥也似乎陷入了
官方和民间的双重谴责永无翻身之日。但第二日,他就在网上写了一篇措辞毫不
客气的文章对王国强加以反驳。

  一直以来,何祚庥就以反对伪科学和学术腐败为公众熟知,而他也因为敢于
直言,在多个领域直言为引来无数争议,此次他借陈晓旭得乳腺癌致死来抨击中
医甚至引来了官方人士的骂声,这个顽固的80岁老人难道真的觉得自己一点错都
没有?

  中医就应该为陈晓旭得死负责
  报纸上的新闻报道足以让我下结论

  青年周末(以下简称为“青周”):您是在什么情形下说“陈晓旭是中医害
死的”?

  何祚庥:我参加全国科技活动周,在南宁给学生们做关于反对伪科学和学术
腐败的讲座,有学生问到有关中医的问题,我就说了。

  青周:是您刻意提到了陈晓旭之死吗?

  何祚庥:学生主动问起,我在回答中也就不回避。当时,这是个非常热的事
情,我在去南宁的飞机上看的几乎每份报纸上都有对她去世情况、她治病的情况
的详细报道。

  青周:您只是通过看报纸也没经过严密的调查研究,就下这么一个肯定的判
断,合适吗?

  何祚庥:我和陈晓旭不熟,也只在电视上看过她演的角色,我能去做什么调
查研究?也没这个必要,根据各大报纸几乎相同的报道,提供的细节,足以让我
下一个基本判断。

  青周:还有不少人看完报道也认为,是陈本人因为讳疾忌医而不愿意看病导
致的结果呢!怎么就单单指中医害死她呢?

  何祚庥:事实上她是看的中医,吃的中药。如果她是不愿意就医,既不愿意
就中医,也不愿意就西医,因为自己的信仰而死,我没话可说。但事实上她是选
择了看中医,吃中药。报道中并没有说她看的中医提醒她去做详细准确的诊断,
如果中医也说过这样的话,那就是陈晓旭自己负责任了。

  像季羡林遇到的好中医太少

  青周:那这也说明,这只是陈看的那个中医有问题,怎么能因为一个人的问
题进而指责整个行业呢?

  何祚庥:中医就没有癌症的概念!更谈不上有能力去治疗癌症。但相当的中
医大夫都宣称自己能治癌,还有众多治癌的特殊办法,也就是“偏方”。

  中医都是这样。季羡林在《病榻杂记》说起自己治病的遭遇,他遇到的那个
中医邹铭西算是一个好医生,他不能确诊,就让季羡林另请高明,但这只是极少
数。季得的也只是天疱疮,还不是什么癌症之类大病。很多中医是敢宣称,专治
西医都看不了的疑难杂症的。

  青周:凭什么西医都治不了的疑难杂症,中医也一定就治不了呢?

  何祚庥:那你中医先把西医能够治愈的大病先治好再说。我们来数一下,历
史上的对人类影响的大病是中医治愈或者说可以治愈的?肺结核、伤寒、疟疾、
鼠疫、霍乱、天花、盲肠炎(中医又称为绞肠痧)……我不是专业医生,我数不
过来,你可以到医院去问,到病人中去问嘛。

  你还可以去翻翻巴金的《家》、《春》、《秋》,林语堂的《京华烟云》,
去看看鲁迅的书,太多的文学作品中都有中医怎么耽误病人的。从五四以来,太
多的先进知识分子都是反对中医的:梁启超、鲁迅、傅斯年、郭沫若……

  我自己就出生在上海一个封建大家庭,家里看病一定是找上海当时著名的老
中医夏应堂。他在我们那个大家庭也是治死了不少人的。我父亲就是其中一个。
我父亲26岁去世,得了伤寒。(接着马上起身拿来一本书《走出寄啸山庄》),
这本书是我一个堂兄所作,讲的就是我们这个大家庭的事情,在这本书里面,他
就好几处提到了夏应堂,夏应堂把他一个姐姐,得天花就没有看出来,几乎治得
送命,把一个姑姑治成了傻子。

  一般的伤风、感冒、泻肚子、拉稀中医倒是可以治,但这些小病,很多时候
不治也是可以痊愈的。在旧社会,如果大病中医治不了,就有这样一种说法,可
以治病不能治命,得了大病命中就该死的。

  青周:西医也照样成批成批的治死人,您为什么只攻击中医,而不去说说西
医的弊端呢?这能说明中医就比西医差吗?

  何祚庥:那不一样。西医也会死人,但西医能够告诉你,为什么治不好。中
医不是,它治不好了,就告诉你命中该死。盲肠炎,中医叫绞肠痧,不知道怎么
治,很多时候只能等死;瘟病,就是我们现在说的霍乱,也只能等死。

  夏应堂的儿子叫夏理彬,也是个名中医。夏应堂去世后,就是夏理彬给我们
家人看病,就用上温度计了,他也认为温度计比用手摸要准确多了。还有一代人
文大师陈寅恪,他家可是中医世家,他都说中医是要不得的。

  中医学泰斗反对中医现代化
  农民也都认疗效显著的西医

  青周:中医在我们人民心目中地位还是很高的,这种高地位恐怕也是它长期
以来的疗效形成的口碑吧?

  何祚庥:那你就错了,我首先问你,有了大病的时候,你是先看西医还是中
医?现在就是在农村,观念也扭转过来了。(何祚庥的爱人庆承瑞插话:我们
(上个世纪)50-60年代下乡的时候,农民朋友最认的就是青霉素针,如果发烧
得厉害了,他们最希望的就是医生给打一针青霉素。)1965年的时候,我在农村
搞四清,有一位社员,得了胃溃疡,快要穿孔了,当时我立即做决定把这位社员
紧急送到医院,做手术,治好了。社员们说,要靠中医,这位社员就完了。

  现在中医院都是靠西医在维持

  青周:说到医院,现在中医院这么多,不照样在行使治病救人的职责,如果
都不能治病救人,也不可能生存下去吧?

  何祚庥:(庆承瑞:最近三个学生铊中毒,第一确诊的就是在中医院。这难
道是中医能诊断出来的?分明是西医吧!现在的中医院,你去看看,那里的设备
和普通的医院完全一样。)现在你去中医院的诊疗,来了以后先做的检查,那一
套完全是西医的套路啊。开的中药里面掺了西药。现在很多所谓的中药里面真正
起疗效的是西药成分,只不过是打着个中药的旗号,卖的却是西药。但是这些都
不让说。

  完全标准的传统中医院活不下去

  青周:即便是现在中医不能治大病,不代表她不会继续发展,今后不能治大
病啊,况且大病不也都是由小病发展来的么?为什么不用发展的眼光来看中医?

  何祚庥:(庆承瑞插话:中医已经存在两千年了,要发展,也早就应该发展
成熟了;但问题是,现在的中医泰斗们却在那里说,中医不能变,变了就不是中
医了。)他们认为中医的现代化是个错误的口号,认为中医现代化就等于消灭中
医,这种认识在中医界是占主导地位的。(庆承瑞:最近在广州开了一个“挺中
医”的大型研讨会,所谓“挺中医”认为现在中医院的做法是完全错误的,现在
用西医的办法来改造中医也绝对是错误的。)

  对现在中医院的格局他们是坚决反对的,他们认为就应该只使用中医的望闻
问切,他们倒是主张要办完全标准的传统的中医院。 这个我赞成!你办吧!所
有的现代化装备都不要,连温度计也不要,你就要靠手摸!你就和普通的医院比
一下,看到底谁最终能活下去!

  我只否定中医的90%
  讲10%精华还是给中医面子

  青周:说来说去,您其实就是要借陈晓旭的死来全盘否定中医罗?

  何祚庥:我没有全盘否认,我只否认90%,我说中医90%是糟粕,10%是精华。
是他们说我全盘否认,是他们放大了。我没说要取消中医,但我说要反对中医的
90%。现在有人硬说我何祚庥全盘否定中医,是故意歪曲我的理念。他们说不出
来反对我的理由,就扣个帽子。

  青周:中医90%是糟粕,10%是精华,您怎么得出这两个数据?

  何祚庥:历史上的大病,对人类有影响的,天花、伤寒、疟疾、鼠疫、肺结
核等等,都是西医治愈的,不但治愈了,而且把病原病理都弄得很清楚了。我数
出的大病,你能治一个就是10%。还有好多人说我给出这个比例还是给中医面子
呢。

  青周:您作为一个搞自然科学的人,最讲究精确的数据了,这样笼统地说
90%和10%好像并不符合科学家的一贯做法?

  何祚庥:我讲过中医的阴阳五行理论是“伪科学”,阴阳五行理论只是中医
的一部分,但我很遗憾,因为这是中医的指导思想,这种错误的指导思想影响的
行动比较严重,所以我说它90%是糟粕。

  中西医没法结合

  青周:那10%的精华又指的是什么呢?

  何祚庥:(庆承瑞:无非就是指几千年的中国民间的医学积累了一些有用的
经验,也许这可以算作精华,你要问我,精华具体表现在什么地方,我就说有一
味中药叫鸦胆子可以来治孩子手上的瘊子)还有就是我小时候曾经手脱臼,是中
医的手法弄好了。后来,我把这件事在网上说了,又有好多人告诉我,这在西医
是入门常识,是学生在学校里就要学的初级知识,这个我就不知道,到底是西医
是学了中医的,还是两家本来就有的。

  青周:您看,现在是人为地把中医和西医分成两个水火不容的体系,但事实
上,中医有些东西和西医道理是相通的,那为什么要把它们弄得那么泾渭分明,
水火不容呢?中西结合一起发展不好吗?

  何祚庥:发展一个医学体系,最重要的是科学的理论基础作为指引,中医的
理念是不准对人体解剖,西医的解剖,要看神经血管,这两者怎么融合?这可不
是我的看法。医学界很多人都这样看,举个例子,钟南山院士就说了,中西医的
理论没法结合。

  中西医已经结合50年了,结合得怎么样?(50年不是太短了吗?西医好几百
年,中西几千年呢?)你以为就我们关心吗?那些搞医学的人比我们关心多了,
不是时间长短的问题,问中医大夫,也问中央卫生部,而是根本没法结合的问题。
理论体系无法结合。

  抗击非典,都说中西医结合做出来的成就,我就问一个问题,我们因为非典
牺牲了200多位医生和护士,在这群人中,有多少是中医?我2003年就在网上提
了这个问题,但没人敢回答我。如果一个也不牺牲,就算重大贡献,我不相信!
现在都说功劳是中西医结合,我就问中医大夫牺牲了多少位?

  治病不能靠文化

  青周:您把有用的东西就认为是西医的,中医即使有用的也是些没来由的雕
虫小技,这公平吗?

  何祚庥:公不公平,推不推崇哪个,要看事实,不是人为拔高,把不好的东
西非说成是好的。中医一些有用的东西是可以归到现代医学里去,大量的还是归
不进去的,而且还不知道怎么回事。90%糟粕,10%精华,我已经做了定量的分析
了,应该说我已经说得够到位了。

  青周:即使如您所说,您怎么就判断这10%就不是中医的主流呢?而且,那
90%也许是以我们目前心智尚不能开掘出来的博大精深的内容?您的划分仅仅依
靠目前历史上的大病诊疗记录就行了么?

  何祚庥:靠这个难道还不够吗?如果作为医学,绝大多数大病都不能治,那
我还能说它是个先进的东西吗?它倒是宣称它可以治愈大病,但它从来不去治现
在西医可以治愈的病证,都是我们可以判断的大病之外的,现在谁也不知道怎么
回事的,疑难杂症,它就说它可以治了!

  中医自己说,“医者,意也”,意念的意;有时候又说,“医者,艺也”,
艺术的艺;有时候又说,“医者,易也”,易经的易;就是不说,医者,科也。
现在他们又说中医是文化,我倒是同意他们这种说法,但治病不能靠文化治吧。

  正式署名骂我的还就是王国强一人
  中医在癌症的诊断上就该全部否定

  青周:您怎么看待您说出对中医的看法就遭来骂声?

  何祚庥:那有什么关系,我讲的是事实,别人谩骂,事实还就是事实。包括
卫生部副部长,他官大,说我“极不严肃,极不道德,极不科学”,我就真的这
样了么?他说“世界上每天都有很多人去世,难道都是中医害死的?更何况,癌
症的治疗是所有疾病中死亡率最高的。”我可没那样说,我说的是陈晓旭啊,说
的是乳腺癌。

  多数癌症死亡率是高,但乳腺癌不是,如果早发现,80%是能够治愈的。他
作为一个卫生部的副部长,就应该告诉人们,应该普及这样的常识;而且同时作
为中医药管理局的局长,他应该知道中医药的短处,应该告诉人们,中医是不能
治乳腺癌的,应该看西医。中医在癌症的诊断上面就该全部否定!

  他现在是袒护啊。而且他还说何祚庥说了这种情况之后,就是不道德。这作
为一个部长说这样的话,太差劲了吧。你是对人民负责呢?还是对你手下的中医
负责?

  讲出让人不高兴得事实被说不道德

  青周:您自己并不是医生,对中医的了解会多过一个管理中医药的官员?如
此下结论的确不足以让人信服,感觉太武断?

  何祚庥:我讲的也不是什么高深的东西,常识而已。我的确不懂医学,有人
比我更懂。(他起身又拿来一本杂志《抗癌之窗》最新一期2007年6月号,他非
常认真地把封二上面的主管主办单位:中华人民共和国卫生部,中国医学科学院,
顾问委员会主席吴阶平等人的名字读出,以示这份杂志的专业性和权威性。翻到
《陈晓旭不该红颜薄命》这一篇。)写这个文章的人自己在文中称自己是乳腺癌
专科医生。他在文章中所讲的,和我所讲的几乎是一个意思。这可是他卫生部主
管的一份权威杂志啊,人家也是这样讲的,我把乳腺癌可以防治,早发现可以用
西医方法治愈的事实讲出来了,难道不道德了吗?

  事实上是,卫生部门长期在做一些乌七八糟的事情。(翻倒杂志的另一页,
《抗癌仙姑“还阳草”骗局始末》读起有关已被他划上线的句子,先是法院1991
年法院对这一诈骗当事人判了6年的有期徒刑。),你看,1982年年底,经推荐,
又获当时卫生部主要领导同意,王淑华的“还阳草”居然进入中国中医研究院所
属的广安门医院,开始了代号为831的临床验证,最后因为无效而告终。这就可
以看见这就是他们的传统,中医领导部门会支持这些没有科学根据的偏方,甚至
是诈骗,宣称能够治癌。是我武断,还是我尖锐,还是我说的事实让人不高兴了
呢?我说话不算尖锐。鲁迅,比我尖锐多了。他甚至说,中医是有意无意的骗子!
当然鲁迅比我更有资格说这样的话,鲁迅学过现代医学。

  我坚持的是我弄懂的科学常识

  青周:为什么那么多人要站出来独独骂你呢?

  何祚庥:网上骂我的人都是匿名的,说明那些人找不到可以驳倒我的理由,
只好躲在阴暗的角落里骂骂我。倒是正式署名的就是我们的王国强副部长。

  那些主张一分为二来看中医的人,也承认我讲出了一些事实的真相,中医会
害人,是事实,没法回避的事实,但这个事实让他们感到不舒畅。

  青周:但也有不少网友也在说中医治好他们病的例子,为什么他们的说法您
不采信呢?

  何祚庥:证明一个全称肯定的说法,孤证不立,要打倒一个全称肯定的命题,
一个个案就行了。其实你真要知道情况,你应该去问问医院的大夫,治癌症的大
夫,你就可以知道事实到底是什么。现在西医虽然他们在实践中发挥重要作用,
但他们不敢对中医说什么,他们也没地方去说。连我这样一个中央卫生部管不着
的圈外人士说了一句话,就遭到副部长的迎头痛击,在他们管辖下的西医大夫,
还敢说什么批评性意见?!

  现在不是提倡创新吗?创新难道不需要怀疑精神吗?中医是不准怀疑的。谁说
中医坏话,就是反对传统文化,全盘打倒传统文化,这没道理啊。

  青周:就是您说,您为什么不用一种能够让普通老百姓接受的方式方法把您
认为正确的东西传达出来,而不让人反感呢?

  何祚庥:我觉得我现在的方法很好,抓住陈晓旭一说,大家都登了,反而我
的观点就出来了,说些不痛不痒的话,谁理你?有人说我炒作,我炒作什么?再
炒作我也不会成为著名大夫,也不会有人找我治病!牵涉到社会公众健康,我认
为我讲的这种观点需要向社会公众传播。何祚庥从来没说,我的意见都科学,我
也没说我什么都懂;我很多不懂,但我现在坚持的是我弄懂了的,而且是普通科
学常识。

译者申明:

这些指南是我在阅读 DirectX9.0 SDK 中逐步翻译出来的。对于初次接触 DirectX Graphics 的编程者而言,这应该是很好的上手资料。其实,本人就是从这些指南开始深入 Direct3D9.0 的;由于这是本人第一次翻译英文材料,言语不通,词不达意之处一定很多,一些术语也译得很勉强,请见谅。

此外,需要转载此文者,请保留以下部分:

———————————————————————–

DirectX图形接口指南 译者:Rise 电子邮箱: Rise.Worlds@gmail.com

———————————————————————–

DirectX 图形接口指南:(应用于 DirectX 9.0 版 C/C++ 编程) 
本区域的指南将说明如何在 C/C++ 程序中使用 Microsoft Direct3D 和 Direct3DX 完成一些普通的工作。这些工作总是被分解成若干个必要的步骤。在某些情况下,为了使表达更清楚,一些步骤还被细分成几个子步骤。 

本区域提供的指南有:


· 指南一:创建设备 

· 指南二:演示顶点 

· 指南三:使用矩阵 

· 指南四:创建和使用光源 

· 指南五:使用纹理映射 

· 指南六:使用Mesh模型 


提示:指南中出现的示例代码来自于每个指南具体提供的路径里的源文件。 


这些指南中的源代码是用 C++ 写成的。如果使用C编译器,你必须适当的改变这些文件使它们能够编译通过。最少的,你需要加入 vtable 然后用它引用接口函数。 

包含在示例代码中的一些注解可能与来自 Microsoft Platform Software Development Kit (SDK) 中的源代码不同。这些改变仅仅为了简化表述并且只限于注解中,这样能够防止示例程序的行为被改变。


指南一:创建设备 

为了使用 Microsoft Direct3D,你首先需要创建一个应用程序窗口,并紧接着创建和初始化 Direct3D 对象。你应该使用这些对象提供的 COM 接口来操纵它们,以及创建描绘一个场景所必需的其它对象。本指南包含的 CreateDevice 示例将例示并说明以下几个工作:创建 Direct3D 设备并且绘制一个简单的蓝色屏幕。 

这个指南使用以下步骤:初始化 Direct3D,绘制场景,以及最后清理与关闭。 


·步骤一:创建一个窗口 

·步骤二:初始化 Direct3D 

·步骤三:处理系统消息 

·步骤四:绘制与显示场景 

·步骤五:关闭与清除 


注意:CreateDevice 示例程序的路径在: 

(SDK root)\Samples\Multimedia\Direct3D\Tutorials\Tut01_CreateDevice. 


步骤一:创建一个窗口

任何 Microsoft Windows 程序执行中必须要作的第一件事就是创建一个应用程序窗口并将其显示给用户。为做到这点,CreateDevice 例程将首先实现它的 WinMain 函数。以下示例代码完成了窗口的初始化。 

INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT ) 



// Register the window class. 

WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, 

GetModuleHandle(NULL), NULL, NULL, NULL, NULL, 

“D3D Tutorial”, NULL }; 

RegisterClassEx( &wc ); 


// Create the application’s window. 

HWND hWnd = CreateWindow( “D3D Tutorial”, “D3D Tutorial 01: CreateDevice”, 

WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, 

GetDesktopWindow(), NULL, wc.hInstance, NULL ); 


前述示例代码是标准的 Windows 编程。例子开始时定义和注册了一个窗口类名为 “D3D Tutorial”。类注册以后,示例代码使用已注册的类创建了一个基本的顶层(top-level)窗口,客户区域为 300 像素宽,300 像数高。这个窗口没有菜单或子窗口。示例使用了 WS_OVERLAPPEDWINDOW 属性创建一个包括最大化,最小化,以及关闭按钮的普通窗口。(如果该例程将运行在全屏模式下,首选的窗口属性应该是WS_EX_TOPMOST,它指定创建的窗口置于并且保持在所有非最高(non-topmost)窗口之前,甚至在窗口失活的情况下。)一旦窗口创建完成,例代码调用标准的 Microsoft Win32 函数显示和更新窗口。 


在应用程序窗口准备好以后,你就能开始设置具体的 Microsoft Direct3D 对象了, 

请见:步骤二:初始化 Direct3D 


步骤二:初始化 Direct3D 

CreateDevice 示例在 WinMain 中创建窗口之后,调用该程序定义的函数 InitD3D 完成 Microsoft Direct3D 初始化过程。在创建窗口之后,程序已经准备好初始化你将用来绘制场景的 Direct3D 对象了。这个过程包括创建一个 Direct3D 对象,设置Present Parameters,以及最后创建 Direct3D 设备。 

创建完 Direct3D 对象之后,你可以立即使用 IDirect3D8::CreateDevice 方法创建 Direct3D 设备。你也能够使用 Direct3D 对象枚举设备,类型,模式以及其他东西。这些工作的代码段应位于使用 Direct3DCreate8 函数创建 Direct3D 对象之后。 

if( NULL == ( g_pD3D = Direct3DCreate8( D3D_SDK_VERSION ) ) ) 

return E_FAIL; 


传递给 Direct3DCreate8 的唯一参数应该始终是 D3D_SDK_VERSION,它告诉 Direct3D 当前使用的头文件信息。无论如何,头文件或者其他的变化将导致这个值增加并强制使用该值的应用程序重新编译。如果此版本不匹配,调用 Direct3DCreate8 将失败。 


下一个步骤是使用 IDirect3D8::GetAdapterDisplayMode 接口找到当前的显示模式,代码如下: 


D3DDISPLAYMODE d3ddm; 

if( FAILED( g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) ) 

return E_FAIL; 


D3DDISPLAYMODE 结构中的 Format 变量将被用于创建 Direct3D 设备。如果是运行于窗口模式下的话,Format 参数通常用来创建一个与适配器当前模式相匹配的后背缓冲 (Back buffer)。 


在给 D3DPRESENT_PARAMETERS 各参数赋值时,你必须指定你的应用程序在3D下工作的方式。本 CreateDevice 例程设置D3DPRESENT_PARAMETERS结构中 Windowed 为 TRUE,SwapEffect 为 D3DSWAPEFFECT_DISCARD,BackBufferFormat 为 d3ddm.Format。 


D3DPRESENT_PARAMETERS d3dpp; 

ZeroMemory( &d3dpp, sizeof(d3dpp) ); 

d3dpp.Windowed = TRUE; 

d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; 

d3dpp.BackBufferFormat = d3ddm.Format; 


最后一步,是利用 IDirect3D8::CreateDevice 函数创建 Direct3D 设备,代码如下: 

if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, 

D3DCREATE_SOFTWARE_VERTEXPROCESSING, 

&d3dpp, &g_pd3dDevice ) ) ) 


前述代码使用 D3DADAPTER_DEFAULT 标志创建了一个使用省缺适配器的设备。在非常多数的情况下,系统只有一个适配器,除非它安装了多个图形加速卡。通过把 DeviceType 参数设成 D3DDEVTYPE_HAL,表示你希望获得一个实际硬件设备 (hardware device) 而不是软件虚拟设备 (software device)。示例代码还使用 D3DCREATE_SOFTWARE_VERTEXPROCESSING 标志通知系统使用软件顶点处理 (software vertex processing)。注意,如果你指定 D3DCREATE_HARDWARE_VERTEXPROCESSING 标志通知系统使用硬件顶点处理 (hardware vertex processing),你可以在支持硬件顶点处理的图形加速卡上得到大幅度的性能提升。 


现在 Direct3D 已经初始化完毕,下一步是确保你的程序具有一个机制用来来处理系统消息, 

见下文:步骤三:处理系统消息 


步骤三:处理系统消息 

完成创建程序窗口以及初始化 Direct3D 以后,你已经准备好绘制场景 (Render scene)。大多数情况下,Microsoft Windows 程序在它们的消息循环里监视系统消息,并且在队列里没有消息时绘制画面帧。然而,CreateDevice 例程仅仅在等到一个WM_PAINT出现在队列里时,才通知应用程序重绘窗口的所有部分。 

// The message loop. 

MSG msg; 

while( GetMessage( &msg, NULL, 0, 0 ) ) 



TranslateMessage( &msg ); 

DispatchMessage( &msg ); 



当每循环一次,DispatchMessage 调用 MsgProc,后者负责处理队列里的消息,当 WM_PAINT 消息进队时,调用该程序自身定义的函数 Render(),它将负责重绘窗口。然后 Microsoft Win32 函数 ValidateRect 执行并将整个客户区域设为有效。 

消息处理函数的例代码如下: 

LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) 



switch( msg ) 



case WM_DESTROY: 

PostQuitMessage( 0 ); 

return 0; 

case WM_PAINT: 

Render(); 

ValidateRect( hWnd, NULL ); 

return 0; 



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




现在,应用程序处理了系统消息,接着的一步是绘制显示,见:步骤四:绘制与显示场景 

步骤四:绘制与显示场景 

为了描绘和显示需要的场景,本例程在这一步把后背缓冲 (back buffer) 填充为蓝色,然后将此后背缓冲的内容传给前景缓冲 (front buffer), 并且将前景缓冲提交至屏幕。 

清除表面,应调用 IDirect3DDevice8::Clear 函数: 

// Clear the back buffer to a blue color 

g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 ); 

Clear() 接受的前两个参数通知 Microsoft Direct3D 被清除的矩形区域数组的基址和大小,该矩形区域数组描述了绘制目标表面 (render target surface) 里需要清除的区域。 

在大多数情况下,只使用单个矩形覆盖整个绘制目标表面。这样你只需设置第一个参数为 0 及第二个参数为 NULL。第三个参数将决定方法的行为,你可以通过设置特定的标志用来清除绘制目标表面 (render target surface),关联的Z缓冲 (associated depth buffer),模版缓冲 (stencil buffer),以及任意这三者的混合。本指南不使用Z缓冲,所以仅仅使用了 D3DCLEAR_TARGET 标志。最后三个参数分别用于设置对应绘制目标表面、Z缓冲和模版缓冲的清除填充值 (reflect clearing values)。该 CreateDevice 例程将绘制目的表面的清除填充色设置为蓝色 (D3DCOLOR_XRGB(0,0,255)。由于相应的标志没有设置,最后两个参数被 Clear() 忽略。 

在清除了视口 (viewport) 之后,CreateDevice 例程告知 Direct3D 绘图将要开始,然后立即通知这次绘制完成,见以下代码段: 

// Begin the scene. 

g_pd3dDevice->BeginScene(); 

// Rendering of scene objects happens here. 

// End the scene. 

g_pd3dDevice->EndScene(); 

当绘制开始或完成时,IDirect3DDevice8::BeginScene 和 IDirect3DDevice8::EndScene 函数将用信号通知系统。你只能在这两函数之间调用其它的绘图函数。即使调用绘图函数失败,你也应该在重新调用 BeginScene 之前调用 EndScene。 

绘制完之后,调用 IDirect3DDevice8::Present显示该场景: 

g_pd3dDevice->Present( NULL, NULL, NULL, NULL ); 

Present() 接受的前两个参数是原始矩形和目标矩形。在这一步,例程设置这两个参数为 NULL 并把整个后备缓冲提交到前景缓冲。第三个参数用于设置该次提交的目标窗口。因为这个参数被设为 NULL,实际使用的窗口是 D3DPRESENT_PARAMETERS 的 hWndDeviceWindow 成员。第四个是 DirtyRegion 参数,在绝大多数情况下应该设为 NULL。 

本指南的最终步骤是关闭应用程序,见:步骤五:关闭与清除 

步骤五:关闭与清除 

在执行的若干时刻,你的应用程序必须立即关闭。关闭一个 Direct3D 应用程序中不只是意味着你必须销毁程序窗口,并且你还要释放程序中使用过的的任何 Direct3D 对象并且无效化它们的指针。当收到一个 WM_DESTROY 消息时,CreateDevice 例程通过调用一个本地定义的函数 Cleanup() 来处理这些工作。 

VOID Cleanup() 



if( g_pd3dDevice != NULL) 

g_pd3dDevice->Release(); 

if( g_pD3D != NULL) 

g_pD3D->Release(); 



上述函数对每个对象调用 IUnknown::Release 方法来释放它们自身。由于DirectX遵循 COM 规则,大多数对象当其引用计数降为0时,DirectX会自动的从内存中释放这个对象。 

对于其他关闭程序情况,可能发生在程序的平常执行中——比如用户改变了桌面的参数或色深——此时你可能需要撤销和重建使用中的 Microsoft Direct3D 对象。因此一个好的主意就是将你的释放代码放到一起,以便能在需要时随时调用它。 

本指南已经说明了如何创建一个设备,指南二:演示顶点(Render Vertex) ,将告诉你如何用顶点(Vertex)创建几何形体。 

指南二:演示顶点(Render Vertex) 

Microsoft Direct3D 写的应用程序使用顶点(Vertex)构造几何物体。每一个三维空间 (3D) 场景包括一个或几个这样的几何物体。Vertices 例程构造简单的物体,一个三角形,并且将它绘制到显示屏上。 

本指南说明如何采用以下步骤从顶点构造一个三角形: 


·第一步:定义一个自定义顶点类型 

·第二步:设置顶点缓冲 

·第三步:绘制至显示屏 

注意:Vertices 示例程序的路径为: 

(SDK root)\Samples\Multimedia\Direct3D\Tutorials\Tut02_Vertices. 

Vertices 程序的示例代码与 CreateDevice 的代码大部分相同。本“演示顶点(Render Vertex)”指南仅仅关注于那些独特的,关于顶点的代码而不包括初始化 Direct3D,处理 Microsoft Windows 消息,绘图,与清理等工作。如要得到有关这些任务的信息,请参考 指南一:创建设备。 

第一步:定义一个自定义顶点类型 

Vertices 例程使用三个顶点构造一个 2D 的三角形。这里提及了顶点缓冲的概念,这是用于保存和演示大量顶点的 Microsoft Direct3D 对象。通过指定一个自定义的顶点结构和相应的可变向量格式 (FVF),顶点能够采用很多方法定义。本 Vertices 例程使用的顶点格式定义于以下代码片断中。 

struct CUSTOMVERTEX 



FLOAT x, y, z, rhw; // The transformed position for the vertex. 

DWORD color; // The vertex color. 

}; 

上面的结构体说明了自定义顶点类型的格式。下一步是定义 FVF 以描述顶点缓冲区中的顶点内容。以下代码片段定义了一个 FVF 并符合此上建立的自定义顶点类型。 

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE) 

可变顶点格式标记描述了使用中的自定义顶点类型。前述示例代码使用了 D3DFVF_XYZRHW 和 D3DFVF_DIFFUSE 标志,这将告诉顶点缓冲,自定义顶点类型包含一组转换过的点坐标并紧跟着一个颜色参数。 

现在自定义向量格式和 FVF 已经被指定好了,下一步将使用顶点填充顶点缓冲区,请参看:第二步:设置顶点缓冲 。 

注意:Vertices 例程中的顶点是转换过的。用另一句话说,它们已经在 2D 窗口坐标系下。这意味着座标点 (0,0) 位于左上角,且正的 x 半轴向右,正的 y 半轴向下。这些顶点同样也是光照过的,这说明它们的着色不通过 Direct3D 照明而由它们自己的颜色代替。 

第二步:设置顶点缓冲 

现在自定义顶点格式已经完成,初始化顶点的时候到了。 Vertices 例程创建了必需的 Microsoft Direct3D 对象之后调用本程序内部定义的函数 InitVB() 进行这个工作。以下代码段将初始化三个自定义顶点的值。 

CUSTOMVERTEX g_Vertices[] = 



{ 150.0f, 50.0f, 0.5f, 1.0f, 0xffff0000, }, // x, y, z, rhw, color 

{ 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00, }, 

{ 50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff, }, 

}; 

前述代码片段采用三角形的三个顶点填充三个Vertex并指定了每个顶点的散射光的颜色。第一个顶点位于 (150,50) ,散射红色 (0xffff0000)。第二个顶点位于 (250,250) ,为绿色 (0xff00ff00)。第三点位于 (50,250) 并散射蓝绿色 (0xff00ffff)。每一点都具有相同的 0.5 Z值及 1.0 的 RHW 参数。关于这些矢量格式的其它信息见 SDK: Transformed and Lit Vertices。 

下一步将调用 IDirect3DDevice8::CreateVertexBuffer 创建顶点缓冲区,如以下代码段所示: 

if( FAILED( g_pd3dDevice->CreateVertexBuffer( 3sizeof(CUSTOMVERTEX), 

0 /
Usage /, D3DFVF_CUSTOMVERTEX, 

D3DPOOL_DEFAULT, &g_pVB ) ) ) 

return E_FAIL; 

CreateVertexBuffer 的头两个参数告诉 Direct3D 新顶点缓冲区预计的大小和用法。紧跟的两个参数指定新缓冲区的矢量格式及存储位置。这里的向量格式是 D3DFVF_CUSTOMVERTEX,就是例程先前定义的 FVF 值。D3DPOOL_DEFAULT 标记告诉 Direct3D 在最合适的位置创建此顶点缓冲区。最后一个参数返回创建完成的顶点缓冲区对象地址。 

创建了顶点缓冲区之后,如以下代码段所示,开始采用自定义格式的顶点填充缓冲区中的数据。 

VOID
pVertices; 

if( FAILED( g_pVB->Lock( 0, sizeof(g_Vertices), (BYTE**)&pVertices, 0 ) ) ) 

return E_FAIL; 

memcpy( pVertices, g_Vertices, sizeof(g_Vertices) ); 

g_pVB->Unlock(); 


首先调用 IDirect3DVertexBuffer8::Lock 锁定顶点缓冲区。函数第一个参数是锁定顶点数据的偏移量,按字节计算。第二个参数是需锁定的顶点数据长度,同样按字节计算。第三个参数是一个 BYTE 类型指针的地址,用于返回指向顶点数据的地址。第四个参数告知顶点缓冲区如何锁定数据。 

通过使用 memcpy,顶点被复制到顶点缓冲区里。将顶点放入缓冲区之后,调用一次 IDirect3DVertexBuffer8::Unlock 以解锁顶点缓冲区。这个锁定——解锁机制是必需的,因为正在使用的顶点缓冲区可能位于设备内存中。 

现在顶点缓冲区已经填入顶点,绘制到显示的时候到了,见描述:第三步:绘制至显示屏 。 

第三步:绘制至显示屏 

现在缓冲区已经填入顶点,现在需要把它绘制到显示屏上。在绘制到屏幕之前,先将背景清除为蓝色并调用 BeginScene。 

g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0L ); 

g_pd3dDevice->BeginScene(); 

从顶点缓冲区绘制顶点数据需要一些步骤。首先,你需要设置流数据源;在当前情况下,使用第 0 个流 。流的数据源是通过调用 IDirect3DDevice8::SetStreamSource 设置的。 

g_pd3dDevice->SetStreamSource( 0, g_pVB, sizeof(CUSTOMVERTEX) ); 

SetStreamSource 的第一个参数告诉 Microsoft Direct3D 设备设置数据流的索引。第二个参数是绑定在该数据流上的顶点缓冲区。第三个参数是数据单元的大小,用字节数表示。在上面的示例代码中,将使用CUSTOMVERTEX 的大小作为数据单元的大小。 

下一步通过调用 IDirect3DDevice8::SetVertexShader 使 Direct3D 了解使用中的顶点处理器(Vertex Shader)。就整体而言,自定义顶点处理器是一种高级的话题,但是在绝大多数情况下顶点处理器仅仅等于 FVF 代码。这能够让 Direct3D 知道处理中的顶点类型。以下代码片段将FVF设置为当前顶点处理器: 

g_pd3dDevice->SetVertexShader( D3DFVF_CUSTOMVERTEX ); 

SetVertexShader() 唯一的参数是当前设置的顶点处理器的句柄。这个参数的值可以是从IDirect3DDevice8::CreateVertexShader 返回的句柄,或者是 FVF 代码。在这儿,使用的参数是定义为 D3DFVF_CUSTOMVERTEX 的 FVF 代码。 

关于顶点处理器的更多信息,请见 SDK: Vertex Shader 一章。 

下一步使用 IDirect3DDevice8::DrawPrimitive 绘制顶点缓冲区中的顶点,见以下代码片段: 

g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 ); 


DrawPrimitive 接受的第一个参数是一个标记,它通知 Direct3D 绘制哪种类型的物件(Primitive)。本例程使用 D3DPT_TRIANGLELIST 标记指定为三角形序列。第二个参数是第一个顶点的索引。第三个参数通知绘制的物件的数目。本例子只画一个三角形,这个值为 1。 

关于不同种类物件的更多信息,可见 SDK: 3-D Primitive 

最后的一步是结束场景并立即将后背缓冲提交为前景缓冲。这些写在以下代码片段中: 

g_pd3dDevice->EndScene(); 

g_pd3dDevice->Present( NULL, NULL, NULL, NULL ); 

当后背缓冲被提交为前景缓冲后,客户窗口将显示出一个三个点颜色各异的三角形。 

本指南已经指导你如何使用顶点构造几何外形了。指南三:使用矩阵 将介绍矩阵的概念以及如何使用它们。 


指南三:使用矩阵 

本指南介绍矩阵的概念及演示如何使用它们。Vertices 例程通过呈递2D的顶点画出了一个三角形。然而,在这个指南中,你将通过顶点变换在 3-D 环境下工作。矩阵和变换也同样用于设置摄影头与视口(Viewport)。 

在 Matrices 例程呈递几何物体之前,它调用程序自定义函数 SetupMatrices 创建并设置用于演示 3-D 三角形的矩阵变换。作为代表,三种类型的变换同时被设置到一个 3-D 场景。创建这些典型变换的步骤如下表: 

·第一步:定义世界变换矩阵 

·第二步:定义观察变换矩阵 

·第三步:定义映射变换矩阵 

注意:Matrices 示例程序的路径为: 

(SDK root)\Samples\Multimedia\Direct3D\Tutorials\Tut03_Matrices. 


创建这三种变换的顺序并不影响场景元素的输出。无论如何,Direct3D 都使用以下顺序依次将矩阵作用于场景:(1) 世界,(2) 观察,(3) 映射。 

Matrices 工程的示例代码几乎与 Vertices 工程的代码相同。该“使用矩阵”指南仅仅关注那些有关矩阵的独特代码,而不重复初始化 Direct3D,处理 Microsoft Windows 消息,演示,以及清除。关于这些工作的信息,请见 指南一:创建设备 。 

本指南使用自定义顶点格式和单个顶点缓冲区呈递几何模型,关于更多的有关选择自定义顶点类型以及执行顶点缓冲区的信息,见 指南二:演示顶点 。 

第一步:定义世界变换矩阵(World Transformation Matrix) 

世界变换矩阵定义了怎样转换、缩放、以及旋转 3-D 模拟空间中的几何物体。 

以下代码片段为 Microsoft Direct3D 设备设置当前的世界变换并且使三角形绕 y-轴 旋转。 

D3DXMATRIX matWorld; 

D3DXMatrixRotationY( &matWorld, timeGetTime()/150.0f ); 

g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); 

第一步是通过调用 D3DXMatrixRotationY 函数使三角形绕 y-轴 旋转。函数第一个参数是指向 D3DMATRIX 结构的指针用于返回操作结果。第二个参数是以弧度表示的旋转角度。 


下一步是调用 IDirect3DDevice8::SetTransform 给 Direct3D 设备设置世界变换。SetTransform 接受的第一个参数通知 Direct3D 被设置的是哪个转换。这个例子用 D3DTS_WORLD 宏指定被设置的是世界变换。第二个参数是一个指向被设为当前变换之矩阵的指针。 


关于世界变换的更多信息,见:SDK: World Transformation 


定义完场景的世界变换后,你可以准备观察变换矩阵了。再一次请注意:定义任一变换的顺序不是关键。无论如何,Direct3D 采用以下顺序将这些矩阵作用于场景:(1) 世界,(2) 观察,(3) 映射。 


定义观察变换矩阵请参看 第二步:定义观察变换矩阵 


第二步:定义观察变换矩阵(View Transformation Matrix) 


观察变换矩阵定义了观察的位置和旋转角度。此观察矩阵就相当于场景的摄影机。 


以下代码片段创建了一个观察变换矩阵并将其设置为 Microsoft Direct3D 设备的当前观察矩阵。 

D3DXMATRIX matView; 

D3DXMatrixLookAtLH( &matView, &D3DXVECTOR3( 0.0f, 3.0f,-5.0f ), 

&D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), 

&D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) ); 

g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); 


第一步是通过调用 D3DXMatrixLookAtLH 定义观察矩阵。第一个参数是一个指向 D3DXMATRIX 结构的指针,用来接受操作结果。第二、三、四个参数定义了观察点、注视点、以及方向“上”。这儿设置观察点为沿 Z-轴 反方向 5 单位再往上 3 单位,注视点为原点,以及作为“上”的方向为 Y-轴。 

下一步是调用 IDirect3DDevice8::SetTransform 给 Direct3D 设备设置观察矩阵。SetTransform 接受的第一个参数通知 Direct3D 哪一个变换将要被设置。该例程使用 D3DTS_VIEW 标记指定为观察矩阵。第二个参数是一个指向矩阵的指针,它被设为当前的变换。 


关于观察矩阵的更多信息,见:SDK: View Transformation 


定义了场景的世界变换后,你可以开始准备映射变换矩阵了。再一次提醒,定义每一变换的顺序不是关键性的。无论如何,Direct3D 总是采用以下顺序将矩阵应用于场景:(1) 世界,(2) 观察,(3) 映射。 

定义映射变换矩阵的工作被描述在 第三步:定义映射变换矩阵 


第三步:定义映射变换矩阵(Projection Transformation Matrix) 

映射变换矩阵定义了将 3-D 观察空间转换为 2-D 视口空间的几何学方法。 

以下代码片段创建映射变换矩阵并将其设为 Microsoft Direct3D 设备的当前映射变换。 

D3DXMATRIX matProj; 

D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f ); 

g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); 


第一步是调用 D3DXMatrixPerspectiveFovLH 设置映射矩阵。函数第一个参数是一个指向 D3DXMATRIX 的结构,用于接受操作的结果。第二个参数定义视场,它说明物体如何随着距离而缩小。一个典型的视场是 1/4 π,就像这个例子使用的一样。第三个参数定义了屏幕纵横比。本示例采用典型的纵横比 1。第四和第五个参数定义最近和最远剪切平面。这是用于确定位于何种距离之外的几何物体无需再绘制。本 Matrices 示例设置它的最近剪切平面为 1,最远剪切平面为 100。 


下一步是调用 IDirect3DDevice8::SetTransfrom 对 Direct3D 应用变换。SetTransfrom 接受的第一个参数通知 Direct3D 何种变换被设置。本例程使用 D3DTS_PROJECTION 标志指定映射变换将被设置。第二个参数是一个指向矩阵的指针,它将被设置为当前的变换。 

关于映射变换的更多信息,参见:“映射变换” 

本指南已经提示你如何使用矩阵。指南四:创建和使用光源 将揭示如何在你的场景中添加光源以增加真实性。 

指南四:创建和使用光源 

Microsoft Direc3D 光照系统给 3-D 物体提供更多的真实性。当使用它时,每个场景中的几何对象将被照亮,基于它们的位置和使用的光源类型。这个指南的例程将介绍关于光照和材质的主题。 

本指南包含以下步骤用于创建材质与光照: 


·第一步:创始化场景几何 

·第二步:设置材置与光照 

注意:Lights 示例程序的路径为: 

(SDK root)\Samples\Multimedia\Direct3D\Tutorials\Tut04_Lights. 

注意:Lights 例程中的代码和 Matrices 例程的代码几乎完全一样。“创建和使用光源”指南仅仅关注于有关创建和使用光照的独特代码,而并不重复有关设置 Direct3D,处理 Microsoft Windows 消息,绘制,或者清理的内容。关于这些任务的其他信息,见:指南一:创建设备。 

本指南使用自定义顶点和顶点缓冲区呈递几何形体。关于选择一个自定义顶点格式并执行顶点缓冲的更多信息,见:指南二:演示顶点。 

本指南采用矩阵变换几何对象。关于矩阵和变换的更多信息,参见:指南三:使用矩阵。 

第一步:创始化场景几何 


使用光照的一个前提是每个表面都应该有法向量。为此,Lights 例程使用一个稍微不同的自定义顶点格式,新的自定义顶点格式具有一个 3-D 位置坐标和一个表面法向量。这个表面法向量被用于 Microsoft Direct3D 光照计算的核心。 

struct CUSTOMVERTEX 



D3DXVECTOR3 position; // The 3-D position for the vertex. 

D3DXVECTOR3 normal; // The surface normal for the vertex. 

}; 


// Custom FVF. 

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL) 

现在适当的矢量格式定义好了,Lights 例程调用 InitGeometry(),一个程序自定义的函数以创建一个圆柱体。最初的步骤是创建一个顶点缓冲区并用它保存这个圆柱体的各点,如以下例代码所示: 

// Create the vertex buffer. 

if( FAILED( g_pd3dDevice->CreateVertexBuffer( 502sizeof(CUSTOMVERTEX), 

0 /* Usage /, D3DFVF_CUSTOMVERTEX, 

D3DPOOL_DEFAULT, &g_pVB ) ) ) 

return E_FAIL; 


下一步是使用圆柱体的顶点填充顶点缓冲区。注意下面的示例代码,每个点都被定义了一个位置和一个法向量。 


for( DWORD i=0; i<50; i++ ) 



FLOAT theta = (2
D3DX_PIi)/(50-1); 

pVertices[2
i+0].position = D3DXVECTOR3( sinf(theta),-1.0f, cosf(theta) ); 

pVertices[2i+0].normal = D3DXVECTOR3( sinf(theta), 0.0f, cosf(theta) ); 

pVertices[2
i+1].position = D3DXVECTOR3( sinf(theta), 1.0f, cosf(theta) ); 

pVertices[2i+1].normal = D3DXVECTOR3( sinf(theta), 0.0f, cosf(theta) ); 




在前述例程使用圆柱体顶点填充了顶点缓冲区之后,这个顶点缓冲区已经准备好用于呈递了。但是首先,这个场景的材质与光照必须在绘制圆柱体之前被设置。这些描述在 第二步:设置材质与光照。 


第二步:设置材质与光照 


为了在 Microsoft Direct3D 中使用光照,你必须创建一个或多个光源。为了确定一个几何物体放射何种颜色的光线,材质必须被创建于绘制几何对象。在绘制这个场景之前,Lights 例程调用 SetupLights,一个程序自定义函数来设置材质和一个方向性光源。 


创建一种材质 


材质被定义为当一束光照到几何物体表面后,反射出的颜色。以下代码片段使用 D3DMATERIAL8 结构来创建一个黄色的材质。 


D3DMATERIAL8 mtrl; 

ZeroMemory( &mtrl, sizeof(D3DMATERIAL8) ); 

mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f; 

mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f; 

mtrl.Diffuse.b = mtrl.Ambient.b = 0.0f; 

mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f; 

g_pd3dDevice->SetMaterial( &mtrl ); 


这个材质的漫射光颜色与环境光颜色都被设为黄色。对 IDirect3DDevice8::SetMaterial 函数的调用将应用此材质到用于绘制场景的 Microsoft Direct3D 设备。SetMaterial() 接受的唯一参数是设置材质的指针。在这个调用完成以后,每个物件都将使用这个材质绘制直到另一次对 SetMaterial 的调用指定了一个不同的材质为止。 


现在材质已经被应用到场景,下一个步骤是创建光源。 


创建一个光源 

Microsoft Direct3D 里有三种可用的光源:点光源,方向形光源,与聚光灯光源。本示例代码创建一个方向形光源,它向一个方向发光,并且不停的变换发光的方向。 

下列代码片段使用 D3DLIGHT8 结构创建一个方向性光源。 


D3DXVECTOR3 vecDir; 

D3DLIGHT8 light; 

ZeroMemory( &light, sizeof(D3DLIGHT8) ); 

light.Type = D3DLIGHT_DIRECTIONAL; 


下列代码片设置光源的漫射光为白色。 

light.Diffuse.r = 1.0f; 

light.Diffuse.g = 1.0f; 

light.Diffuse.b = 1.0f; 


以下代码片在一个环内旋转光源的方向。 

vecDir = D3DXVECTOR3(cosf(timeGetTime()/360.0f), 

0.0f, 

sinf(timeGetTime()/360.0f) ); 

D3DXVec3Normalize( (D3DXVECTOR3
)&light.Direction, &vecDir ); 

对 D3DXVec3Normalize 函数的调用将归一化方向矢量并初始化光源的方向。 

可以设置一个范围告诉 Direct3D 此光源能影响多远的距离。这个成员参数对方向性光源无效。以下代码片指定此光源的范围为 1000 单位。 


light.Range = 1000.0f; 


下面的代码片将这个光源分配到当前的 Direct3D 设备,通过调用 IDirect3DDevice8::SetLight。 


g_pd3dDevice->SetLight( 0, &light ); 


SetLight 接受的第一个参数是此光源被分配的索引号。注意如果在此索引已存在一个光源,它将被新光源覆盖。第二个参数是一个指向新定义光源数据结构的指针。本 Lights 例程设置这个光源位于 0 号索引。 


下列代码片激活这个光源,通过调用 IDirect3DDevice8::LightEnable。 


g_pd3dDevice->LightEnable( 0, TRUE); 


LightEnable 接受的第一个参数是激活光源的索引。第二个参数是一个布尔量通知此光源是开 (TRUE) 还是闭 (FALSE)。在上面的例程中,索引 0 上的光源被打开。 


以下代码片通知 Direct3D 呈递此光源,通过调用 IDirect3DDevice8::SetRenderState。 


g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE ); 


SetRenderState 接受的头两个参数是哪一个设备状态变量被改写以及写入何种值。本例程设置 D3DRS_LIGHTING 设备变量为 TRUE,这将使设备能够演示光照效果。 


本例程的最后一步是通过再一次调用 SetRenderState 打开环境照明光。 


g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x00202020 ); 


当前代码段设置 D3DRS_AMBIENT 设备变量为一种浅灰色 (0x00202020)。环境照明将使用所给的颜色照亮所有的物体。 


关于照明及材质的更多信息,参见 SDK: Lights and Materials。 


本例程向你说明了如何使用照明与材质。指南五:使用纹理映射 将向你说明如何将纹理添加到物体表面上。 



指南五:使用纹理映射 


尽管光照和材质大大增加了场景的真实感,但没有比在表面上添加纹理更能增加真实感的了。纹理能够被想象为一层紧紧包装在表面的贴纸。你能在一个立方体上放置一层木质纹理使它看起来就象用木头制成的一样。本 Texture 例程将在 指南四:创建和使用光照 中构造的圆柱上添加一幅类似香蕉的纹理。此指南介绍的内容包括如何载入纹理,设置纹理,与呈递带有纹理的物体。 


本指南采用以下步骤实现纹理: 


·第一步:定义一个定制顶点格式 

·第二步:初始化屏幕几何 

·第三步:演示场景 

注意:Texture 示例程序的路径为: 

(SDK root)\Samples\Multimedia\Direct3D\Tutorials\Tut05_Textures. 


注意:除了 Texture 示例不创建材质和光照以外,Texture 工程中的示例代码与 Lights 工程的几乎完全一样。本“使用纹理映射”指南仅仅关注于有关于纹理的独特代码,而并不重复有关初始化 Microsoft Direct3D,处理 Microsoft Windows 消息,演示,或清理的内容。关于这些工作的信息,见:指南一:创建设备。 

本指南使用自定义顶点和顶点缓冲区显示几何物体。关于选择一个自定义顶点格式并执行顶点缓冲的更多信息,见:指南二:演示顶点。 


本指南采用矩阵进行几何变换。关于矩阵和变换的更多信息,参见:指南三:使用矩阵。 


第一步:定义一个定制顶点格式 


在使用纹理以前,必须使用包含纹理坐标的自定义顶点格式。纹理坐标告诉 Microsoft Direct3D 在物件上如何将纹理定位于每个顶点上。纹理坐标范围从 0.0 到 1.0,(0.0, 0.0) 的位置代表纹理贴图的左上角而 (1.0, 1.0) 代表纹理贴图的右下角。 


以下示例代码说明了 Texture 例程是如何通过设置它的自定义顶点格式来包含纹理坐标的。 

struct CUSTOMVERTEX 



D3DXVECTOR3 position; // The position. 

D3DCOLOR color; // The color. 

FLOAT tu, tv; // The texture coordinates. 

}; 


// The custom FVF, which describes the custom vertex structure. 

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) 


关于纹理坐标的进一步信息,参见 SDK: Texture Coordinates 一章。 


现在自定义顶点格式已经准备好了,下一步将是载入一幅纹理并创建一个圆柱体,见 第二步:初始化屏幕几何。 


第二步:初始化屏幕几何 


在绘制之前,Texture 例程调用 InitGeometry,一个程序自定义的函数用于创建一幅纹理并初始化圆柱体的几何参数。 


纹理是由基于文件的图像构造的。以下示例代码使用 D3DXCreateTextureFromFile 从 Banana.bmp 文件创建一幅纹理并用它覆盖圆柱的表面。 


if( FAILED( D3DXCreateTextureFromFile( g_pd3dDevice, “Banana.bmp”, 

&g_pTexture ) ) ) 

return E_FAIL; 


D3DXCreateTextureFromFile 接受的第一个参数是一个指向 Microsoft Direct3D 设备的指针,这个设备将用于绘制纹理。第二个参数是一个指向 ANSI 字符串的指针,它指定用于创建纹理的文件名。本例程指定从此文件:“Banana.bmp” 来装载图像。第三个参数是一个指向纹理对象指针的地址。 

当这个类似香蕉的纹理被装载并准备好之后,下一个步骤是创建圆柱体。以下示例代码用一个圆柱体填充顶点缓冲区。注意每一点都具备了纹理坐标 (tu, tv)。 


for( DWORD i=0; i<50; i++ ) 



FLOAT theta = (2D3DX_PIi)/(50-1); 


pVertices[2i+0].position = D3DXVECTOR3( sinf(theta),-1.0, cosf(theta) ); 

pVertices[2
i+0].color = 0xffffffff; 

pVertices[2i+0].tu = ((FLOAT)i)/(50-1); 

pVertices[2
i+0].tv = 1.0f; 


pVertices[2i+1].position = D3DXVECTOR3( sinf(theta), 1.0, cosf(theta) ); 

pVertices[2
i+1].color = 0xff808080; 

pVertices[2i+1].tu = ((FLOAT)i)/(50-1); 

pVertices[2
i+1].tv = 0.0f; 





每一个顶点包括位置,颜色,以及纹理坐标。上面的例程给每一点设置了纹理坐标并使此纹理能够平滑的包裹在圆柱体周围。 


现在纹理和顶点缓冲区已经准备好用于演示了,现在能够呈递和着色图形了,参见 第三步:演示场景。 


第三步:演示场景 


在场景几何被初始化之后,应该是绘制场景的时候了。为了绘制一个带有纹理的物体,使用的纹理必须要设置成当前纹理中的一个。下一步将是设置纹存储器的状态。纹理存储器状态使你能够定义一个或者多个纹理被呈递的方式。比如说,你能将多个纹理混合在一起。 


现在 Texture 示例开始设置需要使用的纹理。以下代码段使用 IDirect3DDevice8::SetTexture 设置 Microsoft Direct3D 设备用于绘制的纹理。 


g_pd3dDevice->SetTexture( 0, g_pTexture ); 


SetTexture 接受的第一个参数是设置纹理存储器的标示符。一个设备能够支持八个已初始化的纹理,所以这儿的最大值是 7。本 Texture 示例仅仅使用一个纹理并且把它设置在存储器 0。第二个参数是一个指向纹理对象的指针。在这儿,Texture 示例使用由它的程序自定义函数 InitGeometry 创建的纹理。 



以下代码片设置纹理存储器状态的值,通过调用 IDirect3DDevice8::SetTextureStageState 方法。 


g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); 

g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); 

g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); 

g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); 



SetTextureState 的第一个参数是需要改变状态的存储器的索引。本示例代码改变位于存储器 0 的纹理,所以这儿置为 0。下一个参数是要设置的纹理状态。关于所有有效的纹理状态以及它们的意义,见 “SDK: D3DTEXTURESTAGESTATETYPE”。再下一个参数是设置为此纹理状态的参数。你放置这儿的值应取决于你要改变的纹理存储器状态。 


在设置完每个纹理存储器状态的合适值之后,这个圆柱体可以被呈递了,现在纹理将被添加在它的表面上。 


使用纹理坐标的其他方法是使它们自动的生成。这是用一种纹理坐标索引 (TCI) 实现的。TCI 使用一个纹理矩阵来变换 (x,y,z) TCI 坐标为 (tu, tv) 纹理坐标。在 Texture 例程中,位于摄像机空间中的顶点位置被用来产生纹理坐标。 


第一步是创建用于转换的矩阵,示范在以下代码片段中: 


D3DXMATRIX mat; 

mat._11 = 0.25f; mat._12 = 0.00f; mat._13 = 0.00f; mat._14 = 0.00f; 

mat._21 = 0.00f; mat._22 =-0.25f; mat._23 = 0.00f; mat._24 = 0.00f; 

mat._31 = 0.00f; mat._32 = 0.00f; mat._33 = 1.00f; mat._34 = 0.00f; 

mat._41 = 0.50f; mat._42 = 0.50f; mat._43 = 0.00f; mat._44 = 1.00f; 



在矩阵创建好之后,它必须通过调用 IDirect3DDevice8::SetTransform 来设置它,如以下代码段所示: 


g_pd3dDevice->SetTransform( D3DTS_TEXTURE0, &mat ); 


D3DTS_TEXTURE0 标志告诉 Direct3D 应用此变换到位于纹理存储器 0 的纹理。本示例的下一步是设置其他的存储器状态值,以得到所需的效果。这些处理在以下代码段中。 


g_pd3dDevice->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2 ); 

g_pd3dDevice->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION ); 


纹理坐标被设置后,现在此场景已准备好被呈递了。注意到现在的坐标是自动设置到圆柱上的。这样精确的设置使几何物体被演示时纹理好象是覆盖在绘制的屏幕上。 


关于纹理的更多信息,见 SDK: Texture 一章。 


本指南已经向你说明了如何给表面添加纹理。指南六:使用Mesh模型 将告诉你如何应用Mesh模型呈递复杂的几何形体。 



指南六:使用Mesh模型 


复杂的几何形状常常使用 3-D 建模软件构造模型并保存为文件。一个例子就是 .x 文件格式。Microsoft Direct3D 使用Mesh对象从文件装载这些物体。Mesh对象稍微有点复杂,但是 Microsoft Direct3DX 包含的函数使应用Mesh对象变的简单。Meshed 例程介绍关于Mesh的话题并展示如何装载,演示,以及卸载一个Mesh对象。 


本指南使用以下步骤说明如何装载,演示,以及卸载一个Mesh对象: 


·第一步:装载一个Mesh对象 

·第二步:演示一个Mesh对象 

·第三步:卸载一个Mesh对象 

注意:Methes 示例程序的路径为: 

(SDK root)\Samples\Multimedia\Direct3D\Tutorials\Tut06_Meshes. 


注意:除了 Meshes 工程中的示例代码不创建材值与光照以外,此工程的示例代码与 Lights 工程几乎完全一样。“使用Mesh模型”指南仅仅关注于有关于Mesh对象的独特代码,而并不重复有关设置 Microsoft Direct3D,处理 Microsoft Windows 消息,演示,或清理的工作。关于这些工作的信息,见:指南一:创建设备。 


本指南使用自定义顶点和顶点缓冲区显示几何物体。关于选择一个自定义顶点格式并执行顶点缓冲的更多信息,见:指南二:演示顶点。 


本指南采用矩阵进行几何变换。关于矩阵和变换的更多信息,参见:指南三:使用矩阵。 


本指南使用纹理覆盖Mesh模型的表面。关于装载和使用纹理的更多信息,参见:指南五:使用纹理映射。 


第一步:装载一个Mesh对象 


在使用之前,Microsoft Direct3D 应用程序必须先装载一个Mesh对象。Meshes 例程通过调用 InitGeometry,一个该程序自定义的函数,装载一只虎的Mesh模型,当然这是在已经装载了必需的 Direct3D 对象以后。 


一个Mesh对象需要用一个材质缓冲保存所有将要用到的材质与纹理。所以该函数最初定义了一个材质缓冲,如以下代码段所示: 


LPD3DXBUFFER pD3DXMtrlBuffer; 


以下代码段使用 D3DXLoadMethFromX 函数装载Mesh对象。 


// Load the mesh from the specified file. 

if( FAILED( D3DXLoadMeshFromX( “tiger.x”, D3DXMESH_SYSTEMMEM, 

g_pd3dDevice, NULL, 

&pD3DXMtrlBuffer, &g_dwNumMaterials, 

&g_pMesh ) ) ) 

return E_FAIL; 


D3DXLoadMeshFromX 接受的第一个参数是一个指向字符串的指针告诉 Microsoft Direct3D 要装载的文件。本例程从 Tiger.x 读取一只虎的Mesh模型。 


第二个参数通知 Direct3D 如何创建Mesh对象。本示例采用 D3DXMESH_SYSTEMMEM 标记,它等于同时指定 D3DXMESH_VB_SYSTEMMEM 与 D3DXMESH_IB_SYSTEMMEM,这两个参数告诉 Direct3D 把Mesh对象的索引缓冲区和顶点缓冲区都放到系统内存中。 


第三个参数是指向将被用于绘制Mesh对象的 Direct3D 设备的指针。 


第四个参数是一个指向 ID3DXBuffer 对象的指针。这个对象装入关于各个面相邻与否的信息。在本例程中此信息是不需要的,所以这个参数被设为 NULL。 


第五个参数同样取得一个指向 ID3DXBuffer 的指针。在函数执行完以后,此对象将被填入该Mesh对象使用的 D3DXMATERIAL 结构。 


第六个参数是一个指针,指向函数执行结束后,返回的置入 ppMaterials 队列中的 D3DXMATERIAL 结构数目。 


第七个参数是一个Mesh对象指针的地址,返回装载的Mesh对象。 


在装载了这个Mesh对象和相关材质信息之后,你需要从材质缓冲区中分解出材质属性及纹理名称。 


本 Mesh 例程先需要得到材质缓冲区指针才能处理这些事情。以下代码段使用 ID3DXBuffer::GetBufferPointer 函数得到这个指针。 


D3DXMATERIAL* d3dxMaterials = 

(D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer(); 


以下代码段创建了一个新的Mesh和纹理对象基于Mesh对象中材质的最大数目。 


g_pMeshMaterials = new D3DMATERIAL8[g_dwNumMaterials]; 

g_pMeshTextures = new LPDIRECT3DTEXTURE8[g_dwNumMaterials]; 


对于每个Mesh对象里的材质都必须进行以下步骤。 


第一步是拷贝材质,如以下代码段所示. 

g_pMeshMaterials[i] = d3dxMaterials[i].MatD3D; 


第二步是设置材值的环境色,见以下代码段。 


g_pMeshMaterials[i].Ambient = g_pMeshMaterials[i].Diffuse; 


最后一步是为该材质创建纹理,如以下代码段。 


// Create the texture. 

if( FAILED( D3DXCreateTextureFromFile( g_pd3dDevice, 

d3dxMaterials[i].pTextureFilename, 

&g_pMeshTextures[i] ) ) ) 

g_pMeshTextures[i] = NULL; 




装载了每个材质以后,你使用完毕了这个材质缓冲区,必须调用 IUnknown::Release 来释放它。 


pD3DXMtrlBuffer->Release(); 


现在,Mesh对象,连同其相应的材质与纹理都已经装载好了。这个Mesh物体已准备好呈递到屏幕上,参看 第二步:演示一个Mesh对象。 


第二步:演示一个Mesh对象 


在第一步中,Mesh对象已经准备号被呈递了。该对象被Mesh对象装载的每个材质分成若干个子集。为了绘制每个子集,应该在一个循环中绘制此Mesh对象。循环的第一步是为每个子集设置材质,如以下代码段所示: 


g_pd3dDevice->SetMaterial( &g_pMeshMaterials[i] ); 


循环的第二步是给每个子集设置纹理,如以下代码段所示。 


g_pd3dDevice->SetTexture( 0, g_pMeshTextures[i] ); 


在给每个子集设置完材质与纹理之后,子集被 ID3DXBaseMesh::DrawSubset 函数所绘制,如以下代码段所示。 


g_pMesh->DrawSubset( i ); 


DrawSubset 函数带有一个 DWORD 参数用于指定Mesh对象的哪个子集被绘制。本例程使这个参数的值每循环一次就被加一。 


在使用完Mesh对象之后,重要的事是要将此Mesh对象完全移出内存,参见 第三步:卸载一个Mesh对象。 


第三步:卸载一个Mesh对象 


在任何 Microsoft Direct3D 程序结束前,它有必要解构它使用的所有 DirectX 对象并且使指向它们的指针无效。本例程使用的Mesh对象同样需要被解构。当它接收到一个 WM_DESTROY 消息时,Meshes 例程调用 Cleanup,一个该程序自定义的函数,来处理此事。 


以下代码段删除材质队列。 


if( g_pMeshMaterials ) 

delete[] g_pMeshMaterials; 


以下代码解构每个装载过的单独纹理并删除纹理队列。 


if( g_pMeshTextures ) 



for( DWORD i = 0; i < g_dwNumMaterials; i++ ) 



if( g_pMeshTextures[i] ) 

g_pMeshTextures[i]->Release(); 



delete[] g_pMeshTextures; 


以下代码段解构Mesh对象。 


Delete the mesh object 

if( g_pMesh ) 

g_pMesh->Release(); 


本指南已经向你说明了如何装载和绘制Mesh对象。这是此区域最后一个指南。如果需要了解一个典型的 Direc3D 应用程序是如何写成的,见:”SDK: DirectX Graphics C/C++ Samples” 。