BIGWORLD 客户端编程指南03

BIGWORLD_05_Client Programming Guide_03

22. 3D Engine (Moo)

Moo是一个使用DirectX 9的3D引擎,提供了基于资源、对象和设备的服务,但不包括场景数据库。

基于资源的服务包括顶点缓冲区、索引缓冲区、顶点着色器、像素着色器和效果的生成和管理。纹理被自动压缩和存储。所有不受Direct3D管理的资源都由Moo管理,允许大量数据进出使用。

基于对象的服务包括完整的动画系统、带皮肤的两足动物、复合骨骼几何和专门的地形渲染。Moo公开了一个视觉的概念,一个重要的中层几何结构,它位于场景数据库之下,并大大简化了场景数据库代码。

基于设备的服务包括渲染状态的细节控制,封装在材料和着色器中。3D窗口被封装为一个RenderContext,并提供了渲染几何图形和存储照明状态的参考框架。

最后,Moo提供了基本的alpha混合排序三角形管道,不幸的是,Direct3D或(大多数)硬件仍然没有提供这种服务。

需要注意的是,在可能的情况下,Moo使用底层的Direct3DX结构,如D3DXMatrix,D3DXVector和D3DXQuaternion。DirectX团队不断改进这些数学类的实现,它们的功能经常利用处理器特定的指令,如MMX, SIMD和3DNow!利用这些工程师的努力是谨慎的。

22.1. Features

Moo的一些高级功能如下:

  • D3DXEffects的顶点和像素着色器支持
  • 立方环境地图
  • 渲染目标
  • 光照
  • 法线贴图/凹凸贴图
  • 地形
  • 动画
  • 定点渐变

以下部分描述了这些特性。

22.1.1. D3DXEffects vertex and pixel shader support

Moo中广泛使用效果文件。大多数渲染管道都是基于类EffectMaterial,它实现了一种管理效果使用的全局值的方法,以及每个实例的效果数据,如纹理、自照明值和镜面反射常数。

效果文件也使它更容易管理像素和顶点着色器,因为他们是一起创建在一个文件。使用D3DXEffect格式消除了向引擎添加新的着色器效果的复杂性,并允许快速创建新的视觉效果原型。

22.1.2. Cubic environment maps

立方体环境映射可以用于一些反射和标准化立方体映射(用于法线映射)。

22.1.3. Render targets

渲染目标用于创建广告牌纹理。它们还可以与GUI组件一起使用,在2D GUI中显示3D对象(例如,用于项目选择预览)。

22.1.4. Lighting

Moo的照明组件支持三种不同的灯:

  • Directional lights(平行光)
  • Point lights(点光源)
  • Spot lights(聚光灯)

任何物体都可以用两个方向灯、四个点灯和两个射灯来照明。这些灯是根据衰减度量来挑选的,所以通常会选择最近的灯。还需要注意的是,使用的灯光越少,光照顶点着色器的执行速度就越快。

22.1.4.1. Light maps

bigworld/src/lib/romp/light_map.cpp被一般化以同时支持flora和sky光贴图。光照贴图可以在XML文件中配置。

22.1.4.1.1. Flora light map

flora.xml中指定了flora.xml光照图的详细信息,它的位置在文件resources.xml中指定。

植物群灯光贴图的材质标签应该是system/materials/light_map。(关于该文件语法的详细信息,请参阅文件语法指南的.mfm)。

flora light贴图的默认宽度/高度是64x64。这是很小的,但仍然足够的分辨率投影到植物群,因为它的可见面积是100x100米。

05_11

22.1.4.1.2. Sky light map

天空灯光的细节在空间的sky.xml文件中指定,它的位置在空间的空间中指定。(关于该文件语法的详细信息,请参阅文件语法指南的space.setting)

天光贴图的材质标签应该是system/materials/sky_light_map。(关于该文件语法的详细信息,请参阅文件语法指南的.mfm)。

天光贴图的默认宽度/高度是512x512。这是相当大的,因为光地图是投射在整个可见世界(通常约1x1公里)。

05_12

天光贴图可以通过SKY_LIGHT_MAP图形设置来禁用

22.1.5. Normal mapping/bump mapping

法线贴图通过使用每个贴图的法线来添加物体的表面细节。Moo目前支持在动态照明的物体上进行高光和漫反射照明的法线贴图,以及在静态照明的物体上进行高光照明。当前版本的导出器只支持切线空间法线映射。

22.1.6. Terrain

Moo利用了支持的显卡的多纹理、顶点和像素着色能力。因为它使用了一个混合的4层纹理系统的地形,Moo利用硬件的四个纹理阶段的最佳,所以大多数地形可以在一个通道渲染。地形也支持来自定向光源的自阴影。

22.1.7. Animation

Moo支持基于节点和基于顶点(变形)的动画。Moo还可以混合动画一起提供他们之间的平滑过渡。

22.1.8. Vertex morphing

Moo的顶点变形适用于表面的小变化,如面部动画。变形目标从内容创建包中导出,并通过普通的动画系统进行控制。

请注意,由于变形目标应用于软件,大量使用将影响渲染性能。

22.2. Supported video cards

支持的显卡

22.3. Hardware requirements for special effects

下表列出了Moo中可用的特效及其硬件要求:

Special effect Vertex shader version Pixel shader version Texture stages required
Bloom 1.1 1.1 4
Cloud shadows 1.1 - 3
Flora 1.1 - 1
Heat Shimmer 1.1 - 1
Normal mapping 1.1 1.1 4
PyModel Shimmer 1.1 - 1
PyModel stipple 1.1 1.1 4
Entity shadows 2.0 2.0 1
Simulated sub-surface scattering 3.0 3.0 4
Sky Gradient dome 1.1 - 3
Terrian shadows 1.1 1.1 4
Terrain Specular 1.1 1.1 4

22.4. Visual

Visual类实现3D引擎绘制的基本可渲染对象。一个可视对象包含用于绘制对象的节点层次结构、顶点、索引和材质,它还包含用于碰撞检测的BSP树。当渲染对象时,节点层次结构为可渲染对象提供了一个参考框架。Visual使用EffectMaterial、Primitives和Vertices类来渲染其几何图形。

22.5. EffectMaterial

BigWorld 3D引擎中的材料系统广泛地使用了D3DXEffects文件

材料系统是通过类EffectMaterial实现的,它包含一个或多个D3DX效果,它们的覆盖值,以及关于碰撞属性和材料表面类型的信息。

材料系统背后的想法是给美工尽可能多的控制权,让他们在不涉及程序员的情况下试验新的渲染技术。

22.5.1. Format

EffectMaterial在磁盘上的格式非常简单,只有少数几个部分,大部分工作都是通过.fx文件完成的。

22.5.2. Automatic variables/Globals

自动变量用于由引擎控制的任何变量,如变换、灯光、相机位置等。自动变量使用变量语义向Effect文件公开。

在Effect文件中,自动变量的定义如下:

1
<type> <variableName> : <semantic>;
  • <type>

    变量类型(float, bool,纹理等)。

  • <veriableName>

    效果文件中变量的名称。

  • <semantic>

    公开给引擎的名称。

自动变量通过类EffectConstantValue连接到效果。可以通过覆盖EffectConstantValue接口、实现操作()以及使用EffectConstantValue::set或使用EffectConstantValue::get返回的句柄添加新类的实例来添加新的自动变量。

以下是Moo::EffectVisualContext设置的自动变量:

  • Ambient(float4)

    当前的环境色。

  • CamerPos(float3)

    当前相机在世界空间的位置。

  • CamerPosObjectSpace(float3)

    当前相机在物体空间中的位置。

  • DepthTex(trxtrue)

  • DirectionalLightCount (int)

  • DirectionalLights (DirectionalLights[2])

  • DirectionalLightsObjectSpace (DirectionalLights[2])

  • EnvironmentCubeMap(texture)

  • EnvironmentTransform(float4x4)

  • EnvironmentShadowTransform(float4x4)

  • FarPlane(float)

  • FloraAnimationGrid(float4[64])

  • FloraTexture(texture)

  • FogColour(float4)

  • FogEnd(float)

  • FogGradientTexture(texture)

  • FogStart(float)

  • FogTextureTransform(matrix)

  • GUIColour(float4)

  • LastViewProjection(float4x4)

  • InvView(float4x4)

  • InvViewProjection(float4x4)

  • MipFilter(int)

  • MinMagFilter(int)

  • MaxAnisotropy(int)

  • NearPlane(float)

  • NormalisationMap(texture)

  • ObjectID(float)

  • PenumbraSize(float)

  • PointLightCount(int)

  • PointLights(PointLights[4])

  • Screen(float4)

  • SkyBoxController(float4)

  • SpecularDirectionalLightCount(int)

  • SpecularDirectionalLights(DirectionLights[2])

  • SpecularDirectionalLightsObjectSpace(DirectionLights[2])

  • SpecularPointLightCount(int)

  • SpecularPointLights(PointLight[2])

  • SpecularPointLightsObjectSpace(PointLights[2])

  • SpotLightCount(int)

  • SpotLights(SpotLight[2])

  • SpotLightsObjectSpace(SpotLight[2])

  • StaticLighting(bool)

  • StippleMap(texture)

  • SunAngle(flaot)

  • TerrainTextureTransform(float4[2])

  • Time(float)

  • View(float4x4)

  • ViewProjection(float4x4)

  • WindAnimation(float4)

  • World(float4x4)

  • WorldPalette(float[17*3])

  • WorldView(flaot4x4)

  • WorldViewProjection(float4x4)

22.5.3. Artist-editable/tweakable variables

Artist-editable变量是被设计为在每个材质的基础上重写的变量。这些变量可以在世界编辑器和模型编辑器中编辑,允许您在可视化的同时实时更改模型的外观

通过将属性artist_Editable或worldBuilderEditable设置为true,可以将Artist-editable变量暴露给引擎。

将artistEditable设置为true将变量暴露给模型编辑器的材料设置面板,而将worldBuilderEditable设置为true将它暴露给模型编辑器,如果bigworld/tools/ worlddeditor /options.xml文件中objects/materialOverrideMode标签设置为1,则暴露给世界编辑器的属性面板。

以下是FX文件中这些变量的表示法:

