原文:D3DTutorial10_Half-Life2_Shading.pdf

m17 转载与编译

 

关于什么是Engine(引擎)?

 

  在Half-Life 完成之后不久,Valve开始筹备他们的下一个主打游戏--Half-Life 2。在商讨游戏引擎的采用问题上,开发小组明白到原版游戏所采用的改良Quake 1引擎对于Half-Life 2来说显得过于破旧,而且在很多的方面都不符合这一代游戏发展的要求。于是,vlave的开发小组决定从美工设计以及游戏代码开始,设计出一个全新的游戏框架,并在其中加入了一套来自havok的定制物理引擎。

  对于一个游戏来说,“引擎”就是一个系统,就像是一辆汽车的发动机一样,他能输入来自加速器的动能,并输出到轮子的转动。软件(在这里为游戏)的引擎实现的东西和汽车的发动机一样,输入来自框架(某些程序或者文件的片段),然后输出程序员们想要的结果。再次拿汽车的发动机为例,一个软件的引擎不能自行运作,他必须依靠另外一个系统,就好像,你只能开着“一台汽车”而非“一台发动机”去上班。在Source中,一个纯粹的引擎只能是一堆代码,完全不具有可玩性。所以不难对引擎下这样的定义:引擎就是一套让游戏元素发生互动关系并将其编译成可玩程序的代码或程式,依靠框架进行操作。



 

Source引擎总览

  Source并不是一套简单的3D引擎,也可以是说,他并非只是一套渲染器。Source引擎包含了很多不同的模组,程序员可以在引擎的程序包中方便地取出以及添加进其他的元素。

  在这篇文章里面,我将会为大家展示这些模组是什么回事并且对游戏产生怎样的影响。在下面将要陈述的问题主要讲解在Source引擎中一些令人惊奇的模组是怎样对整个游戏的画面以及游戏效果产生影响,而不是去解释Source引擎的代码怎样去运作。对于此,可能大家会觉得比较枯燥,毕竟,这些在程序实现上的问题针对的是对游戏有一定研究的玩家。我们并没有打算深入到Source的程序代码进行研究,因为这些已经不属于我们一般老百姓可以研究的范畴了。在这里必须要给读者澄清一下,由于目前Source引擎的非公开性,我们并不能准确地将Source引擎中每一个模组的特性都准确地表示出来,如果你一定要深入研究的话,请参考Half-Life 2发布之后的 SDK 参考文档以及Valve以后的白皮书。本文的章节细分以笔者对Source引擎的了解为依据。请根据实际情况印证并参考其他专著以及文献。

  • 3D 引擎
    • 渲染器
      • Pixel 、vertex shaders
      • 光影效果
      • HDR (High Dynamic Range)
    • 动画以及角色面部表情
    • 几何构成
    • VGUI游戏界面
  • 物理引擎,基于Havok 定制的物理引擎
    • 刚体的动力学模型以及关节约束机制
    • 弹性机构、绳索机构、布纹处理、车辆系统
    • 水以及火光
    • 粒子系统
    • 怪物/NPC/程序 上的物理学系统
  • 材质系统
  • AI 系统


 

在这里,我尚且用3D engine来描述造Source引擎中,生成引擎输出图像及其几何体的模组。

渲染器

  这部分最能体现显卡的功力,也是玩家最为注重的一个重点。渲染器的作用主要主要功能就是采集画面几何体和材质的数据,通过一系列繁杂的过程,生成一个三维的图像。Valve并没有重新创造Source引擎的渲染器而采用了Microsoft DirectX 9.0 的 API,并借助Half-Life SL高阶编程语言编写引擎,在很大程度上节省了宝贵的时间,这归功于 DirectX9良好的硬件兼容性以及先进的代码设计流程。原有的Half-Life 1引擎被设计成支持 OpenGL and Direct3D的双模式,但正如各位所见,这个引擎在Direct3D模式下的渲染十分糟糕,特别是在目前主流的显卡上工作时,其效率以及画质远不及OpenGL模式下的表现。在设计Source引擎的时候,Valve放弃了ogl的渲染模式进而采用DirectX架构,以增强其硬件兼容性以及对未来特效的支持,比如是Shader2.0b甚至是Shader3.0 Model的支持。

Pixel and Vertex shaders

  Shader就是一些能够在GPU/VPU上执行的程序句段。目前市面上流行的游戏中,采用实时渲染的画面均大量运用了Shader,你的显卡无时无刻在处理着这些Shader信息。现代显卡主要的改进就是用Shader代码替代沿用自GeForce GTS时代固定的硬件T&L (Transform and Lighting)。Shader允许程序员创建他们喜欢的Shader特效程序片段以取代有限个数的固化在显卡核心中的预设特效。经过Shader编程之后,如果有一个关于bump mapping的技术推出,程序员只需改写或者加入该部分的Shader代码便能使这些特效运行在现今的显卡上,而不需等待下一代的显卡出现。于是,显卡的处理特性便得以最大程度地延伸。在程序接口方面,由Microsoft推出的HLSL、nVIDIA推出的Cg、以及OpenGL小组推出的GLSL都是编写Shader的工具。能和目前个人PC、工业用电脑上的DirectX以及OpenGL很好地对应。

  Source对directx中的pixel/vertex shaders version 2.0提供了完整的支持,Valve最近的声明指出Source引擎将在未来硬件成熟的时候提供对Shader3.0的支持。目前的Source引擎中,所有的directX 9 Shader均在 (high level shader language)下完成,对代码的运用上只需简单地插入片段,省却了在底层使用汇编语言重编译的麻烦,增加了Source引擎的弹性。在引擎代码方面,所有的代码均用C/C++ using Visual Studio 6.0进行开发,有很大的开放度以及可扩展性,目前已经有数家公司得到Valve授权采用Source引擎进行下一代游戏的开发,除传统的FPS游戏之外,还有aRPG,SPD等模式,相信在明年开初将会见到很多采用Source引擎制作的佳作。

  对于玩家,Shader就能实现依靠驱动程序实现更加多以及更富弹性的特效,在Half-Life 2中最为凸出的Shader2.0特效就是那些吸引眼球的充满反射和折射的水纹效果。在目前推出的基于Source引擎的CS:Source选项设置中单独对水纹效果提供了一个现实效果设置选项。

