Blender Normal mapping and Specular maps

The place for artists, modellers, level designers et al to discuss their approaches for creating content for OGRE.
nfz
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 1263
Joined: Wed Sep 24, 2003 4:00 pm
Location: Halifax, Nova Scotia, Canada

Blender Normal mapping and Specular maps

Post by nfz »

I have been experimenting alot with Blenders procedural textures and using them to generate minute detail for Normal maps. I also found out that its very easy to generate diffuse and specular maps in blender based on the detail on the model. I put together a simple structure (no idea what it represents) to demonstrate what can be done. I'm still working on the tutorial on how to make diffent types of normal maps in Blender and this method is one of them.

The Diffuse Map (RGB 24 bit):
Image

The Normal Map (RGB 24 bit):
Image

The Specular Map (Grey Scale 8 bit):
Image


The final result as rendered in Ogre using a GLSL single pass shader:
Image

The GLSL Vertex Shader:
offsetmapping.vert

Code: Select all

/* Bump mapping with Parallax offset vertex program 
   In this program, we want to calculate the tangent space light end eye vectors 
   which will get passed to the fragment program to produce the per-pixel bump map 
   with parallax offset effect. 
*/ 

/* Vertex program that moves light and eye vectors into texture tangent space at vertex */ 

// outputs 
varying vec2 UV;
varying vec3 LightDir;
varying vec2 EyeDir;
varying vec3 HalfAngle; 
varying vec3 Position;

// user supplied parameters 
uniform vec3 lightPosition; // object space 
uniform vec3 eyePosition;   // object space 
uniform float textureScale;


void main(void) 
{  
  // calculate output position 
  gl_Position = ftransform();
	
  Position = gl_Vertex.xyz;

  // pass the main uvs straight through unchanged 
  UV = gl_MultiTexCoord0.st * textureScale;
	
  // calculate tangent space light vector 
  // Get object space light direction 
  vec3 lightDir = lightPosition - gl_Vertex.xyz; 
  vec3 eyeDir = eyePosition - gl_Vertex.xyz; 

  // Calculate the binormal (NB we assume both normal and tangent are 
  // already normalised) 
  vec3 tangent = gl_MultiTexCoord1.xyz;
  vec3 binormal = normalize(cross( tangent, gl_Normal )); 

  // Form a rotation matrix out of the vectors 
  mat3 rotation = mat3(tangent, binormal, gl_Normal); 

  // Transform the light vector according to this matrix 
  lightDir = normalize( lightDir * rotation ); 
  eyeDir = normalize( eyeDir * rotation ); 

  LightDir = lightDir; 
  EyeDir = eyeDir.xy; 
  HalfAngle = normalize(eyeDir + lightDir); 
}

The GLSL fragment shader:
TS_NormalSpecMapping.frag

Code: Select all

// inputs from vertex shader
varying vec3 LightDir;
varying vec3 HalfAngle;
varying vec2 UV;

// user settings
uniform sampler2D normalMap;
uniform sampler2D diffuseMap;
uniform sampler2D specMap;


vec4 diffuse(vec3 N)
{
  return gl_FrontMaterial.diffuse * max(dot(normalize(LightDir), N), 0.0);
}

vec4 specular(vec3 N, float spec)
{
  return gl_FrontMaterial.specular * pow(max( dot( normalize(HalfAngle), N), 0.0), gl_FrontMaterial.shininess ) * spec;
}

vec3 expand(vec3 v)
{
  return (v - 0.5) * 2.0;
}

void main(void)
{
	
  // get the new normal and diffuse values
  vec3 N = normalize(expand(texture2D(normalMap, UV).xyz));
  vec4 texdiff = texture2D(diffuseMap, UV);
  float spec = texture2D(specMap, UV).x;
	
  gl_FragColor = texdiff * (gl_FrontMaterial.ambient + diffuse(N)) + specular(N, spec);	
}

The material script:

Code: Select all

// GLSL offset bump mapping
vertex_program GLSLDemo/OffsetMappingVS glsl 
{ 
  source offsetmapping.vert 
} 

fragment_program GLSLDemo/TSNormalSpecMappingFS glsl 
{ 
  source TS_NormalSPecMapping.frag 
} 