1
2
3
4
5
<type> <variableName>
<
bool [artistEditable|worldBuilderEditable] = true;
... 1
> = <defaultValue>;
  • <type> — 对象的类型
  • <variableName> —效果文件中的变量名称
  • **<defaultValue>**—该变量的默认值。

目前,受支持的artist-editable变量的类型有:

  • bool
  • float
  • float4
  • float4x4
  • int
  • texture

在任意一个工具中暴露变量的条件如下:

  • 如果artistEditable在FX文件中设置为true

    Exposed in Model Editor?: Yes

    Exposed in World Editor?: No

  • 如果worldBuilderEditable在FX文件中设置为true

    • 如果在bigworld/tools/ worlddeditor /options.xml中materialOverrideMode设置为1

      Exposed in Model Editor?: No

      Exposed in World Editor?: No

    • 如果在bigworld/tools/ worlddeditor /options.xml中materialOverrideMode设置为0

      Exposed in Model Editor?: No

      Exposed in World Editor?: Yes

22.5.4. Multiple-layered effects per material

Moo::EffectMaterial类支持每个材质拥有多层D3DXEffects。

这对于需要使用标准顶点着色器的材质是有用的,但只对效果的像素着色器组件有轻微的改变。这些材质通常只会由资产转换器创建,因为Model Editor不支持为每个材质添加多个效果。

22.5.5. Recording material states

材质状态可以通过类Moo::EffectMaterial通过在材质的begin()和end()对之间调用recordPass()来记录。

这个方法返回一个Moo::StateRecorder,用于存储当前渲染状态以延迟渲染。此对象只在当前渲染循环结束前有效,并且在此之后将不再可用。

22.5.6. Using BigWorld .fx files with 3ds Max

BigWorld .fx文件和3ds Max .fx文件不幸的是不是100%兼容的。然而,可以创建一个可以在两个应用程序中使用的.fx文件。

为了暴露可编辑参数,BigWorld着色器使用下面的注释:

1
bool artistEditable = true;

而3ds Max需要下面的注释字符串:

1
UIName = "name"

示例效果文件bigworld/res/shaders/std_effects/normalmap.fx将其参数正确地暴露给BigWorld和3ds Max。输出器也输出3ds Max材质面板中输入的值。

文件normalmap.Fx还使用了一个单独的技术,称为max_preview,以及额外的顶点和像素着色器。

这是由于两个原因:

  • 在BigWorld引擎和3ds Max中应用照明没有统一的方法。
  • 3ds Max使用右手坐标系,而BigWorld使用左手坐标系。

这本身不是一个大问题,但这意味着如果你想在3ds Max中预览你的着色器,就需要额外的着色器工作。

如果你还没有应用servicepack 1for 3ds Max 7,一个问题是它的材质面板在处理效果文件中的#include指令时非常糟糕。发生的情况是,如果你保存一个应用了法线贴图效果的材质,那么它并不总是会在3ds Max中被正确加载,这可能会给美工带来额外的困惑。这个问题已经在Service Pack 1中修复了,所以如果你想在3ds Max中使用.fx文件,应用它是很重要的。

在你决定让你的.fx文件与3dsMax兼容之前,注意这些问题是很重要的。

22.6. Visual channels

视觉通道在Moo中实现了延迟渲染管道。

现在推行的标准渠道有五个:

  • Sorted channel(排序通道)
  • Internal sorted channel(内部排序通道)
  • Shimmer channel(微光通道)
  • Sorted shimmer channel(排序微光通道)
  • Distortion channel(失真通道)

通过覆盖Moo::VisualChannel接口并实现VisualChannel::addItem,应用程序可以创建任意数量的通道。

下面的小节描述了这些通道。

22.6.1. Sorted channel

排序通道用于需要以back-to-front顺序呈现的对象。

当可视性对象中的原语组添加到此通道时,其三角形不会在内部排序。

该通道主要用于添加对象,即目标混合为1的对象。

22.6.2. Internal sorted channel

内部排序通道用于需要back-to-front顺序呈现的对象,同时也需要back-to-front顺序呈现其三角形。

添加到此通道的对象也将根据已排序通道中的对象进行排序。

此通道对于alpha混合对象和任何没有目标混合对象的透明对象非常有用

22.6.3. Shimmer channel

微光通道用于需要热微光的物体。

添加到此通道的任何对象都应该只写入帧缓冲区的alpha通道。

22.6.4. Sorted shimmer channel

排序的微光通道用于需要热微光的物体,也需要绘制颜色信息。添加到此通道的任何对象都应同时写入帧缓冲区的alpha通道(对于闪烁量)和颜色通道。

22.6.5. Distortion channel

失真通道用于那些想要直接进入最终场景作为纹理的对象。这可以用来实现折射效果(例如,水)。

22.7. Textures

22.7.1. Texture detail levels/compression

纹理根据它们的文件名自动加载和压缩,文件名控制大小、格式转换和压缩级别。

关于该文件语法的详细信息,请参阅 File Grammar Guide’s section .texformat.

系统是通过类Moo::TextureDetailLevel实现的。纹理管理器存储一个纹理细节级别列表,并使用它们根据它们的文件名来调整和压缩纹理。仍然支持legacy .texformat系统。

TextureDetailLevel的属性分为两组:

  • 文件名匹配准则。
  • 转换规则。

属性的文件名匹配准则定义了用于检查当前TextureDetailLevel是否应用于正在检查的纹理的条件。它们的说明如下:

  • contains_

    匹配纹理文件名中包含的字符串。

  • postFixes_

    匹配纹理文件名后缀的字符串。

  • preFixes_

    匹配纹理文件名前缀的字符串。

每个字符串列表中只有一个字符串必须与纹理名称匹配,以便TextureDetailLevel认为它是匹配的。如果其中一个列表中没有字符串,也将被视为匹配。

属性的转换规则集定义了如何转换匹配的纹理。它们的说明如下:

  • compressedFormat_

    纹理转换的格式(当启用纹理压缩时)

  • format_

    纹理转换为的格式。

  • lodMode_

    定义纹理如何响应纹理质量设置。

    纹理质量通过TEXTURE_QUALITY图形设置设置。

  • maxDim_

    纹理的 最大宽度/高度 尺寸。

  • minDim_

    纹理的 最小宽度/高度 尺寸。

  • noResize_

    确定纹理不会被缩放为2的幂次,并且不会有mipmapping、compression或一个.dds版本。

  • reduceDim_

    禁用压缩时将纹理维度减半的次数。

纹理细节级别可以从数据中读取,如下面的例子所示:

1
2
3
4
5
6
7
8
9
<prefix> objects/ </prefix>
<postfix> tga </postfix>
<postfix> bmp </postfix>
<contains> norms </contains>
<contains> normals </contains>
<maxDim> 512 </maxDim>
<minDim> 128 </minDim>
<reduceDim> 1 </reduceDim>
<format> A8R8G8B8 </format>

上面的例子产生了从文件夹objects/的子树中加载的任何纹理,扩展名为.tga或.bmp,它包含字符串规范或法线,只要最小维度不低于128,它的维度就会减半。如果格式不同,它将被更改为带有alpha的32位颜色。

默认的详细级别是:

  • 文件名以规范结尾的任何文件。tga或norms.bmp将被转换为A8R8G8B8纹理

    (这样就不会压缩BigWorld的法线贴图)

  • 其他任何.tga文件都被转换为DXT3格式。

  • 其他任何.bmp文件都被转换为DXT1格式。

默认情况下,如果它们的尺寸大于2048,纹理才会被缩小。

22.7.2. Animated textures

Moo通过纹理交换支持简单的动画纹理。

当纹理管理器加载一个纹理时,它将查找与加载的文件同名,但扩展名为.texanim的文件。如果找到这样的文件,那么它将作为一个动画纹理加载。

.texanim文件是一个简单的XML文件,它引用了许多纹理,包含一个动画字符串和每秒帧数。

.texanim文件的格式如下:

1
2
3
 <frames> string </frames>
<fps> .f </fps>
+<texture> TEXTURE_RESOURCE </texture>

下面是一个.texanim文件的示例:

1
2
3
4
5
6
7
8
9
10
<frames> abcdefgh </frames>
<fps> 10.0 </fps>
<texture> maps/fx/fx_dirt01.tga </texture>
<texture> maps/fx/fx_dirt02.tga </texture>
<texture> maps/fx/fx_dirt03.tga </texture>
<texture> maps/fx/fx_dirt04.tga </texture>
<texture> maps/fx/fx_dirt05.tga </texture>
<texture> maps/fx/fx_dirt06.tga </texture>
<texture> maps/fx/fx_dirt07.tga </texture>
<texture> maps/fx/fx_dirt08.tga </texture>

在这种情况下,动画纹理将以每秒10帧的速度按顺序播放fx_dirt纹理。

您可以通过更改frames标签来更改帧回放的顺序。标签值中的a指的是XML文件中存储的第一个纹理,b指的是存储的第二个纹理,以此类推。

22.7.3. Applying a code-generated texture to a character

要将代码生成的纹理应用到模型中,请遵循以下步骤:

  1. 创建一个自动的.fx变量(例如customTexture)。
  2. 更新角色的着色器,以便使用新的.fx变量进行渲染,而不是使用diffuseMap属性。
  3. 你将需要一个单独的TextureSetter,它是一个Moo::EffectConstantValue。这提供了“current custom texture”到.fx文件。
  4. 在绘制PyModel的实例时,编写一个PyFashion来设置“current custom texture”。
  5. 创建一个包含自定义纹理创建过程的Moo::BaseTexture。
22.7.3.1. Loading textures from disk

当从磁盘加载纹理时,建议让加载线程在后台运行,这样它就不会中断渲染线程。

