Back to Home

Render Diablo3. How it works

Game Development · 3D graphics

Render Diablo3. How it works

How are the graphic engines of popular world-famous games arranged? What technologies are used by developers in the largest gaming companies? Is it really necessary to apply the most advanced technologies of modern 3D graphics to make beautiful game graphics? We will try to answer these questions using the example of a render of a part of the Diablo3 game from Blizzard Entertainment.

I have been involved in the field of game development for a long time, and my hobby is reverse engineering of graphic engines of popular game products. When the long-awaited sequel of the Diablo series came out, I immediately wanted to find out what technologies the developers used in their offspring.

The render of the game is based on Direct3D 9 technology. This allows you to cover a wider hardware base of video cards, and the advanced features that D3D 10 and 11 offer are often either not needed at all, or are implemented in one way or another in the ninth version.

Shadows

For all the static level geometry, calculated lightmaps are used. Yes, yes, the good old method that has been used since the time when 3D accelerators began to support multitexturing.

Image

The lightmap is calculated in advance in the 3D modeling package (3ds max, Maya), or by its own ray tracer in the level editor. One such texture is used for several game objects, or parts of them, if the object is large (for example, terrane).

For dynamic objects (monsters, character figures), dynamic shadows made using the “shadow map” technology are used (stencil shadows are almost never used nowadays). The developers decided to deviate from the classical canons in this area, and did not use hardware shadows (textures that can be used as depth buffers, and supporting hardware Percentage Closer Filtering - PCF), which are offered by all the popular video card manufacturers. Instead, Variance Shadow Maps technology was applied. It allows you to get soft edges by standardly blurring the shadow texture (for classic shadow maps, this method is not applicable, since averaging pixel depth values ​​does not make sense). I won’t describe the details of VSM (see useful links at the end of the article), I’ll only say that for its implementation it is necessary to store 2 values: pixel depth and pixel depth squared. It is the second value that dictates rather stringent conditions for the accuracy of storage of this information, so the texture of the A32B32G32R32 float format was chosen. Its size at maximum settings for the quality of the shadows is 2048x2048.

Image

The process of creating a shadow map is standard. We draw all objects casting a shadow (occluders) into a shadow map from the position of the light source. Blur the shadow map first horizontally and then vertically. When rendering objects that should receive a shadow (receivers), sample the map shade, determine the degree of illumination of the pixel and darken the final color accordingly. Sampling of the shadow map should occur with bilinear filtering. Hardware filtering of the A32B32G32R32F format is not supported by the entire line of shader model 3.0 capable video cards, therefore it is implemented programmatically in the shader (although it is supported on my video card, but this was not taken into account).

Shadow rendering occurs with orthographic projection, if the shadow is from a directional source (sun), or perspective for cone-shaped (omni). Techniques for perspective distortion of the shadow map (for example, Perspective Shadow Maps, Trapezoidal Shadow Maps, etc.) for the camera position used in the game (top-down direction, at a slight angle to the direction of the main light source) are not needed and are not used. Cascading division of shadows into sectors (Cascaded Shadow Maps, Parallel Split Shadow Maps) is not implemented for the same reasons.
512x512 shadow map with and without smoothing:

Image

The shader for the terrain patch in the version with smoothing consists of 12 texture and 59 arithmetic instructions. Without smoothing, 10 and 29, respectively. The difference in arithmetic instructions is the implementation of bilinear filtering and VSM.

Dynamic lighting

Surprisingly, all the dynamic lighting is vertex. Like the good old days. There are no normal maps in the game. A bold decision, but given the final result, it is obvious that he paid off 100%. The lack of detail in the geometry is not at all felt. The vertex shader implements one point light source with quadratic attenuation (as in the classic FFP formulas), one cylindrical source (it is used as a character’s illumination to illuminate the hero’s inner circle) and up to 16 point sources with the simplest linear attenuation over distance.

The game also implements volumetric lighting sources. They are made as follows. We draw a sphere, or other convex figure, in the place of the light source. In the vertex shader, we calculate the alpha of the vertices based on the normal and the camera direction vector. The larger the angle between them, the greater the transparency should be. We get a translucent sphere with an increase in transparency from the center to the edges. Since this sphere will intersect the level geometry, we get a visual artifact of the image at the intersection of the level objects and the sphere itself. This defect is corrected in exactly the same way as the so-called soft particles are made. A sample is taken from the depth buffer and compared with the depth of the rendered pixel. If the values ​​are close, then modifying the alpha (reducing it to zero), we make the intersection of the geometry invisible.

Image

Special effects

