Revision Difference
ShadingModel#550105
<cat>Material.ShaderBasic</cat>
<title>Shading Model</title>
# What is a shading model⤶
⤶
A shading model is what controls the lighting & shading of your surface. By default, Two different shading models are provided, `ShadingModelStandard` which is the default, and `ShadingModelValveStandard`. Not only can you use one of these pre-defined shading models, but you can also create your own shading model to shade your surface the way you want.
# What is a Shading Model⤶
⤶
Shading Models determines how a surface will interact with light. They dictate how the Material's input data is utilized to produce the final visual output.
⤶
Not only can you use one of these pre-defined shading models, but you can also create your own shading model to shade your surface the way you want.⤶
⤶
S&box currently ships with two shading models `ShadingModelStandard` which should be what most people would use and `ShadingModelCustom` as a base for custom shading models to be developed from, they will handle things like atmospherics and debug visualizations for you without a fuzz.⤶
# Using built-in shading models
⤶
Invoking and using your shading model is done by calling the `FinalizePixelMaterial` function, which is defined automatically when you include the `common/pixel.hlsl` file within your pixel shader.⤶
⤶
By default, if you do not provide a shading model to the `FinalizePixelMaterial` call, it will use the `ShadingModelStandard`. For example:⤶
```⤶
// PixelInput, Material⤶
return FinalizePixelMaterial( i, m );⤶
```⤶
⤶
If you wish to use the `ShadingModelValveStandard`, it will need to be passed in as the third argument to `FinalizePixelMaterial` like so.⤶
```⤶
ShadingModelValveStandard sm;⤶
// PixelInput, Material, Shading Model⤶
return FinalizePixelMaterial( i, m, sm );⤶
```⤶
<upload src="76d7b/8db659eb5b0fde4.png" size="8265" name="image.png" />⤶
# Creating a shading model
## Structure
⤶
All shading models contain 4 basic methods which need to be implemented. `Init`, which is called once the shading model is initialized for the first time. `Direct` is called for every single direct light which is currently visible, `Indirect` is called once to calculate indirect lighting & `PostProcess` which allows tweaking of the shading after everything is done, for example adding fog. To begin implementing any of these methods, you will need to create a class & inherit from `ShadingModel`. An empty shading model can be seen below:⤶
```⤶
class ShadingModelExample : ShadingModel⤶
⤶
## Light Structure⤶
⤶
We provide the following light types to be used from shader code:⤶
⤶
* DynamicLight⤶
* StaticLight⤶
* EnvironmentMapLight⤶
* AmbientLight⤶
⤶
All can be created with Light::From() and all but AmbientLight can be iterated with Light::Count(), ⤶
Light types already handle everything from tiled rendering to shadowing seamlessly, for example:⤶
⤶
```c++⤶
//⤶
// Shade direct lighting for dynamic lights⤶
//⤶
for ( uint index = 0; index < DynamicLight::Count( i ); index++ )⤶
{⤶
Light light = DynamicLight::From( i, index );⤶
vResult += light.Color * dot( material.Normal, light.Direction );⤶
}```⤶
⤶
Each light structure gives you the following data:⤶
⤶
```c++⤶
//-----------------------------------------------------------------------------⤶
// Light structure⤶
//-----------------------------------------------------------------------------⤶
struct Light⤶
{
//⤶
// Execute before anything⤶
//⤶
void Init( const PixelInput pixelInput, const Material material )
{
}
//⤶
// Executed for every direct light⤶
//⤶
LightShade Direct( const LightData light )⤶
{
LightShade shade;
shade.Diffuse = float3( 0.0f, 0.0f, 0.0f );⤶
shade.Specular = float3( 0.0f, 0.0f, 0.0f );⤶
return shade;
}
⤶
//⤶
// Executed for indirect lighting, combine ambient occlusion, etc.⤶
//⤶
LightShade Indirect()⤶
{⤶
LightShade shade;⤶
shade.Diffuse = float3( 0.0f, 0.0f, 0.0f );⤶
shade.Specular = float3( 0.0f, 0.0f, 0.0f );⤶
return shade;⤶
}⤶
⤶
//⤶
// Applying any post-processing effects after all lighting is complete⤶
//⤶
float4 PostProcess( float4 vColor )⤶
{⤶
return vColor;⤶
}⤶
// The color is an RGB value in the linear sRGB color space.⤶
float3 Color;⤶
⤶
// The normalized light vector, in world space (direction from the⤶
// current fragment's position to the light).
float3 Direction;
// The position of the light in world space. This value is the same as⤶
// Direction for directional lights.
float3 Position;⤶
⤶
// Attenuation of the light based on the distance from the current⤶
// fragment to the light in world space. This value between 0.0 and 1.0⤶
// is computed differently for each type of light (it's always 1.0 for⤶
// directional lights).
float Attenuation;
⤶
// Visibility factor computed from shadow maps or other occlusion data⤶
// specific to the light being evaluated. This value is between 0.0 and⤶
// 1.0.
float Visibility;
};
```
## Light Data⤶
| Type | Name | Description |⤶
|------|------|-------------|⤶
| float3 | Color | The color is an RGB value in the linear sRGB color space. |⤶
| float3 | LightDir | The normalized light vector, in world space (direction from the current fragment's position to the light). |⤶
| float | NdotL | The dot product of the shading normal (with normal mapping applied) and the light vector. This value is equal to the result of `saturate(dot(getWorldSpaceNormal(), lightData.l))`. This value is always between 0.0 and 1.0. When the value is <= 0.0, the current fragment is not visible from the light and lighting computations can be skipped. |⤶
| float3 | PositionWs | The position of the light in world space. |⤶
| float | Attenuation | Attenuation of the light based on the distance from the current fragment to the light in world space. This value between 0.0 and 1.0 is computed differently for each type of light (it's always 1.0 for directional lights). |⤶
| float | Visibility | Visibility factor computed from shadow maps or other occlusion data specific to the light being evaluated. This value is between 0.0 and 1.0. |⤶
⤶
⤶
## Usage⤶
Once the shading model is created, it can be used like any other shading model. It's first declared and then passed into `FinalizePixelMaterial` like so:⤶
```⤶
float4 MainPs( PixelInput i ) : SV_Target0⤶
{⤶
Material m = GatherMaterial( i );⤶
ShadingModelExample sm;⤶
return FinalizePixelMaterial( i, m, sm );⤶
}⤶
```⤶
⤶
## Finished Example⤶
⤶
<upload src="653cb/8daa51bce5c2fd1.png" size="147268" name="image.png" />⤶
⤶
```⤶
PS⤶
{⤶
#include "common/pixel.hlsl"⤶
//⤶
// Main⤶
//⤶
⤶
class ShadingModelExample : ShadingModel⤶
{⤶
float3 Albedo;⤶
float3 NormalWs;⤶
float3 ViewRayWs;⤶
⤶
//⤶
// Consumes a material and converts it to the internal shading parameters,⤶
// That is more easily consumed by the shader.⤶
//⤶
// Inherited classes can expand this to whichever shading model they want.⤶
//⤶
void Init( const PixelInput pixelInput, const Material material )⤶
{⤶
// Keep track of our albedo & normal⤶
Albedo = material.Albedo;⤶
NormalWs = material.Normal;⤶
⤶
float3 PositionWithOffsetWs = pixelInput.vPositionWithOffsetWs.xyz;⤶
float3 PositionWs = PositionWithOffsetWs + g_vCameraPositionWs;⤶
⤶
// View ray in World Space⤶
ViewRayWs = CalculatePositionToCameraDirWs( PositionWs );⤶
}⤶
⤶
//⤶
// Executed for every direct light⤶
//⤶
LightShade Direct( const LightData light )⤶
{⤶
// Shading output⤶
LightShade shade;⤶
⤶
// Compute our shadow mask⤶
float flShadow = light.NdotL * light.Visibility * light.Attenuation;⤶
⤶
// Make it hard instead of a soft transition⤶
flShadow = step(0.001f, flShadow);⤶
⤶
// Calculate everything we need for specular⤶
float3 vHalfAngleDirWs = normalize(ViewRayWs + light.LightDir);⤶
float flNdotH = dot( vHalfAngleDirWs.xyz, NormalWs );⤶
⤶
// Sharpen our specular⤶
float flSpecular = pow(flNdotH * flShadow, 100.0f);⤶
// Smooth it out a little bit⤶
flSpecular = smoothstep(0.005, 0.01, flSpecular);⤶
⤶
// Diffuse lighting for the current light⤶
shade.Diffuse = saturate(flShadow * light.Color) * g_flTintColor;⤶
⤶
// Calculate our specular for the current light⤶
shade.Specular = flSpecular * Albedo * g_flTintColor;⤶
⤶
return shade;⤶
}⤶
⤶
//⤶
// Executed for indirect lighting, combine ambient occlusion, etc.⤶
//⤶
LightShade Indirect()⤶
{⤶
LightShade shade;⤶
⤶
// Get a flat average ambient⤶
float3 vAmbientCube[6];⤶
SampleLightProbeVolume( vAmbientCube, float3(0,0,0) );⤶
⤶
// Light with our ambient color⤶
float3 flColor = 0.0f;⤶
for(int i = 0; i < 6; i++)⤶
flColor += vAmbientCube[i] * (1.0f / 6.0f); ⤶
shade.Diffuse = flColor * Albedo;⤶
⤶
// No specular⤶
shade.Specular = 0.0f;⤶
⤶
return shade;⤶
}⤶
⤶
float4 PostProcess( float4 vColor )⤶
{⤶
// We don't need any post processing!⤶
return vColor;⤶
}⤶
};⤶
⤶
⤶
float4 MainPs( PixelInput i ) : SV_Target0⤶
{⤶
Material m = GatherMaterial( i );⤶
ShadingModelExample sm;⤶
return FinalizePixelMaterial( i, m, sm );⤶
}⤶
}⤶
```
## Usage⤶