下面的例子演示了一个使用BackgroundTask和BGTaskManager类的纹理加载器。注意,代码提供了两个特性—线程纹理加载,以及将纹理提供给.fx文件系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include "pch.hpp"
#include "cstdmf/bgtask_manager.hpp"
#include "cstdmf/concurrency.hpp"
DECLARE_DEBUG_COMPONENT2( "romp", 0 );
// -------------------------------------------------------------------------
// Section: Texture Setter
// -----------------------------------------------------------------------
/**
* This class sets textures on the device. It is also multi-threaded.
* When it is told to use a new texture, it uses the background loading
thread
* to do so. While it is doing this, the textureName refers to the new
* texture, but isLoading() will return true. And in this state, it will be
* sneakily using the pre-existing texture until the new one is ready.
*/
class ThreadedTextureSetter : public Moo::EffectConstantValue
{
public:
ThreadedTextureSetter():
pTexture_( NULL ),
bgLoader_( NULL ),
textureName_( "" )
{
}
/**
* This method is called by the effect system when a material needs
* to draw using a texture with the given automatic semantic.
*/
bool operator()(ID3DXEffect* pEffect, D3DXHANDLE constantHandle)
{
SimpleMutexHolder holder( mutex_ );
if (pTexture_ && pTexture_->pTexture())
pEffect->SetTexture(constantHandle, pTexture_->pTexture());
else
pEffect->SetTexture(constantHandle, NULL);
return true;
}
/**
* This method sets our texture. If the texture is different then
* the existing one, we schedule the new one for loading, and set
* the textureName and the isLoading() flag. In an unspecified amount
* of time, the new texture will be loaded and used.
*/
void texture( const std::string& texName )
{
if (textureName_ == texName)
return;

if (this->isLoading())
return;

textureName_ = texName;
bgLoader_ = new BackgroundTask(
&ThreadedTextureSetter::loadTexture, this,
&ThreadedTextureSetter::onLoadComplete, this );
#ifndef EDITOR_ENABLED
BgTaskManager::instance()->addTask( *bgLoader_ );
#else
ThreadedTextureSetter::loadTexture( this );
ThreadedTextureSetter::onLoadComplete( this );
#endif
}
/**
* This class-static method is called by the background loading thread
* and allows us to load the texture resource in a blocking manner.
*/
static void loadTexture( void* s )
{
ThreadedTextureSetter* setter = static_cast<ThreadedTextureSetter*>(s);
Moo::BaseTexturePtr pTex =
Moo::TextureManager::instance()->get( setter->textureName() );
setter->pTexture(pTex);
}
/**
* This class-static method is called when the background loading thread
* has finished.
*/
static void onLoadComplete( void* s )
{
ThreadedTextureSetter* setter = static_cast<ThreadedTextureSetter*>(s);
setter->onBgLoadComplete();
}
/**
* This method returns the name of the texture we are currently
* drawing with. If isLoading() is true, then the textureName
* refers to the texture we would like to draw with (however we
* will be actually drawing with the previous texture ptr).
*/
const std::string& textureName() const
{
return textureName_;
}
/**
* This method returns true if we are currently waiting for the
* background loading thread to load our texture.
*/
bool isLoading()
{
SimpleMutexHolder holder( mutex_ );
return (bgLoader_ != NULL);
}
private:
//only called by the background loading thread
void pTexture( Moo::BaseTexturePtr pTex )
{
SimpleMutexHolder holder( mutex_ );
pTexture_ = pTex;
}
//only called by the background loading thread
void onBgLoadComplete()
{
SimpleMutexHolder holder( mutex_ );
delete bgLoader_;
bgLoader_ = NULL;
}
Moo::BaseTexturePtr pTexture_;
std::string textureName_; //store a copy for use while loading.
BackgroundTask* bgLoader_;
SimpleMutex mutex_;
};

纹理加载器的例子

22.7.3.2. Manipulate individual pixels within a texture
  1. 在托管池中创建纹理

    这样就会有一个系统内存副本和一个显存副本。然后你可以锁定纹理并根据需要操作它- DirectX将更新视频内存的更改。

    这个选项的唯一缺点是,在某些硬件配置上,系统内存纹理可能是swizzled存储的,这意味着锁操作必须在进行更改之前解除它的swizzled——这是一个潜在的昂贵的锁操作。

    注意,要使用纹理,你需要mipmaps可用,所以当执行锁时,你必须:

    • 单独更新纹理的每个表面。

      or

    • 使用拉伸矩形从上表面提供mipmap链。

  2. 在显存中创建纹理作为渲染目标

    你将不得不使用着色器来写你的改变到纹理。

    这种方法的缺点是,如果设备被重置,那么您的更改将丢失。如果发生这种情况,您将不得不处理CreateUnmanagedObjects回调(来自DeviceCallback接口)并重新创建自定义纹理。

    不幸的是,您不能只是将数据复制到系统内存,因为CTRL+ALT+DEL将失去设备,而没有给您机会先保存显存数据。

    再次注意,要使用纹理,需要有mipmaps可用。因此,也许在这个实例中,您可以在显存中创建一个单独的mipmap纹理,并使用stretch rect从源渲染目标提供mipmap链。源渲染目标可能是用于构建所有自定义角色纹理的“草稿板”。

22.7.3.3. Using a shader to build a custom texture

关于如何实现这一点,最接近的例子是植物灯光贴图,它使用地形着色器的变化来渲染一个渲染目标的照明信息。详情请参见bigworld/src/lib/romp/ flora_light_map.cpp

注意,要使用着色器,你将需要一个显存/渲染目标表面,如上节所述。

22.7.3.4. Dealing with the texture cache

自定义纹理需要实现Moo::BaseTexture接口,以便它可以将自己添加到纹理管理器。然而,根据下面的步骤(将自定义纹理应用到模型),你甚至可能不需要使用TextureManager,因为它只是提供了一个”通过名称检索纹理”访问纹理。

如果你正在为角色创建一些自定义纹理,那么你可能有一个内部(隐藏)命名方案,这将导致使用纹理缓存的最小好处。简单地使用智能指针来处理缓存就足够了。

22.7.3.5. Assigning custom textures to a model

假设您有一个模型,希望多次显示,每个模型都有一个独特的自定义纹理,那么您必须实现一个派生自PyFashion类的类。

这样的类从Python创建,赋值给PyModel实例,并有机会改变共享模型的呈现方式(即,在模型上设置自定义纹理)。PyFashion类将成为脚本的主接口,因此在Python中,你可以用你想要组合的纹理的名称来构造PyFashion实例,然后通过简单地将fashion设置为其PyModel上的任何命名属性来将其分配给一个播放器的模型(PyModel::pySetAttribute自动检测当时尚被设置时,并将其合并到其渲染链中)。

要在模型上实际设置自定义纹理,我们建议创建一个派生自Moo::EffectConstantValue的类,它按名称为.fx文件提供自定义纹理。在.fx文件中,使用自动变量语义(例如Texture diffuseMap:customCharacterTexture),而不是现有的artistEditable属性。

关于创建纹理设置器的示例代码,请参见 “Loading textures from disk” 。

22.8. Vertex declaration

顶点声明被Direct3D用来将顶点流数据映射到顶点着色器。

Moo使用VertexDeclaration类来简化Direct3D使用顶点声明的处理。顶点声明以XML格式存储在磁盘上,并根据需要加载。存储在磁盘上的顶点声明可以通过调用VertexDeclaration::get()方法来检索。

默认情况下,顶点声明存储在bigworld/res/shaders/ format文件夹下。

两个声明可以使用VertexDeclaration::combine()组合在一起,只要两个声明的元素互斥。

22.8.1. File format

顶点声明文件的格式如下:

