Page 1 of 6

4 light normalmapping for animated meshes [C++/GLSL]

Posted: Mon May 07, 2007 6:04 pm
by TheGameMaker
Hi, some new thingy for you. This time I finaly got managed to create a better Normalmapping!!

-4Lights
-Ambient
-specular power(ok this one has every shader^^)
-specular strength(not that ften seen.. but very usefull)
-bump strength (very usefull, managing the strength of the bumpeffect)
-!!WORKS WITH ANIMATED MESHES!!! NO TANGENTS NEEDED!!!
Image

Code: Select all

#include <irrlicht.h>

// --------------------------------------------------------------------------

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;



IrrlichtDevice *device;


const stringc vertBumpShader =
"uniform vec3 fvLightPosition1;"
"uniform vec3 fvLightPosition2;"
"uniform vec3 fvLightPosition3;"
"uniform vec3 fvLightPosition4;"
""
"uniform float fLightStrength1;"
"uniform float fLightStrength2;"
"uniform float fLightStrength3;"
"uniform float fLightStrength4;"
""
"uniform mat4 matWorldInverse;"
""
"varying vec2 Texcoord;"
"varying vec3 ViewDirection;"
"varying vec3 LightDirection1;"
"varying vec3 LightDirection2;"
"varying vec3 LightDirection3;"
"varying vec3 LightDirection4;"
"varying vec4 LightDistMultiplier;"
""   
"float getLengthSQR (vec3 vec)"
"{"
"return(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z);"
"}"  
""   
"void main( void )"
"{"
""   
"   mat4 LightTransform= gl_ModelViewMatrix;"
"   LightTransform=LightTransform*matWorldInverse;"
""      
"   gl_Position = ftransform();"
"   Texcoord    = gl_MultiTexCoord0.xy;"
""    
"   vec4 fvObjectPosition = gl_ModelViewMatrix * gl_Vertex;"
"   vec4 fvLightPos1 = LightTransform * vec4(fvLightPosition1,1.0);"
"   vec4 fvLightPos2 = LightTransform * vec4(fvLightPosition2,1.0);"
"   vec4 fvLightPos3 = LightTransform * vec4(fvLightPosition3,1.0);"
"   vec4 fvLightPos4 = LightTransform * vec4(fvLightPosition4,1.0);"
""   
"   vec3 fvViewDirection  =  - fvObjectPosition.xyz;"
""   
"   vec3 fvLightDirection1 = (fvLightPos1.xyz - fvObjectPosition.xyz);"
"   vec3 fvLightDirection2 = (fvLightPos2.xyz - fvObjectPosition.xyz);"
"   vec3 fvLightDirection3 = (fvLightPos3.xyz - fvObjectPosition.xyz);"
"   vec3 fvLightDirection4 = (fvLightPos4.xyz - fvObjectPosition.xyz);"
""   
"   LightDistMultiplier[0]=1.0/(getLengthSQR (fvLightDirection1)/(fLightStrength1*10000.0));"
"   LightDistMultiplier[1]=1.0/(getLengthSQR (fvLightDirection2)/(fLightStrength2*10000.0));"
"   LightDistMultiplier[2]=1.0/(getLengthSQR (fvLightDirection3)/(fLightStrength3*10000.0));"
"   LightDistMultiplier[3]=1.0/(getLengthSQR (fvLightDirection4)/(fLightStrength4*10000.0));"
""     
"   vec3 fvNormal         = gl_NormalMatrix * gl_Normal;"
""   
"   vec3 fvTangent   = -vec3(abs(gl_Normal.y) + abs(gl_Normal.z), abs(gl_Normal.x), 0);"
"   vec3 fvBinormal =cross(fvTangent,gl_Normal);  "
"   fvTangent=gl_NormalMatrix*cross(fvBinormal,gl_Normal);"
"   fvBinormal=gl_NormalMatrix*fvBinormal;"
"" 
""      
"   ViewDirection.x  = dot( fvTangent, fvViewDirection );"
"   ViewDirection.y  = dot( fvBinormal, fvViewDirection );"
"   ViewDirection.z  = dot( fvNormal, fvViewDirection );"
""   
"   LightDirection1.x  = dot( fvTangent, fvLightDirection1.xyz );"
"   LightDirection1.y  = dot( fvBinormal, fvLightDirection1.xyz );"
"   LightDirection1.z  = dot( fvNormal, fvLightDirection1.xyz );"
""   
"   LightDirection2.x  = dot( fvTangent, fvLightDirection2.xyz );"
"   LightDirection2.y  = dot( fvBinormal, fvLightDirection2.xyz );"
"   LightDirection2.z  = dot( fvNormal, fvLightDirection2.xyz );"
""   
"   LightDirection3.x  = dot( fvTangent, fvLightDirection3.xyz );"
"   LightDirection3.y  = dot( fvBinormal, fvLightDirection3.xyz );"
"   LightDirection3.z  = dot( fvNormal, fvLightDirection3.xyz );"
""   
"   LightDirection4.x  = dot( fvTangent, fvLightDirection4.xyz );"
"   LightDirection4.y  = dot( fvBinormal, fvLightDirection4.xyz );"
"   LightDirection4.z  = dot( fvNormal, fvLightDirection4.xyz );"
""   
"}";
// --------------------------------------------------------------------------

