Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
The Technology behind 
Jarosław Pleskot 
Senior Engine Programmer 
Flying Wild Hog 
2014
Facts about Shadow Warrior 
 published by Devolver Digital 
 18 months production time 
 team ~35 people (2 tech progra...
Presentation overview 
Act I, Skinned decals generation and rendering 
Act II, Foliage authoring and rendering 
Act III, S...
Act I, Skinned decals
Skinned decals – entry point 
 In Hard Reset decals only on non-skinned geometry 
(static or movable) 
 Characters destr...
Skinned decals – two techniques 
1. Deffered decals [1][2] 
+ mesh generation not needed 
+ small amount of data to store ...
Skinned decals – two techniques 
2. Geometry based [3] 
+ stable result when animating mesh 
+ cover only desired surface ...
Skinned decals – two techniques 
2. Geometry based [3] 
+ stable result when animating mesh 
+ cover only desired surface ...
Skinned decals – input data 
 Load vertex buffer into CPU memory 
 Generating adjacency per each triangle in mesh 
 3 a...
Skinned decals – generation 
 Asynchronous (job based) 
 Copy decal parameters and skinning matrices to job 
Basic algor...
Skinned decals – generation 
1. Find triangle closest to hit point 
 Don't want to process entire mesh 
 Skeleton and we...
Skinned decals – generation 
2. Expand decal 
add hit triangle to “open” list 
while open list not empty 
{ 
pop front and...
Skinned decals – generation 
2. Expand decal 
add hit triangle to “open” list 
while open list not empty 
{ 
pop front and...
Skinned decals – dismemberment 
 Character dismemberment 
implemented 
 Decals must be split, how?
Skinned decals – dismemberment 
First version 
Store decal spawn info, 
recompute on 
destruction 
- additional CPU time 
...
Skinned decals – dismemberment 
Second version 
 Character dismemberment is hand-made 
by creating separate 
meshes 
 Mo...
Skinned decals – dismemberment 
Second version 
+ no recomputation 
+ split decals available instantly 
- more draws for i...
Skinned decals – rendering 
 Rendered through dynamic vertex buffers 
 One pass (compose) or 2 passes (normal+compose) 
...
Skinned decals – details 
 Decal size hard limit: 10k vertices 
 Decal count limit: 100 decals (FIFO) 
 Vertex memory: ...
Act II, Foliage system
Foliage system – entry point 
• In Hard Reset vegetation only in one area of 
one DLC level 
• In Shadow Warrior many open...
Foliage system – entry point 
Requirements: 
● Instancing 
● Specific LoD system 
● Easy to plant (levels created in 3dsma...
Foliage system – entry point 
Requirements: 
● Instancing 
● Specific LoD system 
● Easy to plant (levels created in 3dsma...
Foliage system – planting 
Spawn meshes for level 2 in 3ds Max
Foliage system – planting 
• Render spawn meshes in top-down view to an 
image (density, position.z and normal) 
• In 50x5...
Foliage system – planting 
Many levels of foliage possible by splitting 
spawn meshes to separate 3dsmax objects
Foliage system – editor
Foliage system – storage 
Initially one quad tree per map, batch index 
and LoD level stored with transformation 
Changed ...
Foliage system – storage 
Grid node contains min/max Z coord and object 
ranges for low and high density arrays 
Transform...
Foliage system – storage 
typedef UInt64 Vec3Packed64; 
#define POSITION_PACKED_PACK_SCALE 200.0f 
#define POSITION_PACKED...
Foliage system – storage 
inline Vec3 Vec3Packed64Unpack( const Vec3Packed64 vecPacked, Float unpackScale ) 
{ 
Vec3 resul...
Foliage system – rendering 
• Dynamic vertex buffer, 8192 instances max 
• Several batches (one batch == all visible 
obje...
Foliage system – details 
• Gather time for 8192 instances in 9 batches: 0.41 ms on Intel 
core i7 3.4 GHz 
• GPU time: 1....
Foliage system – results 
~4200 instances 
gathered
Foliage system – results 
~6400 instances 
gathered
Foliage system – results 
Not only plants. Our artists are very creative!
Act III, Seawater
Seawater – entry point 
• Docks location with stormy weather planned 
in Shadow Warrior 
• DX9 renderer (no hw tesselation...
Seawater – mesh 
• 3 LoD levels (quad size: 0.5 x 0.5, 2x2, 8x8 
meters, LoD 0 dims: 48x48 meters) 
• Edge vertices stretc...
Seawater – vertex shader 
Position processing 
Distortion Filter Asymmetry Choppiness 
Add vertex 
texture 
Flatten 
edges...
Seawater – distortion 
float4 fWaterGeomWave0; // xy - frequency, z - speed, w – amplitude 
… 
float DistortionFunc( float...
Seawater – waves 
• Sea waves are the signal, mesh is a sampling 
mechanism 
• Nyquist theroem: sampling frequency must be...
Seawater – waves 
Solution: 
• Calculate cuttoff frequency for each vertex 
• Pass it to a shader as a vertex attribute 
•...
Seawater – filter 
Diagonal direction has lowest 
sampling frequency 
Lerp cutoff frequencies on 
LoD boundaries 
Only fc0...
Seawater – filter 
float DistortionFilter( vert_in i, float2 waveFreq ) 
{ 
float waveFreqEff = length( waveFreq.xy ); 
fl...
Seawater – filter 
float DistortionFilter( vert_in i, float2 waveFreq ) 
{ 
float waveFreqEff = length( waveFreq.xy ); 
fl...
Seawater – wave asymmetry 
float distort = 0.0; 
distort += FILTER_0( DISTORTION_0( arg0 ) * fWaterGeomWave0.w ); 
… 
// F...
Seawater – wave asymmetry 
// ASYMMETRY AND CHOPPINESS 
#define ASYMMETRY( arg, power ) ( arg > 0.0 ? power * arg*arg : 1....
Seawater – vertex texture 
• Displace vertices with perlin 
noise to avoid wave tiling 
• Only LoD 1 and LoD 2 
• Calculat...
Seawater – pixel shader 
• Translucent at the begining, changed to opaque 
later on 
• 2 x diffuse (water + foam) 
• 2 sli...
Seawater – results 
• GPU time 0.80 ms (0.02 ms mask, 0.25 ms normal, 
0.53 ms compose) @ Radeon R9 270, 1920x1080 
• 0 pi...
Special thanks 
Łukasz Zdunowski – Lead Artist 
Zbigniew Siatecki – Environment Artist 
Dominik Misiurski – FX Artist 
Art...
References 
1. http://broniac.blogspot.com/2011/06/deferred-decals.html 
2. http://humus.name/index.php?page=3D&ID=83 
3. ...
Upcoming SlideShare
Loading in …5
×

The Technology behind Shadow Warrior, ZTG 2014

The presentation covers three engine systems created for new Shadow Warrior: skinned decals, vegetation, and seawater rendering.

Subsequent presentation here:
https://www.slideshare.net/jaros_p/shadow-warrior-2-and-the-evolution-of-the-roadhog-engine

  • Be the first to comment

The Technology behind Shadow Warrior, ZTG 2014

  1. 1. The Technology behind Jarosław Pleskot Senior Engine Programmer Flying Wild Hog 2014
  2. 2. Facts about Shadow Warrior  published by Devolver Digital  18 months production time  team ~35 people (2 tech programmers, 6 total)  modified Hard Reset’s engine (Roadhog)
  3. 3. Presentation overview Act I, Skinned decals generation and rendering Act II, Foliage authoring and rendering Act III, Seawater rendering
  4. 4. Act I, Skinned decals
  5. 5. Skinned decals – entry point  In Hard Reset decals only on non-skinned geometry (static or movable)  Characters destruction by showing/hiding parts of a model or by changing texture  Lot of blood and gore in Shadow Warrior, must have skinned decals
  6. 6. Skinned decals – two techniques 1. Deffered decals [1][2] + mesh generation not needed + small amount of data to store and pass to graphics card - decal floats when mesh is animated - can be projected on other surfaces, need to mask out (additional gbuffer usage or additional passes) Source: http://broniac.blogspot.com/2011/06/deferred-decals.html
  7. 7. Skinned decals – two techniques 2. Geometry based [3] + stable result when animating mesh + cover only desired surface - mesh generation: time and memory - additional input data required to generate a decal
  8. 8. Skinned decals – two techniques 2. Geometry based [3] + stable result when animating mesh + cover only desired surface - mesh generation: time and memory - additional input data required to generate a decal
  9. 9. Skinned decals – input data  Load vertex buffer into CPU memory  Generating adjacency per each triangle in mesh  3 adjacent triangles  Mesh can consist of many isolated elements => adjacency groups (store first triangle index of each adjacency group) struct STriangleAdjacency { UInt32 m_adj0; UInt32 m_adj1; UInt32 m_adj2; UInt32 m_group; };
  10. 10. Skinned decals – generation  Asynchronous (job based)  Copy decal parameters and skinning matrices to job Basic algorithm for all adjacency groups { find triangle closest to hit point expand decal by adding adjacent triangles until size reached and calculate UVs and TBN for each new triangle }
  11. 11. Skinned decals – generation 1. Find triangle closest to hit point  Don't want to process entire mesh  Skeleton and weights == skinned mesh is naturally divided  In preprocess step create triangle list for every bone  Iterate through selected and adjacent bones' lists  Use skinning matrices to get worldspace positions
  12. 12. Skinned decals – generation 2. Expand decal add hit triangle to “open” list while open list not empty { pop front and calculate its vertices’ positions if any inside decal (bounding box test, sizeZ = max( sizeX, sizeY )) { add triangle to the output list with new UVs and TBN add adjacent triangles to “open” list if not already processed } }
  13. 13. Skinned decals – generation 2. Expand decal add hit triangle to “open” list while open list not empty { pop front and calculate its vertices’ positions if any inside decal (bounding box test, sizeZ = max( sizeX, sizeY )) { add triangle to the output list with new UVs and TBN add adjacent triangles to “open” list if not already processed } } Special case for first triangle: If triangle field > decal field always pass bounding box test
  14. 14. Skinned decals – dismemberment  Character dismemberment implemented  Decals must be split, how?
  15. 15. Skinned decals – dismemberment First version Store decal spawn info, recompute on destruction - additional CPU time (5 enemies destroyed at once can produce 50 jobs) - cannot show anything until recomputed decal arrive == blink
  16. 16. Skinned decals – dismemberment Second version  Character dismemberment is hand-made by creating separate meshes  Modify spawn algorithm – create separate decal chunks for every visible mesh  Input: adjacency per chunk  On cutting create new decals that references initial decal geometry  Render proper decal chunks
  17. 17. Skinned decals – dismemberment Second version + no recomputation + split decals available instantly - more draws for initial decal (merge on render) Modified algorithm for all visible chunks { for all adjacency groups { find triangle closest to hit point expand decal chunk by adding adjacent triangles until size reached and calculate UVs and TBN for each new triangle } }
  18. 18. Skinned decals – rendering  Rendered through dynamic vertex buffers  One pass (compose) or 2 passes (normal+compose)  Possible animation through alpha test level shifting (lower alpha test reference value == bigger decal)
  19. 19. Skinned decals – details  Decal size hard limit: 10k vertices  Decal count limit: 100 decals (FIFO)  Vertex memory: 30 MB total, in pool  Typical bullet decal (500 triangles) spawn time around 0.5 ms on Intel core i7 (async, still can do better)  Big decals == skinned geometry rendered multiple times, avoid them, use other techniques, e.g. texture layering  Use „clamp to border color” with alpha 0.0
  20. 20. Act II, Foliage system
  21. 21. Foliage system – entry point • In Hard Reset vegetation only in one area of one DLC level • In Shadow Warrior many open levels: forests, villages, towns, etc. • Vegetation made as level geometry == no LoD, no instancing, hard to create and control (overdraw)
  22. 22. Foliage system – entry point Requirements: ● Instancing ● Specific LoD system ● Easy to plant (levels created in 3dsmax, gameplay in game editor)
  23. 23. Foliage system – entry point Requirements: ● Instancing ● Specific LoD system ● Easy to plant (levels created in 3dsmax, gameplay in game editor) Spawn meshes – meshes with relative foliage density stored as vertex color
  24. 24. Foliage system – planting Spawn meshes for level 2 in 3ds Max
  25. 25. Foliage system – planting • Render spawn meshes in top-down view to an image (density, position.z and normal) • In 50x50cm blocks generate random plant positions (ρ = ρmesh * ρblock, pos.z interpolated) • Set random yaw • Optionally align with normal vector • Store packed matrix All random values are static!
  26. 26. Foliage system – planting Many levels of foliage possible by splitting spawn meshes to separate 3dsmax objects
  27. 27. Foliage system – editor
  28. 28. Foliage system – storage Initially one quad tree per map, batch index and LoD level stored with transformation Changed to multi resolution grids (2 levels: 4x4 and 64x64 meters, one grid per batch)
  29. 29. Foliage system – storage Grid node contains min/max Z coord and object ranges for low and high density arrays Transformation packed into 32 bytes struct SObject { Half4 m_plane0; // 8 Half4 m_plane1; // 16 Half4 m_plane2; // 24 Vec3Packed64 m_position;// 32 };
  30. 30. Foliage system – storage typedef UInt64 Vec3Packed64; #define POSITION_PACKED_PACK_SCALE 200.0f #define POSITION_PACKED_UNPACK_SCALE ( 1.0f / POSITION_PACKED_PACK_SCALE ) // 21 bit max #define VEC3PACKED64_MASK 0x00000000001fffff #define VEC3PACKED64_SIGN_RECOVER_SHIFT 11 inline Vec3Packed64 Vec3Packed64Pack( const Vec3& vec, Float packScale ) { return ( ( UInt64( vec.X * packScale ) & VEC3PACKED64_MASK ) << 42 ) | ( ( UInt64( vec.Y * packScale ) & VEC3PACKED64_MASK ) << 21 ) | ( UInt64( vec.Z * packScale ) & VEC3PACKED64_MASK ); } 21 bits per component ± 5,2km with 2 mm resolution
  31. 31. Foliage system – storage inline Vec3 Vec3Packed64Unpack( const Vec3Packed64 vecPacked, Float unpackScale ) { Vec3 result; { Int32 value = Int32( vecPacked & VEC3PACKED64_MASK ); value <<= VEC3PACKED64_SIGN_RECOVER_SHIFT; value >>= VEC3PACKED64_SIGN_RECOVER_SHIFT; result.Z = Float( value ) * unpackScale; } { Int32 value = Int32( ( vecPacked >> 21 ) & VEC3PACKED64_MASK ); value <<= VEC3PACKED64_SIGN_RECOVER_SHIFT; value >>= VEC3PACKED64_SIGN_RECOVER_SHIFT; result.Y = Float( value ) * unpackScale; } { Int32 value = Int32( ( vecPacked >> 42 ) & VEC3PACKED64_MASK ); value <<= VEC3PACKED64_SIGN_RECOVER_SHIFT; value >>= VEC3PACKED64_SIGN_RECOVER_SHIFT; result.X = Float( value ) * unpackScale; } return result; } Shift arithmetic right!
  32. 32. Foliage system – rendering • Dynamic vertex buffer, 8192 instances max • Several batches (one batch == all visible objects with the same mesh and materials) • LoD levels: Low – 20% density, range multiplier x1 High – 100% density, range multiplier x1 Ultra – 100% density, range multiplier x2 • Gather with Z range ±15 meters • Dissolve out on last 5 meters
  33. 33. Foliage system – details • Gather time for 8192 instances in 9 batches: 0.41 ms on Intel core i7 3.4 GHz • GPU time: 1.22 ms (0.89 normal + 0.33 compose, 730k + 470k PSPixelsOut) on Radeon R9 270, 1920x1080 • Memory usage: from 0.7 to 10 MB per level
  34. 34. Foliage system – results ~4200 instances gathered
  35. 35. Foliage system – results ~6400 instances gathered
  36. 36. Foliage system – results Not only plants. Our artists are very creative!
  37. 37. Act III, Seawater
  38. 38. Seawater – entry point • Docks location with stormy weather planned in Shadow Warrior • DX9 renderer (no hw tesselation) • Dedicated translucent water shader used in Hard Reset (simple waves, refraction, water fog, foam)
  39. 39. Seawater – mesh • 3 LoD levels (quad size: 0.5 x 0.5, 2x2, 8x8 meters, LoD 0 dims: 48x48 meters) • Edge vertices stretched beyond camera far Z • 33k tris total • Mesh moved with camera, snapped to integer world coordinates (constant sampling positions) • Stencil test
  40. 40. Seawater – vertex shader Position processing Distortion Filter Asymmetry Choppiness Add vertex texture Flatten edges Distortion derivative Filter Flatten edges Modulate Normals Distortion derivative w. phase offset Filter Bias and modulate Foam multiplier Affect and orthogonalize TBN
  41. 41. Seawater – distortion float4 fWaterGeomWave0; // xy - frequency, z - speed, w – amplitude … float DistortionFunc( float arg, float4 params ) { float modBase = 0.5 + sin( arg * params.x ) * 0.5; float modArg = modBase * params.y - params.y; float modAmp = modBase * params.z - params.z; return sin( arg * ( 1.0 + modArg ) ) * ( 1.0 + modAmp ); } float DistortionDerivativeFunc( float arg, float4 params ) { float modBase = 0.5 + sin( arg * params.x ) * 0.5; float modArg = modBase * params.y - params.y; float modAmp = modBase * params.z - params.z; return cos( arg * ( 1.0 + modArg ) ) * ( 1.0 + modAmp ); } Randomize waves reusing wave parameters (HACK) #define DISTORTION_0( arg ) DistortionFunc( arg, fWaterGeomWave0FunctionParam ) … #define D_DISTORTION_0( arg ) DistortionDerivativeFunc( arg, fWaterGeomWave0FunctionParam ) … float arg0 = dot( posWS.xy, fWaterGeomWave0.xy ) + time * fWaterGeomWave0.z; …
  42. 42. Seawater – waves • Sea waves are the signal, mesh is a sampling mechanism • Nyquist theroem: sampling frequency must be at least 2 times higher than peak signal frequency to avoid aliasing • Different LoD == different sampling frequencies
  43. 43. Seawater – waves Solution: • Calculate cuttoff frequency for each vertex • Pass it to a shader as a vertex attribute • Filter waves generated in vertex shader using this frequency limit struct WaterVertex { Vec3 m_pos; Half2 m_uv0; Half2 m_uv1; Float m_geomSoftness; Float m_waveFreqLimit; };
  44. 44. Seawater – filter Diagonal direction has lowest sampling frequency Lerp cutoff frequencies on LoD boundaries Only fc0 and fc1 used in practice
  45. 45. Seawater – filter float DistortionFilter( vert_in i, float2 waveFreq ) { float waveFreqEff = length( waveFreq.xy ); float val = -2.5 / i.m_waveFreqLimit * ( waveFreqEff - i.m_waveFreqLimit * 0.8 ); float filter = saturate( 0.5 + 0.5 * val ); return filter; }
  46. 46. Seawater – filter float DistortionFilter( vert_in i, float2 waveFreq ) { float waveFreqEff = length( waveFreq.xy ); float val = -2.5 / i.m_waveFreqLimit * ( waveFreqEff - i.m_waveFreqLimit * 0.8 ); float filter = saturate( 0.5 + 0.5 * val ); return filter; } #define CALC_FILTER_0 float filter0 = DistortionFilter( i, fWaterGeomWave0.xy ) #define CALC_FILTER_1 float filter1 = DistortionFilter( i, fWaterGeomWave1.xy ) #define CALC_FILTER_2 float filter2 = DistortionFilter( i, fWaterGeomWave2.xy ) #define CALC_FILTER_3 float filter3 = DistortionFilter( i, fWaterGeomWave3.xy ) #define FILTER_0( val ) filter0*val #define FILTER_1( val ) filter1*val #define FILTER_2( val ) filter2*val #define FILTER_3( val ) filter3*val
  47. 47. Seawater – wave asymmetry float distort = 0.0; distort += FILTER_0( DISTORTION_0( arg0 ) * fWaterGeomWave0.w ); … // FLATTENING STAGE posWS.z += distort * i.m_geomSoftness; // NORMAL DISTORTION CODE NEEDED TO AFFECT POSITION float cos0 = FILTER_0( D_DISTORTION_0( arg0 ) ); float2 diff0 = i.m_geomSoftness * normalize( fWaterGeomWave0.xy ) * cos0 ); … // ASYMMETRY AND CHOPPINESS #define ASYMMETRY( arg, power ) ( arg > 0.0 ? power * arg*arg : 1.0 ) posWS.xy += diff0 * fWaterGeomWaveChoppiness.x * ASYMMETRY( cos0, fWaterGeomWaveAsymmetry.x );
  48. 48. Seawater – wave asymmetry // ASYMMETRY AND CHOPPINESS #define ASYMMETRY( arg, power ) ( arg > 0.0 ? power * arg*arg : 1.0 ) posWS.xy += diff0 * fWaterGeomWaveChoppiness.x * ASYMMETRY( cos0, fWaterGeomWaveAsymmetry.x );
  49. 49. Seawater – vertex texture • Displace vertices with perlin noise to avoid wave tiling • Only LoD 1 and LoD 2 • Calculate proper mip level to avoid aliasing • 256x256 R32F vertex texture • 1024x1024 normals (read in PS)
  50. 50. Seawater – pixel shader • Translucent at the begining, changed to opaque later on • 2 x diffuse (water + foam) • 2 sliding normals + perlin noise normal • Environment map • Deffered lighting
  51. 51. Seawater – results • GPU time 0.80 ms (0.02 ms mask, 0.25 ms normal, 0.53 ms compose) @ Radeon R9 270, 1920x1080 • 0 pixels draw (depth & stencil fail): 0.16 ms (0.00 ms mask, 0.07 ms normal, 0.09 ms compose) • 0 pixels draw (stencil fail): 0.39 ms (0.00 ms mask, 0.30 ms normal, 0.09 ms compose)
  52. 52. Special thanks Łukasz Zdunowski – Lead Artist Zbigniew Siatecki – Environment Artist Dominik Misiurski – FX Artist Artur Maksara – Producer … and the rest of our team.
  53. 53. References 1. http://broniac.blogspot.com/2011/06/deferred-decals.html 2. http://humus.name/index.php?page=3D&ID=83 3. “Character Animation with Direct3D”, Carl Granberg, Charles River Media, 2009 Questions? Contact: Email: jarek.pleskot AT flyingwildhog.com Facebook: Jarosław Pleskot Twitter: @JaroslawPleskot

×