1
2
3
4
5
6
7
<root>
+<USAGE> (usage index, optional defaults to 0)
?<stream> (strm #, opt, dflt is strm used by the prev elmnt) </stream>
?<offset> (ofst into strm, opt, dflt is nxt ofst aft prev elmnt) </offset>
?<type> (data type, opt, defaults to FLOAT3) </type>
</USAGE>
</root>

USAGE标签映射到枚举类型D3DDECLUSAGE,其可能的值如下所示:

POSITION

BLENDWEIGHT

BLENDINDICES

NORMAL

PSIZE

TEXCOORD

TANGENT

BINORMAL

TESSFACTOR

POSITIONT

COLOR

FOG

DEPTH

SAMPLE

在类型标记中输入的数据类型映射到枚举类型D3DDECLTYPE,其可能的值如下所示:

D3DCOLOR

DEC3N

FLOAT1

FLOAT16_2

FLOAT16_4

FLOAT2

FLOAT3

FLOAT4

SHORT2

SHORT2N

SHORT4

SHORT4N

UBYTE4

UBYTE4N

UDEC3

USHORT2N

USHORT4N

例如,xyznuv_d顶点格式的定义如下

1
2
3
4
5
6
7
8
9
10
11
12
<xyznuv_d.xml>
<POSITION/>
<NORMAL/>
<TEXCOORD>
<type> FLOAT2 </type>
</TEXCOORD>
<COLOR>
<stream> 1 </stream>
<offset> 0 </offset>
<type> D3DCOLOR </type>
</COLOR>
</xyznuv_d.xml>

22.9. Graphics settings

为了让游戏客户端在最广泛的系统中尽可能平稳地运行,BigWorld的3D引擎暴露了几个参数。玩家可以根据自己特定的硬件配置调整这些内容,以实现视觉质量和游戏响应之间的最佳平衡。

这些参数被称为图形设置,如下所示:

  • FAR_PLANE

    可选:FAR、MEDIUM、NEAR”

    修改观看距离。可视距离按空间定义,此图形选项将可视距离修改为一个因子。这些因素和选项可以在bigworld/res/ system/data/graphics_settings.xml文件中配置

  • FLORA_DENSITY

    可选:HIGH、MEDIUM、LOW”

    将植物群详细信息对象的密度设置为一个因子。这些因素和选项可以在bigworld/res/system/data/graphics_settings.xml文件中配置。

  • FOOT_PRINTS

    可选:ON,OFF

    开关脚印,开和关

  • OBJECT_LOD

    可选:HIGH,MEDIUM,LOW

    对LOD转换的相对距离做一个调整。

  • SHADER_VERSION_CAP

    可选:SHADER_MODEL_3,SHADER_MODEL_2,SHADER_MODEL_1,SHADER_MODEL_0

    设置引擎可用的最大着色器模型版本。

    如果客户端运行的图形卡只支持Shader Model 1,SHADER_MODEL_0是禁用的。

    SHADER_MODEL_0需要具备顶点着色器2.0的能力,无论是软件还是硬件的顶点处理。SM1显卡,如nVidia TI4400,支持硬件顶点处理,但只支持顶点着色器1.1,所以SHADER_MODEL_0选项不能应用。

  • SHADOWS_COUNT

    可用选项:从1到maxCount(定义在shadows.xml)

    设置同时可见的动态实体阴影的数量。

  • SHADOWS_QUALITY

    可选:HIGH,MEDIUM,LOW,OFF

    设置实体阴影质量。HIGH使用12点滤镜来过滤阴影,MEDIUM使用4点滤镜来过滤阴影,LOW使用1点滤镜来过滤阴影,OFF将阴影关闭。

  • SKY_LIGHT_MAP

    可选:ON,OFF

    开启和关闭云阴影。

  • TERRAIN_SPECULAR

    可选:ON,OFF

    切换地形反射照明的开关

  • TERRAIN_LOD

    可选:FAR,MEDIUM,NEAR

    通过一个因子修改地形地理变形距离。这些因素和选项可以在bigworld/res/system/data/terrain2.xml文件中配置。

  • TERRAIN_MESH_RESOLUTION

    可选:HIGH,MEDIUM,LOW

    选择要使用的地形分辨率。HIGH使用最高的可用分辨率,MEDIUM使用可用分辨率的一半,LOW使用可用分辨率的四分之一。

  • TEXTURE_COMPRESSION

    可选:ON,OFF

    切换纹理压缩开关

  • TEXTURE_FILTERING

    可选:ANISOTROPIC_16X,ANISOTROPIC_8X,ANISOTROPIC_4X,ANISOTROPIC_2X,TRILINEAR,BILINEAR,LINEAR,POINT

    选择纹理过滤。通过使用自动变量MinMagFilter, MipFilter和MaxAnisotropy来设置MINFILTER, MAGFILTER, MipFilter和MaxAnisotropy的采样状态,着色器可以利用这个设置来修改。详情请参见bigworld/res/shaders/speedtree/ speedtree.fx。

  • TEXTURE_QUALITY

    可选:HIGH,MEDIUM,LOW

    设置纹理质量等级

  • SPEEDTREE_QUALITY

    可选:VERYHIGH,HIGH,MEDIUM,LOW,LOWEST

    设置速度树(speed tree)渲染的质量。VERYHIGH在所有树的部分上启用每像素照明(法线映射),HIGH只在树的分支上启用每像素照明(法线映射),MEDIUM Trees使用简单的照明,LOW Trees使用简单的动画和照明,使用固定的功能管道。

  • WATER_QUALITY

    可选:HIGH,MEDIUM,LOW,LOWEST

    设置水反射渲染的质量。HIGH表示允许绘制所有对象类型,MEDIUM表示不允许绘制动态对象,LOW表示不允许绘制动态对象,将渲染目标分辨率减半。LOWEST只呈现镜面反射。

  • WATER_SIMULATION

    可选:HIGH,MEDIUM,OFF

    设置水模拟的质量。HIGH表示细胞间水分模拟,MEDIUM表示细胞间水分模拟,OFF表示关闭水分模拟。

  • POST_PROCESSING

    可选:HIGH,MEDIUM,LOW,OFF

    设置默认的后处理链。这些是从chains文件夹中选择的,例如:bigworld/res/system/post_processing/chains/high_graphics_setting.xml。如果您想编辑默认的后处理,那么就在那里编辑三个链文件。每个链有两个版本,一个有FXAA,一个没有,请参阅下面的FXAA_PROCESSING

  • FXAA_PROCESSING

    可选:ON,OFF

    设置FXAA(快速近似抗混叠)是否在后处理链中

  • MRT_DEPTH

    可选:ON,OFF

    允许创建深度纹理(通过DepthTex语义向.fx文件公开)。这可以实现各种高级的后期处理效果,如景深和深度淡出,以及基于深度的水着色。

22.9.1. Customising options

尽管大多数这些设置都在引擎中确定了它们的选项,但有些设置可以通过配置文件定制它们的选项。

这些设置及其自定义将在以下主题中讨论。

22.9.1.1. TEXTURE_QUALITY and TEXTURE_COMPRESSION

TEXTURE_QUALITY和TEXURE_COMPRESSION的设置都是通过纹理格式(.texformat)和细节级别(texture_detail_level.xml)文件来定制的(关于该文件的语法,请参阅 File Grammar Guide’s section .texformat)。

降低纹理质量和使用压缩格式有助于通过减少存储纹理所需的内存数量来提高引擎性能。这意味着每帧需要发送到显卡的数据更少,最终提高了帧速率。

TEXURE_QUALITY通过阻止它的最高mipmap级别被加载来调整纹理的分辨率。这意味着,在最高的质量水平上,纹理将具有与原始纹理贴图相同的分辨率。在中等水平,它的规模将只有原来的一半,质量将是最低水平的四分之一。

在每个纹理的每个质量级别中跳过多少mipmap级别可以由纹理的detailLevel部分中的lodMode标记控制。

下表列出了lodMode可以假设的值,在每个纹理质量设置级别跳过mipmaps的数量旁边:

Value Description High quality Medium quality Low quality
1 Normal 0 1 2
2 Low bias 0 1 1
3 High bias 0 0 1

每个质量设 setting level/ LOD level 跳过mipmap的数量

TEXURE_COMPRESSION影响在内存中存储纹理贴图的格式。当纹理压缩被禁用时,所有的纹理都使用纹理detailLevel部分的format标签定义的格式存储。当启用纹理压缩时,将使用formatCompressed定义的格式。如果没有为纹理定义formatCompressed,则使用format,无论压缩设置是什么。

注意,对于format和formatCompressed使用的纹理格式没有限制,但是为了使纹理压缩设置产生显著的性能结果,formatCompressed应该定义一个比非压缩格式占用更少纹理内存的纹理格式。

关于如何设置纹理的细节级别的详细信息,请参阅Texture detail levels/compression 关于配置文件语法的详细信息,请参见 File Grammar Guide’s section .texfor.mat.。

22.9.1.2. SHADOWS_COUNT

SHADOWS_COUNT定义了场景中同时动态实体阴影投射的最大数量。

使用阴影缓冲区渲染阴影,因此消耗纹理内存。在每一帧填充缓冲区需要为每个阴影投射者重新渲染场景。减少同时动态阴影投射器的数量可以减少内存需求和用于更新每帧缓冲区的处理量。

取值范围从1到shadows.xml文件中指定的值maxCount ,通过两个步骤(定义在shadows.xml中-关于该文件语法的详细信息,请参阅File Grammar Guide’s section shadows.xml).。

22.9.1.3. FLORA_DENSITY

FLORA_DENSITY定义了用于渲染flora详细信息对象的顶点缓冲区的大小。

因为植物群绘制到相机的距离是固定的,定义顶点缓冲区的大小也决定了植物群的密度。因为它使用alpha混合,绘制植物群会对帧渲染时间产生负面影响,特别是在填充率性能有限的硬件上。在任何时候减少渲染的植物群数量可以帮助提高帧率。

Flora密度选项在.xml中定义(关于这个文件的语法细节,请参阅File Grammar Guide’s section “*.xml”) 作为顶点缓冲区的乘数,它的实际大小是在< Flora >.xml中每个空间定义的(关于这个文件的语法细节,请参阅File Grammar Guide’s section “<flora>*.xml”).。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<graphics_settings.xml>
<flora>
<option>
<label> HIGH </label>
<value> 1.0 </value>
</option>
<option>
<label> LOW </label>
<value> 0.5 </value>
</option>
<option>
<label> OFF </label>
<value> 0 </value>
</option>
</flora>
</graphics_settings.xml>

Example .xml — Configuring flora density

22.9.1.4. FAR_PLANE

FAR_PLANE定义了渲染3D世界时的最大观看距离。

减少观看距离会减少发送到渲染管道的几何图形数量,从而产生更高的帧率。

因为远平面距离可以在每个空间的基础上定义, FAR_PLANE实际上是乘以空间的特定远平面值,然后应用到相机上。

.xml中指定(关于该文件语法的详细信息,请参阅File Grammar Guide’s section “**.xml”),,远平面可以配置如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<graphics_settings.xml>
<farPlane>
<option>
<value> 1.0 </value>
<label> FAR </label>
</option>
<option>
<value> 0.75 </value>
<label> MEDIUM </label>
</option>
<option>
<value> 0.5 </value>
<label> NEAR </label>
</option>
</farPlane>
</graphics_settings.xml>

Example .xml — Configuring far plane

22.9.1.5. OBJECT_LOD

OBJECT_LOD定义在LOD转换发生之前到摄像机的相对距离。

对于标准的BigWorld模型,LOD距离是使用模型编辑器定义的。粒子编辑器用于设置粒子系统的LOD距离(系统仍然可见的最大距离)。SpeedTree的LOD级别和转换距离在SpeedTreeCAD中定义(尽管它们可以被SpeedTree的XML配置文件覆盖,该配置文件在resources.xml文件的speedTreeXML标记中指定。有关该文件的详细信息,请参阅resources.xml)。

OBJECT_LOD设置指定将在运行时修改这些距离的乘数,允许用户用一些视觉质量换取更好的性能。

LOD乘数在.xml中定义(关于该文件语法的详细信息,请参阅File Grammar Guide’s section “**.xml”):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<graphics_settings.xml>
<objectLOD>
<option>
<value> 1.0 </value>
<label> HIGH </label>
</option>
<option>
<value> 0.66 </value>
<label> MEDIUM </label>
</option>
<option>
<value> 0.33 </value>
<label> LOW </label>
</option>
</objectLOD>
</graphics_settings.xml>

Example .xml — Configuring object LOD

22.9.2. Using settings

在启动时,客户端自动尝试从磁盘加载图形设置。所有图形设置都存储在resources.xml的engineConfigXML标记中指定的文件的preferences标记中指定的文件的graphicsPreferences部分(详细信息,请参阅“file <preferences>.xml”)。应用程序第一次运行时,当显卡或其驱动程序发生变化时,图形设置将尝试自动检测最适合设备的设置。

另一方面,保存不是自动执行的;必须从脚本控制,使用BigWorld.savePreferences 方法,它将保存图形设置和视频和脚本的首选项。

游戏脚本可以使用函数BigWorld.graphicsSettings 和 BigWorld.setGraphicsSettings 去查询和更改设置的当前状态(通常通过图形用户界面)。

22.9.2.1. Auto-detecting settings

通过resources.xml中的标记graphicsSettingsPresets指定的xml配置文件,支持设置的自动检测。这个文件定义了匹配条件,试图将一组设置与特定的设备(显卡)匹配。有三种不同的方式将一组设置与设备匹配。所有的匹配都是针对D3DADAPTER_IDENTIFIER9结构完成的。自动检测本身在方法Moo::GraphicsSetting::init中执行

  • GUID

    将特定的设备类型和驱动程序版本匹配到一个设置组,当某个驱动程序/设备对存在已知问题时,这是有用的,你可以相应地调整你的图形设置。

  • VendorID/DeviceID pair

    将特定的设备类型匹配到设置,当你知道特定设备的供应商和设备id时,这是有用的。

  • Device description string

    将字符串匹配到设备描述,所有的字符串都需要匹配到要选择的设置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <graphicsPreferences> HIGH 
    <entry>
    <label> CLASSIC_TERRAIN_QUALITY </label>
    <activeOption> 0 </activeOption>
    </entry>
    <GUIDMatch> 01234567.89abcdef.01234567.89abcdef </GUIDMatch>
    <VendorDeviceIDMatch>
    <VendorID> 4318 </VendorID>
    <DeviceID> 1554 </DeviceID>
    </VendorDeviceIDMatch>
    <DeviceDescriptionMatch>
    <string> nvidia </string>
    <string> 6800 </string>
    </DeviceDescriptionMatch>
    <defaultSetting> true </defaultSetting>
    </graphicsPreferences>
22.9.2.2. GraphicsPresets class

还提供了一个简单的python图形预设类。该类有助于将多个图形设置分组在一起,以便您可以为特定硬件或性能级别的多个图形选项提供预定义设置。GraphicsPresets类可以在bigworld/res/ scripts/GraphicsPresets.py文件夹中找到。

22.9.2.3. Delayed settings

大多数设置在调用BigWorld.setGraphicsSettings后立即生效。但有些只是标记为延迟。这意味着在被设置之后,它们会被添加到待处理设置的列表中,并且只有当该列表被提交时,它们才会生效。

这样设计是为了让界面程序员有机会警告用户,在客户端应用程序阻塞几秒钟之前,处理新设置可能需要一段时间才能完成(目前,不支持在处理设置时显示进度条)。

以下函数允许脚本管理挂起的设置列表:

  • BigWorld.hasPendingGraphicsSettings
  • BigWorld.commitPendingGraphicsSettings
  • BigWorld.rollBackPendingGraphicsSettings
22.9.2.4. Settings that require restarting the client

某些设置只有在重新启动客户端应用程序后才会被应用。当客户端需要重新启动最近更改的设置以生效时,函数BigWorld.graphicsSettingsNeedRestart将返回true。

22.10. Taking Screenshots

BigWorld客户端能够截屏并将其保存为多种不同的格式。它通过在调用截图操作时检索后台缓冲区的当前内容来实现这一点。要在游戏中截图,请按下PrtScn按钮,或使用 BigWorld.screenShot(extension, name)Python API函数。

默认的图像类型、文件名前缀和输出位置都在engine_config.xml中配置(参见” .xml”)。<screenShot>标签的模式是:

1
2
3
4
5
6
7
8
9
10
11
<engine_config.xml>
...
<screenShot>
<path> relativePath
<pathBase> basePathName </pathBase>
</path>
<name> prefixName </name>
<extension> extension </extension>
</screenShot>
...
</engine_config.xml>
  • relativePath

    这是相对于basePath的输出路径(概述如下)。例如,如果relativePath是MY_DOCS,你可以设置它为“ “My Company/Game Name/Screenshots”。留空或指定”./“直接放置在basePathName中(这是默认行为)。

  • basePathName

    • EXE_PATH

      相对于客户端可执行文件位置的截图。如果没有提供,这是默认位置。

    • CWD

      截图将相对于当前工作目录存储。注意,如果工作目录在运行时发生更改,它将保存在新的工作目录中。

    • ROAMING_APP_DATA

      截图将相对于当前用户的roaming AppData目录存储。换句话说,如果用户在域上,当用户登录和退出Windows时,数据将与域控制器同步。

    • LOCAL_APP_DATA

      截图将相对于当前用户的local AppData目录存储。

    • APP_DATA

      这与ROAMING_APP_DATA相同。

    • MY_DOCS

      截图将相对于当前用户的My Documents目录存储。

    • MY_PICTURES

      截图将相对于当前用户的“我的图片”目录存储。

    • RES_TREE

      截图将相对于paths.xml中找到的第一个资源路径存储。

    默认值为“EXE_PATH”。

  • prefixName

    指定生成有编号的屏幕截图时使用的前缀。系统默认值为shot。

  • extension

    指定保存屏幕截图时使用的文件格式。这可以是bmp,“jpg”、“tga”、“png”或“dds”。默认值为“bmp”。

因此,生成的截图的完整路径将是basePathName/relativePath/prefixName_<sequence>。扩展,其中<sequence>是一个四位数非负整数,用前导零填充(例如,shot_0012.bmp)。屏幕捕获机制不会覆盖现有的文件。

22.10.1. High Resolution Screenshots

通常情况下,在全屏模式下,后台缓冲区的大小与窗口或屏幕分辨率相同。然而,当在窗口模式下运行时,有可能有一个比窗口本身更大的后缓冲区,从而增加屏幕截图的大小。

22.11. Dynamic Entity Shadows

客户端支持两种不同的方式来允许实体模型将阴影投射到场景中。您选择使用的方法取决于目标硬件,因为它们的成本差别很大。

22.11.1. Splodges

这个系统的工作原理是在实体模型脚下的几何图形上绘制一个特殊的“污渍”纹理,投影在太阳的方向(它们只有在外部块是可见的)。这是一个很好的低端解决方案,因为它们绘制起来很便宜,但是它们只会提供一个粗略的阴影近似值。

通过使用PySplodge API,可以将插件添加到实体模型中。PySplodge类是一种连接类型,连接到实体模型的脚(每脚一个)。例如:

1
2
3
4
>>> lsplodge = BigWorld.Splodge()
>>> rsplodge = BigWorld.Splodge()
>>> model.node( "biped L Toe0" ).attach( lsplodge )
>>> model.node( "biped R Toe0" ).attach( rsplodge )

虽然所有的splodge都使用相同的材质来绘制(这可以通过修改resources.xml中的environment/splodgeMaterial部分来配置),每个PySplodge实例可以修改以下参数:

  • 距离相机的最大LOD距离,在此之后它们被剔除。默认为50米。
  • 单个污迹的大小。

由于碰撞场景被用来确定在哪里绘制污损,只有固体物体会接收污损阴影。

22.11.2. Shadow maps

实体模型可以被配置,这样它们就可以根据太阳光线的方向,利用动态阴影地图,将阴影投射到场景中。每一帧引擎将选择一组最接近相机的阴影投射实体,其数量通过SHADOWS_COUNT图形设置配置。对于每个阴影投射器,它会根据光线的方向将投射器渲染成纹理,然后通过重新渲染每个与阴影相交的物体将这个纹理投射到场景中(使用阴影贴图作为输入)。

为了投射阴影,实体必须显式地添加到实体阴影管理器中。提供了两个Python API:

  • BigWorld.addShadowEntity

    这通常从onEnterWorld实体回调调用

  • BigWorld.delShadowEntity

    这通常从onLeaveWorld实体回调调用。

全局阴影设置(如阴影贴图分辨率、强度和着色器)在阴影中配置

XML文件(定义在shadows.xml中)-关于该文件语法的详细信息,请参阅(File GrammarGuide’s section shadows.xml).

用户可控制的图形设置是SHADOWS_COUNT和SHADOWS_QUALITY。详情请参阅“Graphics settings”。

阴影将投射到地形、实体模型和植物上。排序三角形(即半透明物体)将不会投射或接收阴影。

请记住,使用实体阴影的费用会随着渲染阴影实体的数量增加而增加,所以建议保持将SHADOWS_COUNT设置得尽可能低以保持性能。因此,这并不是一个通用的全场景阴影系统。

23. Post Processing

后期处理效果广泛应用于所有现代游戏,并有许多应用-从HDR色调映射和颜色校正到卡通效果,可能性几乎是无限的。

BigWorld Technology支持后期处理链的完整用户定制,支持艺术家通过内置编辑工具,支持程序员通过提供对python中的每个参数的完全控制,并以DirectX/HLSL效果文件的方式提供插入着色器。

然而,在你开始创建自己的新的时髦效果之前,你需要考虑一下。效果应该很好地结合在一起,它们应该在可能的情况下重用呈现目标,并且您需要监视性能。

23.1. Pipeline Overview

在不透明场景、半透明和镜头效果之后,在GUI绘制之前,PostProcessing::Manager绘制其当前链。链包含按顺序绘制的效果列表。在内部,每个效果都包含一个按顺序绘制的阶段列表。一个阶段通常使用效果文件将一个全屏四边形绘制到屏幕上,尽管也有其他转移网格可用。

后处理的实现分为三个部分。核心是用c++编写的,在bigworld/src/ lib/post_processing库中。所有的特性都通过_PostProcessing模块向python公开。

在Python中,PostProcessing模块存在于bigworld/res/scripts/client/PostProcessing中,并从PostProcessing导入所有的方法。这允许您重写或包装任何c++方法。因此,所有的python调用都应该指向PostProcessing,而不是PostProcessing。

默认情况下,PostProcessing模块注册3个图形设置。如果用户选择高/中/低,则加载适当的后处理链。这些在bigworld/res/system/post_processing/ chains/和**”High Graphics Setting.ppchain”, “Medium Graphics Setting.ppchain” and “Low Graphics Setting.ppchain”**因此,通过在World Editor中创建新链并保存在这些文件的顶部,开发人员可以很容易地重新定义默认的后处理链。

一般来说,我们都希望游戏能够结合使用默认的后期处理链文件,并动态地将其与游戏玩法相关的效果混合在一起。为了实现这一点,请遵循PostProcessing模块中的示例。另外,看看PyMaterial的Python API,因为它将演示如何平稳地淡入/淡出动态后期处理效果。

最后在世界编辑器,后期处理选项卡是一个功能齐全的编辑器和预览工具链。它加载并保存.ppchain文件。

23.2. Creating a Custom Post-Processing Effect

虽然《BigWorld》带有一套基本的后期处理着色器,阶段和效果,但你可能会发现自己需要执行一种独特的游戏效果。

对于这个例子,我们将创建一个后期处理,将反转屏幕上的所有颜色。

我们将创作一个后期处理效果,可以添加作为客户的整体后期处理链的一部分,我们将写一个自定义着色器,执行实际的颜色倒置。

23.2.1. Creating the Custom Pixel Shader

那么我们如何让GPU反转屏幕上所有的颜色呢?

因为BigWorld客户端支持插入DirectX Effect文件(.fx),所以这一步相对简单。我们所需要做的就是编写一个.fx文件,它接受一个输入纹理,转换颜色,然后输出该值。

1
2
3
4
5
6
7
float4 ps_invert(PS_INPUT input) : COLOR0
{
float4 map = tex2D(inputTextureSampler, input.tc0);
float4 invMap = float4(1,1,1,1) - map;
invMap.w = 1;
return invMap;
}

好了,这是简单的部分。这个像素着色器假设了一些事情,即顶点着色器通过一组纹理坐标,有一个读取正确纹理映射的采样器,有一个定义好的PS_INPUT结构。

幸运的是,通过包括文件post_processing.fxh的效果,所有这些都得到了处理。(bigworld/res/shaders/post_processing/post_processing.fxh)

使用像素着色器的完整效果是相当直接的,看起来像这样:

1
2
3
4
5
6
7
8
9
10
#include "post_processing.fxh"
DECLARE_EDITABLE_TEXTURE( inputTexture, inputSampler, CLAMP, CLAMP, LINEAR,"Input texture/render target" )
float4 ps_invert(PS_INPUT input) : COLOR0
{
float4 map = tex2D(inputSampler, input.tc0);
float4 invMap = float4(1,1,1,1) - map;
invMap.w = 1;
return invMap;
}
STANDARD_PP_TECHNIQUE(compile vs_2_0 vs_pp_default(), compile ps_2_0ps_invert())

着色器也可以在FX Composer中通过添加以下内容来编辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define FX_COMPOSER 1
#include "post_processing.fxh"
FX_COMPOSER_STANDARD_VARS
DECLARE_EDITABLE_TEXTURE( inputTexture, inputSampler, CLAMP, CLAMP, LINEAR,"Input texture/render target" )
float4 ps_invert(PS_INPUT input) : COLOR0
{
float4 map = tex2D(inputSampler, input.tc0);
float4 invMap = float4(1,1,1,1) - map;
invMap.w = 1;
return invMap;
}
STANDARD_PP_TECHNIQUE(compile vs_2_0 vs_pp_default(), compile ps_2_0ps_invert())
STANDARD_FX_COMPOSER_TECHNIQUE(compile vs_2_0 vs_pp_default(), compile ps_2_0ps_invert(), "RenderColorTarget0=inputTexture;")

在FX Composer设置中,添加post_processing的路径。FXH和其他后期处理头到您的包括路径。注释#define FX_COMPOSER 1在客户端或世界编辑器中使用着色器。

23.2.2. Previewing the Results

由于后处理链包含许多阶段,这些阶段通常写入中间的、不可见的呈现目标,因此通常希望看到后处理效果或链的中间结果。有两个方法可用于此目的,PostProcessing.debug()和World Editor的预览功能。

在客户端中,您可以用_PostProcessing模块注册一个任意大小的呈现目标,并让它记录所有中间步骤。然后可以通过在GUI中显示来查看这个呈现目标。一个辅助类,ChainView,在PostProcessing模块中可用,这将在屏幕上实时显示整个链。

在World Editor中,后处理编辑器中有一个预览按钮,它显示编辑图中每个阶段节点内的中间结果。

23.2.3. Writing a Custom Pixel Shader for Previewing the Results

有时候,这种简单的预览并不合适。默认情况下,预览直接显示每个阶段的像素着色器的输出。然而,通常中间步骤的输出被写入一个不直接映射到可见颜色范围的浮点呈现目标,其他时候以特定的方式编码的信息不是直接可见的。

以景深镜头模拟为例。一种可能的实现可能是解码深度缓冲,并将场景分为7个单独的区域,3级模糊在焦距前,对焦内和3级模糊后。该信息可以写入单组件浮点呈现目标,并包含-3到+3之间的值。

当一个像素着色器的输出不直接可见时,你可以创建另一个用于预览功能的像素着色器。要做到这一点,你需要为你的效果添加一种新技术。这种技术必须称为“预览”,如果可用,将在预览后处理链时代替主要技术。这种技术输出的数据在普通R8G8B8A8渲染目标上和在屏幕上查看时都是可见的。

在上面的例子中,您可以编写一个预览技术,在焦点范围前的模糊区域显示3个深浅的红色,在所有焦点区域显示全绿色,在焦点范围后的所有模糊区域显示3个深浅的蓝色。

23.2.4. Authoring a Post-Processing Effect in Python

那么现在我们如何获得在后期处理时操纵屏幕的新效果呢?我们必须编写一个PostProcessing::Effect。大多数情况下,这将通过世界编辑器中的后期处理编辑器完成。世界编辑器保存.ppchain文件,这些文件包含效果和阶段链,可以简单地加载并设置为当前的后期处理链。然而,知道如何使用Python API也很有用,因为你确实可以访问整个链,通常你会想要将后期处理效果直接绑定到游戏逻辑。这也有助于理解发生了什么。

对于这个示例效果,我们必须用反向缓冲区中任何颜色的倒数来写入反向缓冲区中的每一个像素。

PC硬件无法读取正在写入的同一个纹理,所以我们需要首先抓取一个回缓冲区的副本,并将其存储在另一个纹理中。

这段python代码创建了一个CopyBackBuffer阶段,创建了一个与返回缓冲区大小相同的渲染目标,并将两者挂钩起来。

1
2
3
4
import PostProcessing
phase1 = PostProcessing.CopyBackBuffer()
bbcRT = BigWorld.RenderTarget("backBufferCopy", 0, 0)
phase1.renderTarget = bbcRT

在世界编辑器中,我们可以简单地将“BackBufferCopy”阶段放入效果中,然后就完成了。

注意,在这种情况下,我们创建了一个新的渲染目标,但通常你想要在效果和阶段之间共享渲染目标,特别是像上面这样的全屏渲染目标。所以我们可以像这样使用RenderTargets模块,而不是创建一个渲染目标:

1
bbcRT = PostProcessing.RenderTargets.rt("backBufferCopy")

现在我们有了一个后缓冲的副本,我们可以读取它作为纹理,现在是时候做我们的颜色反转了

1
2
3
4
5
phase2 = PostProcessing.Phase()
phase2.material = BigWorld.Material("shaders/post_processing/colour_invert.fx")
phase2.material.inputTexture = phase1.renderTarget.texture
phase2.renderTarget = None
phase2.filterQuad = PostProcessing.TransferQuad()

这个代码示例创建了一个新阶段,这次是一个通用的PyPhase对象。PyPhase对象有一个PyMaterial和一个FilterQuad。它使用这些来写入一个RenderTarget。

我们已经从“colour_invert.fx”创建了一个新的PyMaterial”,我们之前编写的着色器。

效果文件使用了一个名为“inputTexture”的纹理变量。因为我们在效果中将这个变量标记为’editable’,它会在python字典中显示。因此,我们可以将它直接设置为返回缓冲区复制渲染目标保存的纹理。

我们已经将这个阶段的renderTarget属性设置为None。具体来说,这意味着“不要设置渲染目标”,在实践中这意味着我们想要直接写入主场景的后台缓冲区,而不是屏幕外的渲染目标。

注意,每当我们写回缓冲区,我们改变它的内容,并且下一个后处理效果或阶段必须使用一个包含这些改变的新副本。BackBufferCopy阶段在内部检测自获取最后一个副本以来,back缓冲区是否被修改。因此,一直使用BackBufferCopy阶段是可以的,如果那个时候实际上不需要那个阶段,也不会有性能损失。

最后阶段使用一个FilterQuad来绘制;它们通常使用n组滤镜,在这种情况下,我们只想从源纹理中读取单个像素,对于输出渲染目标中的每个像素。因此,我们已经创建了一个PyTransferQuad,它只有一个样本点,并且没有偏移。如果我们想做一些纹理过滤,我们可以使用PyFilterQuad代替,并指定n个采样点-每个采样点表示(u-偏移texels, v-偏移texels,权重,未使用)。

1
2
3
4
colourInvert = PostProcessing.Effect()
colourInvert.phases = [phase1, phase2]
colourInvert.name = "Invert Colours"
PostProcessing.chain([colourInvert])

最后的代码示例将我们的两个阶段打包成一个Effect,并将Effect注册为后处理链。从这里开始,屏幕上的颜色将会颠倒。

23.3. Render Targets

后处理链使用的主要资源之一是渲染目标。它们往往是后台缓冲区大小的倍数,并具有不同的表面格式和用途。BigWorld客户端公开了PyRenderTarget类,可用于按需创建自定义渲染目标。请参阅Python Client API 关于如何使用PyRenderTarget的详细说明。

注意,你可以创建尽可能多的渲染目标,因为实际的表面内存只在渲染目标首次用于绘制时分配(通过RenderTarget.push)。因此,您可以定义实际未使用的渲染目标,开销可以忽略不计。但是对于正在使用的渲染目标来说,视频内存会迅速增加,所以在设计后期处理链时需要格外小心。任何特定链使用的总内存可以在世界编辑器中查看,或者通过调用函数PostProcessing.RenderTargets.reportMemoryUsage()。

在Python中,PostProcessing模块有自己的RenderTargets模块,位于bigworld/res/scripts/client/PostProcessing/RenderTargets中。如果您想添加更多的呈现目标供后处理链使用,那么请将它们添加到这里的呈现目标列表中。这样做是必要的,因为这是World Editor获取可用后处理呈现目标列表的地方。

23.4. Performance

在创建后处理链时,需要注意两个主要的性能指标。这些是:内存使用(主要是由渲染目标);以及花在GPU上的时间。渲染目标及其相关的内存使用在前一章中有描述。与往常一样,动态创建的PostProcessing资源应该在后台线程中加载,以避免渲染线程停止。

23.4.1. Measuring the Time Spent on the GPU

后处理链往往具有较低的CPU成本——包括通过效果和阶段进行的简单迭代和简单的几何设置——但GPU成本较高,使用复杂的像素着色器执行全屏传递和每个像素获取许多纹理。因此,主要的开销通常是GPU带宽:填充率和纹理读取。

BigWorld客户端有一个python API函数,PostProcessing.profile(迭代),用来测量GPU所花费的时间。参数迭代通常应该在10左右,以确保测量到一个准确的值。由于主要的成本是填充率和纹理获取,这个值取决于屏幕的分辨率,所以需要在不同的gpu和不同的分辨率下进行profile。注意,World Editor在后处理面板上还附带了一个工具栏按钮,该按钮也对链进行了概要分析。

23.4.2. Background Loading

没有直接支持在后台创建.ppchain文件,因为库使用了许多只能在主线程中创建的PyObject指针。相反,可以通过postprocessing .prerequisites()方法来实现对.ppchain文件后台加载的支持。这将从XML文件中提取适当的资源(主要是效果材料),并返回所需资源的列表,然后可以直接将这些资源传递给BigWorld.loadResourceListBG()。

例如:

1
2
filename = "system/post_processing/chains/underwater.ppchain"
BigWorld.loadResourceListBG(PostProcessing.prerequisites(filename), onLoadBG)

通过SFX系统加载的后处理链会在后台自动加载。

因为收集PostProcessing先决条件依赖于已经加载的.ppchain文件数据部分,所以建议预先加载所有.ppchain XML文件。

24. Job System

24.1. Overview

Job System允许多核系统中的额外核心执行代码并计算数据,以便及时呈现。它还将D3D命令的问题移动到自己的核心上,而主线程就可以简单地记录它们,以便在下一帧中执行。

一帧的渲染被分成几个块。块按顺序呈现。每个块只有在前一个块完成后才开始。

每个块包含任何D3D渲染命令和相关的顶点和索引缓冲区,纹理,着色常量或任何其他数据。该块可以结合来自主线程的传统D3D渲染和在分配给运行作业的核心上并行运行的作业的输出来生成。

任何数量的jobs都可以产生一个块的输入。由于一次只渲染一个块,并且作业是并行执行的,你应该使用至少与核心数量相同的job,否则会有空闲的核心。一个块中的jobs可以以任何顺序完成,但在所有job完成之前,块不会被呈现。当job执行时,D3D执行前一个块。因此,job核心和D3D核心一直在工作,而同时主线程正在准备另一帧的块和它们对应的job。

24.2. Under the Hood

所有渲染命令和jobs都存储在命令缓冲区中。这是通过包装D3D来完成的。所有D3D函数调用都转到包装器,包装器记录下它们在下一帧执行,而与此同时,上一帧的命令在D3D核心的另一个线程中执行。

当刷新时,D3D核心首先会停止读取第一个区块的job结果。与此同时,job集群中的每个核心开始从区块中抢夺job。没有中央分派机制。核心获取任务,并在写完结果后自动为该块减少一个计数器。请注意,job是按顺序获取的,但不一定是按顺序完成的。例如,如果第一个任务需要很长时间,那么第二个任务可能会先完成,这个核心可以开始另一个任务。这意味着在最后一个job完成并将计数器减为零之前,不能保证输出是连续的。

只有这样,D3D核心才能开始处理第一个块的结果,而集群开始操作第二个块,将结果输出到另一个缓冲区。

如果D3D内核先完成,它就会退出缓冲区并暂停,直到下一个缓冲区准备好。如果集群先完成,它就会暂停,直到D3D内核回收它正在消耗的缓冲区,以便接收来自集群的输出。

24.3. Wrapper API

除了包装D3D,包装器还有一个小的API来控制它的行为。

DX::newBlock():启动一个新块。所有来自前一个块(block)的渲染将在这个块开始之前完成,所有用于渲染前一个块的job输出将不再可访问。这意味着所有为这个块产生输出的job必须在下一次调用DX::newBlock()之前分配。

DX::setWrapperFlags()和DX::getWrapperFlags():这些函数获取和设置控制包装器行为的标志。

IMMEDIATE_LOCK:刷新命令缓冲区,然后在主线程中执行锁。如果您不打算填充整个锁定区域,则这是必需的。这是一种非常昂贵的锁定方式,应该尽可能避免。

DEFERRED_LOCK:该标志用于锁定将被作业填充的缓冲区。锁返回的指针只能用于存储到作业中,然后在作业执行时访问。实际的锁在作业执行的下一帧发生。

24.4. Job System API

这个Job System API 是通过 JobSystem 单例来访问的. 它通过withJobSystem::instance()获得.

allocJob():该方法将job放入当前块的job列表中,并返回从job派生的用户实现类。它有一个名为execute()的虚函数,实际执行任务。派生类存储job执行所需的任何东西。注意,块中的job不会按顺序执行。

allocOutput():这个job需要产生输出,这是通过allocOutput()预先分配的。它从主线程调用,其结果放置在Job对象中。此时并没有实际分配用于输出的内存,但是在下一帧执行job时,内存就已经准备好了。

在一个块中,可以使用job和输出分配的任意组合。例如,您可以分配一个输出,并将其分配给多个job,反之亦然,或者您喜欢的任何组合。唯一的规则是输出和job必须都来自同一个块。

24.5. An Example

让我们想象一下,我们正在更新和渲染一个简单的粒子系统。这将在一个块内完成。粒子系统由4096个点组成,每个点将被更新并生成一个顶点到顶点缓存中用于渲染。

我们的区块将包括设置一个顶点缓冲区并在其上调用DrawPrimitive。顶点缓冲区将使用job填充。顶点缓冲区将被分为8个部分,每个部分包含512个顶点。每个部分将填写一个job。

主线程:

  • 新建Block
  • 使用DEFERRED_LOCK标志锁定4096个点的顶点缓冲区
  • 设置8个job,每个job填写512顶点
  • 设置渲染状态
  • 绘制
  • 重置包装标志

以上所有步骤都不是立即执行的,而是在下一帧执行时被记录下来。

Jobs和D3D核心:

在下一帧中,默认块呈现,直到到达粒子的新块。同时执行填充顶点缓冲区的8个job。当到达新的区块和工作完成时,粒子被渲染。与此同时,执行下一个块的job。

24.6. Implementing it

既然我们理解了这个示例的工作原理,那么我们就可以完成实现它的步骤了。

首先,我们需要实现我们的job对象。

1
2
3
4
5
6
7
8
9
10
class PointSpriteParticleJob : public Job
{
public:
void set( Particle* particles, Moo::VertexXYZDP* pVertex, uint nPoints );
virtual void execute();
private:
Particle* particles_;
Moo::VertexXYZDP* pVertex_;
uint nPoints_;
};

job对象继承了虚拟execute()方法,该方法在下一帧被调用,就在D3D内核需要使用作业输出之前。

我们还实现了一个set()方法,该方法从主线程调用并存储execute()中所需的所有信息。

execute()方法将做两件事。它将更新粒子的位置,并将新位置输出到顶点缓冲区。每个给定的任务对象只会对粒子系统和顶点缓冲区的一部分执行此操作,这样整个任务就可以被分成几个任务,并在几个核心上并行执行。

现在我们可以在呈现代码中使用job类了。

我们从一个新的区块开始。

1
DX::newBlock();

现在我们已经准备好对呈现命令进行排队了。

首先,我们需要锁定一个延迟的顶点缓冲区,为此,我们需要在锁定之前设置适当的包装器标志。

通常在锁定时,你会得到一个指针,可以立即使用它来写入顶点数据。然而,我们的顶点数据将在下一帧由job计算,所以锁实际上必须在那个时候发生。因此,我们使用延迟锁,它现在返回一个指针,但直到需要时才执行锁。

1
2
3
4
5
uint32 oldFlags = DX::getWrapperFlags();
DX::setWrapperFlags( DX::WRAPPER_FLAG_DEFERRED_LOCK );
Moo::DynamicVertexBufferBase2<Moo::VertexXYZDP>& vb =
Moo::DynamicVertexBufferBase2<Moo::VertexXYZDP>::instance();
Moo::VertexXYZDP* pVertex = vb.lock2( 4096 );

现在我们准备分配和设置我们的工作。锁中的指针用于设置作业。

1
2
3
4
5
for ( uint i = 0; i < 8; i++ )
{
job = jobSystem.allocJob<UpdateParticlesJob>();
job.set( particles + i*512, vertices + i*512, 512 );
}

最后,我们可以解锁缓冲区,重置包装标志和渲染。

此时,我们可以将分配和设置的job渲染为完成的状态,因为在此之前不会执行以下呈现命令。

1
2
3
4
5
vb.unlock();
uint32 lockIndex = vb.lockIndex();
DX::setWrapperFlags( oldFlags );
vb.set( 0 );
Moo::rc().drawPrimitive( D3DPT_POINTLIST, lockIndex, nPoints );

25. Debugging

本节介绍BigWorld客户端的一些调试特性。

调试是任何开发过程的重要组成部分,客户端有许多功能,有助于及早发现bug、游戏内调试、远程调试,以及无需重新启动即可立即应用更改。

许多调试功能都是通过一个特殊定义的调试键访问的。默认情况下,调试映射到键盘上的grave (~)键,但是可以通过编辑引擎configuration.XML文件来重新配置。

25.1. Build configuration — conditional feature inclusion

BigWorld提供了许多有助于运行时调试的特性,例如调试菜单和telnet Python服务。

要从客户端的最终版本中删除这些特性,需要存在许多编译保护。它们由src/lib/cstdmf/config.hpp中的以下定义共同控制。

1
#define CONSUMER_CLIENT_BUILD 0

如果CONSUMER_CLIENT_BUILD为0,那么将编译开发特性。

各个特性被封装在各自的编译保护中,可以使用上面的定义进行切换。也可以通过将相应的强制启用定义设置为非零值来启用单个特性。

下面的例子说明了这一点:

1
2
3
4
#define CONSUMER_CLIENT_BUILD 0
#define FORCE_ENABLE_DEBUG_KEY_HANDLER 0
#define ENABLE_DEBUG_KEY_HANDLER (!CONSUMER_CLIENT_BUILD \
|| FORCE_ENABLE_DEBUG_KEY_HANDLER)

