All_In_Wonder.cg
//TITLE:
//
// FAST_MONSTER_SHADER
//DESCRIPTION:
// lots of tuneable params, in 1-pass, less accurate (see PURE THEORY)
//TODO:
//
// Alpha testing, specular level in diffuse alpha, environment lookup, ambient occlusion map,
// 2-texture using cheap and nice detail texturing (even for offset-maps), etc. could be still
// added, happy tweaking pioneers out there.
// Though less fast, multipass solution would allow for more instructions in 1 pass, if all
// the goodies listed above won't fit into 64 instuction limit of ps2.0 .
//WARNING: PURE THEORY :)
//
// This version has 1 defect, which is precision:
// Non-linear interpolation of per-vertex lightdir and eyedir(->halfangle) in vertex
// shader causes distortions when light is about poly-size distance from a large poly (or closer)
// but this is unperceptible when cam is farther (eg. in most cases).
// When cam approaches surface that close, shader switching is the optimal solution
// though that other shader being nice and accurate, is quite costy, and cannot be realized
// for 3 lights in 1 pass, because far more calculations (lightdir and eyedir) are in
// fragment shader, and instruction count is limited to 64 in ps2.0
//
// Remaining options:
// - not use this precise one (aka. never let cam that close to a poly)
// - use a second shader like this monster, but only for 1 light, and make it
// multipass (a bit slower)
// (I will try if 2 lights can be pushed below instruction count limit though
// 'cause there might be cases when 2 lights would be enought)
// - never use more than 1(maybe 2 if I can do it) lights that close, thus monster remains
// an 1-pass shader, though still quite costy (about -10..-15 FPS max.)
//
// (Also note that distance for attenuation is calculated at per-vertex level - attenuation
// can't be because it is non-linear, so remains to the frag shader, but as always, I will
// try how it looks, and if you are fine with just linear attenuation, who knows? Might work
// as well, and fast :)
//
// (thanks for all this info on distorsion Sinbad!)
//BUGS(?) AND FLAWS:
//
// Ogre log complains about not being able to hand the shader all uniforms and custom
// parameters when running the shader with reduced parameter count
// - hardly effects performance or quality :) - but if you put comments ('//') into
// material parser before those unused params, all is perfect
//
// Please report any bugs, performance bottlenecks, possible optimizations. See CONTACT.
//HOW I COMPUTE THINGS (just to be consistent with techicals)
//
// For this shader, I put performance over quality.
// This means: - NdotL, pow((NdotH),exponent)) -> no reflection vector
// - less accuracy when light is close to surface (see PURE THEORY on this)
// - attenuation has just linear component varying(k1), and is calculated in
// vertex shader, and interpolated to frag shader automatically
// - specular exponents are the same for the 3 lights, change it if u wish
//
// ALSO NOTE WELL:
// - I use no material light reflection coefficients, they are doing (1,1,1) to
// all kinds of lights (diff, amb, spec)
// - diffuse texture is modulating diffuse light component only
// - there in no such thing as ambient light component here, just big global ambient
// colour as a general environment lighting imitation
// - I attenuate diffuse and specular both, and only them
//
// - at maximum-complexity, it does perpixel offset-mapping with modulative
// diffuse texturing, and with ambient, diffuse, and specular components,
// also attenuation
// Expect extensions later.
//
// - Since I have only 7 texcoords in vs2.0/ps2.0, and for attenuation, I need
// to pass distance to frag shader, and not calculate it there, to be fast,
// I need an 8th.
// To have attenuation, I decided that it will use the slot of halfvector of
// light3.
// IF YOU USE ATTENUATION, YOU WON'T GET SPECULAR OF THE 3RD LIGHT!
// To add, this version suffers from the precision bug (mentioned in THEORY),
// so expect proper attenuation only when perpixel is proper, too.
// (You might change this to let it have specular, IF it does not have diffuse,
// if wish, but I believe this way is more common.
// Or, do you know a situation when specular of 3rd light is needed with atten?)
//
// As you see, this shader is quite efficient (mainly being 1-pass), supports 3 lights,
// but techniques used are quite simple.
//CONTACT:
//
// the name is guilderstein, email: forgamedev@yahoo.com, insert OGRE into subject please
//USAGE SUGGESTIONS:
//
// PLEASE AVOID OBSCURE/ESOTERIC USAGE PARAMETERS
// (like OFFSET on, but ANY_TEXTURE off)
// I guess you will try it just to make it sweat, anyway :)
//
// REQUIREMENTS: (VERY IMPORTANT FOR EFFICIENCY AND CORRECT RESULTS)
// 1. Can be used with no lights just DIFFUSE_TEXTURE, but turn off OFFSET/NORMAL-mapping,
// ATTENUATION, DIFFUSE, and SPECULAR!
// 2. When doing OFFSET/NORMAL-mapping, leave ANY_TEXTURE and at least LIGHT0
// AND DIFFUSE or SPECULAR on !
// 3. DIFFUSE_TEXTURE needs ANY_TEXTURE on. LIGHTx needs at least DIFFUSE or SPECULAR.
// DIFFUSE and SPECULAR needs LIGHTx.
// 4. When using with fewer than 3 lights, turn off lights in this order: 2,1,0 !
// 5. Use ATTENUATION with AT_LEAST light0 AND diffuse OR specular on !
// 6. Due to technical constraints, using ATTENUATION means no specular from light2 !
// 7. Try to use it with healty parameter-combos, at all time !
//
//
//
// YOU'VE BEEN WARNED :D
//
//
//
//Tried working configs: full, full with just offset 0, full but just perpixel,
// any above with less lights (not 0, see above!),
// many combos with diffuse, specular, and ambient with above ones
// zero lights with just texturing (good for debugging, etc.)
//
// Attenuation might have issues. No guarantee for that. Yet.
//
// So shall be workin' if you keep yourself to the 6 rules above.
//
// If you want to look at the code/know what happens when you turn sg. on/off, I suggest
// using MSVC8. Though cannot compile, and you get syntax highlighting only for C parts,
// it grays out unused code for you - real-time, during you write it.
// Quite handy if you ask me :).
//LIMITATIONS, PROS, CONTRAS, FUTURE
//
// The good:
//
// - VERY fast, thanks to being 1 pass
// (in fact, when run with the same options, faster than every other shader I wrote :)
// - Compile time tuneable options, 1 fast shader for LOTS of scenarios.
// Just reload the shader after changing some 0s to 1s in first few lines with a key
// binding or anything in Ogre.
//
// The bad:
//
// - Limited to 3 lights, and with attenuation, you mush make other restrictions, due to its
// massive instruction count (read on about this)
// - Not exact in when lights are close to surfaces which have very few, very large polys
// (though this is rare case, and you can switch it that situation to another shader)
//
// And the ugly:
//
// - Anytime you add another feature, either new limitations arise, to keep it below instruction
// count, being a ps2.0 shader (64 is way_not_enough), or
// - Another ugly thing: this beast needs LOTS of data to traverse between vert and frag
// shaders, and bingding semantics are limited, again way_tooo_much
// (8 texcoords, 1 pos, 1 normal, few worthless extras like color)
//
// So, it can run full speed, full feature wo. attenuation. Thats the deal.
// With atten on, you much cut it somewhere. Either set offset and ambient to 0
// (you can still have normal-mapping though), or use only 2 lights.
// AND apart from that, I need another binding semantic, but I do not have any left, so light2's
// specular is the sacrifice.
// (again, in major number of environments, the 3rd closest lights specular contrib can be
// omitted, given attenuation is more important, at least I think so)
//
//
// In closing, about future: more features, and 2 more versions: 1 slow but featurefull multipass
// one, and 1 slow VERY accurate one, which you can use in front of players looking at 1 poly
// walls.
//TWEAK HERE
//
// #define PER_VERTEX 1 -> TODO /?/
// #define SPOT_LIGHTS 1 -> TODO
// #define DETAIL_BUMP_TEXTURE 1 -> TODO (bump means normal map, height in alpha optional)
// #define DETAIL_DIFFUSE_TEXTURE 1 -> TODO
// #define SPECULAR_POWER_TEXTURE 1 -> TODO (stored in diffuse texture's alpha)
// #define ENVIRONMENT_MAPPING 1 -> TODO
// #define AMBIENT_OCCLUSION 1 -> TODO (modulates envir. lighting aka. ambient)
// #define OPACITY_TEXTURE 1 -> TODO (offset-mapped decals, for.ex)
//
// Other suggestions?
#define ATTENUATION 1
#define ANY_TEXTURE 1
#define DIFFUSE_TEXTURE 1
#define AT_LEAST_PERPIXEL 1
#define AT_LEAST_NORMAL_MAPPING 1
#define OFFSET_MAPPING 1
#define AMBIENT 1
#define DIFFUSE 1
#define SPECULAR 1
#define LIGHT0 1
#define LIGHT1 1
#define LIGHT2 0
// Helper: Expand a range-compressed vector
float3 expand(float3 v)
{
return (v - 0.5) * 2;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// VERT COMES
/////////////////////////////////////////////////////////////////////////////////////////////////
//quite chaotic form, maybe more logical arrangement possible
void WonderShader_Lim3_Fast_Vert
(
float4 position : POSITION,
float3 normal : NORMAL,
out float4 oPosition : POSITION,
#if ANY_TEXTURE
float2 uv : TEXCOORD0,
out float2 oUv : TEXCOORD0,
uniform float scale,
#endif
#if AT_LEAST_NORMAL_MAPPING
float3 tangent : TEXCOORD1,
#elif AT_LEAST_PERPIXEL //just normal perpixel, use texcoord7 to transmit to frag (holds eyedir in offset case)
out float3 oNorm : TEXCOORD7,
#endif
// Still in frag shader!
// #if AMBIENT
// uniform float4 ambient,
// out float4 oColor : COLOR,
// #endif
#if LIGHT0
uniform float4 lightPosition0, // object space
#if DIFFUSE
out float3 oLightDir0 : TEXCOORD1,
#endif
#if SPECULAR
out float3 oHalfAngle0 : TEXCOORD4,
#endif
#endif
#if LIGHT1
uniform float4 lightPosition1, // object space
#if DIFFUSE
out float3 oLightDir1 : TEXCOORD2,
#endif
#if SPECULAR
out float3 oHalfAngle1 : TEXCOORD5,
#endif
#endif
#if LIGHT2
uniform float4 lightPosition2, // object space
#if DIFFUSE
out float3 oLightDir2 : TEXCOORD3,
#endif
#endif
#if ATTENUATION
out float4 dist : TEXCOORD6, // .x, .y, and .z is used for light distances
#elif SPECULAR
out float3 oHalfAngle2 : TEXCOORD6,
#endif
#if OFFSET_MAPPING //needs eye no matter specular
uniform float3 eyePosition, // object space
out float3 oEyeDir : TEXCOORD7,
#elif SPECULAR //no need to output eye, even if normal-mapping is on
uniform float3 eyePosition, // object space
#endif
uniform float4x4 worldviewproj
)
{
oPosition = mul(worldviewproj , position);
#if ANY_TEXTURE
oUv = uv * scale;
#endif
#if AT_LEAST_NORMAL_MAPPING
float3 binormal = cross(tangent, normal);
float3x3 rotation = float3x3(tangent, binormal, normal);
#elif AT_LEAST_PERPIXEL
oNorm = normal;
#endif
#if OFFSET_MAPPING //no matter specular, I need eye for offsetting
float3 eyeDir = eyePosition - position.xyz;
eyeDir = normalize(mul(rotation, eyeDir));
oEyeDir = eyeDir;
#elif SPECULAR //I need eye only if specular other than offset, plus no eye output now
float3 eyeDir = normalize(eyePosition - position.xyz);
#if AT_LEAST_NORMAL_MAPPING //eye needs adjustment if normal-mapping
eyeDir = normalize(mul(rotation, eyeDir));
#endif
#endif
//if DIFFUSE is commented out, we only need temp_lightDirX for oHalfAngle
//and if both DIFFUSE and SPECULAR is out, we shall not calculate any lighting
#if LIGHT0
#if ATTENUATION
float3 temp_lightDir0 = lightPosition0.xyz - (position * lightPosition0.w);
dist.x = length(temp_lightDir0);
temp_lightDir0 = temp_lightDir0 / dist.x; //normalize it this way
// oatten.x = 1/(atten.y + dist0*atten.z + dist0*dist0*atten.w);
//some attenuation calc, could be faster with some approximation
//I think at least leaving only kl component
#else
float3 temp_lightDir0 = normalize(lightPosition0.xyz - (position * lightPosition0.w));
#endif
#if AT_LEAST_NORMAL_MAPPING
temp_lightDir0 = normalize(mul(rotation, temp_lightDir0));
#if DIFFUSE
oLightDir0 = temp_lightDir0;
#endif
#elif DIFFUSE //just normal perpixel
oLightDir0 = temp_lightDir0;
#endif
#if SPECULAR
oHalfAngle0 = normalize(eyeDir + temp_lightDir0);
#endif
#endif
#if LIGHT1
#if ATTENUATION
float3 temp_lightDir1 = lightPosition1.xyz - (position * lightPosition1.w);
dist.y = length(temp_lightDir1);
temp_lightDir1 = temp_lightDir1 / dist.y; //normalize it this way
// oatten.y = 1/(atten.y + dist1*atten.z + dist1*dist1*atten.w);
//some attenuation calc, could be faster with some approximation
//I think at least leaving only kl component
#else
float3 temp_lightDir1 = normalize(lightPosition1.xyz - (position * lightPosition1.w));
#endif
#if AT_LEAST_NORMAL_MAPPING
temp_lightDir1 = normalize(mul(rotation, temp_lightDir1));
#if DIFFUSE
oLightDir1 = temp_lightDir1;
#endif
#elif DIFFUSE //just normal perpixel
oLightDir1 = temp_lightDir1;
#endif
#if SPECULAR
oHalfAngle1 = normalize(eyeDir + temp_lightDir1);
#endif
#endif
#if LIGHT2
#if ATTENUATION
float3 temp_lightDir2 = lightPosition2.xyz - (position * lightPosition2.w);
dist.z = length(temp_lightDir2);
temp_lightDir2 = temp_lightDir2 / dist.z; //normalize it this way
// oatten.z = 1/(atten.y + dist2*atten.z + dist2*dist2*atten.w);
//some attenuation calc, could be faster with some approximation
//I think at least leaving only kl component
#else
float3 temp_lightDir2 = normalize(lightPosition2.xyz - (position * lightPosition2.w));
#if SPECULAR
oHalfAngle2 = normalize(eyeDir + temp_lightDir2);
#endif
#endif
#if AT_LEAST_NORMAL_MAPPING
temp_lightDir2 = normalize(mul(rotation, temp_lightDir2));
#if DIFFUSE
oLightDir2 = temp_lightDir2;
#endif
#elif DIFFUSE //just normal perpixel
oLightDir2 = temp_lightDir2;
#endif
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// FRAG COMES
/////////////////////////////////////////////////////////////////////////////////////////////////
void WonderShader_Lim3_Fast_Frag(
#if DIFFUSE_TEXTURE
uniform sampler2D diffuseMap : register(s1),
float2 uv : TEXCOORD0,
#elif ANY_TEXTURE
float2 uv : TEXCOORD0,
#endif
#if LIGHT0
#if DIFFUSE
float3 LightDir0 : TEXCOORD1,
uniform float4 lightDiffuse0,
#endif
#if SPECULAR
float3 HalfAngle0 : TEXCOORD4,
uniform float4 lightSpecular0,
#endif
#endif
#if LIGHT1
#if DIFFUSE
float3 LightDir1 : TEXCOORD2,
uniform float4 lightDiffuse1,
#endif
#if SPECULAR
float3 HalfAngle1 : TEXCOORD5,
uniform float4 lightSpecular1,
#endif
#endif
#if LIGHT2
#if DIFFUSE
float3 LightDir2 : TEXCOORD3,
uniform float4 lightDiffuse2,
#endif
#endif
#if OFFSET_MAPPING
float3 EyeDir : TEXCOORD7,
uniform float4 scaleBias,
uniform sampler2D normalHeightMap : register(s0),
#elif AT_LEAST_NORMAL_MAPPING
uniform sampler2D normalHeightMap : register(s0),
#elif AT_LEAST_PERPIXEL //no normal-mapping, use standard normal, in eyedir's place
float3 normalvec : TEXCOORD7,
#endif
#if SPECULAR
uniform float exponent0,
// uniform float exponent1,
// uniform float exponent2,
#endif
#if AMBIENT
uniform float4 ambient,
#endif
#if ATTENUATION //if on, supposed to have light0 on as well!
float4 dist : TEXCOORD6,
uniform float4 atten0,
#if LIGHT1
uniform float4 atten1,
#endif
#if LIGHT2
uniform float4 atten2,
#endif
#elif SPECULAR
#if LIGHT2
float3 HalfAngle2 : TEXCOORD6,
uniform float4 lightSpecular2,
#endif
#endif
out float4 oColor : COLOR
)
{
#if OFFSET_MAPPING
float height = tex2D(normalHeightMap, uv).a;
float scale = scaleBias.x;
float bias = scaleBias.y;
float displacement = (height * scale) + bias;
float3 uv2 = float3(uv, 1);
float2 newTexCoord = ((EyeDir * displacement) + uv2).xy;
float3 bumpVec = expand(tex2D(normalHeightMap, newTexCoord ).xyz);
float3 N = normalize(bumpVec);
#if DIFFUSE_TEXTURE
float3 diffusetex = tex2D(diffuseMap, newTexCoord).xyz;
#endif
#elif AT_LEAST_NORMAL_MAPPING
float3 bumpVec = expand(tex2D(normalHeightMap, uv).xyz);
float3 N = normalize(bumpVec);
#if DIFFUSE_TEXTURE
float3 diffusetex = tex2D(diffuseMap, uv).xyz;
#endif
#elif AT_LEAST_PERPIXEL
float3 N = normalize(normalvec);
#if DIFFUSE_TEXTURE
float3 diffusetex = tex2D(diffuseMap, uv).xyz;
#endif
#elif DIFFUSE_TEXTURE
float3 diffusetex = tex2D(diffuseMap, uv).xyz;
#endif
//If different exponents for different specular-lights, change 3 places here!
#if LIGHT0
#if DIFFUSE
float NdotL0 = dot(normalize(LightDir0), N);
#if SPECULAR //both
float NdotH0 = dot(normalize(HalfAngle0), N);
float4 Lit0 = lit(NdotL0,NdotH0,exponent0);
#else //just diffuse
float4 Lit0;
Lit0.y = saturate(NdotL0);
#endif
#elif SPECULAR //just specular
float NdotH0 = dot(normalize(HalfAngle0), N);
float4 Lit0;
Lit0.z = pow(saturate(NdotH0),exponent0);
#endif
#endif
#if LIGHT1
#if DIFFUSE
float NdotL1 = dot(normalize(LightDir1), N);
#if SPECULAR //both
float NdotH1 = dot(normalize(HalfAngle1), N);
float4 Lit1 = lit(NdotL1,NdotH1,exponent0);
#else //just diffuse
float4 Lit1;
Lit1.y = saturate(NdotL1);
#endif
#elif SPECULAR //just specular
float NdotH1 = dot(normalize(HalfAngle1), N);
float4 Lit1;
Lit1.z = pow(saturate(NdotH1),exponent0);
#endif
#endif
#if LIGHT2
#if DIFFUSE
float NdotL2 = dot(normalize(LightDir2), N);
#if SPECULAR //both
#if !ATTENUATION
float NdotH2 = dot(normalize(HalfAngle2), N);
float4 Lit2 = lit(NdotL2,NdotH2,exponent0);
#else
float4 Lit2;
Lit2.y = saturate(NdotL2);
#endif
#else //just diffuse
float4 Lit2;
Lit2.y = saturate(NdotL2);
#endif
#elif SPECULAR //just specular
#if !ATTENUATION
float NdotH2 = dot(normalize(HalfAngle2), N);
float4 Lit2;
Lit2.z = pow(saturate(NdotH2),exponent0);
#else
float4 Lit2;
Lit2 = float4(1,0,0,1);
#endif
#endif
#endif
oColor =
#if ATTENUATION //since usage rules specify it, we have at least light0 on with at least either
//diffuse or specular also on !
// - if you want no lighting, just textures, turn attenuation off!
//But yes, you can have diffusetex with just specular :)
//Note that you can't have specular of light2 with attenuation on. See docs.
// Final color formula with atten
// oColor = atten0 * (diffusetex * lightDiffuse0 * Lit0.y + lightSpecular0 * Lit0.z)
// atten1 * (diffusetex * lightDiffuse1 * Lit1.y + lightSpecular1 * Lit1.z)
// atten2 * (diffusetex * lightDiffuse2 * Lit2.y + lightSpecular2 * Lit2.z)
// + ambient;
#if !DIFFUSE //we start with the no diffuse just diffusetex case, here only spec is attenuated
#if DIFFUSE_TEXTURE
float4(diffusetex,1) +
#endif
#endif
1/(atten0.y + atten0.z*dist.x + atten0.w*dist.x*dist.x)* (
//we always have light0 on here, and spec0 or diffuse0 with it
#if SPECULAR
lightSpecular0 * Lit0.z
#if DIFFUSE
+
#else //light0 has only spec, light0 done
)
#endif
#endif
#if DIFFUSE
lightDiffuse0 * Lit0.y //without + sign, we are good at no specular case as well
#if DIFFUSE_TEXTURE
* float4(diffusetex,1)) //light0 done
#else
) //light0 done
#endif
#endif
#if LIGHT1 //optional, but if we have it, we have either diff1 or spec1, difftex can be 0/1
+ 1/(atten1.y + atten1.z*dist.y + atten1.w*dist.y*dist.y) * (
#if SPECULAR
lightSpecular1 * Lit1.z
#if DIFFUSE
+
#else //light1 has only spec, light1 done
)
#endif
#endif
#if DIFFUSE
lightDiffuse1 * Lit1.y //without + sign here, we are good at no specular case as well
#if DIFFUSE_TEXTURE
* float4(diffusetex,1)) //light1 done
#else
) //light1 done
#endif
#endif
#endif
#if LIGHT2 //ditto goes for diff2, and difftex (no specular here)
+ 1/(atten2.y + atten2.z*dist.z + atten2.w*dist.z*dist.z) * (
#if DIFFUSE
lightDiffuse2 * Lit2.y //without + sign here, we are good at no specular case as well
#if DIFFUSE_TEXTURE
* float4(diffusetex,1)) //light2 done
#else
) //light2 done
#endif
#endif
#endif
#else
// And wo. atten
// oColor = diffusetex
// * (lightDiffuse0 * Lit0.y + lightDiffuse1 * Lit1.y + lightDiffuse2 * Lit2.y)
// + lightSpecular0 * Lit0.z + lightSpecular1 * Lit1.z + lightSpecular2 * Lit2.z
// + ambient;
float4(0,0,0,0) //this is needed to simplify preprocessor stuff, no FPS cost :)
#if DIFFUSE_TEXTURE //at least diffuse texture, no lighting yet
+ float4(diffusetex,1)
#if DIFFUSE //modulate texture with lighting
*(
#if LIGHT0
lightDiffuse0 * Lit0.y
#endif
#if LIGHT1
+ lightDiffuse1 * Lit1.y
#endif
#if LIGHT2
+ lightDiffuse2 * Lit2.y
#endif
)
#endif
#elif DIFFUSE //just diffuse lighting, no modulation
#if LIGHT0
+ lightDiffuse0 * Lit0.y
#endif
#if LIGHT1
+ lightDiffuse1 * Lit1.y
#endif
#if LIGHT2
+ lightDiffuse2 * Lit2.y
#endif
#endif
#if SPECULAR
#if LIGHT0
+ lightSpecular0 * Lit0.z
#endif
#if LIGHT1
+ lightSpecular1 * Lit1.z
#endif
#if LIGHT2
+ lightSpecular2 * Lit2.z
#endif
#endif
#endif
#if AMBIENT
+ ambient
#endif
;
}