// OpenGL Fragment Program 1.1
const stringc fragBumpShader =
"uniform vec4 fvAmbient;"
"uniform vec4 fvLight1Color;"
"uniform vec4 fvLight2Color;"
"uniform vec4 fvLight3Color;"
"uniform vec4 fvLight4Color;"

"uniform float fSpecularPower;"
"uniform float fSpecularStrength;"
"uniform float fBumpStrength;"

"uniform sampler2D baseMap;"
"uniform sampler2D bumpMap;"

"varying vec2 Texcoord;"
"varying vec3 ViewDirection;"
"varying vec3 LightDirection1;"
"varying vec3 LightDirection2;"
"varying vec3 LightDirection3;"
"varying vec3 LightDirection4;"
"varying vec4 LightDistMultiplier;"

"void main( void )"
"{"
   
"   vec3  fvLightDirection1 = normalize( LightDirection1 );"
"   vec3  fvLightDirection2 = normalize( LightDirection2 );"
"   vec3  fvLightDirection3 = normalize( LightDirection3 );"
"   vec3  fvLightDirection4 = normalize( LightDirection4 );"
   
"   vec3  fvNormal         = texture2D( bumpMap, Texcoord ).yxz;"
"   fvNormal.xy*=2.0;"
"   fvNormal.xy-=1.0;"
   
"   fvNormal=(vec3(0.0,0.0,1.0)-fvNormal)*fBumpStrength+fvNormal;"
   
"   fvNormal=normalize(fvNormal);"
   
"   float fNDotL1           = max(dot(fvNormal, fvLightDirection1),0.0)-0.1; "
"   float fNDotL2           = max(dot(fvNormal, fvLightDirection2),0.0)-0.1; "
"   float fNDotL3           = max(dot(fvNormal, fvLightDirection3),0.0)-0.1; "
"   float fNDotL4           = max(dot(fvNormal, fvLightDirection4),0.0)-0.1; "
   
"   vec3  fvReflection1     = normalize( ( ( 2.0 * fvNormal )  ) - fvLightDirection1 ); "
"   vec3  fvReflection2     = normalize( ( ( 2.0 * fvNormal )  ) - fvLightDirection2 ); "
"   vec3  fvReflection3     = normalize( ( ( 2.0 * fvNormal )  ) - fvLightDirection3 ); "
"   vec3  fvReflection4     = normalize( ( ( 2.0 * fvNormal )  ) - fvLightDirection4 ); "
   