25.2. Watchers

监视器是一个对象,它将变量或函数结果包装到程序中,并将其转换为字符串。

监视器可以是读写或只读的,可以以各种方式查看。它们的层次结构通常是由功能组织的。还有一些观察器可以动态匹配不断变化的数据,例如序列或映射的内容。

25.2.1. Watcher types

最简单的监视器类型是DirectoryWatcher,它允许创建其他监视器(包括DirectoryWatcher)的目录。这是建立层次结构的一种方式。例如,在BigWorld客户端中由降雨系统绘制的降雨量的一个浮点值被指定在’ Client Settings/Rain/ amount’.它使用三个DirectoryWatchers:根目录、Client Settings目录和Rain目录。

对于任何可以流到std::stream对象上的数据类型,也有模板化的监视器。它们有两种类型,一种是变量的DataWatchers,另一种是类成员函数的结果(或唯一参数)的MemberWatchers。

DataWatchers和MemberWatchers也可以接受一个’base object’参数,它们所指向的数据被认为是该参数的成员。当与更复杂类型的观察者(如SequenceWatcher)结合使用时,这是非常有用的。

SequenceWatcher和MapWatcher看起来与DirectoryWatcher相同,但是它们监视任何支持STL序列或映射方法的类。它们只有一个客户端监视器,但是它们呈现的子容器的数量与包装容器的元素数量相同。调用子观察器时,将其“基对象”设置为容器的元素。为了处理这些容器中的指针,还有BaseDereferenceWatcher,它不提供额外的层次结构,而是解除对其基对象的引用,并使用该值调用唯一的子watcher。子进程可以通过静态列表来命名,也可以通过在子进程监视器中请求某个值(例如name)来命名。