光影效果:

在 Source 引擎中使用了两类的方法处理光影:辐射度光影贴图和环境光立方体贴图。

  • 辐射度光影贴图主要用于静态的大型的物体,比如地形模型。
  • 环境光立方体贴图用于动态 的模型或模型库中可重复调用的模型,比如各种游戏中的人物角色、家具陈设、各种在游戏中可拿起的具有物理特性的物体。

辐射度光影贴图

  整个引擎的光影效果实现占据了Source引擎的绝大比重,这里只略略讲述Half-Life 2画面的秘诀--辐射度光影贴图。在 Source 引擎的渲染器中,辐射度光影贴图扮演着一个最为重要的角色。先给大家一个辐射度光影贴图所能实现的大概效果:

方向性光照
辐射度光照

  在Source以及整个Half-Life 2架构中,辐射度法线化光影贴图起到的作用就是将真实精确的光影效果(辐射度光照)以及更加细腻的模型细节(法线贴图)通过算法整合到最后的效果图中。

 

辐射度法线贴图的三个不同的偏移法线基线

  在传统的法线贴图中,每次工作只会累积一个光源的效果,多重漫射光将会由法线和光线方向的点积 N*L 多次进行合成。而另一方面处理辐射度光影贴图的时候只能处理一个颜色值。在以辐射度法线贴图预处理每个灯光时,并不直接是和表面的法线做运算,而是和变换到切线空间中的三个不同的偏移法线基线 bumpBasis 做运算,并把运算结果保存到三张光影贴图中。在DirectX中,再由法线从这三张光影贴图中合成,以扩展出相应的光线与颜色……。 这样辐射度法线贴图能在一遍渲染中实现任意多光线的处理,并把法线也加入渲染计算中。在辐射度贴图中,使用下面的方程将法线从多张辐射度法线贴图转换为最终的辐射度法线贴图 :

float3 dp;

dp.x = saturate( dot( normal, bumpBasis[0] ) );
dp.y = saturate( dot( normal, bumpBasis[1] ) );
dp.z = saturate( dot( normal, bumpBasis[2] ) );
dp *= dp;
diffuseLighting = dp.x * lightmapColor1 +
                  dp.y * lightmapColor2 +
                  dp.z * lightmapColor3;

这三个不同的偏移法线基线并不是随便选择的,而是三个长度为 1 的互相垂直的矢量并能均匀的分布在表面上。

 

  在3D物体创建上,一般都会使用cube map制作镜面反射光线,在Source引擎中,cube map会在引擎的渲染器中预先处理,根据嗅探器的作用,在编辑器中创建的环境会选用最近、最贴切的cube map创造反射光线,为解决材质的边界问题,可以手动为某个表面手动添加cube map

  在Hammar这个Half-Life 2地图编辑器中,设计者在材质表面安设了环境探测用的实体以作为镜面反射的参照物。

  接下来将会为大家讲解在Half-Life 2中整个静态环境的生成。首先将会为大家介绍一下我们想得到的效果,并以一个小范围为例说明整个Shader序列的创建过程:

实现的效果可以用下面的流程图进行说明:

  左下脚的Normal法线贴图和三张Lightmap混合后的辐射度贴图再加上漫反射系数的控制就可以创建出以上所说的辐射度法线贴图;再以加法加上传统的表面光滑度以及反射度的贴图结果,就可以生成最终的贴图效果。在Source中创建一个静态的环境时还需要用到三张Lightmap,主要是考虑到为了适应任意光源的作用。在下面的分组图中,大家会看得更加明白:

LightMap #1
LightMap #2
LightMap #3

 三张直接辐射能贴图整合之后的效果,由于缺乏了法线贴图的支撑,整体没有细节可言。

由于这个过程处于贴图的预处理程序,需要考虑到整体执行的效率,因此只采用了分辨率较低的lightmaps贴图(因为diffuse材质的irradiance十分平滑的,只需很小的分辨率,渲染结果是通过filter还原),在这一步骤中,渲染的贴图可以说丢失了所有的细节。如果盲目增大lightmaps的分辨率,处理时间会成倍增加,显存带宽也会被大量占据。

因此,Source引擎将lightmaps分成三个成分,每个贴图都有其对应的基本方程,最后根据法线贴图储存的法线进行混合得出第一步的处理结果,在很大程度上利用了低分辨率的好处,并且能将法线贴图所最擅长的细节表达发挥出来。

另一方面,程序设计员可以采用不同的法线贴图与之结合,在debug模式下实时检查渲染结果变得比以前方便得多。

接下来,Source会将上面由Lightmaps生成的辐射贴图附上法线贴图,

辐射度贴图
法线贴图
被细节化的辐射度贴图

  物体的表面有凹凸的细节,形状方面需要依靠法线贴图实现,至于光线的表现方面,则需要依靠漫反射系数来确定。下面的流程就是继续细化并加上法线贴图的辐射度贴图。

辐射度法线贴图
漫反射系数
被加上漫反射系数的辐射度法线贴图

同时,考虑物体表面反光程度的镜面反射贴图也在进行之中,第一步也是采用立方体反射贴图和法线贴图的混合:

立方体反射贴图
法线贴图
法线贴图加载到镜面反射贴图上
上一步生成的镜面反射法线贴图
镜面反射系数
镜面反射法线贴图和镜面反射系数的混合

最后的步骤,由加上了漫反射系数的辐射度法线贴图和加上了镜面反射系数的镜面反射贴图进行加法运算,得出最后结果

加上了漫反射系数的辐射度法线贴图
加上了镜面反射系数的镜面反射贴图

最后结果

在整个渲染流程中,法线贴图起着引导性作用,ATI目前的顶级产品Radeon X800系列已经完整支持新一代的法线贴图处理方式,并对大材质的贴图采用了全新的3Dc纹理压缩技术。虽然我们目前未收到任何关于Source引擎采用3Dc纹理压缩的消息,但从ATI发布的白皮书来看,X800系列显卡在处理法线贴图的算法技术上会和Valve有更好的沟通,实操效果会占据一定的优势。

环境光立方体贴图
 

    《半条命2》动态物件和人物模型的光影效果

    前面介绍了静态场景的光影效果,下面我们来看看在《半条命2》中的动态模型是如何绘制的。

    核心技术-基于环境光立方体 (Ambient cube)的光能传递凹凸贴图技术

    由于动态模型的位置和形状都有可能发生变化,所以就不能像静态场景那样处理了。动态模型所受到的光照也是由直接光照和间接光照组成的。在Source引擎中,只能从照射一个模型的所有直接光源中选出有两个最主要的来使用比较复杂的光照公式进行实时计算,而其他比较次要的直接光源以及场景中的间接光源都会被预先保存到一个称为“环境光立方体”的东西中。

环境光立方体

    首先Valve的技术人员在关卡场景中放置一个虚拟的立方体,然后地图编辑器就分别对立方体的每个面计算出垂直通过这个面的环境光的颜色和强度。场景里那些比较次要(远或暗)的直接光源,也被计算在内。

    对于每个面都计算一个光照颜色,可以得到六个颜色,基本上代表了从四面八方射来的穿过这个体积中的所有比较次要的光线的信息。这六个颜色是可以预先计算出来保存在关卡场景数据里的。

    通过模型表面的法线来决定取这六个颜色的比重并将它们混合,就可以得到一种类似于光能传递的效果。在环境光立方体的六个面中,必然有至少三个面会在法线的相反方向,这些面的颜色忽略不计。所以一般只有另外三个面的颜色会用到。法线方向越正对哪个面,这个面对应颜色所占的比重就越大。

    用环境光立方体技术渲染的蚁狮模型,虽然没有加入凹凸贴图,但是已经有一种类似光能传递的效果。

从立方体环境贴图中获取光照的着色器代码:

float3 AmbientLight( const float3 worldNormal)
{
   float3
nSquared = worldNormal * worldNormal;
   int3
isNegative = ( worldNormal< 0.0 );
   float3
linearColor;
   linearColor = nSquared.x * cAmbientCube[isNegative.x] +
                 nSquared.y * cAmbientCube[isNegative.y+2] +
                 nSquared.z * cAmbientCube[isNegative.z+4];
   return
linearColor;
}

 
    在前面说道为了结合使用法线贴图,对于静态场景,一个面对应着三张光照贴图。而在这里也是类似的,一个动态模型对应着三个环境光立方体。这三个环境光立方体面上的颜色是分别根据各面局部坐标系的三个基向量来计算出来的。

 a.b.c分别使用相对于三个基向量的环境光照图和环境光立方体的效果

    我们把法线贴图贴到场景中看一看:

 法线贴图

    按照和静态场景类似的方法用法线贴图来寻址环境光立方体的颜色,就可以得到下面的效果了:

    和前面没有凹凸细节的图对比一下吧:

    不要忘了,模型本身也是有材质贴图的。我们把它贴上去看一看:

模型自身的材质贴图,没有光照

    如果把光照效果和它混合,就成了这个样子:

混合操作依然是颜色各通道相乘。角色表面具有凹凸细节。

    和没有使用法线贴图的效果对比一下:

没有凹凸贴图

    漫反射的部分完成后,就该加入高光了。对于角色的高光,Source引擎仍然使用高光环境反射贴图。

完全的高光环境反射,也没有加入凹凸贴图,好一只光滑的金属蚁狮

加了凹凸贴图

高光强度贴图,也是越白的地方高光越强,
我们可以看出蚁狮表面的高光并不是很强的

用高光强度贴图去过滤高光

把高光叠加到漫反射部分上去,得到最后的效果

  点光源处理代码:

   void AddBumpedPointLight( ...)
   {

float3 lightDir = cLightInfo[lightNum].pos-worldPos; // Light direction
float lightDistSquared = dot( lightDir, lightDir); // Light distance^2
float ooLightDist = rsqrt( lightDistSquared); // 1/lightDistance
lightDir *= ooLightDist; // Normalize
float4 attenuationFactors; // Dist attenuation
attenuationFactors.x = 1.0f;
attenuationFactors.y = lightDistSquared* ooLightDist;
attenuationFactors.z = lightDistSquared;
attenuationFactors.w = ooLightDist;
float4 distanceAtten = 1.0f / dot( cLightInfo[lightNum].atten, attenuationFactors);
// Compute N dot L
float3 lambertAtten= float3( dot( worldBumpBasis1, lightDir),
                            dot( worldBumpBasis2, lightDir),
                            dot( worldBumpBasis3, lightDir) );
lambertAtten= max( lambertAtten, 0.0 );
float3 color = cLightInfo[lightNum].color* distanceAtten;
color1 += color * lambertAtten.x;
color2 += color * lambertAtten.y;
color3 += color * lambertAtten.z;

   }

Half-Life2 Episode 1 中的材质更新

