游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

分享制作2D角色动态照明的技巧

发布时间:2014-08-29 12:08:06 Tags:,,,

作者:Oliver Franzke

大家都知道,照明可以极大优化游戏的视觉质量。我将在本文描述易于执行并且不需要额外资产(如标准图)的不同动态2D角色照明技巧。我已在如《猴岛特别版》、《Lucidity》以及最近的《断剑》等多款游戏中成功使用这些技巧。

但角色照明在2D游戏中为何如此重要呢?通常情况下,美术人员会直接在场景美术中绘制光亮(以及阴影),这种做法并无不妥,因为其游戏世界是静止的。事实上,我们很难估计照明的用途,如实时背景图片(因为我们无法获知如标准方向等空间信息)。而角色在游戏世界中移动,并且在含不同照明条件的多个地点均具有可视性,所以一般不太可能在精灵(或纹理)中直接绘制出光源的效果。因此唯一的解决方法就是在运行时间中计算角色照明。

环境照明

环境照明背后的理念就是让角色看起来像是游戏世界的一部分而不是游离于其之上的物体。所以,当玩家从一个阴暗的地方转向一个完全照明的地方时,角色的视觉形象就会从暗转亮。

所幸我们可以通过给精灵(或纹理)染色来轻松实现这一点。使用渐变染色可以让我们独立控制或低或高部分的色彩,这就有利于效仿地面和角色之间的环境闭塞效果。

环境渐变染色是《断剑》照明系统的主心骨,运用于每个角色的每个场景。

我们可以用不同的方法为角色身体增添渐变色。最简单的方法就是使用边界框,即头顶使用渐变顶端,而脚部使用渐变的底部。这种方法极适用于基于精灵的角色,但并不太容易控制渐变的形状。

《断剑》运用了截然不同的方法,它计算了从边界框转化中心到每个顶点的矢量标准值。之后再根据所得标准值的Y坐标轴来抽取环境渐变。这种技巧不但极适用于剥皮几何体,它还可以通过更改中心的位移来决定身体添加渐变的速度。

局部照明

局部照明的目标是显示附近光源的影响。换句话说,它所要回答的问题就是角色与光照的位置关系。《猴岛特别版》中的一幕场景就呈现了篝火局部照明点亮了两位角色的效果。

monkey_island._special_edition(from gamebuy)

monkey_island._special_edition(from gamebuy)

如果你凑近一点看,就会发现篝火只点亮了角色脸所朝向的那部分。我们可以用不同的方法实现这种效果。《断剑》中的角色手动创造了之后用于计算反射光的标准图。虽然绘制标准图让美术人员得到许多艺术创作自由,但显然也需要投入大量的创作时间。

在《猴岛特别版》中,我们由于开发计划的时间限制无法采用这种方法,所以我们需要采用一种无需额外资产,只要少量调整的技巧。我们的解决方案就是利用包含附近光源照明的屏幕空间光照图。

为了计算光照图,我们首先要在缩减采样和用高斯核模糊化之前将角色的alpha面具渲染到画面外的渲染目标。所得图像可以理解为一个包含二进制大对象近似角色的软化高度域。这样我们就可以像高度域坡度一样计算二进制大对象近似值的标准值。现在有了这个标准信息,我们就可以通过从所有附近光源积累反射照明来计算光照图。在《猴岛特别版》中,我们可以使用极为简单的Labertian照明模型,但也可以轻松使用一种更为复杂的模式。

其中所得照明数据可以用不同方式与场景相结合。最简单的解决方案就是使用额外混合在帧缓存的顶端位块传输光照图。我们在《猴岛特别版》中使用了这个方法并发现它甚为管用,但其劣势就在于反射光在明亮的像素中几乎不可见。

另一个影像合成战略就是从角色着色器的光照图中抽取照明。这样光照就可以用不同方式与精灵的纹素(或纹理)相结合,所以角色能够有效呈现多变的反射性。

光源

我还没有提到的一点就是光源的创造方式。它应该很容易以特定参数同游戏世界的不同部分相结合,以便匹配角色照明。

径向基函数一样的代表光源同时适用于环境和局部照明。每个角色的渐变染色都可以像基于与光源距离的加权平均值来计算。就我的经验来看,这也是展示一些全局照明的好方法。

光照图可以通过绘制像近似形状到渲染目标的光源来计算。每个输出片断的色彩和强度都是通过评估你所选择的光照模式来计算。在《猴岛特别版》中我们计算反射光的方法如下:

vec l = light_pos – fragment_world_pos
vec n = tex2D(normal_map, fragment_screen_pos)
scalar nDotL = saturate(dot(normalize(l), n))
scalar dist = length(l)
vec result = light_color * nDotL / (dist * dist)

使用径向基函数的另一个好处就在于,它极易通过更改变形(如位置、比例)以及参数(如色彩、强度)来制作光源动画。你可以通过在两个状态间使用一个良好的噪声函数来实现闪烁的篝火这种效果。将这种光源置于角色手中就可以有效制作一个很酷的火炬。

总结

动态角色照明有助于制作游戏世界及其中居民,以便让它们看起来更具可信度和趣味性。所幸我们可以相当简便地执行能够产生良好视觉效果的照明系统,希望本文能够为你提供一些实用建议和启发。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

Dynamic 2D Character Lighting

by Oliver Franzke

It is certainly no secret that lighting can improve the visual quality of a game tremendously. In this blog post I’ll describe various techniques for dynamic 2D character lighting that are very easy to implement and don’t require any additional assets (e.g. normal maps). I have successfully used these techniques in various games such as Monkey Island: Special Edition,Lucidity and most recently in Broken Age.