25.2.2. Using watchers

从上面的类型可以看出,可以设置一个监视器层次结构,既可以公开简单的设置,也可以跟踪复杂的结构。

制作监视器的最简单方法是使用MF_WATCH宏。这个宏接受监视器的路径名,以及一个变量或成员函数来获取/设置它的值。它扩展为添加适当的监视器的代码。

要更改值(作为字符串)的解析或显示,不需要编写新的监视程序。相反,可以使用成员函数以字符串的形式获取和设置值。这就是’Client Settings/Time of day’值的工作原理-有简单的访问器timeOfDayAsString,格式化一天的时间为’小时:分钟’,并从相同的格式解析回来。设置一个观察者也可以用来触发一个命令—例如,只要设置了一天中的时间,服务器就会收到通知(仅用于调试目的)。

要跟踪复杂的结构,必须直接创建适当的监视器,并将其插入到监视器层次结构中。参与这种安排的类通常会实现一个getWatcher()静态方法,该方法返回一个监视对象,当“基对象”被设置为该类的一个实例时,该监视对象就会工作。同一个监视器可以被类的所有实例共享,并且通常是DataWatchers和MemberWatchers的DirectoryWatcher类型。

这是在客户端使用实体管理器维护的实体映射完成的。实体由它们的ID命名。