调整过的光照曲线

   Half-Life2 Epsisode 1 发布的时候,支持 SM 3.0 的显卡已经大量的上市了。所以可以编写更复杂的效果,对于模型物体来讲,反射的效果又加强了。两个动态光源的高光反射也加入了计算,另外对于支持 SM3.0 的显卡还加入了高光反射指数和菲涅耳反射系数的计算。

前面介绍 Source 引擎会根据物体的位置选择环境立方体反射贴图和环境立方体光线,在 Half-Life2 中物体移动时因为选择了不同的环境立方体,光线的突然变化会显得非常生硬。在 Half-Life2 Episode 1 可以在两个相邻的环境立方体之间做采样过渡来解决这个问题。

 
折射效果:

折射效果用于场景中的玻璃或者水面之类的特效。如果我们把场景中的一个多边形面所挡住的东西画到贴图上,再贴到这个面上去,那么这个面看起来就是透明的了。Source 引擎中使用 StretchRect() 从场景渲染后暂存的帧缓存中复制出一个纹理,就是我们常说的实时渲染到纹理。如果我们在贴这个贴图时做一个扰动,比如加一些水波或者凹凸的扰动,那么这个面看起来就像是水面或者凹凸不平的玻璃的折射的效果了。这个纹理将参照 dudv 凹凸贴图或法线贴图在屏幕空间中做适当的偏移,然后再投射到几何体上。借助法线贴图和最新的DirectX 9.0 Pixel Shader技术,加入这个扰动的过程十分容易实现。

部分着色器代码:

sampler RefractSampler:          register( s2 );
sampler NormalSampler:           register( s3 );
sampler RefractTintSampler:      register( s5 );
const float3 g_EnvmapTint:       register( c0 );
const float3 g_RefractTint:      register( c1 );
const float3 g_EnvmapContrast:   register( c2 );
const float3 g_EnvmapSaturation: register( c3 );
const float2 g_RefractScale:     register( c5 );

struct PS_INPUT
{
float2 vBumpTexCoord: TEXCOORD0;
float3 vWorldVertToEyeVector: TEXCOORD1;
float3 x3tangentSpaceTranspose: TEXCOORD2;
float3 vRefractXYW: TEXCOORD5;
float3 projNormal: TEXCOORD6;
};

float4 main( PS_INPUT i ) : COLOR
{
// Load normal and expand range
float4 vNormalSample = tex2D( NormalSampler, i.vBumpTexCoord);
float3 tangentSpaceNormal= vNormalSample * 2.0 -1.0;
float3 refractTintColor= 2.0 * g_RefractTint * tex2D( RefractTintSampler, i.vBumpTexCoord);
// Perform division by W only once
float ooW= 1.0f / i.vRefractXYW.z;
// Compute coordinates for sampling refraction
float2 vRefractTexCoordNoWarp= i.vRefractXYW.xy * ooW;
float2 vRefractTexCoord= tangentSpaceNormal.xy;
float scale = vNormalSample.a * g_RefractScale;
vRefractTexCoord = vRefractTexCoord * scale;
vRefractTexCoord += vRefractTexCoordNoWarp;
float3 result = refractTintColor * tex2D( RefractSampler, vRefractTexCoord.xy);
return float4( result, vNormalSample.a);
}

水面的视觉效果:

    首先我们来看反射。Source 引擎首先将水面上要反射的景物上下颠倒地渲染到一张贴图上。然后是折射,将水下的景物渲染到另一张贴图上。然后将这两张贴图贴到水面的多边形上,同时根据凹凸贴图,使用 Pixel Shader 对纹理坐标进行调整,以加入扰动,得到水的最终效果!简单吧。 然后将这两张贴图贴到水面的多边形上,同时根据凹凸贴图,使用 Pixel Shader 分别加入扰动,得到水的最终效果!简单吧。如果没有DirectX 9.0中的Pixel Shader技术,想对反射和折射贴图进行象素级的扰动来实现这样的水面效果是十分困难的。在Source引擎中对水面的视觉模拟范例 只采用了30行左右的代码:

最终的效果
反射贴图
折射贴图

float4 main( PS_INPUT i ) : COLOR
{
// Load normal and expand range
float4 vNormalSample= tex2D( NormalSampler, i.vBumpTexCoord);
float3 vNormal= vNormalSample * 2.0 -1.0;
float ooW= 1.0f / i.W; // Perform division by W only once
float2 vReflectTexCoord, vRefractTexCoord;
float4 vN; // vectorize the dependent UV calculations (reflect = .xy, refract = .wz)
vN.xy = vNormal.xy;
vN.w = vNormal.x;
vN.z = vNormal.y;
float4 vDependentTexCoords = vN* vNormalSample.a * g_ReflectRefractScale;
vDependentTexCoords += ( i.vReflectXY_vRefractYX * ooW);
vReflectTexCoord = vDependentTexCoords.xy;
vRefractTexCoord = vDependentTexCoords.wz;
float4 vReflectColor = tex2D( ReflectSampler, vReflectTexCoord) * vReflectTint; // Sample reflection
float4 vRefractColor = tex2D( RefractSampler, vRefractTexCoord) * vRefractTint; // and refraction
float3 vEyeVect= texCUBE( NormalizeSampler, i.vTangentEyeVect) * 2.0 -1.0;
float fNdotV= saturate( dot( vEyeVect, vNormal) ); // Fresnel term
float fFresnel= pow( 1.0 - fNdotV, 5 );
if( g_bReflect && g_bRefract) {
  return lerp( vRefractColor, vReflectColor, fFresnel);
 }
else if( g_bReflect) {
  return vReflectColor;
 } else if( g_bRefract) {
  return vRefractColor;
 } else{
  return float4( 0.0f, 0.0f, 0.0f, 0.0f );
 }
}

