I've been working for a while on a generic multi functional cg shader and i've finally finished it. Well... sort of. (More on that later)
This shader includes:
- diffuse, specular and normal mapping (with scale and scroll)
- pixel lighting
- point and spot light attenuation
- spot light effect
- VSM shadows for all kinds of light (except cube mapping for point lights) - based on nullsquared example
- Linear and exponential Fog
- **NEW**Cel-Shading
Has you can see in the following code, i'm using all the 4 channels of the shadow map. I'm doing this because i need to have all the light types casting shadows. So, due to the fact that VSM uses 2 channels to store the depth and depth squared, i use the first two for Spot and Point lights and the last two for Directional lights. I don't know if there is another workaround to this (if you do, please let me know ), but for now i find this solution satisfying.
######## CODE #########
-- Main Shader (newLighting.cg)
Code: Select all
#include "newUtils.cg"
void ambient_vs
(
float4 iPosition : POSITION,
float2 iUV : TEXCOORD0,
uniform float3 iAmbient,
uniform float4 iFogParams,
uniform float4x4 iWorldViewProj,
out float2 oUV : TEXCOORD0,
out float3 oAmbient : TEXCOORD1,
out float oFog : TEXCOORD2,
out float4 oPosition : POSITION
)
{
oPosition = mul(iWorldViewProj, iPosition);
oAmbient = iAmbient;
oUV = iUV;
oFog = 1;
if(iFogParams.x==0)
{
if(iFogParams.w>0)
oFog = smoothstep(iFogParams.y, iFogParams.z, iFogParams.z-oPosition.z);
}
else
oFog = exp2(-iFogParams.x*oPosition.z);
}
void ambient_ps
(
float2 iUV : TEXCOORD0,
float3 iAmbient : TEXCOORD1,
float iFog : TEXCOORD2,
uniform sampler2D dMap : TEXUNIT0,
uniform float3 iFogColour,
uniform float2 iScale,
uniform float2 iScroll,
out float4 oColour : COLOR
)
{
iUV.x = (iUV.x + iScroll.x)*iScale.x;
iUV.y = (iUV.y + iScroll.y)*iScale.y;
float3 diffuse = tex2D(dMap, iUV).rgb;
oColour = float4(iFog * iAmbient * diffuse + iFogColour*(1-iFog),1);
}
void diffuse_vs
(
float4 iPosition : POSITION,
float4 iNormal : NORMAL,
float2 iUV : TEXCOORD0,
uniform float4x4 iWorld,
uniform float4x4 iWorldIT,
uniform float4x4 iWorldViewProj,
uniform float4 iFogParams,
#if _SPOTLIGHT
uniform float4 iSpotDir,
out float3 oSpotDir : TEXCOORD3,
#endif
#if _NORMAL
out float3 oTangent : TEXCOORD4,
out float3 oBinormal : TEXCOORD5,
#endif
#if _SHADOWS
uniform float4x4 iTextViewProj,
out float4 oShadowUV : TEXCOORD6,
#endif
out float2 oUV : TEXCOORD0,
out float4 oWorldPos : TEXCOORD1,
out float3 oNormal : TEXCOORD2,
out float oFog : TEXCOORD7,
out float4 oPosition : POSITION
)
{
oWorldPos = mul(iWorld,iPosition);
oPosition = mul(iWorldViewProj,iPosition);
oNormal = normalize(mul(iWorldIT, iNormal).xyz);
oUV = iUV;
#if _SPOTLIGHT
oSpotDir = mul(iWorld, iSpotDir).xyz;
#endif
#if _NORMAL
oTangent = normalize(-float3(abs(iNormal.y) + abs(iNormal.z), abs(iNormal.x), 0));
oBinormal = normalize(cross(oTangent,oNormal));
#endif
#if _SHADOWS
oShadowUV = mul(iTextViewProj, oWorldPos);
oShadowUV = oShadowUV / oShadowUV.w;
#endif
oFog = 1;
if(iFogParams.x==0)
{
if(iFogParams.w>0)
oFog = smoothstep(iFogParams.y, iFogParams.z, iFogParams.z-oPosition.z);
}
else
oFog = exp2(-iFogParams.x*oPosition.z);
}
void diffuse_ps
(
float2 iUV : TEXCOORD0,
float4 iWorldPos : TEXCOORD1,
float3 iNormal : TEXCOORD2,
float iFog : TEXCOORD7,
uniform float2 iScale,
uniform float2 iScroll,
uniform float3 iLightDif,
uniform float4 iLightPos,
uniform sampler2D dMap : TEXUNIT0,
#if _SPOTLIGHT
float3 iSpotDir : TEXCOORD3,
uniform float4 iSpotParams,
#endif
#if !_DIRECTIONAL
uniform float4 iLightAtt,
#if _SHADOWS
uniform float4 iDepthRange,
#endif
#endif
#if _SPECULAR
uniform float4 iLightSpec,
uniform float3 iEyePos,
uniform float iShininess,
uniform sampler2D spMap : TEXUNIT1,
#endif
#if _NORMAL
uniform sampler2D nMap : TEXUNIT2,
float3 iTangent : TEXCOORD4,
float3 iBinormal : TEXCOORD5,
#endif
#if _SHADOWS
uniform float4 iInvShMapSize,
uniform sampler2D shMap : TEXUNIT3,
float4 iShadowUV : TEXCOORD6,
#endif
#if _CELSHADING
#if !_SPECULAR
uniform float3 iEyePos,
#endif
uniform sampler1D dCelMap : TEXUNIT4,
uniform sampler1D sCelMap : TEXUNIT5,
uniform sampler1D eCelMap : TEXUNIT6,
#endif
out float4 oColour : COLOR
)
{
if(iFog == 0)
discard;
float3 lightDir = iLightPos.xyz - (iLightPos.w * iWorldPos.xyz);
float distanceLight = length(lightDir);
lightDir = normalize(lightDir);
iUV.x = (iUV.x + iScroll.x)*iScale.x;
iUV.y = (iUV.y + iScroll.y)*iScale.y;
#if _NORMAL
float3 normalTex = (tex2D(nMap,iUV).rgb - 0.5)*2;
iNormal = normalize(normalTex.x * iTangent - normalTex.y * iBinormal + normalTex.z * iNormal);
#endif
float nDotL = max(dot(lightDir,iNormal),0);
float4 difTex = tex2D(dMap,iUV);
if(difTex.a<0.5f)
discard;
#if _CELSHADING
// Step functions from textures
float edge = max(dot(iNormal,normalize(iEyePos - iWorldPos.xyz)),0);
nDotL = tex1D(dCelMap, nDotL).x;
edge = tex1D(eCelMap, edge).x;
#if _SPECULAR
float4 specTex = tex2D(spMap,iUV);
float specular = getSpecularContribution(iLightSpec.w,iEyePos,iWorldPos,lightDir,iNormal,nDotL,iShininess);
specular = tex1D(sCelMap, specular).x;
float3 light = edge*(difTex.xyz * (iLightDif * nDotL) + (specular * specTex.xyz * iLightSpec.xyz));
#else
float3 light = edge*(difTex.xyz * (iLightDif * nDotL));
#endif
#else
float3 light = iLightDif * nDotL * difTex.xyz;
#if _SPECULAR
float4 specTex = tex2D(spMap,iUV);
light += getSpecularContribution(iLightSpec.w,iEyePos,iWorldPos,lightDir,iNormal,nDotL,iShininess) * specTex.xyz * iLightSpec.xyz;
#endif
#endif
#if !_DIRECTIONAL
half lightAtt = getLightAttenuation(distanceLight,iLightAtt);
light *= lightAtt;
#endif
#if _SPOTLIGHT
float spot = getSpotlightEffect(lightDir,iSpotDir,iSpotParams);
light *= spot;
#endif
#if _SHADOWS
#if !_DIRECTIONAL
float lD = (distanceLight - iDepthRange.x) * iDepthRange.w;
float2 moments = btex2D_rg(shMap,iShadowUV.xy,iInvShMapSize).rg;
#else
float lD = iShadowUV.z;
float2 moments = btex2D_rg(shMap,iShadowUV.xy,iInvShMapSize).ba;
#endif
float p = lD<=moments.x?1:0;
float variance = moments.y - (moments.x*moments.x);
variance = max(variance, 0.001);
float d = lD - moments.x;
float p_max = variance / (variance + d*d);
p_max = max(p,p_max);
light *= clamp((p_max - 0.6) / (1 - 0.6), 0, 1);
#endif
light *= iFog;
oColour = float4(light, 1);
}
Code: Select all
#ifndef _newUtils_cg
#define _newUtils_cg
half getLightAttenuation(float distanceLight, float4 lightAtt)
{
half att = distanceLight / lightAtt.r;
att *= att;
return 1.0 - att;
}
float getSpecularContribution(float lightSpec, float3 eyePos, float4 worldPos, float3 direction, float3 normal, float nDotL, float shininess)
{
float3 viewVector = normalize(eyePos - worldPos.xyz);
float3 half = normalize(direction + viewVector);
float nDotH = dot(normal, half);
#if _CELSHADING
return pow(max(nDotH,0), lightSpec * shininess);
#else
return lit(nDotL, nDotH, lightSpec * shininess).z;
#endif
}
float getSpotlightEffect(float3 ld0, float3 spotDir, float4 spotParams)
{
float spot = dot(ld0, normalize(-spotDir));
spot = saturate((spot - spotParams.y) / (spotParams.x - spotParams.y));
return spot;
}
float4 btex2D_rg(sampler2D shadowMap, float2 uv, float offset)
{
float4 c = tex2D(shadowMap, uv.xy); // center
c += tex2D(shadowMap, uv.xy - offset); // top left
c += tex2D(shadowMap, uv.xy + offset); // bottom right
c += tex2D(shadowMap, float2(uv.x - offset, uv.y)); // left
c += tex2D(shadowMap, float2(uv.x + offset, uv.y)); // right
c += tex2D(shadowMap, float2(uv.x, uv.y + offset)); // bottom
c += tex2D(shadowMap, float2(uv.x, uv.y - offset)); // top
c += tex2D(shadowMap, float2(uv.x - offset, uv.y + offset)); // bottom left
c += tex2D(shadowMap, float2(uv.x + offset, uv.y - offset)); // top right
return c / 9;
}
#endif
Code: Select all
vertex_program ambient_vs cg
{
source newLighting.cg
profiles vs_1_1 arbvp1
entry_point ambient_vs
default_params
{
param_named_auto iAmbient ambient_light_colour
param_named_auto iWorldViewProj worldviewproj_matrix
param_named_auto iFogParams fog_params
}
}
fragment_program ambient_ps cg
{
source newLighting.cg
profiles ps_2_0 arbfp1
entry_point ambient_ps
default_params
{
param_named_auto iFogColour fog_colour
}
}
vertex_program diffuse_directional_normal_shadow_vs cg
{
source newLighting.cg
profiles vs_1_1 arbvp1
entry_point diffuse_vs
compile_arguments -D_DIRECTIONAL=1 -D_POINT=0 -D_SPOTLIGHT=0 -D_SPECULAR=0 -D_NORMAL=1 -D_SHADOWS=1
default_params
{
param_named_auto iWorld world_matrix
param_named_auto iWorldIT inverse_transpose_world_matrix
param_named_auto iWorldViewProj worldviewproj_matrix
param_named_auto iTextViewProj texture_viewproj_matrix
param_named_auto iFogParams fog_params
}
}
vertex_program diffuse_point_normal_shadow_vs cg
{
source newLighting.cg
profiles vs_1_1 arbvp1
entry_point diffuse_vs
compile_arguments -D_DIRECTIONAL=0 -D_POINT=1 -D_SPOTLIGHT=0 -D_SPECULAR=0 -D_NORMAL=1 -D_SHADOWS=1
default_params
{
param_named_auto iWorld world_matrix
param_named_auto iWorldIT inverse_transpose_world_matrix
param_named_auto iWorldViewProj worldviewproj_matrix
param_named_auto iTextViewProj texture_viewproj_matrix
param_named_auto iFogParams fog_params
}
}
vertex_program diffuse_spot_normal_shadow_vs cg
{
source newLighting.cg
profiles vs_1_1 arbvp1
entry_point diffuse_vs
compile_arguments -D_DIRECTIONAL=0 -D_POINT=0 -D_SPOTLIGHT=1 -D_SPECULAR=0 -D_NORMAL=1 -D_SHADOWS=1
default_params
{
param_named_auto iWorld world_matrix
param_named_auto iWorldIT inverse_transpose_world_matrix
param_named_auto iWorldViewProj worldviewproj_matrix
param_named_auto iTextViewProj texture_viewproj_matrix
param_named_auto iSpotDir light_direction_object_space 0
param_named_auto iFogParams fog_params
}
}
fragment_program diffuse_directional_specular_normal_shadow_ps cg
{
source newLighting.cg
profiles ps_2_x arbfp1
entry_point diffuse_ps
compile_arguments -D_DIRECTIONAL=1 -D_POINT=0 -D_SPOTLIGHT=0 -D_SPECULAR=1 -D_NORMAL=1 -D_SHADOWS=1
default_params
{
param_named_auto iLightPos light_position 0
param_named_auto iLightDif light_diffuse_colour 0
param_named_auto iLightSpec light_specular_colour 0
param_named_auto iEyePos camera_position
param_named_auto iInvShMapSize inverse_texture_size 3
}
}
fragment_program diffuse_point_specular_normal_shadow_ps cg
{
source newLighting.cg
profiles ps_2_x arbfp1
entry_point diffuse_ps
compile_arguments -D_DIRECTIONAL=0 -D_POINT=1 -D_SPOTLIGHT=0 -D_SPECULAR=1 -D_NORMAL=1 -D_SHADOWS=1
default_params
{
param_named_auto iLightPos light_position 0
param_named_auto iLightDif light_diffuse_colour 0
param_named_auto iLightSpec light_specular_colour 0
param_named_auto iLightAtt light_attenuation 0
param_named_auto iEyePos camera_position
param_named_auto iDepthRange shadow_scene_depth_range 0
param_named_auto iInvShMapSize inverse_texture_size 3
}
}
fragment_program diffuse_spot_specular_normal_shadow_ps cg
{
source newLighting.cg
profiles ps_2_x arbfp1
entry_point diffuse_ps
compile_arguments -D_DIRECTIONAL=0 -D_POINT=0 -D_SPOTLIGHT=1 -D_SPECULAR=1 -D_NORMAL=1 -D_SHADOWS=1
default_params
{
param_named_auto iLightPos light_position 0
param_named_auto iLightDif light_diffuse_colour 0
param_named_auto iLightSpec light_specular_colour 0
param_named_auto iLightAtt light_attenuation 0
param_named_auto iSpotParams spotlight_params 0
param_named_auto iEyePos camera_position
param_named_auto iDepthRange shadow_scene_depth_range 0
param_named_auto iInvShMapSize inverse_texture_size 3
}
}
fragment_program diffuse_directional_specular_celshading_shadow_ps cg
{
source newLighting.cg
profiles ps_2_x arbfp1
entry_point diffuse_ps
compile_arguments -D_DIRECTIONAL=1 -D_POINT=0 -D_SPOTLIGHT=0 -D_SPECULAR=1 -D_NORMAL=0 -D_CELSHADING=1 -D_SHADOWS=1
default_params
{
param_named_auto iLightPos light_position 0
param_named_auto iLightDif light_diffuse_colour 0
param_named_auto iLightSpec light_specular_colour 0
param_named_auto iEyePos camera_position
param_named_auto iInvShMapSize inverse_texture_size 3
}
}
fragment_program diffuse_point_specular_celshading_shadow_ps cg
{
source newLighting.cg
profiles ps_2_x arbfp1
entry_point diffuse_ps
compile_arguments -D_DIRECTIONAL=0 -D_POINT=1 -D_SPOTLIGHT=0 -D_SPECULAR=1 -D_NORMAL=0 -D_CELSHADING=1 -D_SHADOWS=1
default_params
{
param_named_auto iLightPos light_position 0
param_named_auto iLightDif light_diffuse_colour 0
param_named_auto iLightSpec light_specular_colour 0
param_named_auto iLightAtt light_attenuation 0
param_named_auto iEyePos camera_position
param_named_auto iDepthRange shadow_scene_depth_range 0
param_named_auto iInvShMapSize inverse_texture_size 3
}
}
fragment_program diffuse_spot_specular_celshading_shadow_ps cg
{
source newLighting.cg
profiles ps_2_x arbfp1
entry_point diffuse_ps
compile_arguments -D_DIRECTIONAL=0 -D_POINT=0 -D_SPOTLIGHT=1 -D_SPECULAR=1 -D_NORMAL=0 -D_CELSHADING=1 -D_SHADOWS=1
default_params
{
param_named_auto iLightPos light_position 0
param_named_auto iLightDif light_diffuse_colour 0
param_named_auto iLightSpec light_specular_colour 0
param_named_auto iLightAtt light_attenuation 0
param_named_auto iSpotParams spotlight_params 0
param_named_auto iEyePos camera_position
param_named_auto iDepthRange shadow_scene_depth_range 0
param_named_auto iInvShMapSize inverse_texture_size 3
}
}
Code: Select all
material newLighting
{
technique
{
pass Ambient
{
ambient 1 1 1
diffuse 0 0 0
specular 0 0 0 0
emissive 0 0 0
vertex_program_ref ambient_vs
{
}
fragment_program_ref ambient_ps
{
param_named iScale float2 1 1
param_named iScroll float2 0 0
}
texture_unit
{
texture diffuse.png
}
}
pass Directional
{
ambient 0 0 0
diffuse 1 1 1
specular 1 1 1 255
max_lights 8
scene_blend add
iteration once_per_light directional
vertex_program_ref diffuse_directional_shadow_vs
{
}
fragment_program_ref diffuse_directional_specular_celshading_shadow_ps
{
param_named iScale float2 1 1
param_named iScroll float2 0 0
param_named iShininess float 128
}
texture_unit
{
texture diffuse.png
}
texture_unit
{
texture specular.png
}
texture_unit
{
texture normal.png
}
texture_unit
{
content_type shadow
tex_address_mode clamp
}
texture_unit
{
texture cel_diff.png 1d
tex_address_mode clamp
filtering point point none
}
texture_unit
{
texture cel_spec.png 1d
tex_address_mode clamp
filtering point point none
}
texture_unit
{
texture cel_edge.png 1d
tex_address_mode clamp
filtering point point none
}
}
pass Point
{
ambient 0 0 0
diffuse 1 1 1
specular 1 1 1 255
max_lights 8
scene_blend add
iteration once_per_light point
vertex_program_ref diffuse_point_shadow_vs
{
}
fragment_program_ref diffuse_point_specular_celshading_shadow_ps
{
param_named iScale float2 1 1
param_named iScroll float2 0 0
param_named iShininess float 128
}
texture_unit
{
texture diffuse.png
}
texture_unit
{
texture specular.png
}
texture_unit
{
texture normal.png
}
texture_unit
{
content_type shadow
tex_address_mode clamp
}
texture_unit
{
texture cel_diff.png 1d
tex_address_mode clamp
filtering point point none
}
texture_unit
{
texture cel_spec.png 1d
tex_address_mode clamp
filtering point point none
}
texture_unit
{
texture cel_edge.png 1d
tex_address_mode clamp
filtering point point none
}
}
pass Spot
{
ambient 0 0 0
diffuse 1 1 1
specular 1 1 1 255
max_lights 8
scene_blend add
iteration once_per_light spot
vertex_program_ref diffuse_spot_shadow_vs
{
}
fragment_program_ref diffuse_spot_specular_celshading_shadow_ps
{
param_named iScale float2 1 1
param_named iScroll float2 0 0
param_named iShininess float 128
}
texture_unit
{
texture diffuse.png
}
texture_unit
{
texture specular.png
}
texture_unit
{
texture normal.png
}
texture_unit
{
content_type shadow
tex_address_mode clamp
}
texture_unit
{
texture cel_diff.png 1d
tex_address_mode clamp
filtering point point none
}
texture_unit
{
texture cel_spec.png 1d
tex_address_mode clamp
filtering point point none
}
texture_unit
{
texture cel_edge.png 1d
tex_address_mode clamp
filtering point point none
}
}
}
}
Code: Select all
void shadow_caster_vs
(
float4 iPosition : POSITION,
float2 iUV : TEXCOORD0,
uniform float4x4 iWorldView,
uniform float4x4 iWorldViewProj,
out float4 oPosition : POSITION,
out float2 oUV : TEXCOORD0,
out float4 oDepth : TEXCOORD1,
out float4 oDepthDir : TEXCOORD2
)
{
oDepth = mul(iWorldView, iPosition);
oDepthDir = mul(iWorldViewProj, iPosition);
oPosition = oDepthDir;
oUV = iUV;
}
void shadow_caster_ps
(
float2 iUV : TEXCOORD0,
float4 iDepth : TEXCOORD1,
float4 iDepthDir : TEXCOORD2,
uniform sampler2D dTex : TEXUNIT0,
uniform float4 iDepthRange,
uniform float iAlphaReject,
out float4 oColour : COLOR
)
{
if(tex2D(dTex,iUV).a < iAlphaReject)
discard;
float d = (length(iDepth.xyz) - iDepthRange.x) * iDepthRange.w;
oColour = float4(d, d * d, iDepthDir.z, iDepthDir.z*iDepthDir.z);
}
Code: Select all
vertex_program shadow_caster_vs cg
{
source vsmCaster.cg
profiles vs_1_1 arbvp1
entry_point shadow_caster_vs
default_params
{
param_named_auto iWorldView worldview_matrix
param_named_auto iWorldViewProj worldviewproj_matrix
}
}
fragment_program shadow_caster_ps cg
{
source vsmCaster.cg
profiles ps_2_0 arbfp1
entry_point shadow_caster_ps
default_params
{
param_named_auto iDepthRange scene_depth_range
}
}
Code: Select all
material shadowCaster
{
technique
{
pass
{
vertex_program_ref shadow_caster_vs
{
}
fragment_program_ref shadow_caster_ps
{
param_named iAlphaReject float 0.5
}
texture_unit
{
texture diffuse.png
}
}
}
}
Directional light with linear fog:
Point light:
Spot light:
Several spot lights:
Cel-Shading:
############### ISSUES ###############
1º - Point + Spot lights
For some reason if i have a spot light between the lighted area and a point light, the lighting done by the point light is ignored has shown in the images below.
Point light:
Spot light:
Another issue is the light limit that is visible in the point light screenshot shouldn't be there. The range is big enough to light the whole scenario.
This is because of the way the depth is calculated. Specifically, this line:
Code: Select all
float d = (length(iDepth.xyz) - iDepthRange.x) * iDepthRange.w
I don't know how to avoid this. Any help would be appreciated.
2º - I have to use FocusedShadowCamera or the shadowing doesn't work correctly. Don't know why this is....
Bare in mind that this isn't the most efficient way to achieve the result you might want for a specific scenario. But i think it does a good job in illustrating all the available options.
Any suggestions to improve/optimize the code or to correct any problem you might find is highly appreciated.
If you would like a more detail explanation of the code, i'll be glad to had some comments.
**EDIT** Added support to cel-shading. Has you can see on the .program i disabled normal mapping when using cel-shading because the effect is just ugly and weird. But you can try it out. You just have to enable the compile flag _NORMAL. Also in the .material i added three 1D textures that basically define the way the shading looks like (you can get these images from the Ogre Cel-Shading sample).
Also the shininess value was moved to be a parameter instead of being hard-coded.
22/09 Added support for alpha-rejected transparency in shadows. For this to work you'll have to set the shadow caster material in the techniques of the passes that use alpha rejection. You will also have to change the texture of the shadow caster material to the diffuse texture of the corresponding model.
Note: I've been testing this quite extensively and found several small issues. When i'm finished, i'll post the final shader.
Thank you.
JGuerra
PS: If you'd rather have the code in attachments let me know.