Cheap Realistic Skin Shading Overview • Popular Skin Models • New Skin Model – Ideas – BRDF – Layers – Back Scattering – Blended Normals – Shadows – Extras • Results • Conclusion Popular Skin Models Popular Skin Models • Red wrapped lighting – http://http.developer.nvidia.com/GPUGems/gpugems_ch16.html • Texture-space diffusion – http://http.developer.nvidia.com/GPUGems3/gpugems3_ch14.html • Texture-space diffusion (12-tap) – http://advances.realtimerendering.com/s2010/Hable-Uncharted2(SIGGRAPH 2010 Advanced RealTime Rendering Course).pptx • Screen-space diffusion – http://giga.cps.unizar.es/~diegog/ficheros/pdf_papers/TAP_Jimenez_LR.pdf • Blended Normals – http://advances.realtimerendering.com/s2010/Hable-Uncharted2(SIGGRAPH 2010 Advanced RealTime Rendering Course).pptx • Offline “Fast-Skin Shaders” – http://www.google.ca/#hl=en&source=hp&biw=1920&bih=965&q=fast+skin+shader&aq=f&aqi=gm5&aql=&oq=&gs_rfai=&fp=bc38547320fe36d4 Popular Skin Models (cont.) • The diffusion approximation techniques are the most popular when it comes to high-fidelity realism. • Each model is in the extremes. – Either extremely cheap/poor approximation or really good/expensive. • Wrapped lighting can work in practice but fails in sharp lighting. • Need a good alternative that’s somewhere in the middle. – Has to be cheap but still look up-to-par. New Skin Model New Skin Model • Trick is to improvise! • Perceptual rendering, not physical. – Doesn’t have to be correct, just has to • Use core concepts of different techniques to approximate. – Simulate multiple layers. – Use a good (physically based) BRDF. – Approximate common visual properties. • Red bleeding on shadow edges. • Soft appearance. • Harsh falloff. New Skin Model - Ideas • Use concepts from other models. – Use Kelemen/Szirmay-Kalos BRDF. • Looks great. • Can be relatively cheap with proper optimizations. – Simulate “Fast-Skin Shaders” Multiple layers. • • • • • Each layer has it’s own texture (epidermal, subdermal). Epidermal layer is blurred lightly. Subdermal layer is blurred a lot. Diffuse is not blurred at all. Sum the layers at the end. – Soften normal map using blended normals. • Gives soft look without washing out lighting. New Skin Model - BRDF • Kelemen/Szirmay-Kalos BRDF Optimizations. – Saves a crap-load of instructions. – Save fresnel to texture. – Math version may be cheaper depending on the system. • Why use just 1 specular term? – You can have 4 channels in your beckmann texture. Why not use them? – Calculate the shader as usual but use a 4-value float instead. – Weigh them at the end using a simple dot product. • Use energy conservation if you think it’ll help. New Skin Model – BRDF (Code) Code modified from NVIDIA’s implementation. // Computes beckmann distribution // To bake to texture: texCoord.x = NdotH, texCoord.y = Exp float4 GetBeckmannDistribution( float NdotH, float Exp ) { // Some roughness weights float4 m = half4(1, 0.12, 0.023, 0.012) * (Exp * Exp); float alpha = acos( NdotH ); float ta = tan( alpha ); float4 val = 1.0 / (m * pow(NdotH, 4.0)) * exp(-(ta * ta) / m); // Scale the value to fit within [0-1] return 0.5 * pow( val , 0.1 ); } // Computes fresnel reflectance (can be computed on the fly no problem) // To bake to texture: HdotV = texCoord.x, texCoord.y = F0 float GetFresnelKS( float3 HdotV, float F0 ) { float base = 1.0 - HdotV; float exponential = pow( base, 5.0 ); return exponential + F0 * ( 1.0 - exponential ); } Don’t use these textures, compute them yourself for better precision. New Skin Model – BRDF (Code) float KelemenSzirmayTex( float3 N, float3 L, float3 V, float Exp, float F0 ) { // Pretty straightforward float NdotL = saturate(dot(N, L)); float h = L + V; float H = normalize(h); float HdotV = dot(H, V); // Get fresnel from texture; 0.028 is a good value for F0 float fFresnel = tex2D(fresnelTex, float2(HdotV, F0)); // float fFresnel = GetFresnelKS(HdotV, F0 ); // Math version. // Get beckmann distributions from texture float4 fBeckmann = pow(2.0 * tex2D(beckmannSampler, float2(NdotH, Exp)), 10); float4 fSpec = max( (fBeckmann * fFresnel) / dot( h, h ), 0 ); // Weight results using dot product float result = saturate( NdotL ) * dot(fSpec, half4(1.0, 0.625, 0.075, 0.005)); return result; } New Skin Model – BRDF (Result) (Image intensified for clarity) New Skin Model - Layers • Simulating multiple layers. – Create new textures for each layer. • Can be simulated using diffuse map with simple color operations in real-time. – Light each layer. • • • • Instead of blurring, just use lightly wrapped lighting for each layer. No wrapping for diffuse layer. ~0.8 – 0.9 for epidermal. ~0.7 – 0.8 for subdermal. – You’ll get ugly ambient like the standard wrap method. – Apply lighting to each texture and weigh accordingly. Epidermal = 0.3, Subdermal = 0.2, Diffuse = 0.5 New Skin Model – Layers (Textures) Diffuse Epidermal Subdermal Back Scattering Specular Normal New Skin Model – Layers (Image) New Skin Model – Back Scattering • Extremely simple. – Just a few calculations with N·L. • Mask by translucency texture. – Store in subdermal map alpha channel. – Alternatively, use vertex colors/alpha for mask. • Use non-sharpened shadows. – Soft shadows let the backscattering through more. – Translucent shadow maps. – Might not properly be occluded in a shadowed area. • Technique still needs work (incomplete). New Skin Model – Back Scattering (Code) float3 BackLighting(float3 lightColor, float NdotL, float shadowMap, float transTex) { // Calculate back scattering. float backLight = lerp(NdotL, 1.0, transTex) - lerp(NdotL, 1.0, 0.4); float3 result = saturate(backLight) * lightColor * shadowMap * backScatterStrength * backScatterColor; return result; } New Skin Model – Back Scattering (Image) New Skin Model – Blended Normals • Use blended normals to soften bump-mapping. – Calculate N·L for vertex normals and bumped normals. – Blend between them with different strengths for different color channels. • Use “lerp(0.0, max, intensity)” for intensity of each channel – Prevents perfectly smooth normals (we don’t want those). – Good values for max: » Red = 0.5 – 0.7 » Green/Blue = 0.15 – 0.4 – Intensity is contstant for all » 0-1 – Use new value for N·L for diffuse lighting. New Skin Model – Blended Normals (Code) float3 BlendNormals(float lightDiffusion, float vertexNdotL, float bumpNdotL, float3 lightPos) { // Tweak max values as you see fit. float redIntensity = lerp(0.0f, 0.6f, skinDiffusionAmount); float greenBlueIntensity = lerp(0.0f, 0.4f, skinDiffusionAmount); float red = lerp(vertexNdotL, bumpNdotL, redIntensity); float greenBlue = lerp(vertexNdotL, bumpNdotL, greenBlueIntensity); greenBlue = min(red, greenBlue); // remove unwanted green/blue // Put it all together. float3 result = float3(red, greenBlue.xx); return saturate(result); } New Skin Model – Blended Normals (Image) New Skin Model – Shadows • Need to compensate for not including shadows in diffusion process. • 2 Options: – Blur shadow map for each layer. • Blurring shadow maps is expensive. • Gets worse if done per-object. New Skin Model - Shadows (cont.) • Sharpening shadows. – – – – Use pow() function to sharpen shadows. Subdermal shadow has no pow() applied. Epidermal has small pow() applied (~2-4). Diffuse has huge pow() applied (~8-16). • Should be relatively soft/jittered/blurred. • Otherwise you’ll get specularity bleeding into shadowed areas. • Mileage may vary (depends on shadow map, scale of object, etc). New Skin Model - Shadows (cont.) • Blended Shadows. – Simply varying the sharpness of the shadows for each layer might not give enough bleeding color (artistic preference). • Calculate 2 different powers for the shadow map. • Blend between them for red and green/blue channels. – This gives a nice red edge. – Desaturate if the edge is too red. • Make difference higher for subdermal layer ( – ). • Make difference lower for epidermal layer ( – ). • Make them both exactly the same for diffuse ( – • Can be ignored if results were already good. ). New Skin Model – Shadows (Code) float3 BlendShadows(float2 shadowPow, float shadowMap) { // Calculate 2 different power factors. float shadowR = pow(shadowMap, shadowPow.x); float shadowGB = pow(shadowMap, shadowPow.y); // Blend shadows float red = lerp(shadowGB, shadowR, skinDiffusionAmount); float greenBlue = lerp(shadowGB, shadowR, skinDiffusionAmount * 0.5); float3 result = float3( red, greenBlue.xx ); // Result may be a bit too red, desaturate it a bit. result = lerp(result, dot(result, float3(0.33, 0.59, 0.11)), 0.75); return saturate(result); } New Skin Model – Shadows (Image) Pure diffuse layer Pure epidermal/subdermal layers New Skin Model - Extras • Rim Lighting – Calculate fresnel (N·V) – Calculate rim term • Rim = smoothstep(0.0, 0.5, fresnel) * rimStrength; – Add to specular during lighting pass. • spec += rim * lightColor * pow(N·H, rimPower) * N·L * shadowMap; • Melanin – Calculate luminance of diffuse texture. • lum = dot(diffuse, float3(0.33, 0.59, 0.11)); – Blend between 1 and diffuse*luminance. – Multiply new result with original diffuse • diffuse * lerp(1.0, diffuse*lum, melanin); • “Oily” specular. – Second independent specular term to give a bit of “oily” shine to the surface. New Skin Model – Notes • Shadow sharpening doesn’t need to be done for every layer. – Can simplify and apply single blended shadows beforehand. – Can still provide good bleeding. • Can do blended normals more than once. – Create variety between bump strengths for each layer. • Not limited to constant color for backscattering. – Subdermal texture. – Translucency ramp. Results Results Results (cont.) Standard NdotL + Blinn-phong (physical model) Skin Shading, no SSS Skin Shading, full SSS Conclusion • Use Kelemen/Szirmay-Kalos BRDF. – Bake beckmann distribution and fresnel into textures. – Use 4 specular terms instead of 1. • Approximate subsurface scattering with lightly wrapped texture layers. – Epidermal, subdermal. – Keep wrapping at a minimum to avoid washing out the lighting. • Use blended normals to soften normal maps. • Sharpen shadows for layers using pow() to create bleeding shadows. – Faster than blurring. • Use simple masked N·L calculations for backscattering. – Really cheap and easy to do. • Can add rim lighting or melanin for extra effect. • Might prove more effective if mixed with more methods (diffusion maybe?) Conclusion (cont.) • New Skin Model is cheap and looks good. • Simulates various scattering effects (bleeding shadows, back lighting). • Scales well with multiple characters. • Not as accurate as diffusion methods. • Still early in it’s development (experimental). • Wrapped lighting can start to show with very bright lights. • However, lights that bright shouldn’t occur in the first place. • Good shadow maps should help hide this. Thanks for viewing! • References: • Screen-Space Perceptual Rendering of Human Skin, Jorge Jimenez, Veronica Sundstedt, Diego Gutierrez, 2009 – • Efficient Rendering of Human Skin, Eugene d'Eon, David Luebke, and Eric Enderton, Eurographics 2007 – • http://advances.realtimerendering.com/s2010/HableUncharted2(SIGGRAPH%202010%20Advanced%20RealTime%20Rendering%20Course).pptx Crafting Physically Motivated Shading Models for Game Development, Naty Hoffman, 2010 – • http://http.developer.nvidia.com/GPUGems/gpugems_ch16.html Uncharted 2: Character Lighting and Shading, John Hable, 2010 • • http://http.developer.nvidia.com/GPUGems3/gpugems3_ch14.html Real-Time Approximations to Subsurface Scattering, Simon Green, 2004 – • http://giga.cps.unizar.es/~diegog/ficheros/pdf_papers/TAP_Jimenez_LR.pdf http://renderwonk.com/publications/s2010-shadingcourse/hoffman/s2010_physically_based_shading_hoffman_b.pdf Real-Time Realistic Skin Translucency, Jorge Jimenez, David Whelan, Veronica Sundstedt, Diego Gutierrez, 2010 • http://giga.cps.unizar.es/~diegog/projects/IEEE/ieee.html Fin Head model available at Infinite-3D