色泽贴图(tonemap)

  如果你有一间只有一角被照亮的,灰暗的房子,那么在这个角落里的物体旁边的光线会比房间其他地方要亮。这就是一个”泛光”的现象。如果你将整个房间照亮的话,在这个角落上的物体也会在视觉上出现高于正常的亮度。在这个黑暗的房子的顶棚开一个洞,比如是天窗或者是破旧的穿洞什么的,让外面的阳光直射进屋,这时在天窗或者孔洞的周围就会出现泛光的现象,这一圈的范围会出现很多鲜明生动的色彩。在 Half Life 2 中,这些泛光或光晕是由光泽贴图进行处理的。

由于一般 HDR 也在后期处理的时候使用光泽贴图,所以许多游戏中应用的光泽贴图并且往往被误认为是 HDR。
 

 

HDR (High Dynamic Range)

在 Half Life 2 新放出的 Lost Coast 地图中采用的 HDR 的渲染方式。当开发小组开始考虑通过什么场景来展现 HDR 的超强性能时,海滩成为了我们的首选。 天空、海水以及岩石的视觉关系只有通过 HDR 才能完美重现。 为了获得高动态范围,以便准确模拟光线与周围表面(如潮湿的岩石)之间的相互影响,开发小组需要掌握过去从未了解过的、涉及物体表面的精确信息。每种材质贴图都需要按一个真实的比例进行反射值的调节,比如 一般的材质都不能完全反射或完全吸收光线,所以白色物体的反射值将被降低到 80% 甚至更低而不是 100% 的白色。而黑色物体的反射值被提高到 20% 或更高。因此,开发小组借助 3D 程序对纹理进行了模拟,从而确保纹理中的物理信息让 HDR 能够准确地形成表面的反射光。我们还设计了每种表面的颜色和值,确保它们能够正确涵盖所有曝光量级。

另外由于传统的 24 位色的 RGB 值的范围被限制在 0 到 1 之间,而且单色的精度只有 8 位即 256 个亮度级,很多的反射值在经过大量的运算后就会出现误差。而且按照常规的渲染方法,如果屏幕物体具有 20% 的反光度,例如湿润的沙地,其纹理最亮的地方是赫色的,则最大反射亮度将只有显示器最高亮度的 20%,也就是说不会比这个赫色还要亮。 HDR 更加精确的光线模拟能力将确保阳光照射湿润沙地时产生的反射更加逼真,反射光线可达显示器的最高亮度,也就是说反光可以是白色的。此外,HDR 通过模糊效果来模拟超过显示器最大亮度的光线。

目前ATI的8500以上显卡均已经支持浮点着色以及缓冲区的多对象着色,nVIDIA的显卡也已经从NV40开始支持该功能,能通过硬件实现 HDR 效果 。最简单的来说,只要把光源的格式帧和缓存格式设置为 HDR 的格式,很多近似于现实的物理效果能被模拟出来。

HDR Light Source(HDR光源):
未经压缩的光线数据值令设计人员能够从任何给定的场景中使用更大范围的光线数据值。 这些光源将影响到辐射度光影贴图、辐射度环境立方体贴图的生成。

HDR 光影贴图:
通过辐射度渲染过程产生,将光辐射/全局照明也考虑在内。所有的静态的物体都是使用光影贴图的,所以我们可以在墙上看到这种特效。

半条命2,Lost Coast,HDR,《半条命2》新关卡



半条命2,Lost Coast,HDR,《半条命2》新关卡


HDR Cube Map(HDR立方体贴图):
由引擎生成,使用 HDR 天空盒贴图和 HDR 光源及 HDR 光影贴图合力产生。HDR 立方体贴图让一个物体的反射光线更符合光源的亮度。 这影响到场景中所有的动态的物体。

半条命2,Lost Coast,HDR,《半条命2》新关卡



半条命2,Lost Coast,HDR,《半条命2》新关卡

以上是对应前面介绍的静态物体和动态物体的光影渲染方式对光源进行的 HDR 的改造。其它物体上的贴图,如纹理、法线、反射度等贴图只要进行略微的调整或几乎不需要进行调整就可以在 HDR 渲染中继续使用。

HDR 天空盒:
因为天空盒是渲染完场景物体后在渲染合成的,那么只需要把天空盒的纹理格式换为 HDR 的格式即可。实时曝光调整 将在后期由动态光泽贴图进行处理。

半条命2,Lost Coast,HDR,《半条命2》新关卡



半条命2,Lost Coast,HDR,《半条命2》新关卡

HDR折射效果:

Source 引擎支持的着色范围广泛。窗口中的折射阴影需要我们将场景复制到纹理中,加上折射,然后再重新应用到窗口的表面上。折射实际上是一种用到了帧缓存的纹理特效,为了完全支持 HDR,引擎中的每种着色代码都需要进行更新,从而改进这种折射阴影以支持完整范围的对比 ,其中最重要的就是把帧缓存的格式提升到 HDR 所需要的精度。HDR 光照穿过可折射材料传输时产生的符合这些材料性质的效果(比如阳光照射在LC中修道院的彩色玻璃时产生绚丽的五彩光芒) ,如果不使用 HDR 由于渲染精度问题将造成效果的错误。

半条命2,Lost Coast,HDR,《半条命2》新关卡



半条命2,Lost Coast,HDR,《半条命2》新关卡

 

