MONSTER_2.cg
// Title: // MONSTER v2.0 // What is this? (for newcomers) // // Ever wrote a shader to support one particular setup, then another for another setup, // finally ending up with lots of shaders doing almost the same thing? // With #define-s and other preproc. directives, you can have "branching" in compile-time, // no need to slow the code down with conditional statements. // This also allows to write ONE shader code that is VERY close to optimal for many situations. // (Of course you need to recompile, but auto-generated versions are far easier this way.) // That is what you'll find here. // Check features below. // Notes: // - mainly for outdoor rendering, due to hemispherical lighting // (I wanted to have offset-mapping on every side of an object, which is quite // hard with directional lights, also they are too stark for an ambient outdoor // lighting) // - watch out for light positions rivalizing in distance from object's center: // a typical artifact source /closest light switches between 2 lights/ // - avoid crazy parameter combos // - notice how shader instruction count varies with shader complexity: // HLSL vertex: a full-blown vs is 58, while it is 38 with no normal mapping // and only 25 if light0-light2 are disabled also (hemi still on!) // BTW the minimal possible with MONSTER_2 is 4 instr. :D // HLSL pixel: full-blown gets to 73, won't compile, just as expected, // with no normal-mapping: 56. // with light0-light2 disabled (hemi and offset on): 29. // with just hemi (but fading, specular gloss, diffusetex): 12. // BTW the minimal is 2 instr. Pretty useless :D. // Limitations: // // As you will notice, many parameters are #define-s, for faster code, but if // real-time change is needed, they could be handed as uniforms as well. // (This applies inversely as well, so some parameters might be #defined instead of passing // them as uniforms, for faster code.) // // I also skipped the Ka, Kd, Ks, etc. constants present in many shaders, to // be used as coefficient when summing lighting parts, obviously for performance. // They can be 'faked' by properly adjusting the diffuse/gloss/AO textures' brighness // in an image editor. Add them if you wish, but they slow MONSTER down. // (Ka can be coded into AO map, Kd into diffuse map, Ks into gloss map) // They also contribute to instruction count, so beware. // // Also note that I placed no lighting restrictions on the user in this release. // It's a free for all. You decide what you want, but do not expect any compiler to // create you a shader that does *MAX* candy for *MAX* lights, given the instruction // count limits of ps_2_0 / arbfp1. // // No directional lights. Hemisphere is better, and leaving them makes syntax clearer too. // // One more thing: all lights have diffuse color = specular color as an optimization. // I believe it to be reasonable. // // I use 2 UV sets for now, 1 is for auto-generated AOmap, and the other is // for diffuse/normal maps. I you are skilled/lucky, you can go with 1 UV set as well. // Artist/model dependant. // Features: // // - improved lighting accuracy on low-poly meshes (2 tri quad for ex) // // - clearer code // // - 2 uv sets // // - faster attenuation // // - 1 hemispheric skylight with specular // /tune with: SKY_LIGHT_DIR, SKY_COLOR, GROUND_COLOR, SKY_EXPONENT/ // // - max. 3 local lights // /diffuse part, specular part, attenuation/ // // - per-pixel lighting / normal-mapping / offset-mapping // // - texturing: (diffuse + gloss), (normal + height), ambient occlusion // // - buggy distance-fadeout effect (someone should fix it, I can't get it right) // // - oFusion compatibility as usual // // - some typos, as usual :D // GLSL users: // // I do not compile this file as cg, because that way is not optimal. // Cg compiler sometimes generates longer code than HLSL. // If you need OpenGL, just change the definition in .material file to cg, // and the "target" word to "profiles" like: // // vertex_program Simple_Perpixel_Vert cg // .. // profiles vs_1_1 arbvp1 // //target vs_1_1 // // and of course do the same for fragment_program. // Or, for real-time support of both HLSL and GLSL, create 2 versions of MONSTER, // on with cg, one with HLSL, and use 2 techniques. // // BTW interesting thing is that GLSL code has only ~2/3 instructions. Dunno why. // This lets you have more complex version running with GLSL. // Want more lights? // // What to do if you want *MORE* dynamic lights affecting a single static mesh? // (obviously this works mainly for large meshes and small area, attenuated lights) // // 0. use deferred shading (needs a good card to be fast enough) // 1. use multiple passes (costy) // 2. use cheaper frag shader (looks less cool) or vertex-lights (ugly without tessellation) // 3. divide mesh to small parts in editor, bake it in static geometry, and you're done // (set up lots of lights swarming around the mesh, and each part will be affected by 2, // but the 2 closest, which will differ with the parts position) // ADDITIONAL DOCS: // // check earlier versions of MONSTER in Ogre WIKI, write mail, etc. // TODO: // // alpha-maps, environment-mapping, detail-texturing (both diffuse-map and offset-map) // volume-lighting, lightmaps, emissive-texture mapping, spot-lights, fog (volumetric?), // skinning, instancing, // ... // // Future dream-plan: // a demo app like GLSLdemo with texture/mesh browser, uniform sliders, and recompile option. // CONTACT: // // the name is guilderstein, email: forgamedev@yahoo.com, insert OGRE into subject please, // private message is better however // // That's all for now, go and shade :D //pre-defines for compiler, ignore them #define DIFFUSE_MAP 0 #define EXTRA_GLOSS_MAP 0 #define AMBIENT_OCC_MAP 0 #define NORMAL_MAP 0 #define EXTRA_HEIGHT_MAP 0 #define LIGHT_0 0 #define LIGHT_1 0 #define LIGHT_2 0 #define LIGHT_0_SPECULAR 0 #define LIGHT_1_SPECULAR 0 #define LIGHT_2_SPECULAR 0 #define HEMI_SKYLIGHT 0 #define HEMI_SKYLIGHT_SPECULAR 0 #define ATTENUATION 0 #define SPECULAR 0 #define SKY_LIGHT_DIR 0 #define GROUND_COLOR 0 #define SKY_COLOR 0 #define SKY_EXPONENT 0 #define EXPONENT_0 0 #define EXPONENT_1 0 #define EXPONENT_2 0 #define ATTEN_0 0 #define ATTEN_1 0 #define ATTEN_2 0 // general switches #define DISTANCE_FADING 0 //do not use fading, buggy #define TEXTURING 1 #define LIGHTING 1 #if TEXTURING #define DIFFUSE_MAP 1 #if DIFFUSE_MAP #define EXTRA_GLOSS_MAP 1 #endif #define AMBIENT_OCC_MAP 1 #if LIGHTING #define NORMAL_MAP 1 #if NORMAL_MAP #define EXTRA_HEIGHT_MAP 1 #endif #endif #endif // ATTEN values are ~ 1/light range, larger value -> stronger atten. #if LIGHTING #define ATTENUATION 1 #define SPECULAR 1 //diffuse in on if light is on #define LIGHT_0 1 #if SPECULAR #define LIGHT_0_SPECULAR 1 #endif #define LIGHT_1 1 #if SPECULAR #define LIGHT_1_SPECULAR 1 #endif #define LIGHT_2 1 #if SPECULAR #define LIGHT_2_SPECULAR 0 #endif #define HEMI_SKYLIGHT 1 #if SPECULAR #define HEMI_SKYLIGHT_SPECULAR 1 #endif #endif // per-light switches #if HEMI_SKYLIGHT #define SKY_LIGHT_DIR float3(0,1,0) #define GROUND_COLOR float3(0.1,0.1,0.1) #define SKY_COLOR float3(0.9,0.9,1.0) #if HEMI_SKYLIGHT_SPECULAR #define SKY_EXPONENT 8 #endif #endif #if LIGHT_0 #if LIGHT_0_SPECULAR #define EXPONENT_0 120 #endif #if ATTENUATION #define ATTEN_0 0.002 #endif #endif #if LIGHT_1 #if LIGHT_1_SPECULAR #define EXPONENT_1 120 #endif #if ATTENUATION #define ATTEN_1 0.002 #endif #endif #if LIGHT_2 #if LIGHT_2_SPECULAR #define EXPONENT_2 120 #endif #if ATTENUATION #define ATTEN_2 0.002 #endif #endif //diff: first texture (reg s0), uv1, UVs.xy, TEXCOORD0 //AO: second texture (reg s1), uv2, UVs.zw, TEXCOORD1 //norm: third texture (reg s2), uv1, UVs.xy, TEXCOORD0 ////////////////////////////////////////////////////////////////////////////////////////////// // VERTEX SHADER // ////////////////////////////////////////////////////////////////////////////////////////////// void MONSTER_vs ( /////////////////////// //VS INPUT PARAMETERS// /////////////////////// //texturing #if TEXTURING #if DIFFUSE_MAP float2 uv1 : TEXCOORD0, uniform float tile_factor, //tiles diffuse & normal maps via uv1 #elif NORMAL_MAP float2 uv1 : TEXCOORD0, uniform float tile_factor, //you can have only 1 on, and both on as well #endif //both normal and diffuse uses uv1 #if AMBIENT_OCC_MAP float2 uv2 : TEXCOORD1, //ao uses uv2 #endif out float4 oUVs : TEXCOORD1, //oUVs: oT1 //texturing means oUVs on, and both uv1 //and uv2 get passed in oUVs #if NORMAL_MAP float3 tangent : TEXCOORD2, #endif #endif //lighting #if LIGHTING //lightposX is in object space #if HEMI_SKYLIGHT #if NORMAL_MAP out float3 oSkyLightDir : TEXCOORD2, //oSkyDir: oT2 #endif //if no normal mapping is used, ps can use //the defined SKY_LIGHT_DIR otherwise we //need to transform it to tangent space #endif #if LIGHT_0 uniform float4 lightpos0, out float3 oLightDir0: TEXCOORD3, //oLDir0: oT3 #if LIGHT_0_SPECULAR out float3 oHalfAngle0: TEXCOORD6, //oHA0: oT6 #endif #endif #if LIGHT_1 uniform float4 lightpos1, out float3 oLightDir1: TEXCOORD4, //oLDir1: oT4 #if LIGHT_1_SPECULAR out float3 oHalfAngle1: TEXCOORD7, //oHA1: oT7 #endif #endif #if LIGHT_2 uniform float4 lightpos2, out float3 oLightDir2: TEXCOORD5, //oLDir2: oT5 #if LIGHT_2_SPECULAR out float3 oHalfAngle2: TEXCOORD0, #endif #endif //I don't have more oTX space for oHalfAngle2, //could interleave since many oTX uses only 3 float of 4, //but that is messy, and ps might overrun its instr. limit, too, //so LIGHT_2_SPECULAR is disabled by default #endif //eye/camera position in object space #if DISTANCE_FADING #if EXTRA_HEIGHT_MAP out float3 oEyeDir : TEXCOORD0, //oEye: oT0 //if offset mapping is used, oT0 is free, normal does not go to ps #endif out float4 oFadeColor : COLOR, uniform float fade_start_dist, uniform float fade_end_dist, uniform float3 eyepos, //we can have fading with offset mapping, and wo it #elif EXTRA_HEIGHT_MAP out float3 oEyeDir : TEXCOORD0, //oEye: oT0 uniform float3 eyepos, #elif SPECULAR uniform float3 eyepos, #endif //mixed basics #if !NORMAL_MAP #if LIGHTING out float3 oNorm: TEXCOORD0, //oNorm: oT0 #endif #endif float4 pos : POSITION, uniform float4x4 wvp, float3 norm : NORMAL, out float4 oPos : POSITION ) { /////////////////////// //VS CODE STARTS HERE// /////////////////////// //mixed basics oPos = mul(wvp, pos); #if !NORMAL_MAP #if LIGHTING oNorm = norm; #endif #endif #if DISTANCE_FADING float ratio = saturate(1-(distance(eyepos, pos.xyz)-fade_start_dist) / fade_end_dist); oFadeColor = float4(0,0,0,ratio); #endif //texturing #if TEXTURING oUVs = float4(0,0,0,0); #if DIFFUSE_MAP oUVs.xy = uv1 * tile_factor; #elif NORMAL_MAP oUVs.xy = uv1 * tile_factor; #endif #if AMBIENT_OCC_MAP oUVs.zw = uv2; #endif #endif //lighting & normal mapping #if LIGHTING #if NORMAL_MAP float3 binormal = cross(tangent, norm); float3x3 rotation = float3x3(tangent, binormal, norm); #if HEMI_SKYLIGHT oSkyLightDir = mul(rotation, SKY_LIGHT_DIR); #endif #if EXTRA_HEIGHT_MAP float3 EyeDir = eyepos - pos.xyz; EyeDir = mul(rotation, EyeDir); oEyeDir = EyeDir; #if SPECULAR float3 normed_EyeDir = normalize(EyeDir); #endif //if I pass a vector un-normalized to frag shader, //I get better interpolation, but normalization //is needed for halfvectors #elif SPECULAR float3 normed_EyeDir = normalize(eyepos - pos.xyz); //this allows having specular with only normal maps #endif #elif SPECULAR float3 normed_EyeDir = normalize(eyepos - pos.xyz); #endif #if LIGHT_0 #if ATTENUATION oLightDir0 = ATTEN_0 * (lightpos0 - pos).xyz; #else oLightDir0 = (lightpos0 - pos).xyz; #endif #if NORMAL_MAP oLightDir0 = mul(rotation, oLightDir0); #endif #if LIGHT_0_SPECULAR oHalfAngle0 = normalize(normalize(oLightDir0) + normed_EyeDir); #endif #endif #if LIGHT_1 #if ATTENUATION oLightDir1 = ATTEN_1 * (lightpos1 - pos).xyz; #else oLightDir1 = (lightpos1 - pos).xyz; #endif #if NORMAL_MAP oLightDir1 = mul(rotation, oLightDir1); #endif #if LIGHT_1_SPECULAR oHalfAngle1 = normalize(normalize(oLightDir1) + normed_EyeDir); #endif #endif #if LIGHT_2 #if ATTENUATION oLightDir2 = ATTEN_2 * (lightpos2 - pos).xyz; #else oLightDir2 = (lightpos2 - pos).xyz; #endif #if NORMAL_MAP oLightDir2 = mul(rotation, oLightDir2); #endif #if LIGHT_2_SPECULAR oHalfAngle2 = normalize(normalize(oLightDir2) + normed_EyeDir); #endif #endif #endif } // Note: instead of passing v2f the HalfAngle of each light, it (or reflection vector) could be calculated in fs! // oTx space vs. instr.count decision, and would make vs real fast! ////////////////////////////////////////////////////////////////////////////////////////////// // PIXEL SHADER // ////////////////////////////////////////////////////////////////////////////////////////////// void MONSTER_ps ( /////////////////////// //PS INPUT PARAMETERS// /////////////////////// //mixed basics #if !NORMAL_MAP #if LIGHTING float3 norm : TEXCOORD0, #endif #endif #if DISTANCE_FADING float opacity : COLOR, #endif //texturing #if TEXTURING float4 UVs : TEXCOORD1, //uv2 AND already scaled uv1 #if DIFFUSE_MAP uniform sampler2D DiffuseGlossMap : register(s0), //first texture #endif #if AMBIENT_OCC_MAP uniform sampler2D AoMap : register(s1), //second texture #endif #if NORMAL_MAP uniform sampler2D NormalHeightMap : register(s2), //third texture #endif #endif //lighting #if LIGHTING #if HEMI_SKYLIGHT #if NORMAL_MAP float3 TSkyLightDir : TEXCOORD2, #endif //again, if I do no normal mapping with hemi, I can go with //just using the #define-d SKY_LIGHT_DIR #endif #if LIGHT_0 uniform float3 lightColor0, float3 LightDir0 : TEXCOORD3, #if LIGHT_0_SPECULAR float3 HalfAngle0 : TEXCOORD6, #endif #endif #if LIGHT_1 uniform float3 lightColor1, float3 LightDir1 : TEXCOORD4, #if LIGHT_1_SPECULAR float3 HalfAngle1 : TEXCOORD7, #endif #endif #if LIGHT_2 uniform float3 lightColor2, float3 LightDir2 : TEXCOORD5, #if LIGHT_2_SPECULAR float3 HalfAngle2 : TEXCOORD0, #endif #endif #if EXTRA_HEIGHT_MAP uniform float2 scaleBias, float3 EyeDir : TEXCOORD0, //offset mapping needs no normal from vs, hence T0 is free #endif #endif //final color out float4 oColor : COLOR ) { /////////////////////// //PS CODE STARTS HERE// /////////////////////// //lighting and texturing #if LIGHTING #if !NORMAL_MAP float3 N = normalize(norm); #else #if EXTRA_HEIGHT_MAP EyeDir = normalize(EyeDir); //get offset uvs float height = tex2D(NormalHeightMap, UVs.xy).a; float2 newTexCoord = UVs.xy + EyeDir.xy * (height * scaleBias.x + scaleBias.y); float3 bumpVec = tex2D(NormalHeightMap, newTexCoord ).xyz * 2 - 1; //lookup with texcoord 0, and expand from range comressed #else float3 bumpVec = tex2D(NormalHeightMap, UVs.xy ).xyz * 2 -1; //same, but just normal-mapping #endif float3 N = normalize(bumpVec); #endif //okay, now we have a normal to do lighting //TODO/?/: send a HalfAngle_Sky from vs to get cheaper sky-specular, but that // would cost 1 additional texcoord, maybe taken from light_2 // /again, interleaving is an option, since many times I use only 3 // float values of texcoords to pass data vert2frag, but all texcoords // are float4 vectors; biggest problem is usage complexity/ //TODO: Issues with SKY_SPECULAR, now sky can only have spec. if offset is on #if HEMI_SKYLIGHT #if NORMAL_MAP float SKYdotN = dot(TSkyLightDir, N); #if EXTRA_HEIGHT_MAP #if HEMI_SKYLIGHT_SPECULAR float SKYdotH = dot(normalize(EyeDir + TSkyLightDir), N); float4 hemispec = pow(saturate(SKYdotH), SKY_EXPONENT); //try with lit()! #endif #endif #else float SKYdotN = dot(SKY_LIGHT_DIR, N); #endif float3 hemi = lerp(GROUND_COLOR, SKY_COLOR, 0.5 + 0.5 * SKYdotN); #endif #if LIGHT_0 LightDir0 = normalize(LightDir0); #if ATTENUATION float atten0 = saturate(1 - dot(LightDir0, LightDir0)); #endif float NdotL0 = dot(LightDir0, N); #if LIGHT_0_SPECULAR float NdotH0 = dot(HalfAngle0, N); float4 Lit0 = lit(NdotL0,NdotH0,EXPONENT_0); #endif #endif #if LIGHT_1 LightDir1 = normalize(LightDir1); #if ATTENUATION float atten1 = saturate(1 - dot(LightDir1, LightDir1)); #endif float NdotL1 = dot(LightDir1, N); #if LIGHT_1_SPECULAR float NdotH1 = dot(HalfAngle1, N); float4 Lit1 = lit(NdotL1,NdotH1,EXPONENT_1); #endif #endif #if LIGHT_2 LightDir2 = normalize(LightDir2); #if ATTENUATION float atten2 = saturate(1 - dot(LightDir2, LightDir2)); #endif float NdotL2 = dot(LightDir2, N); #if LIGHT_2_SPECULAR float NdotH2 = dot(float3(0,1,0), N); float4 Lit2 = lit(NdotL2,NdotH2,EXPONENT_2); #endif #endif #endif //color texturing #if TEXTURING #if EXTRA_HEIGHT_MAP #if EXTRA_GLOSS_MAP float4 diff = tex2D(DiffuseGlossMap, newTexCoord).xyzw; float3 diffusetex = diff.xyz; float gloss_power = diff.w; #elif DIFFUSE_MAP float3 diffusetex = tex2D(DiffuseGlossMap, newTexCoord).xyz; //lookup with offsetted texcoord 0 #endif #else #if EXTRA_GLOSS_MAP float4 diff = tex2D(DiffuseGlossMap, UVs.xy).xyzw; float3 diffusetex = diff.xyz; float gloss_power = diff.w; #elif DIFFUSE_MAP float3 diffusetex = tex2D(DiffuseGlossMap, UVs.xy).xyz; #endif //lookup with texcoord 0 #endif #if AMBIENT_OCC_MAP float occlusionfactor = tex2D(AoMap, UVs.zw).x; //lookup with texcoord 1 //Clumpsy solution, since it is a grayscale map anyway, //it could go into for ex. diffuse's alpha channel if no //specular gloss mapping is done //Though you will need to have either 2 lookup with the 2 uv sets on //the same texture, OR have 1 uv set for all maps, and go with 1 lookup #endif #endif //All is set up, now comes the most complex part, final color computation: //Though many variations are possible, I go with this one: // (at maximum, and in readable form) // // oColor = hemi * occlusionfactor //-> ambient lighting // + sky_color * sky_specular_coeff //-> ambient specular // + sum( atten[i] * lightcolor[i] * diffuse_coeff[i]) * diffusetex // + sum( atten[i] * lightcolor[i] * specular_coeff[i]) * gloss_power // //Try modulating (hemi * AO) also with diffusetex, etc. //Though syntax might seem chaotic, compiler does a great job in optimizing out float3(0,0,0) for ex. oColor = float4 ( float3(0,0,0) #if !LIGHTING #if TEXTURING #if DIFFUSE_MAP + diffusetex #if AMBIENT_OCC_MAP //just ao * occlusionfactor.xxx #endif #elif AMBIENT_OCC_MAP + occlusionfactor.xxx #endif //TODO: add normal map output as diffuse for debug purposes #endif #endif #if HEMI_SKYLIGHT + hemi #if AMBIENT_OCC_MAP * occlusionfactor #endif #if EXTRA_HEIGHT_MAP #if HEMI_SKYLIGHT_SPECULAR + hemispec #if EXTRA_GLOSS_MAP * gloss_power #endif #endif #endif #endif #if LIGHT_0 + lightColor0 * ( #if ATTENUATION atten0 * ( #if LIGHT_0_SPECULAR Lit0.y #if DIFFUSE_MAP * diffusetex #endif + Lit0.z #if EXTRA_GLOSS_MAP * gloss_power )) #else )) #endif #else NdotL0 )) #endif #elif LIGHT_0_SPECULAR //no atten, but spec and diff Lit0.y #if DIFFUSE_MAP * diffusetex #endif + Lit0.z #if EXTRA_GLOSS_MAP * gloss_power) #else ) #endif #else //no. atten, just diffuse NdotL0 #if DIFFUSE_MAP * diffusetex ) #else ) #endif #endif #endif #if LIGHT_1 + lightColor1 * ( #if ATTENUATION atten1 * ( #if LIGHT_1_SPECULAR Lit1.y #if DIFFUSE_MAP * diffusetex #endif + Lit1.z #if EXTRA_GLOSS_MAP * gloss_power )) #else )) #endif #else NdotL1 )) #endif #elif LIGHT_1_SPECULAR //no atten, but spec and diff Lit1.y #if DIFFUSE_MAP * diffusetex #endif + Lit1.z #if EXTRA_GLOSS_MAP * gloss_power) #else ) #endif #else //no. atten, just diffuse NdotL1 #if DIFFUSE_MAP * diffusetex ) #else ) #endif #endif #endif #if LIGHT_2 + lightColor2 * ( #if ATTENUATION atten2 * ( #if LIGHT_2_SPECULAR Lit2.y #if DIFFUSE_MAP * diffusetex #endif + Lit2.z #if EXTRA_GLOSS_MAP * gloss_power )) #else )) #endif #else NdotL2 )) #endif #elif LIGHT_2_SPECULAR //no atten, but spec and diff Lit2.y #if DIFFUSE_MAP * diffusetex #endif + Lit2.z #if EXTRA_GLOSS_MAP * gloss_power) #else ) #endif #else //no. atten, just diffuse NdotL2 #if DIFFUSE_MAP * diffusetex ) #else ) #endif #endif #endif , #if DISTANCE_FADING opacity #else 1 #endif ); }