This shows you the differences between two versions of the page.
ibl_sample_shader_lys [2017/04/29 12:05] adavies [Downloading and running the shader in FX Composer] |
ibl_sample_shader_lys [2017/05/23 03:49] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Image Based Lighting Sample Shader ====== | ||
- | Throughout the following page we are providing a free IBL sample shader, the associated source files for the shader and a general user guide in that you may test the various aspects of IBL with a minimal setup and iteration time. | ||
- | |||
- | [{{https://s3.amazonaws.com/docs.knaldtech.com/docuwiki/freeLysIblSample.jpg?nolink&1175|The IBL sample shader running in FX Composer 2.51.}}] | ||
- | |||
- | ===== Downloading and running the shader in FX Composer ===== | ||
- | |||
- | ---- | ||
- | |||
- | |||
- | - Download the shader and asset files from HERE. | ||
- | - Download FX Composer from HERE & install. | ||
- | - Extract the .zip file to a location of your choosing. | ||
- | - Double click the freeLysIblSample.fxcproj file found in the extracted folder. | ||
- | - Ensure that the renderer is set to Direct3D10 by selecting the option found within in the dropdown menu located at the far right of the top top row of buttons. | ||
- | - Select the IblLysBurley material in the Materials panel. | ||
- | - Assign the cubemap and textures within the Properties panel by clicking each texture slot and their associated tool buttons> clicking the Image icon> clicking the + icon located at the top left of the popup and then finally selecting the textures you want to load. Repeat for all the textures as required. | ||
- | - Enjoy! | ||
- | |||
- | Below is the full shader. You can download the sample shader and assets HERE | ||
- | |||
- | <code glsl> | ||
- | /* | ||
- | |||
- | % Free IBL sample using cube map exported from Knaldtech's tool Lys | ||
- | % The cube map was made with offset set to 3 and exported as GGX with Burley roughness drop. | ||
- | */ | ||
- | |||
- | #define FLT_EPSILON 1.192092896e-07f // smallest such that 1.0+FLT_EPSILON != 1.0 | ||
- | |||
- | // assume the default value of 3 which assigns maximum roughness to mip level 8x8 | ||
- | const int nMipOffset = 3; | ||
- | |||
- | // global matrices | ||
- | float4x4 g_mObjToViewProj : WorldViewProjection; | ||
- | float4x4 g_mObjToWorld : World; | ||
- | float4x4 g_mWorldToObjTransposed : WorldInverseTranspose; | ||
- | float4x4 g_mViewToWorld : ViewInverse; | ||
- | float4x4 g_mObjToView : WorldView; | ||
- | float4x4 g_mViewToObj : WorldViewInverse; | ||
- | float4x4 g_mViewToObjTransposed : WorldViewInverseTranspose; | ||
- | |||
- | // sampler | ||
- | SamplerState samLinear | ||
- | { | ||
- | Filter = MIN_MAG_MIP_LINEAR; | ||
- | AddressU = Wrap; | ||
- | AddressV = Wrap; | ||
- | }; | ||
- | |||
- | |||
- | // textures | ||
- | |||
- | TextureCube lysBurleyCube < | ||
- | string UIName = "IBL. cube"; | ||
- | string ResourceType = "cube"; | ||
- | >; | ||
- | |||
- | Texture2D albedo_tex < | ||
- | string UIName = "albedo Texture"; | ||
- | string ResourceType = "2D"; | ||
- | >; | ||
- | |||
- | Texture2D smoothness_tex < | ||
- | string UIName = "smoothness Texture"; | ||
- | string ResourceType = "2D"; | ||
- | >; | ||
- | |||
- | Texture2D metalness_tex < | ||
- | string UIName = "metalness Texture"; | ||
- | string ResourceType = "2D"; | ||
- | >; | ||
- | |||
- | Texture2D normal_tex < | ||
- | string UIName = "normal Texture"; | ||
- | string ResourceType = "2D"; | ||
- | >; | ||
- | |||
- | Texture2D ao_tex < | ||
- | string UIName = "ao Texture"; | ||
- | string ResourceType = "2D"; | ||
- | >; | ||
- | |||
- | |||
- | // vertex shader input | ||
- | struct vertInput | ||
- | { | ||
- | float4 position : POSITION; | ||
- | float3 normal : NORMAL; | ||
- | float3 tang : TANGENT; | ||
- | float3 bino : BINORMAL; | ||
- | float2 texcoord : TEXCOORD0; | ||
- | }; | ||
- | |||
- | // vertex shader output (fed to pixel shader) | ||
- | struct vertexOutput | ||
- | { | ||
- | float4 position : POSITION; | ||
- | float3 pos : TEXCOORD0; | ||
- | float3 normal : TEXCOORD1; | ||
- | float2 stcoord : TEXCOORD2; | ||
- | float3 tang : TEXCOORD3; | ||
- | float3 bino : TEXCOORD4; | ||
- | }; | ||
- | |||
- | float SpecularPowerFromPerceptualRoughness(float fPerceptualRoughness); | ||
- | float PerceptualRoughnessFromSpecularPower(float fSpecPower); | ||
- | float3 EvalBRDF(TextureCube lysBurleyCube, float3 vN, float3 vN_unit, float3 to_cam, float perceptualRoughness, float metalness, float3 albedo, float ao); | ||
- | float3 GetSpecularDominantDir(float3 vN, float3 vR, float fRealRoughness); | ||
- | float RoughnessFromPerceptualRoughness(float fPerceptualRoughness); | ||
- | float gain(float value, float g); | ||
- | float GetReductionInMicrofacets(float perceptualRoughness); | ||
- | float EmpiricalSpecularAO(float ao, float perceptualRoughness); | ||
- | float ApproximateSpecularSelfOcclusion(float3 vR, float3 vertNormalNormalized); | ||
- | |||
- | |||
- | float BurleyToMip(float fPerceptualRoughness, int nMips, float NdotR) | ||
- | { | ||
- | float fSpecPower = SpecularPowerFromPerceptualRoughness(fPerceptualRoughness); | ||
- | fSpecPower /= (4*max(NdotR, FLT_EPSILON)); // see section "Pre-convolved Cube Maps vs Path Tracers" | ||
- | float fScale = PerceptualRoughnessFromSpecularPower(fSpecPower); | ||
- | return fScale*(nMips-1-nMipOffset); | ||
- | } | ||
- | |||
- | float BurleyToMipSimple(float fPerceptualRoughness, int nMips) | ||
- | { | ||
- | float fScale = fPerceptualRoughness*(1.7 - 0.7*fPerceptualRoughness); // approximate remap from LdotR based distribution to NdotH | ||
- | return fScale*(nMips-1-nMipOffset); | ||
- | } | ||
- | |||
- | int GetNumMips(TextureCube cubeTex) | ||
- | { | ||
- | int iWidth=0, iHeight=0, numMips=0; | ||
- | cubeTex.GetDimensions(0, iWidth, iHeight, numMips); | ||
- | return numMips; | ||
- | } | ||
- | |||
- | // sRGB not built into fx composer. Must do by hand. | ||
- | // don't do this in your own engine. | ||
- | float3 GammaToLinear( float3 color) | ||
- | { | ||
- | return pow(color,2.2); | ||
- | } | ||
- | |||
- | float3 LinearToGamma( float3 linearColor) | ||
- | { | ||
- | return pow(linearColor,1.0/2.2); | ||
- | } | ||
- | |||
- | |||
- | // vertex shader | ||
- | vertexOutput main_VS(vertInput IN) | ||
- | { | ||
- | vertexOutput res; | ||
- | |||
- | res.stcoord = float2(IN.texcoord.x, 1.0-IN.texcoord.y); | ||
- | |||
- | // transform attributes to world space | ||
- | res.pos = mul(float4(IN.position.xyz,1), g_mObjToWorld).xyz; | ||
- | res.tang = normalize( mul(float4(IN.tang, 0), g_mObjToWorld ).xyz ); | ||
- | res.bino = -normalize( mul(float4(IN.bino, 0), g_mObjToWorld ).xyz ); // bitangent negated in fxcomposer | ||
- | |||
- | // normals are transformed using inverse transposed so this gives us the normal in world space. | ||
- | res.normal = normalize( mul(float4(IN.normal.xyz,0), g_mWorldToObjTransposed).xyz ); | ||
- | |||
- | // used by rasterizer | ||
- | res.position = mul(float4(IN.position.xyz, 1.0), g_mObjToViewProj); | ||
- | |||
- | return res; | ||
- | } | ||
- | |||
- | // pixel shader | ||
- | float4 main_FP(vertexOutput IN) : COLOR | ||
- | { | ||
- | // gather inputs | ||
- | float3 vN = IN.normal; | ||
- | float3 vT = IN.tang; | ||
- | float3 vB = IN.bino; | ||
- | float3 vN_unit = normalize(vN); | ||
- | float3 pos = IN.pos; | ||
- | float2 st = IN.stcoord.xy; | ||
- | |||
- | // material properties from texture | ||
- | float smoothness = smoothness_tex.Sample(samLinear, st).x; // not gamma corrected | ||
- | float perceptualRoughness = 1.0 - smoothness; | ||
- | float metalness = metalness_tex.Sample(samLinear, st).x; // not gamma corrected | ||
- | float3 texNormal = 2*normal_tex.Sample(samLinear, st).xyz - 1.0; // not gamma corrected | ||
- | float3 albedo = GammaToLinear( albedo_tex.Sample(samLinear, st).xyz ); | ||
- | float ao = ao_tex.Sample(samLinear, st).x; // not gamma corrected | ||
- | |||
- | // get camera position and direction in world space | ||
- | float3 eyePos = float3(g_mViewToWorld[3].x,g_mViewToWorld[3].y,g_mViewToWorld[3].z); | ||
- | float3 to_cam = normalize(eyePos - pos); // to view vector | ||
- | |||
- | // normal mapping | ||
- | |||
- | //vN = normalize(vT*texNormal.x + vB*texNormal.y + vN*texNormal.z); // tangent space normal map | ||
- | vN = normalize( mul(float4(texNormal.xyz,0), g_mWorldToObjTransposed).xyz ); // object space normal map | ||
- | //vN = vN_unit; // normal mapping disabled (use interpolated vertex normal) | ||
- | |||
- | // evaluate ibl based brdf | ||
- | float3 outRadiance = EvalBRDF(lysBurleyCube, vN, vN_unit, to_cam, perceptualRoughness, metalness, albedo, ao); | ||
- | |||
- | |||
- | // sRGB not built into fx composer. Must do by hand. | ||
- | // don't do this in your own engine. | ||
- | return float4(LinearToGamma(outRadiance), 1.0); | ||
- | } | ||
- | |||
- | float3 EvalBRDF(TextureCube lysBurleyCube, float3 vN, float3 org_normal, float3 to_cam, float perceptualRoughness, float metalness, float3 albedo, float ao) | ||
- | { | ||
- | int numMips = GetNumMips(lysBurleyCube); | ||
- | const int nrBrdfMips = numMips-nMipOffset; | ||
- | float VdotN = clamp(dot(to_cam, vN), 0.0, 1.0f); // same as NdotR | ||
- | const float3 vRorg = 2*vN*VdotN-to_cam; | ||
- | |||
- | float3 vR = GetSpecularDominantDir(vN, vRorg, RoughnessFromPerceptualRoughness(perceptualRoughness)); | ||
- | float RdotNsat = saturate(dot(vN, vR)); | ||
- | |||
- | #if 1 | ||
- | float l = BurleyToMip(perceptualRoughness, numMips, RdotNsat); | ||
- | #else | ||
- | float l = BurleyToMipSimple(perceptualRoughness, numMips); | ||
- | #endif | ||
- | |||
- | |||
- | // fxcomposer uses a right hand coordinate frame (unlike d3d which uses left) | ||
- | // and has Y axis up. We've exported accordingly in Lys. For conventional | ||
- | // d3d11 just set Y axis as up in Lys before export. | ||
- | float3 specRad = lysBurleyCube.SampleLevel(samLinear, vR, l).xyz; | ||
- | float3 diffRad = lysBurleyCube.SampleLevel(samLinear, vN, (float) (nrBrdfMips-1)).xyz; | ||
- | |||
- | |||
- | float3 spccol = lerp( (float3) 0.04, albedo, metalness); | ||
- | float3 dfcol = lerp( (float3) 0.0, albedo, 1-metalness); | ||
- | |||
- | // fresnel | ||
- | float fT = 1.0-RdotNsat; | ||
- | float fT5 = fT*fT; fT5 = fT5*fT5*fT; | ||
- | spccol = lerp(spccol, (float3) 1.0, fT5); | ||
- | |||
- | // take reduction in brightness into account. | ||
- | float fFade = GetReductionInMicrofacets(perceptualRoughness); | ||
- | fFade *= EmpiricalSpecularAO(ao, perceptualRoughness); | ||
- | fFade *= ApproximateSpecularSelfOcclusion(vR, org_normal); | ||
- | |||
- | // final result | ||
- | return ao*dfcol*diffRad + fFade*spccol*specRad; | ||
- | } | ||
- | |||
- | float GetReductionInMicrofacets(float perceptualRoughness) | ||
- | { | ||
- | // this is not needed if you separately precompute an integrated FG term such as proposed | ||
- | // by epic. Alternatively this simple analytical approximation retains the energy | ||
- | // loss associated with Integral GGX(NdotH)*NdotH * (NdotL>0) dH which | ||
- | // for GGX equals 1/(roughness^2+1) when integrated over the half sphere. | ||
- | // without the NdotL>0 indicator term the integral equals one. | ||
- | float roughness = RoughnessFromPerceptualRoughness(perceptualRoughness); | ||
- | return 1.0 / (roughness*roughness+1.0); | ||
- | } | ||
- | |||
- | float EmpiricalSpecularAO(float ao, float perceptualRoughness) | ||
- | { | ||
- | // basically a ramp curve allowing ao on very diffuse specular | ||
- | // and gradually less so as the reflection hardens. | ||
- | float fSmooth = 1-perceptualRoughness; | ||
- | float fSpecAo = gain(ao,0.5+max(0.0,fSmooth*0.4)); | ||
- | |||
- | return min(1.0,fSpecAo + lerp(0.0, 0.5, fSmooth*fSmooth*fSmooth*fSmooth)); | ||
- | } | ||
- | |||
- | // marmoset horizon occlusion http://marmosetco.tumblr.com/post/81245981087 | ||
- | float ApproximateSpecularSelfOcclusion(float3 vR, float3 vertNormalNormalized) | ||
- | { | ||
- | const float fFadeParam = 1.3; | ||
- | float rimmask = clamp( 1 + fFadeParam * dot(vR, vertNormalNormalized), 0.0, 1.0); | ||
- | rimmask *= rimmask; | ||
- | |||
- | return rimmask; | ||
- | } | ||
- | |||
- | float RoughnessFromPerceptualRoughness(float fPerceptualRoughness) | ||
- | { | ||
- | return fPerceptualRoughness*fPerceptualRoughness; | ||
- | } | ||
- | |||
- | float PerceptualRoughnessFromRoughness(float fRoughness) | ||
- | { | ||
- | return sqrt(max(0.0,fRoughness)); | ||
- | } | ||
- | |||
- | float SpecularPowerFromPerceptualRoughness(float fPerceptualRoughness) | ||
- | { | ||
- | float fRoughness = RoughnessFromPerceptualRoughness(fPerceptualRoughness); | ||
- | return (2.0/max(FLT_EPSILON, fRoughness*fRoughness))-2.0; | ||
- | } | ||
- | |||
- | float PerceptualRoughnessFromSpecularPower(float fSpecPower) | ||
- | { | ||
- | float fRoughness = sqrt(2.0/(fSpecPower + 2.0)); | ||
- | return PerceptualRoughnessFromRoughness(fRoughness); | ||
- | } | ||
- | |||
- | // frostbite presentation (moving frostbite to pbr) | ||
- | float3 GetSpecularDominantDir(float3 vN, float3 vR, float fRealRoughness) | ||
- | { | ||
- | float fInvRealRough = saturate(1 - fRealRoughness); | ||
- | float lerpFactor = fInvRealRough * (sqrt(fInvRealRough)+fRealRoughness); | ||
- | |||
- | return lerp(vN, vR, lerpFactor); | ||
- | } | ||
- | |||
- | float bias(float value, float b) | ||
- | { | ||
- | return (b > 0.0) ? pow(value, log(b) / log(0.5)) : 0.0; | ||
- | } | ||
- | |||
- | // contrast function. | ||
- | float gain(float value, float g) | ||
- | { | ||
- | return 0.5 * ((value < 0.5) ? bias(2.0*value, 1.0-g) : (2.0 - bias(2.0-2.0*value, 1.0-g))); | ||
- | } | ||
- | |||
- | |||
- | // set depth, cull and blend states | ||
- | DepthStencilState EnableDepth | ||
- | { | ||
- | DepthEnable = TRUE; | ||
- | DepthWriteMask = ALL; | ||
- | DepthFunc = LESS_EQUAL; | ||
- | }; | ||
- | |||
- | BlendState NoBlending | ||
- | { | ||
- | AlphaToCoverageEnable = FALSE; | ||
- | BlendEnable[0] = FALSE; | ||
- | }; | ||
- | |||
- | RasterizerState RasterizerSettings | ||
- | { | ||
- | CullMode = FRONT; | ||
- | | ||
- | }; | ||
- | |||
- | technique10 Render { | ||
- | pass p0 { | ||
- | SetVertexShader( CompileShader( vs_4_0, main_VS() ) ); | ||
- | SetPixelShader( CompileShader( ps_4_0, main_FP() ) ); | ||
- | | ||
- | SetBlendState( NoBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF ); | ||
- | SetDepthStencilState( EnableDepth, 0 ); | ||
- | SetRasterizerState(RasterizerSettings); | ||
- | } | ||
- | } | ||
- | </code> |