"   vec3  fvViewDirection  = normalize( ViewDirection );"
   
"   float fRDotV1          = max( 0.0, dot( fvReflection1, fvViewDirection ) );"
"   float fRDotV2          = max( 0.0, dot( fvReflection2, fvViewDirection ) );"
"   float fRDotV3          = max( 0.0, dot( fvReflection3, fvViewDirection ) );"
"   float fRDotV4          = max( 0.0, dot( fvReflection4, fvViewDirection ) );"
   
"   vec4  fvBaseColor      = texture2D( baseMap, Texcoord );"
   
"   vec4  fvTotalAmbient   = fvAmbient * fvBaseColor; "
   
"   vec4  fvTotalDiffuse   = fvLight1Color * fNDotL1* fvBaseColor*LightDistMultiplier[0]; "
"   vec4  fvTotalSpecular  = fNDotL1*fvLight1Color * ( pow( fRDotV1, fSpecularPower ) )*LightDistMultiplier[0];"
   
"         fvTotalDiffuse   += fvLight2Color * fNDotL2* fvBaseColor*LightDistMultiplier[1]; "
"         fvTotalSpecular  += fNDotL2*fvLight2Color * ( pow( fRDotV2, fSpecularPower ) )*LightDistMultiplier[1];  "
   
"         fvTotalDiffuse   += fvLight3Color * fNDotL3* fvBaseColor*LightDistMultiplier[2]; "
"         fvTotalSpecular  += fNDotL3*fvLight3Color * ( pow( fRDotV3, fSpecularPower ) )*LightDistMultiplier[2];  "
         
"         fvTotalDiffuse   += fvLight4Color * fNDotL4* fvBaseColor*LightDistMultiplier[3]; "
"         fvTotalSpecular  += fNDotL4*fvLight4Color * ( pow( fRDotV4, fSpecularPower ) )*LightDistMultiplier[3]; "
   
"   vec4 color=( fvTotalAmbient + fvTotalDiffuse+ (fvTotalSpecular*fSpecularStrength));"
"   if(color.r>1.0){color.gb+=color.r-1.0;}"
"   if(color.g>1.0){color.rb+=color.g-1.0;}"
"   if(color.b>1.0){color.rg+=color.b-1.0;}"
"   gl_FragColor = color;"

"}"
;

class Shader_Bump_callback: public video::IShaderConstantSetCallBack
{
public:
    float fLightStrength[4]; //you know I=1/r²?? i changed it to I=1/(r²/fLightStrength) for better results
    vector3df fvLightPosition[4]; //the light positions
    SColorf fvLightColor[4]; 
    SColorf fvAmbient;      
    float fSpecularPower;   // S=pow(fDiffuseIntensity,fSpecularPower)*fSpecularStrength; 
    float fSpecularStrength;// will result in less glossing surfaces
    float fBumpStrength;    //strength of the bumpmapping.. higher values will result in more "bumpy" surfaces 
    