HDR 水反射/折射:
渲染有水的场景也是一种用到了帧缓存的纹理特效。上文介绍过,事实上场景被渲染 3 次。一次用于反应水下的折射,一次用于反应水上物体的折射,还有一次是从最终视图。在 Lost Coast 中能在两个小的窗口屏幕上看到反射和折射的场景。在折射中,我们逐像素计算您看穿的水有多深,然后制作水下立体雾,模拟颗粒物质。由于 HDR 需要把帧缓存改为 HDR 所需的精度格式,对于完整的 HDR 解决方案,必须仔细检查整个引擎,并修改计算光线和颜色的代码。 例如,这些水反射和折射渲染必须经过改进才能支持 HDR 完整范围的对比值。

半条命2,Lost Coast,HDR,《半条命2》新关卡



半条命2,Lost Coast,HDR,《半条命2》新关卡


当把所有的着色代码改为 HDR 的格式后,就能精确的做一些后期的特效。因为眼睛会对你看到的亮度作出调节,虹膜会不断地受到周遭光源环境的影响,而促使瞳孔扩张和收缩活动,眼镜会让瞳孔处在一个能接受所见景物最高亮度和最低亮度之间的一个平衡点上,并会不断随环境变换而作出调节,视觉区域中,和这个平衡点相距甚远的色彩区域则不会表现该点的细节,而且会和背景以及光线在大气中的折射产生丰富的色彩范围,于是,泛光的区域便会出现。

Exposure Control(曝光控制):

Lost Coast 中的 HDR 解决方案的特色之一就是动态光泽贴图。 简单说来,动态光泽贴图是一种模拟人眼对光的反应的方法。 在现实世界中,当您走进一间黑暗的房间中时,会发现您的眼睛会对黑暗进行调节,以使您在适应一段时间之后看得更清。 或者当您走进一个明亮的空间时,会感到阳光刺眼,只有眼睛调节之后才能正常用眼。 您的虹膜会自动调节以适应射入您眼睛中光的强度。 动态光泽贴图模拟的就是这一过程,通过自动调节场景的曝光程度来模拟虹膜的调节过程。此外,相对于屏幕边缘的光线,我们对屏幕中心的光线考虑的更多,从而能更好地模拟眼睛看到的几何构造。

在渲染的时候,按照 HDR 格式渲染到帧缓存以后,帧缓存被复制为几份。其中一份将被缩小为一个 1x1 像素的贴图以测量整个场景的亮度。因为“眼睛”的瞳孔对视线不同位置的感受不同,所以不同位置的像素缩放是的亮度比例也是不同的,场景中央的像素对测量整个场景的亮度范围影响最大。这个测量出的亮度值将对帧缓存中的图像进行亮度调解,使之变得更亮或更暗,以保证尽可能多的像素亮度在 0 到 1.0 之间。这样模拟了眼睛瞳孔在不同光线下的缩放,令我们在过亮或过暗的场景中看到更多细节。

Bloom(泛光):
由于现在的显示设备不能显示亮度值大于 1.0 的亮度,所以一般使用泛光效果对这些亮度值超过 1.0 的像素进行处理,以模仿曝光过度。帧缓存另外被复制出一份和前面测量出的亮度值进行比较,大于 1.0 的像素被选出进行横向和纵向的高斯模糊处理,然后再与原来的渲染结果进行混合,也就是进行光泽贴图的处理以生成最终的显示结果。


半条命2,Lost Coast,HDR,《半条命2》新关卡



半条命2,Lost Coast,HDR,《半条命2》新关卡

 

 



 

动画、几何体以及游戏界面

动画以及角色面部造型

  就如目前很多的现代游戏引擎一样,角色的动作效果由一个骨骼系统控制。事实上,Half-Life 1 是这个骨骼系统使用的先行者。通过这骨骼系统,多种的动画以及细腻的动作可以被绑定到同一个角色上,由绑定在不同骨架部位的动画互相作用而生成整个角色的动作。Source引擎还在这个骨架系统中整合进Havok先进的物理引擎。

  对于玩家来说,使用附加有物理特性的骨骼系统会让我们看到更加真实的角色动作以及怪物的运动动作。因为在这些骨骼上会混合地播放角色动画,举个例子,一个角色在跑动的时候向对手开枪,然后被对手还击的子弹击中手部,手中的枪脱手飞出……这一系列的动作都能在一个角色上同时上演,加入了物理特性的骨骼系统能对受伤的手部作出反应,比如是承受冲击力时的晃动以及身体的扭转。

面部动画系统

  也许Source中最为神奇的特效来自于引擎中对面部表情的动画,Valve觉得,Half-Life 2里面的角色应该拥有与现实无异的表情,为了达到这个目的,Valve建立了一个包含面部肌肉模拟系统以及一个基于文本文件的半自动声音识别系统。

  在文本识别系统中,Source采用了“Keyshape”的动画,类似于Valve里面资深动画师Bill Buren所描述的渐变以及顶点动画。这套系统中包含了一系列预先设定的表情脚本,能控制角色面肌肉群产生相应的面部动画,并进行相加/混合/插值运算以创建现实生活中所见的自然真实的角色表情。

  为了缩短整个角色动画的构建过程,Valve在Source中创建了一套VRS (Voice Recognition Software,声音识别软件),有了准确自然的角色面部表情,就需要有精密的发音口形与之搭配,于是,一个文本文件会在语音对话中起到指挥的作用,关卡的设计师只需简单地将一个声效文件以及对应的脚本放进Source中,便能让一个角色在适当的时候说话。所有的角色动作会由VRS作出发音单词的判定然后调用该单词的口形,这些基本的口型就被称为Keyshape,通过骨骼系统和顶点渐变过程实现Keyshape之间的动画,于是便产生了精确的发音口形。更为神奇的是这些Keyshape能绑定角色的情感因素,Keyshape根据权重来确定在面部产生表情的正确部位,Valve将其称之为面部肌肉的仿真模拟。