material GLSL/TSNormalSpecMapping
{ 
  technique
  { 
    pass 
    { 
      ambient 0.1 0.1 0.1
      diffuse 0.7 0.7 0.7
      specular 0.5 0.5 0.5 128
			
      vertex_program_ref GLSLDemo/OffsetMappingVS
      {
        param_named_auto lightPosition light_position_object_space 0
        param_named_auto eyePosition camera_position_object_space
        param_named textureScale float 1.0
      }

      fragment_program_ref GLSLDemo/TSNormalSpecMappingFS 
      { 
        param_named normalMap int 0
        param_named diffuseMap int 1
        param_named specMap int 2
      } 

      // Normal map
      texture_unit 
      {
        texture NormalMapTUT.png
        tex_coord_set 0
        filtering trilinear
      }

      // Base diffuse texture map
      texture_unit 
      {
        texture NormalMapTUTDiff.png
        filtering trilinear
        tex_coord_set 1
      }

      // spec map for shinnines
      texture_unit
      {
        texture specMapTUT.png
        filtering trilinear
        tex_coord_set 2
      }

    } 

  } 

}

User avatar
monster
OGRE Community Helper
OGRE Community Helper
Posts: 1098
Joined: Mon Sep 22, 2003 2:40 am
Location: Melbourne, Australia
Contact:

Post by monster »

That looks really cool!

I hope it's a debug build though. Only 50fps for 384 triangles!
:)

It looks like some sort of circuit board, like in this;
http://www.ysrnry.co.uk/articles/fatworm.htm
randall
Halfling
Posts: 63
Joined: Sun Jan 23, 2005 1:04 pm
Location: Poland

Post by randall »

looks great! can't wait for the tutorial...
User avatar
Robomaniac
Hobgoblin
Posts: 508
Joined: Tue Feb 03, 2004 6:39 am

Post by Robomaniac »

DAMN YOU ALL, VICTORY IS YOURS

good job :)
phear hingo

My Webpage
User avatar
sinbad
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 19269
Joined: Sun Oct 06, 2002 11:19 pm
Location: Guernsey, Channel Islands
x 66
Contact:

Post by sinbad »

Nice, I'm sure this will be well received :)
nfz
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 1263
Joined: Wed Sep 24, 2003 4:00 pm
Location: Halifax, Nova Scotia, Canada

Post by nfz »

monster: its a release build. The editor allows you to set the frame rate through the display properties sheet so I normally set it to 50 fps.
nfz
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 1263
Joined: Wed Sep 24, 2003 4:00 pm
Location: Halifax, Nova Scotia, Canada

Post by nfz »

Image

Self Portrait?

Maybe. Expanded the capabilities of the above shader to be able to use the following types of maps:

Specular Intensity
Specular Size (is the power value of the specular function)
Diffuse Intensity
Emmissive

Since they are all grey scales, they are combined into a single texture and extracted in the pixel shader.

Here is the GLSL fragment shader which can be used with the vertex shader posted earlier:

Code: Select all

/**

*/

// inputs from vertex shader
varying vec3 LightDir;
varying vec3 HalfAngle;
varying vec2 UV;

// user settings
uniform sampler2D normalMap;
uniform sampler2D diffuseMap;
uniform sampler2D fxMap;
/*
  fxMap color channel mapping:
    Red:    Specular Intensity
    Green: Specular Size (Power)
    Blue:   Diffuse Intensity
    Alpha: Emissive Intensity
*/

vec4 diffuse(vec3 N, float diffLevel)
{
  return gl_FrontMaterial.diffuse * diffLevel * max(dot(normalize(LightDir), N), 0.0);
}

vec4 specular(vec3 N, float specLevel, float specPower)
{
  return gl_FrontMaterial.specular * specLevel * pow(max( dot( normalize(HalfAngle), N), 0.0), gl_FrontMaterial.shininess * specPower ) ;
}

vec3 expand(vec3 v)
{
  return (v - 0.5) * 2.0;
}

void main(void)
{
	
  // get the new normal and diffuse values
  vec3 N = normalize(expand(texture2D(normalMap, UV).xyz));
  vec4 texdiff = texture2D(diffuseMap, UV);
  vec4 fxVals = texture2D(fxMap, UV);
	
  gl_FragColor = texdiff * (gl_FrontMaterial.ambient * fxVals.w + diffuse(N, fxVals.z)) + specular(N, fxVals.x, fxVals.y);	
}