  virtual void OnSetConstants(video::IMaterialRendererServices* services, s32 userData)
  {

    int var0=0;
    services->setPixelShaderConstant("baseMap", (float*)(&var0), 1); //the colormap
    int var1=1;
    services->setPixelShaderConstant("bumpMap", (float*)(&var1), 1); //the normalmap
    
    core::matrix4 invWorld = services->getVideoDriver()->getTransform(video::ETS_WORLD);
    invWorld.makeInverse();
    services->setPixelShaderConstant("matWorldInverse", (float*)(&invWorld), 16);
    
    services->setPixelShaderConstant("fLightStrength1", (float*)(&fLightStrength[0]), 1);
    services->setPixelShaderConstant("fLightStrength2", (float*)(&fLightStrength[1]), 1);
    services->setPixelShaderConstant("fLightStrength3", (float*)(&fLightStrength[2]), 1);
    services->setPixelShaderConstant("fLightStrength4", (float*)(&fLightStrength[3]), 1);
    
    services->setPixelShaderConstant("fvLightPosition1", (float*)(&fvLightPosition[0]), 3);
    services->setPixelShaderConstant("fvLightPosition2", (float*)(&fvLightPosition[1]), 3);
    services->setPixelShaderConstant("fvLightPosition3", (float*)(&fvLightPosition[2]), 3);
    services->setPixelShaderConstant("fvLightPosition4", (float*)(&fvLightPosition[3]), 3);
    
    services->setPixelShaderConstant("fvAmbient", (float*)(&fvAmbient), 4);
    services->setPixelShaderConstant("fvLight1Color", (float*)(&fvLightColor[0]), 4);
    services->setPixelShaderConstant("fvLight2Color", (float*)(&fvLightColor[1]), 4);
    services->setPixelShaderConstant("fvLight3Color", (float*)(&fvLightColor[2]), 4);
    services->setPixelShaderConstant("fvLight4Color", (float*)(&fvLightColor[3]), 4);

    services->setPixelShaderConstant("fSpecularPower", (float*)(&fSpecularPower), 1);
    services->setPixelShaderConstant("fSpecularStrength", (float*)(&fSpecularStrength), 1);
    services->setPixelShaderConstant("fBumpStrength", (float*)(&fBumpStrength), 1);


	}
};




// --------------------------------------------------------------------------

#pragma comment(lib, "Irrlicht.lib")


int main()
{
   // Create device
   video::E_DRIVER_TYPE driverType = video::EDT_OPENGL;

   device = createDevice(driverType, dimension2di(800, 600), 32, false, false, false, 0);
   if (!device) {
      printf("Error creating Irrlicht device\n");
      return 0;
   }

   // Obtain device internals
   IVideoDriver* driver = device->getVideoDriver();
   ISceneManager* smgr = device->getSceneManager();
   // Set a window caption
   device->setWindowCaption(L"NormalMapShader - Irrlicht Engine Demo");

   // Create GLSL shaders
   video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
   s32 mtlToonShader = video::EMT_SOLID; // Fallback material type
   bool bCanDoGLSL_1_1 = false;
   if (gpu && (driverType == video::EDT_OPENGL)) {
      bCanDoGLSL_1_1 = true; // provisionally accept
      if (!driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)) {
         printf("queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1) failed\n");
         bCanDoGLSL_1_1 = false;
      }
      if (!driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)) {
         printf("queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1) failed\n");
         bCanDoGLSL_1_1 = false;
      }
   }
    
   if (bCanDoGLSL_1_1) {
   Shader_Bump_callback *callback= new Shader_Bump_callback;
   
   callback->fBumpStrength=4;
   callback->fSpecularStrength=0.9;
   callback->fSpecularPower=20;
   
   callback->fvAmbient=SColorf(0.02,0.02,0.02);
   
   callback->fLightStrength[0]=1;
   callback->fvLightColor[0]=SColorf(1.00,0.52,0.31); 
   callback->fvLightPosition[0]=vector3df(100,-10,0);
    
   callback->fLightStrength[1]=1;
   callback->fvLightColor[1]=SColorf(0.31,0.55,1.0);
   callback->fvLightPosition[1]=vector3df(-100,10,0); 
   
    
 mtlToonShader = gpu->addHighLevelShaderMaterial(
               vertBumpShader.c_str(), "main", video::EVST_VS_1_1,
               fragBumpShader.c_str(), "main", video::EPST_PS_1_1,
               callback, video::EMT_SOLID);
   } else {
      // This demo is for OpenGL!
      printf("This demo requires OpenGL with GLSL High-Level shaders\n");
      mtlToonShader = video::EMT_SOLID;
   }

   // Add an animated mesh
   IAnimatedMesh* mesh = smgr->getMesh("dwarf.x");


   ISceneNode* node = smgr->addAnimatedMeshSceneNode(mesh);
   if (node)
   {
      node->setMaterialFlag(EMF_LIGHTING, false);
      node->setMaterialTexture( 0, driver->getTexture("Fieldstone.tga") );
      node->setMaterialTexture( 1, driver->getTexture("Fieldstonebumpdot3.tga") );
      node->setMaterialType((video::E_MATERIAL_TYPE)mtlToonShader); // Override material type
   }

   // Add a viewing camera
   smgr->addCameraSceneNodeFPS();

   // Main rendering loop
   while(device->run())
   {
        vector3df rot=node->getRotation();
        rot.Y+=0.1;
        node->setRotation(rot);
      driver->beginScene(true, true, SColor(0,0,0,0));
      smgr->drawAll();
      driver->endScene();
   }

   device->drop();

   // Done
   return 0;
}