Projective texturing can be distinguished from interesting effects. To apply textures of various game spells to the surface of the earth (for example, screams of a barbarian, puddles of poison, fiery paths of monsters, etc.), all these effects are rendered into a separate texture:

Image

Then, all geometry is re-rendered, onto which projective texturing should be performed with using the constructed cumulative image with projected graphic effects. Image mixing is performed on the alpha channel.

For some effects (post-process, in particular), information is needed on the depth of the scene at a given point. Standard Direct3D 9 tools do not allow you to get the depth buffer as a texture for later reading. An obvious option would be to render the entire scene again, with the output of the pixel depth in the texture of the R32F format. This method is in most cases unacceptable, since doubling the rendered geometry will greatly affect the overall performance of the game. Graphics card manufacturers have long been aware of this problem, and offer special texture formats that can be used both as a texture in a shader and as a depth buffer for rendering. One of these formats is the so-called INTZ format. It is then used in Diablo III. This type of texture is used when rendering the scene as depth buffer, and then the values ​​from it can be obtained in shaders, where information about the depth is needed. I don’t know how to render on hardware that does not support INTZ textures (not all shader model 3 video cards support this “hack”), I don’t have a video card without such support. Perhaps an additional pass is performed, or effects, depending on the depth, are implemented differently, or completely turned off.

Highlighting objects under the cursor is implemented by rendering the selected object into a separate texture. In this case, the simplest shader is used - the output of the unit to the alpha channel render the target, and the highlight color in the rgb channels. Then the resulting texture is blurred horizontally and vertically. To correctly apply the effect and obtain the final image, you need to leave only the halo of the object, but not its main silhouette. Having the original (not blurry) image, the final blending shader checks the alpha channel value in this texture. If it is 1 (there is an object in this pixel), then the output alpha channel is set to zero. If the value is 0 (there is no object in this pixel), then the alpha channel of the blurry texture is used.

Image

Post-process effects

The game cannot boast of the amount of post-process effects. Among the entire arsenal were bloom, full screen distortion and full-screen anti-aliasing performed using FXAA technology. Distortion is implemented according to the classical scheme. We draw the particles that should introduce distortion into the final picture (hot air, for example) in a special texture. The recorded data are u and v offsets of texture coordinates. In the next full-screen passage, we use this texture and shift the texture coordinates to sample the main image of the scene.

Image Hosted by ImageShack.us

Full-screen anti-aliasing is also performed as a post-process effect. There are several reasons for this. Using the INTZ depth buffer becomes impossible (you cannot create a pure multisampled INTZ depth buffer for later copying it to a non-multisampled INTZ texture), and the shadow map will take up a lot of memory (I recall that its format is A32R32G32B32F, i.e. 16 bytes per pixel ) Full-screen anti-aliasing in the game is made using Fast Approximate Anti-Aliasing (FXAA) technology.

Image Hosted by ImageShack.us

Geometry and Materials

All data tops of game models are packed in cache-friendly 32 bytes format. The exception is animated models, there are 48. Additional data is the weight of the bones and their indices. The game uses skeletal animation, which is performed in the shader. For this reason, for animated models, the number of point sources is limited to seven, due to the lack of constant registers for storing the parameters of light sources and bone matrices.

The total number of draw call's is small. The value ranges from 300-800 DIP, which is a good indicator.

Shaders are made using uber-shader technology, i.e. compilation of many variants of one effect by enumerating a set of preprocessor defines. For example, the effect can be with fog and without, with shadow and without, with lightmap and without. For fog, shadow and lightmap there is a certain define of the form: #define USE_FOG 1. In the shader body, the code block responsible for applying the fog is executed inside the #if USE_FOG ... #endif block. Thus, switching the value of USE_FOG 1/0, we get a shader with fog and without. All effects are done in a similar way. The assembly system of all shader options automatically iterates over the entire set of define values, and compiles a shader for each set.

User interface

The in-game interface is rendered to the screen quite standardly. A special grouping of elements in order to reduce draw calls (DIP calls) is not observed. I would like to note the rendering of the text. Preparing characters for rendering is very similar to the method used in Scaleform GFX. All unique characters are drawn into a separate texture, and this texture is already used to render text. Despite the similarity of the text render, Scaleform itself is not used.

Afterword

The render itself leaves a good impression. Such a mix of old school and some modern trends. Performance at an altitude with a beautiful picture (as always with games from Blizzard, in fact). A large role in this whole beauty is played by the work of artists and designers. Diablo III once again proves that very beautiful graphics can be done on a not very high-tech render.

Useful links
- Variance Shadow Maps. www.punkuser.net/vsm
- FXAA. developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf
- A list of famous GPU hacks. aras-p.info/texts/D3D9GPUHacks.html

Read Next