Geometry

  几何模型的体现主要来自网格(例如表现一个物体或者角色的整体),静态网格(非生成整个物体),BSP序列以及一些几何建模实体。在Source中,所有的几何体均被储存在BSP序列中,当你查阅该序列的时候,你可以看到几何体均在 Hammer 地图编辑器中的光影关系,正如上面所说的场景合成一样,所有的辐射度、漫反射系数、镜面反射系数等均在编辑器中生成。

  BSP序列 (Binary Space Partitioning tree) 用以储存在Source里面大量的固态几何体(或许还有一些非固态的),不过,关于BSP序列的创建以及具体的操作将不在本文的讨论范围,各位有兴趣的玩家可以在google上搜索有关地图制作的知识,特别是关于Hammar地图编辑器的使用帮助。

  当我提及“mesh”网格的时候,具体指的是一些将几何体储存在地图文件之外一些独立的文件中,Source采用了.mdl文件来储存mesh网格。mesh多数用在创建能移动的物体或者一些复合的静态物体,主要是因为这种格式的文件能被XSI以及3DMax这些常用作三维建模以及动画设计的软件所导入。此外,.mdl文件还能多次地在场景中出现而避免重复地将几何体插进地图文件,采用了.mdl之后,你就不会再为已经够复杂的BSP序列添加进更多的冗余信息。
 

VGUI

  值得欣喜的是,Valve的图形控制界面将在Source中获得全新的定义。大家都知道,GUI (Graphic User Interface,用户图形界面) 就是和命令行(拼命地打字输入命令)相对的概念,意味着你根本不需要输入任何的命令,因为你有形象的图形操作界面能实现交互的人机操作。VGUI更是让开发者使用Source进行GUI的渲染,能在游戏中任何的地方显示并交互执行命令。这时,VGUI已经不再只限于一套菜单系统,他提供了程序接入窗口、按钮、弹出窗口、内核控制命令行等等控件。所有的这一切能按照程序员甚至玩家的意愿显示或者放置。另一方面,由于Source的VGUI采用了兼容的Unicode编码,能很方便地实现游戏语言以及文字的本地化,在CS:Source中任何地方的中文输入甚至全世界的语言文字输入已经让我们见识到了下一代VGUI的威力。

  在Source中,VGUI能在游戏中以及游戏外面进行显示,Steam的菜单就是一个很好的例子。在CS:Source出版之前,我们还不清楚,STEAM看似革命性的菜单是否就是采用了全新的VGUI编写。到了今天,CS:Source正式公开测试,可以这样下一个判断,CS:S中的,才是真正的VGUI,因为他采用了游戏的渲染引擎进行菜单的润色渲染,并能通过菜单操作所有的界面命令以及内核命令(半透明以及淡出淡入的效果、Debug Option菜单、附着完整信息的Console控制台),而Steam的窗口菜单只是GUI菜单的一个比较完善的改进罢了。据称,Valve将为Source制定的VGUI命名为VGUI2

物理系统

  有一样东西是Valve想在Half-Life 2中需要解决的首要问题,那就是一个能让角色和场景中任何物体都能发生互动关系增加整个游戏可玩性的物理引擎。Valve一直在找这样一个引擎,能让物品能和主角发生互动关系而不再是一个摆设,最后,他们将目光定格到 Havok 的物理引擎上。

  当和Havok整合之后,Source的一切得到了重生般的感觉,引擎里面所描述的所有东西都有了他们独到的物理特性,包括了声音、外观、材质、AI以及角色动画。当Valve被问道,是否会将Havok 1 引擎升级到Havok 2 的时候,Valve指出目前并不会作出此等举动,因为二代的引擎并没有比目前他们所拥有的引擎有更多的性能提升。

刚体动力学 以及 约束、关节链

  正如名称一样,刚性物体不能在游戏中被打碎、弯曲或者其他形式的扭曲。刚体的动力学是目前游戏中最常见的一个物理引擎表现部分--仅仅是非形体变形上的物理模仿,通常会以小盒子、铁桶、临时的木板等形式出现,不过有时候也会以小玩偶的形式出现(通过关节链将刚体组成一个简单的整体)

  关节节点就是常说的活动连接,这种连接允许你将两个物体有机地连接在一齐,你可以为这个关节添加约束,使他们只能在你规定的范围之内活动而不会走远。这就是在游戏中NPC被创建出来的关键。在众多角色中数不清的关节里面,有一些是能够被打断的,而有一些不能,在这些能够被打断的关节上,在被折断前就存在着一个受力的极限值。

  楼房被怪物击中,整个墙壁以及上面挂着的招牌被怪物强劲的冲击波衔翻,在Half-Life 2中似乎到处都充满这种可以被破坏的墙体。

柔性体:

  和刚性体相对的就是柔性以及弹性体,就和Source中的物理特性--作用力与反作用力一致,具体来说就是受力物体对施力物体的一种反作用,起到两者的排斥反应,只不过在弹性力中,这种排斥力是柔和的,加速度相对较小。在和橡胶轮胎的碰撞中,我们就能体验到这种弹性的反作用力。绳索的定义只需在地图编辑器中简单地连接两点并指定其属性即可,最重要的一个属性就是取样点的多少。当然,取样点越多绳结看上去越真实,但会对显示性能造成负面的影响。布料的模拟就类似于绳索,取样点点的多少将直接对布料在物理环境(大风、皱褶等)中的真实程度。当然,布料的模拟将消耗比绳索更加多的CPU资源而且按照目前的3D设计来说,要真实地模拟出一块布料在各种环境中的形态还是未能做到精确。