thx to sio for his tut on shadercallbacks.. i allways use the scructure of his code!
hope you like it!! maybe i gonna build some real material from it..
greetz TGM

Posted: Tue May 08, 2007 10:36 am
by white tiger
very great work!! Thanks!! :) I will use this instead of the irrlicht normal map, wich needs tangents!

Is there a method to deactive at all any type of lights on the mesh using your shader? Like set 'Lighting' parameter to false in SMaterial and all lights have no effect.

Also what is the license? Is the license the same of your previous shaders?

Posted: Tue May 08, 2007 12:28 pm
by Virion
Nice work. :D

Posted: Tue May 08, 2007 12:49 pm
by TheGameMaker
well, I´m a big fan of totaly free software, so you can do with it what ever you want!

to turn off al lights you just have to set the color to 0,0,0...
maybe you want to have a few lines of code inside the callback as like as:

Code: Select all

bool lightOn[4];
Scolorf black(0,0,0);
if(!lightOn[0])
{
services->setPixelShaderConstant("fvLight1Color", (float*)(&black), 4); 
}
else
{
services->setPixelShaderConstant("fvLight1Color", (float*)(&fvLightColor[0]), 4); 
}

Posted: Wed May 09, 2007 2:11 pm
by BlindSide
No tangents?!?! Awesome! :D

What Shader model version is required for this?

I will try to make an HLSL version with low shader model requirements...

Posted: Wed May 09, 2007 3:00 pm
by Saturn
BlindSide wrote:No tangents?!?! Awesome! :D
Not quite. It still operates in tangent space, but builds tangent space base itself just from the normal.
But the shader has no way to know in what direction the tangent has to be in order to be aligned properly along the texture coords. The result is that you can't use this shader for highly structured normap maps, only for unstructured surfaces like stone, dirt, etc.. And even then you will have artifacts, that might be sometimes more, sometimes less visible, depending on how the mesh is unwrapped.

A HLSL shader with the same functionality fits easily in SM2.0. SM1.x probably not, because of the low instruction limit.

Posted: Wed May 09, 2007 5:04 pm
by TheGameMaker
ok.. it has to build the tangents, thats correct. But i haven´t noticed any artifacts(there is just one possiblke tangent per normal). If you don´t believe me just give it a try!!
The result is that you can't use this shader for highly structured normap maps, only for unstructured surfaces like stone, dirt, etc.. And even then you will have artifacts, that might be sometimes more, sometimes less visible, depending on how the mesh is unwrapped.
I gonna post some screens that will show you that this is definitiv wrong!
just give me a minute..
ok here they are: the first one is a modell done by myself, the second one a model made by psyonic(i think..) its free, so give it a test. I just rendered the normalmapping for you to hav a better view on the linghtning.
Image
Image
I know that it is not as good as creating the tangent with the uv coords, but i have to say, that i wasn´t able to see a different..
And even if it has a slightly less quality, you should choose wisely between using animated Meshes with a "not as good shader" and unanimated Meshes with a "better" shader. besides the irrlicht normalmapping shader lacks of a lot of very usefull funktions.