客户机的大部分c++状态也可以通过显式添加的监视程序获得,因为调试的某些阶段已经需要它们了。

25.2.3. Watcher Console

观看者控制台提供了从游戏内部进入观看者系统的权限。它可以在任何时候打开,并将游戏屏幕覆盖为当前观察者目录的列表。

可以在后面跟着目录类条目,可以通过选择任何可写监视器,按Enter,然后输入一个新值来设置它的值。

还有一些键可以通过1、10、100或1000来调整数值。

25.2.4. Remote watcher access

watcher系统使用一个非常简单的基于udp的watcher消息协议从客户端导出。服务器为了自己的调试需要广泛地使用这个协议。

这与Mercury无关,尽管它可以(目前是)连接到它的输入回路。当客户端启动时,它广播一个watcher nub通知包,通告它的watcher nub正在监听的端口(当它干净地关闭时,它发送一个类似的消息)。

要访问watcher值,可以使用许多工具,但到目前为止最有用的是UNIX守护进程“watcher”。这个守护进程监听任何广播监视器数据包,并从内部列表中添加或删除它们。

守护进程的另一端向它所知道的所有监视节点提供一个HTTP接口。它将观察者节点的名称(包含在注册消息中,例如’client of John’, ‘cell14’等……)插入到观察者超级层次结构的顶层。它将每个观察器树表示为这个顶级目录的分支。请求URL中的路径被转换为监视器值请求路径。该接口可以获取和设置值。