Last edited by nfz on Sun Mar 27, 2005 6:55 am, edited 1 time in total.
User avatar
monster
OGRE Community Helper
OGRE Community Helper
Posts: 1098
Joined: Mon Sep 22, 2003 2:40 am
Location: Melbourne, Australia
Contact:

Post by monster »

Holy crap that's cool!

P.S. That fps is fair enough then, didn't realise it was set by the editor
User avatar
Robomaniac
Hobgoblin
Posts: 508
Joined: Tue Feb 03, 2004 6:39 am

Post by Robomaniac »

Holy Crip its a Crapple!
phear hingo

My Webpage
User avatar
jacmoe
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 20570
Joined: Thu Jan 22, 2004 10:13 am
Location: Denmark
x 179
Contact:

Post by jacmoe »

I guess this will only work in OpenGL ?
My OpenGL is broken atm - but this is seriously good looking stuff! 8)
/* Less noise. More signal. */
Ogitor Scenebuilder - powered by Ogre, presented by Qt, fueled by Passion.
OgreAddons - the Ogre code suppository.
nfz
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 1263
Joined: Wed Sep 24, 2003 4:00 pm
Location: Halifax, Nova Scotia, Canada

Post by nfz »

It works in Dx9 too if you convert the GLSL stuff to HLSL or Cg. If you want I can post those shaders too. I didn't post them since I am trying to do a little bit of PR for GLSL since it doesn't seem to get much attention.
User avatar
jacmoe
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 20570
Joined: Thu Jan 22, 2004 10:13 am
Location: Denmark
x 179
Contact:

Post by jacmoe »

If that's not a lot of trouble, it would be cool to have these shaders posted. :)
Looking forward to give that material editor a spin one of these days. 8)
/* Less noise. More signal. */
Ogitor Scenebuilder - powered by Ogre, presented by Qt, fueled by Passion.
OgreAddons - the Ogre code suppository.
nfz
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 1263
Joined: Wed Sep 24, 2003 4:00 pm
Location: Halifax, Nova Scotia, Canada

Post by nfz »

here is the HLSL version of the GLSL shaders along with the Ogre material script.

FXMap_HLSL.vert

Code: Select all

// FxMap vertex shader

float4x4 worldViewProj_matrix; 
float3 lightPosition;
float3 eyePosition; 

struct VS_OUTPUT
{
  float4 Pos : POSITION;
  float2 UV : TEXCOORD0;
  float3 LightDir : TEXCOORD1;
  float3 HalfAngle : TEXCOORD2;
};

VS_OUTPUT main(float4 Pos : POSITION, float2 Tex : TEXCOORD0, float3 Normal : NORMAL, float3 Tangent : TEXCOORD1 )
{
  VS_OUTPUT Out = (VS_OUTPUT)0;
  // transform Position
  Out.Pos = mul(worldViewProj_matrix, Pos); 

  Out.UV = Tex.xy;

  // calculate tangent space light vector 
  // Get object space light direction 
  float3 lightDir = lightPosition - Pos.xyz; 
  float3 eyeDir = eyePosition - Pos.xyz; 

  // compute the 3x3 tranform matrix 
  // to transform from object space to tangent space
  float3x3 rotation;
  rotation[0] = Tangent;
  // calculate the binormal (NB we assume both normal and tangent are 
  // already normalised) 
  rotation[1] = cross(Tangent, Normal);
  rotation[2] = Normal;

  // rotate the light and eye vectors according to tangent space rotation matrix 
  lightDir = mul(rotation, lightDir); 
  eyeDir = normalize( mul(rotation, eyeDir) ); 

  Out.LightDir = lightDir; 
  Out.HalfAngle = eyeDir + lightDir; 

  return Out;
}
FXMap_HLSL.frag

Code: Select all

// Fx Map pixel shader

float4 ambientColor;
sampler normalMap;
sampler diffuseMap;
sampler fxMap;
/*
  Fx Map has the following data in the color channels
  RED: specular intensity
  GREEN: specular size (power exponent)
  BLUE: diffuse intensity
  ALPHA: ambient/emmisive intensity (optional)
*/