Posted: Wed May 09, 2007 5:57 pm
by Luben
Isn't it computationally expensive to produce tangents in a vertex shader? How does it compare to meshes with tangents?
there is just one possible tangent per normal
If i'm not mistaken, there's a whole plane of possible tangents!

Posted: Wed May 09, 2007 6:00 pm
by Saturn
Hey, I didn't want to talk your shader down or anything, just note the difference. :)
If there was no need for tangents, then no one would use them, and I had my share of problems with wrong tangents in the past. ;)
If tangents are not aligned along the UVs, then lighting is off, it fits locally, since lighting delta is correct, but on the whole mesh it was off. For a fine ridge on a plate mail for instance, that is a curve on the diffuse and normal map, the apparent light direction changed along the ridge on the final image, even though it was lit by a directional light. That is one of the fine structures I meant in my earlier post.

Your mesh though looks good. With point lights this is due to perspective projection not so easily noticeable in close range anyway.

Posted: Wed May 09, 2007 6:02 pm
by Saturn
Luben wrote:Isn't it computationally expensive to produce tangents in a vertex shader? How does it compare to meshes with tangents?
No, a single cross product is needed, that's cheap, multiply-add is what cards can do best probably.
Luben wrote:
there is just one possible tangent per normal
If i'm not mistaken, there's a whole plane of possible tangents!
Yep, a plane of tangents and the direction on this plane matters for correct lighting.

Edit: Correction. Cross product, not dot product as preaviously written.

Posted: Wed May 09, 2007 6:35 pm
by TheGameMaker
yep.. there would be a plane, but the tangent is always the same for the same normal.. so ther aren´t such artifacts as noise etc. Of curse it looks slightly less good as precomputed tangents, but as already said i never saw the different.

Posted: Thu May 10, 2007 8:28 am
by belfegor
Great shader.
to turn off al lights you just have to set the color to 0,0,0...
I think this isnt correct, that light will still be computated.correct?

Posted: Thu May 10, 2007 10:28 am
by TheGameMaker
yaeh.. thats right... if you want the shader to work with multiple lights(non-hardcoded number) you would have to use a loop instead of a hardcoded lightcalculation for each light.. this would be a bigger change...

Posted: Thu May 10, 2007 1:54 pm
by omaremad
Thats a very nice shader, your lighting setup is very atmosphereic.
Hey, I didn't want to talk your shader down or anything, just note the difference. Smile
If there was no need for tangents, then no one would use them, and I had my share of problems with wrong tangents in the past. Wink
If tangents are not aligned along the UVs, then lighting is off, it fits locally, since lighting delta is correct, but on the whole mesh it was off. For a fine ridge on a plate mail for instance, that is a curve on the diffuse and normal map, the apparent light direction changed along the ridge on the final image, even though it was lit by a directional light. That is one of the fine structures I meant in my earlier post.

Your mesh though looks good. With point lights this is due to perspective projection not so easily noticeable in close range anyway.
His shader is assuming that the tangent lie on a planne perpendicular to the normal which is a 100% correct assumption (thats what a normal is defined as 90 deg to the surface)


The problem comes when your normals are not perpendicular, this ussually happens when you average your normals via smoothing in blender max etc.... so as long as you dont smooth your normals you will be fine. (but thats will result in losing the benifits of normal smoothing, or keep the smoothing and hope none of saturn's artefacts appear)

btw a small tip

Code: Select all

"attribute vec3 rm_Binormal;"
"attribute vec3 rm_Tangent;"
""   
"float getLengthSQR (vec3 vec)"
"{"
"return(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z);"
"}" 
""   
you dont need the attributes any more :wink:

and length sqaured is just dot(vector,vector)
the swizzling some times doesnt get optimised by compilers

btw i wont be around irrlicht for long,i have exams coming up and i already started making my own engine.

Posted: Thu May 10, 2007 3:15 pm
by TheGameMaker
thx for the tipps! I corrected the attribute thingy..