车辆系统

  车辆系统在Source中就是物理系统和建模系统的有机结合,加入了属于专有的脚本系统以便能让玩家操纵车辆。在Source引擎中的车辆系统由另外一群主要的专业设计师担任设计,以在达到最真实效果之余在最大限度上节省创作时间。毕竟Half-Life 2不是一个专业的赛车游戏,玩家只会用方向键对车辆进行操控,因此,在游戏中出现的航船、气垫船等可操控的交通工具均保持了最高的易用度:和读者想像中最简单的操作方法一样,WSAD控制方向,鼠标控制视觉以及枪械的瞄准。当然,车辆拥有属于自己的一套独立的脚本系统。

水、火

  Source中另外一个令人迷惑的东西就是水的模拟系统。当Valve说,水的效果会在物理的层面被模拟的时候,各界均推断在Source中将会引入流体动力学并加入物体的浮力特性到其物理指标中。但到了后来,Valve在声明中指出,在Source中并没有内建流体动力学的模型,water区域只是一个加入了特别的物理特性的场地。当其他的物体进入到这个区域后,会作出与之正常行为不同的动作,比如是行动变得缓慢、并且会出现重力上的变化(浮力)。当然,如果是真正的流体动力学模拟的话,将会是完全的自身物体特性引起的行为动作而非单纯的两者之间的互相作用。

  火,在Source系统中并非一个独立的系统,但作为一个FPS游戏中不可或缺的部分(特别是Half-Life 2这种充满毁灭性镜头的大作),火的效果是最能吸引玩家的眼球的。在Source中,火混合了粒子系统和材质系统,按照道理来说,在游戏关卡中的火苗如果在旁边出现易燃的物体的话,火将会向这些物体蔓延出去,不过并没有确切的消息肯定这个推断。

粒子系统和材质系统

 

  在Source中,粒子可以理解为“点状物”,常在一些单面的几何面上出现,这种粒子没有区域,没有体积,只能依靠坐标系或者某些参照物来描述其存在的位置,在字面上解,他们就是在空间上的点,是Source中比较容易进行模拟的物体。Source中的粒子除了长宽高的几何尺寸和明显的扭转系统之外,拥有和刚体完全一致的物理特性,毕竟,例子系统并不需要和刚体系统那样丰富的互动性。在游戏中,使用粒子系统创建的最常见的物体就是烟雾、沙尘、火、雨、雪、血雾、火花以及飞溅的碎片。

 

材质系统

  在Source引擎中的材质系统被赋予了物理特性以及贴图外观的特性。例如,给一块板赋予木材的材质,这块板便具有了木材的所有物理以及外观特征,重力、韧性、声学特性等均被附加到这块板上。材质特性方面保留了诸如密度、表面材质、耐压度、以及当折断或者敲击时发出的声响,虽然这些在众人眼中看是一些很简单很必然的事情,但在游戏中,这些细节将会是游戏增加其感染力的最佳途径。此外,AI系统还能就材质系统提供的信息,反馈给NPC以使其对战术作出相应的调整。当然,玩家也可以充分利用材质的特性从而得到更多的通关方法。

木板被赋予了物理刚节点之后,破裂之后的断层不再如一般游戏那样刀切般平整。

AI系统

  AI,Artificial Intelligence,近年来在电脑领域是一个突飞猛进的科研领域,一个优秀的AI系统,能对其周围的环境智能地作出非线性的反应并采取相应的行动。最重要的是具有依据周围环境参数的变更而决定系统行动的能力,否则,就不能称之为真正的AI。一个惯常的AI系统,会使角色根据当前任务的可行性、对主要任务影响的重要性以及目前的周遭环境作出判断以选择达到预定目的的最佳途径,而怎样选择最佳的动作以达到这个目的,就是一个AI系统优秀与否的体现。

聪明的NPC借助矮墙以及队友在对角线火力的掩护下弓身前进。

  老实地说句,笔者并不完全明暸Source的AI系统所能够完成的任务,Valve关于Half-Life 2的白皮书中就说过很多的东西表明Source里面的AI系统是多么的强大,大意就是下面的几点(已经足够让目前每一个游戏汗颜的了)。
 

  • 允许关卡设计师以I/O接口联系游戏中的实体以控制AI系统。
  • 成熟的导航系统,可以实现角色的跑动、飞行、跳跃、下蹲、上楼梯和爬手扶梯甚至在地下的挖掘动作。我们在cz中的机器人已经见识过新一代的导航系统是如何地高超。
  • AI系统能依靠视觉、听觉以及嗅觉感知到物体 的存在
  • AI的关系决定敌友以及其他实体的关系
  • 战斗AI允许AI角色进行团队攻防,他们知道何时推进、何时撤退、何时伏倒寻找掩护等等的战斗动作
  • 如果一个NPC看到你向一个关键的物体跑去的时候,他会判断你对该物体要作出的动作而采取相应的行动
  • 大规模的NPC进攻将一道桥弄垮这些细节将会脱离Valve的剧本随时地,在不可预见的情况下发生
  • AI知道移动重型物体是难以被快速移动的。
  • AI能清楚地知道他看到你的最后位置,并会对你逃跑的路线作出判断,以采取相应行动。
  • NPC会拒绝执行来自玩家的愚蠢的命令
  • AI能使用物理存在的物体。

  在游戏中NPC的AI着实让笔者大吃一惊,比如在TrainStation的一幕,主角刚刚到达City17,被NPC带到一个阴暗的长廊上准备接受洗脑,途中听到一间房间内发出人类疼苦的哀嚎,好奇的主角把头凑过去那个房门上打开的小窗户想了解里面发生什么事情,这时,屋内的NPC会判定屋子里面发生的事情不是主角所应该看到的,于是,NPC会走过来,唰地一声将小窗户关上……类似这种行为判定的例子在游戏中往往会是通关的关键。Source引擎做到了主角和NPC之间很好的互动以及沟通。