因此,通过使用任何web浏览器连接到这个守护进程,就可以看到本地网络上所有正在运行的客户机,然后调查或操作它们,甚至可以更改实体中Python变量的值。Python变量可以设置为任意Python,在客户端执行该变量以查找其值。如果需要的话,客户端还可以将其watcher nub通知发送给另一个外部地址,因此即使是远程客户端也可以通过这种方式访问。

25.3. Memory tracking

内存跟踪系统可以用来确定分配给类或模块的内存数量。ResourceCounters类是用于执行跟踪的主要类。

25.4. Scripts

使用BigWorld客户端实现游戏可能涉及许多脚本。这些脚本将相互作用,并具有实时的需求和依赖性,因为它们是网络游戏的一部分——在某些情况下,游戏不能简单地停止并逐步通过,而不影响正在调试的客户端的行为。

因此,编写脚本必须以与任何其他编码相同的方式进行,而不是作为c++编码的糟糕替代品。必须正确地设计脚本,并且必须考虑适当地使用类层次结构和其他语言概念。

25.4.1. Python Console

Python控制台可以在游戏期间的任何时候启动。

这是一个看起来和操作都完全像Python解释器的控制台,因此可以在其中输入任意Python。所有的c++模块都可以被访问,因此环境与脚本执行的环境是相同的。

脚本本身没有捕捉到的Python错误和异常(在它们到达c++调用点之前)会输出到控制台,因此是输入到Python控制台本身的命令产生的错误。当游戏中出现错误时,控制台是第一个访问的地方。

Python控制台支持多行命令的方式与标准Python解释器相同,它还支持像UNIX shell一样的宏。宏调用以美元符号($)开始,它们的扩展包含在人格文件中的字典中(参见fantasydemo人格脚本中的示例)。它实现了BWPersonality.expandMacros()回调函数。

该控制台还提供了自动代码完成的功能。按Tab,它试图将当前单词与引用上下文定义的属性匹配,从而自动完成当前单词。如果找到唯一匹配,则在插入点之后添加到它。如果存在多个匹配,则按Tab键循环所有匹配。

25.4.2. Remote Python Console

Python控制台提供的服务也可以通过网络远程使用。

客户端在TCP端口50001上接受使用telnet协议的连接。支持许多telnet选项,例如模拟行编辑。

25.4.3. Script reloading

所有的脚本可以重新加载按Caps Lock+F11。

现有的实体类实例将被更新,以便它们是新类的实例,而不会丢失它们的字典。实体脚本类可以提供一个reload方法来重新获取在重新加载后更改的存储函数引用。例如,player脚本中的reload函数重新计算其键绑定,否则将继续引用旧版本的类中的函数。

当服务器向脚本发送更新时,只有该脚本被重新加载,只有它的实例被更新。

25.4.4. Common errors

下面列出了一些常见的脚本错误:

  • 找不到类<entity>

    在<res>/scripts/client/<entity>.py文件中有一个Python错误。检查当前工作文件夹中的python.log文件。

  • app::init: BigWorldClientScript: init()失败

    Python脚本与<res>/scripts/entity_defs/<entity>.def之间不匹配。

  • EntityType:: init: EntityDescriptionMap:解析失败

    在文件<res>/scripts/entity_defs/<entity>.def或< res > /scripts/ entity_defs / alias.xml文件中使用了未定义的数据类型。

关于实体定义和Python脚本文件的详细信息,请参见 Server Programming Guide’s sections Directory Structure for Entity Scripting → “The Entity Definition File” and Directory Structure for Entity Scripting → “The Entity Script Files。

25.5. Script interactive debugging

您可以将BigWorld Client作为Python扩展模块运行,而不是作为可执行文件运行。这将允许你使用第三方交互式调试器(如Wing IDE和Komodo),或Python的内置调试模块(pdb)来调试你的游戏脚本。

25.6. Client Access Tool (CAT)

CAT提供了一个图形界面,可以在远程或本地客户机上更改监视器值并运行Python控制台命令。它通过客户端提供的远程Python Console服务来实现

25.6.1. Connecting to the client

为了连接到客户端,你必须提供它运行的计算机名和/或IP地址。

如果您同时提供计算机名和IP地址,CAT首先尝试使用计算机名连接,如果连接失败,则使用IP地址。指定本地主机为IP地址中的计算机名或127.0.0.1,以连接到本地客户端。

CAT在启动时自动重新连接到最后一个客户机。

25.6.2. CAT Scripts

CAT在文件夹<res>/../tools/cat/scripts,其中<res>是资源文件夹列表中的一个条目。

例如,如果<res>中的一个条目是C:/mf/fantasydemo/res,那么CAT将在C:/mf/fantasydemo/tools/ CAT /scripts文件夹下寻找脚本。然后,它将呈现脚本的树形视图。

CAT脚本允许查看和操作客户机监视器值,并允许在客户机的Python控制台中执行命令。有关如何创建自己的脚本的信息,请参阅文件夹fantasydemo/tools/cat/ scripts中提供的示例。

25.7. Timing

BigWorld库提供了许多用于分析游戏性能的计时工具。其中最重要的一个就是DogWatch计时器类。

当创建DogWatch时,它被注册在一个全局列表中。无论何时启动计时器,它都会根据启动时正在运行的其他计时器自动进入计时统计层次结构。如果同一个计时器在不同的上下文中运行多次,那么它在层次结构中可以有多个实例。

根计时器被称为帧,并且总是在每一帧的整个帧时间内运行。所有其他计时器实例都是这个计时器实例的子实例。在框架边界处要小心,以确保没有时间不知去向。计时器甚至可以跨帧边界运行。如果是这种情况,它们将暂时停止,然后在下一帧的时间切片中重新开始。

调试控制台中显示了DogWatch计时器层次结构的视图,其中显示了

调试控制台中显示了DogWatch计时器层次结构的视图,其中显示了过去一秒DogWatch时间的平均值。用户可以在层次结构中导航和下钻,该层次结构在单个控制台页面上使用缩进显示。任何计时器实例也可以被绘制成图形,并且可以根据需要同时显示尽可能多的图形。缺省情况下,客户端保留120帧DogWatch计时器历史记录。