float diffuse(float3 LightDir, float3 N, float diffLevel)
{
  return max(dot(normalize(LightDir), N), 0.0) * diffLevel;
}

float specular(float3 HalfAngle, float3 N, float specLevel, float specPower)
{
  return specLevel * pow(max( dot( normalize(HalfAngle), N), 0.0), 128.0 * specPower ) ;
}


float4 main(float2 UV: TEXCOORD0, float3 LightDir : TEXCOORD1, 
float3 HalfAngle : TEXCOORD2) : COLOR
{
  float4 texdiff = tex2D(diffuseMap, UV);
  // expand the normal vector from the normal map form 0..1 to -1..1
  float3 N = tex2D(normalMap, UV).xyz * 2 - 1;
  float4 fxVals = tex2D(fxMap, UV);

  return texdiff * (ambientColor * fxVals.w + diffuse(LightDir, N, fxVals.z)) + specular(HalfAngle, N, fxVals.x, fxVals.y); 
}
Ogre Material script

Code: Select all

vertex_program FxMap_HLSL_VS hlsl 
{ 
  source FXMap_HLSL.vert
  entry_point main 
  target vs_1_1 
} 

fragment_program FxMap_HLSL_PS hlsl 
{ 
  source FXMap_HLSL.frag 
  entry_point main 
  target ps_2_0 
} 



material FxNormalMap
{
  technique HLSL_DX9
  { 
    pass 
    { 
			
      vertex_program_ref FxMap_HLSL_VS
      {
        param_named_auto worldViewProj_matrix worldviewproj_matrix 
        param_named_auto lightPosition light_position_object_space 0
        param_named_auto eyePosition camera_position_object_space
      }

      fragment_program_ref FxMap_HLSL_PS 
      { 
        param_named ambientColor float4 0.2 0.2 0.2 0.2
      } 

      // Normal map
      texture_unit 
      {
        texture TUT_NM.png
        tex_coord_set 0
        filtering trilinear
      }

      // Base diffuse texture map
      texture_unit 
      {
        texture TUT_Diff.png
        filtering trilinear
        tex_coord_set 1
      }

      // Fx map for specular intensity, specular size, diffuse intensity, emissive intensity
      texture_unit
      {
        texture TUT_FxMap.png
        filtering trilinear
        tex_coord_set 2
      }

    } 

  } 

}
nfz
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 1263
Joined: Wed Sep 24, 2003 4:00 pm
Location: Halifax, Nova Scotia, Canada

Post by nfz »

:oops: Fubared the GLSL to HLSL conversion. I forgot to nomalize the light and half angle vectors in the HLSL vertex program. Here is the corrected HLSL verter program:

Code: Select all

// FxMap vertex shader 

float4x4 worldViewProj_matrix; 
float3 lightPosition; 
float3 eyePosition; 

struct VS_OUTPUT 
{ 
  float4 Pos : POSITION; 
  float2 UV : TEXCOORD0; 
  float3 LightDir : TEXCOORD1; 
  float3 HalfAngle : TEXCOORD2; 
}; 

VS_OUTPUT main(float4 Pos : POSITION, float2 Tex : TEXCOORD0, float3 Normal : NORMAL, float3 Tangent : TEXCOORD1 ) 
{ 
  VS_OUTPUT Out = (VS_OUTPUT)0; 
  // transform Position 
  Out.Pos = mul(worldViewProj_matrix, Pos); 

  Out.UV = Tex.xy; 

  // calculate tangent space light vector 
  // Get object space light direction 
  float3 lightDir = lightPosition - Pos.xyz; 
  float3 eyeDir = eyePosition - Pos.xyz; 

  // compute the 3x3 tranform matrix 
  // to transform from object space to tangent space 
  float3x3 rotation; 
  rotation[0] = Tangent; 
  // calculate the binormal (NB we assume both normal and tangent are 
  // already normalised) 
  rotation[1] = cross(Tangent, Normal); 
  rotation[2] = Normal; 

  // rotate the light and eye vectors according to tangent space rotation matrix 
  lightDir = normaize(mul(rotation, lightDir)); 
  eyeDir = normalize( mul(rotation, eyeDir) ); 

  Out.LightDir = lightDir; 
  Out.HalfAngle = normalize(eyeDir + lightDir); 

  return Out; 
}
User avatar
jacmoe
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 20570
Joined: Thu Jan 22, 2004 10:13 am
Location: Denmark
x 179
Contact:

Post by jacmoe »

Excellent, nfz! :D
I'd have a play later! :)
/* Less noise. More signal. */
Ogitor Scenebuilder - powered by Ogre, presented by Qt, fueled by Passion.
OgreAddons - the Ogre code suppository.
Chris_with_a_Gun
Greenskin
Posts: 106
Joined: Wed Nov 24, 2004 5:58 pm

Re: Blender Normal mapping and Specular maps

Post by Chris_with_a_Gun »

Code: Select all

        tex_coord_set 0
        tex_coord_set 1
        tex_coord_set 2
Hi, looks really great, but why the different set of texture coordinates - the maps are lying excatly upon each other or why's that?
User avatar
alphageek
Gnome
Posts: 365
Joined: Mon Jan 03, 2005 11:56 am

Post by alphageek »

This is flaming awesome.

Edit: To elaborate, both things are separately and independently awesome.
1) Blender procedurals (I must get into this at some point) -- flaming awesome.
2) Wicked shaders -- flaming awesome.
Axiomatic: action-packed space shooter
TaskScheduler 0.3
User avatar
RyanN
Halfling
Posts: 82
Joined: Thu Aug 19, 2004 6:41 am
Location: Victoria, Australia
Contact:

Post by RyanN »

great work NFZ! on line 38 in the Vertex shader 'normaize' needs to be changed to 'normalize'. Other than that i applied the shader in one of my test scenes however the speuclar effect shows up really strange with little white dots flickering where there should be a nice specular effect, does yours do this? (im using the HLSL version) also, a bracket in the (the specular texture pass) material code needs to be taken down to the next line. Playing around with the pixel shader i found that it looks better with specpower set at 12.
Mariusz Plaskowicki
Gnoblar
Posts: 19
Joined: Thu Feb 10, 2005 9:48 am
Contact:

Post by Mariusz Plaskowicki »

How about this tutorial? Is it ready? If so - where can I find it?

Great job by the way.
nfz
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 1263
Joined: Wed Sep 24, 2003 4:00 pm
Location: Halifax, Nova Scotia, Canada

Post by nfz »

Still working on the tutorial. Its slowly progressing but got delayed a little bit while I improved the tangent normal map generation within blender. See this thread about the tool: http://www.ogre3d.org/phpBB2/viewtopic.php?t=8595
nfz
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 1263
Joined: Wed Sep 24, 2003 4:00 pm
Location: Halifax, Nova Scotia, Canada

Post by nfz »

I've put up the initial tutorial and Gui help: http://www.neuralfuzz.com/opengl/blende ... ping/Help/

Its a WIP and will be updated as time permits.
Mariusz Plaskowicki
Gnoblar
Posts: 19
Joined: Thu Feb 10, 2005 9:48 am
Contact:

Post by Mariusz Plaskowicki »

Thx
I'll take a look at this when I get back home. I saw that there is actually only part one - modelling in blender - but It will also be very usefull. Thanx a lot.
User avatar
NeOmega
Halfling
Posts: 86
Joined: Mon Apr 04, 2005 2:30 am
Location: Washington State, USA

Post by NeOmega »

Quick question:

What specs do I look at to see if this can be done on my video card?

I have an NVidia chipset MSI FX5700LE.

I tried to see if it supported .nmf, but couldn't find anything confirming this.
nfz
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 1263
Joined: Wed Sep 24, 2003 4:00 pm
Location: Halifax, Nova Scotia, Canada

Post by nfz »

NeOmega: your card can handle the shaders posted above.

.nmf stands for normal mapper format is is just a binary storage format used by ATI's Normal Mapper utility and is not used by your graphics card.
Chris_with_a_Gun
Greenskin
Posts: 106
Joined: Wed Nov 24, 2004 5:58 pm

Post by Chris_with_a_Gun »

Code: Select all

// get the new normal and diffuse values
vec3 N = normalize(expand(texture2D(normalMap, UV).xyz));
Are the normals from the NormalMap not already normalized?

thanks
Post Reply