But why is character lighting important in 2D games? Very often artists will paint lighting (and shadowing) directly into the environment artwork, which is okay because the world is generally static. In fact it would be very difficult to compute the lighting for, let’s say, a background image in real-time, since no spatial information (e.g. z-depth, normal direction) is available. Characters on the other hand move through the world and will be visible in various locations with different lighting conditions, so it is generally not possible to paint the influence of light sources directly into the sprites (or textures). Therefore the only solution is to calculate the character lighting during run-time.

The video below demonstrates how Shay (who is one of the main characters in Broken Age) looks with and without lighting. I hope you’ll agree that lighting helps to integrate Shay into (the beautiful) world.

Ambient Lighting

The idea behind ambient lighting is to make a character look like they are part of the game world rather than floating on top of it. So if, for example, a player moves from a shadowed into a fully lit area the visual appearance of the character should change from dark to bright.

B_D2DCL_Ambient_Gradient

Thankfully this can be achieved very easily by tinting the sprite (or texture). Using a gradient tint allows independent control of the color of the lower and upper part of the body, which makes it possible to emulate ambient occlusion between the floor and the character.

Ambient gradient tinting is the backbone of Broken Age’s lighting system and is used in every scene for every character. The following picture demonstrates how the ambient gradient changes based on Shay’s location of Shay.

B_D2DCL_Ambient_Gradient_Sampling

There are different ways to interpolate the gradient color across the body of a character. The simplest approach is to use the bounding box in which case vertices on the head sample from top of the gradient whereas the feet use the bottom of the gradient. This approach works great for sprite-based characters, but doesn’t offer much control over the shape of the gradient.

B_D2DCL_Normals

Broken Age applies a different strategy by computing normals as the vectors from the translated center of the bounding box to each vertex. The ambient gradient is then sampled based on the y-coordinate of the resulting normal. Not only does this technique work great with skinned geometry it also makes it possible to define how quickly the gradient interpolates across the body by changing the offset of the center. My GDC Europe talk ‘Broken Age’s Approach to Scalability’ contains more information about this approach, so please feel free to check it out if you want to know more about it.

Local Lighting

The goal of local lighting is to show the influence of nearby light sources. In other words it answers the questions where the characters are in relation to the lights. The video of Monkey Island: Special Edition (MISE) below shows how the local lighting of the campfire illuminates the two characters. Please make sure to watch it fullscreen and in high definition as the effect is quite subtle.

If you look closely you’ll notice that the campfire only illuminates the parts of the characters that are facing towards it. There are different ways to achieve this look. The characters in Broken Age have hand authored normal maps that are then used to compute the reflected light. While painted normals offer a lot of artistic freedom they obviously require a lot of time to create.

In MISE we weren’t able to go down this route due to time constraints of the development schedule, so we needed a technique that required no extra assets and only a minimal amount of tweaking. Our solution utilizes a screen-space light map that contains the illumination of nearby light sources. The following image shows the light map for the lookout scene.

B_D2DCL_Lookout_Lightmap

In order to calculate the light map we first render the alpha mask of the characters into an offscreen render-target (see image A below) before downsampling and blurring it using a Gaussian kernel (image B). The resulting image can be interpreted as a soft height field containing a blobby approximation of the characters. With this in place we can now compute the normals of the blob approximation as the gradient of the height field (see image C). Now that normal information is available we can finally calculate the light map by accumulating the reflected illumination from all nearby light sources (image D). In MISE we used a very simple Labertian lighting model (aka ‘n dot l’), but more a sophisticated formula can easily be used instead.

B_D2DCL_Lightmap_Calculation

The resulting lighting data can be combined with the scene in different ways. The easiest solution is to blit the light map on top of the framebuffer using additive blending. We used this approach in MISE and it worked great, but has the disadvantage that reflected light is barely visible on bright pixels (e.g. Guybrush’s shirt).

Another compositing strategy would be to sample the illumination from the light map in the character shader. This way the light can be integrated in different ways with the texels of the sprite (or texture), so characters could effectively have varying reflectivity. In this case the light map essentially represents the results of a light pre-pass.

Light Sources

One thing I haven’t talked about up until now is how light sources are authored. It should be easy to associate different parts of the world with specific parameters in order to be able to match the character lighting with the illumination painted into the environment.

B_D2DCL_RBF_Blending

Representing light sources as radial basis functions works great for ambient as well as local lighting. The gradient tint for each character can easily be computed as the weighted average based on the distance from the light sources. In my experience it is also a good idea to expose some kind of global light, which is sampled if there are no light sources nearby. The following image illustrates the radial basis function blending for ambient gradient tints.

The light map can be calculated by drawing the light sources as approximated shapes (e.g. quad or circle) into the render-target. The color and intensity of each output fragment is computed by evaluating the lighting model of your choice. In MISE we calculated the reflected light basically like this:

vec l = light_pos – fragment_world_pos
vec n = tex2D(normal_map, fragment_screen_pos)
scalar nDotL = saturate(dot(normalize(l), n))
scalar dist = length(l)
vec result = light_color * nDotL / (dist * dist)

Using radial basis functions has the additional benefit that it’s very easy to animate a light source both by changing its transformation (e.g. position, scale) as well as parameters (e.g. color, intensity). A flickering campfire can easily be achieved by interpolating between two states using a (nice) noise function. Attaching this light source to the hand of a character effectively makes it a cool looking torch.

Conclusion

Dynamic character lighting helps to make a game world and its inhabitants look more believable and interesting. Thankfully it’s relatively easy to implement a lighting system that produces nice looking results and I hope that my blog posts could provide you with some inspiration as well as practical advice. (source:gamasutra


上一篇:

下一篇: