#1




Normal Map, Tangent Binormal on Mirrored Objects
Dear Support,
I had a question regarding your calculation of tangent and binormal attributes for shaders. On my 3ds objects, to conserve texture space I am using symmetries. This causes the UV coordinates to be shared, but mirrored. When I bring the object into Vizard and use a Normal Map shader, I get every other symmetry (mirrored) element with reversed tangent and binormal attributes. I was wondering if you know of a fix for reversing the orientation of the tangent attribute, or if there might be a way for you to check in your automatic calculation ( I am guessing you use the texture coordinates for this) to determine if the sign of the normal is in the proper direction? Attached is an image of the problem, if I go into max and mirror the UVs back, it fixes the alternating problem. However, there are some assets that can not be mirrored (not symmetrical). Thanks, George 
#2




How are you mirroring the UVs in Max? Are you using the Coordinates rollout in the material properties or are you mirroring the actual coordinates through the Edit UVWs dialog of the mesh?
The tangent/binormal calculation is based on the raw UV values, and does not take the texture tiling mode into account. So you should try to use the second method if you are not already doing so. 
#3




The mirroring takes place when I use the symmetry modifier on the polygonal mesh. In this case, I created a high resolution mesh for 1/8th the roof of a cylindrical building.
I then use the symmetry modifier 3 times (rotating the modifier pivot each time), the first creates 1/4 of the roof, and then 1/2 and finally the entire roof. I had already UV Mapped the roof so that each symmetry would have the same UV coordinates, increasing the space available in the texture map. There is no tiling actually occurring, all UVs are within the 0 to 1 area. Attached is an image of the UVs, with a selection made to show how they are mirrored across the 3D object. 
#4




After reading a while on Normal maps for other engines, I found that this is a common problem when the attributes do not contain a 4th 'W' component indicating mirrored portions of the mesh. Apparently, other engines such as Unreal and CryEngine are able to determine the flipped normals and have them flipped by multiplying by the W coordinate ( +1 or 1).
Fixes for other engines (Unity in this case) referred to exporting the binormal and tangent vectors through the FBX file format. In this case, I don't see anything that can be similarly done with the OSG exporter. 
#5




Do you mind posting your vertex/fragment shader code? I tried applying a normal map to a model with mirrored UVs and it seemed to work correctly.

#6




I went ahead and attached the model and textures.
Thanks, George Vertex: Code:
#version 120 uniform int NumLights; varying vec3 vPosition; varying vec3 tPosition; varying vec3 tView; varying vec3 tang; varying vec3 binorm; varying vec3 normal; varying vec3 tLightPosition[gl_MaxLights]; varying vec3 tLightDirection[gl_MaxLights]; attribute vec3 Tangent; attribute vec3 Binormal; void main() { //Calculate components in view space vec3 position = vec3(gl_ModelViewMatrix * gl_Vertex); normal = normalize(gl_NormalMatrix * gl_Normal); tang = normalize(gl_NormalMatrix * Tangent); binorm = normalize(gl_NormalMatrix * Binormal); mat3 mToTangentSpace = mat3( tang.x, binorm.x, normal.x, tang.y, binorm.y, normal.y, tang.z, binorm.z, normal.z); tView = mToTangentSpace * (position * 1.0); tPosition = mToTangentSpace * position; //Iterate over every light and transition position into tangent space for( int i = 0 ; i < NumLights ; i++) { tLightPosition[i] = mToTangentSpace * gl_LightSource[i].spotDirection.xyz; tLightPosition[i] = mToTangentSpace * gl_LightSource[i].position.xyz; } //Pass and transform texture coordinates gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; //Pass Perspecitive Coordinates gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; //Compute Fog gl_FogFragCoord = gl_FogCoord; } Code:
#version 120 uniform int NumLights; varying vec3 vPosition; varying vec3 tPosition; varying vec3 tView; varying vec3 normal; varying vec3 tang; varying vec3 binorm; varying vec3 tLightPosition[gl_MaxLights]; varying vec3 tLightDirection[gl_MaxLights]; uniform sampler2D Albedo; uniform sampler2D NormalMap; uniform sampler2D AmbientOcclusionMap; //Calculates and returns the contribution of a directional light void DirectionalLight(in int i, in vec3 position, in vec3 view, in vec3 normal, inout vec4 ambient, inout vec4 diffuse, inout vec4 specular) { float nDotL; //normal dotted with light vector float nDotH; //normal dotted with half vector float pf; nDotL = max(0.0, dot(normal, normalize(tLightPosition[i]))); nDotH = max(0.0, dot(normal, normalize( normalize(tLightPosition[i]) + view) )); if( nDotL > 0.0) pf = pow(nDotH, gl_FrontMaterial.shininess); else pf = 0.0; ambient += gl_LightSource[i].ambient; diffuse += gl_LightSource[i].diffuse * nDotL; specular += gl_LightSource[i].specular * pf; } //Caculates and returns the contribution of a spotlight void PointLight( in int i, in vec3 position, in vec3 view, in vec3 iNormal, inout vec4 ambient, inout vec4 diffuse, inout vec4 specular) { float nDotL; float nDotH; float pf; float attenuation; float d; vec3 LP; vec3 halfVector; LP = tLightPosition[i]  position; d = length(LP); // distance from surface to light for attenuation vec3 tLP = normalize(tLightPosition[i]); attenuation = 1.0 / (gl_LightSource[i].constantAttenuation + gl_LightSource[i].linearAttenuation * d + gl_LightSource[i].quadraticAttenuation * d * d); halfVector = normalize(tLP + view); nDotL = max(0.0, dot(iNormal, tLP)); nDotH = max(0.0, dot(iNormal, halfVector)); if (nDotL > 0.0) pf = pow(nDotH, gl_FrontMaterial.shininess); else pf = 0.0; ambient += gl_LightSource[i].ambient * attenuation; diffuse += gl_LightSource[i].diffuse * nDotL * attenuation; specular += gl_LightSource[i].specular * pf * attenuation; } void SpotLight(in int i, in vec3 position, in vec3 view, in vec3 normal, inout vec4 ambient, inout vec4 diffuse, inout vec4 specular) { float nDotL; float nDotH; float pf; float spotDot; float spotAttenuation; float attenuation; float d; vec3 LV; vec3 halfVector; LV = tLightPosition[i]  position; d = length(LV); LV = normalize(LV); attenuation = 1.0 / (gl_LightSource[i].constantAttenuation + gl_LightSource[i].linearAttenuation * d + gl_LightSource[i].quadraticAttenuation * d * d); spotDot = dot(LV, normalize( tLightDirection[i])); if(spotDot < gl_LightSource[i].spotCosCutoff) spotAttenuation = 0.0; else spotAttenuation = pow(spotDot, gl_LightSource[i].spotExponent); attenuation *= spotAttenuation; halfVector = normalize(LV + view); nDotL = max(0.0, dot(normal, LV)); nDotH = max(0.0, dot(normal, halfVector)); if(nDotL == 0.0) pf = 0.0; else pf = pow(nDotH, gl_FrontMaterial.shininess); ambient += gl_LightSource[i].ambient * attenuation; diffuse += gl_LightSource[i].diffuse * nDotL * attenuation; specular += gl_LightSource[i].specular * pf * attenuation; } void main() { vec4 albedo = texture2D(Albedo , gl_TexCoord[0].xy); vec3 texNormal = normalize(texture2D(NormalMap, gl_TexCoord[0].xy).rgb); vec3 ao = texture2D(AmbientOcclusionMap, gl_TexCoord[0].xy).rgb; vec4 ambient = vec4(0.0); vec4 diffuse = vec4(0.0); vec4 specular = vec4(0.0); vec3 ntView = normalize(tView); for(int i = 0; i < NumLights; i++) { if (gl_LightSource[i].position.w == 0.0) DirectionalLight(i, tPosition, ntView, texNormal, ambient, diffuse, specular); else if (gl_LightSource[i].spotCutoff == 180.0) PointLight(i, tPosition, ntView, texNormal, ambient, diffuse, specular); else SpotLight(i, tPosition, ntView, texNormal, ambient, diffuse, specular); } //Linear fog calculation float dist = abs(vPosition.z); float fogFactor =(gl_Fog.end  dist) * gl_Fog.scale; fogFactor = clamp(fogFactor, 0.0, 1.0); vec4 color = (ambient * gl_FrontMaterial.ambient + diffuse * gl_FrontMaterial.diffuse) * albedo; //Albedo color = color + (specular * gl_FrontMaterial.specular);//Spec //color = color * vec4(ao,1.0); color = mix( gl_Fog.color, color, fogFactor); //Fog gl_FragColor = color; } 
#7




Thanks. I think the problem is with your normal map lookup code. The texture color values range from 0 to 1. However, the normals need to range from 1 to 1. Also, it appears the green channel in your normal map is flipped. Try modifying your normal map lookup to the following:
Code:
vec3 texNormal = normalize(texture2D(NormalMap, gl_TexCoord[0].xy).rgb * 2.0  1.0) * vec3(1.0,1.0,1.0); 
#8




Wow, I must have hit undo at some point in my code because I remember renormalizing the normal map from 1 to +1 ... but it sure isn't in that code...
I'll give your tips a try. Not sure why the green channel is flipped, I'll look into that as well. Thanks for your great support, ~George 
Tags 
binormal, normal, tangent 
Thread Tools  
Display Modes  Rate This Thread 


Similar Threads  
Thread  Thread Starter  Forum  Replies  Last Post 
Per vertex attributes in OTF objects?  stefs  Vizard  1  10232009 03:36 PM 
Semicircle array containing target and distractor objects  ptjt255  Vizard  3  08042009 03:09 AM 
Normal Map Workflow for Vizard  shivanangel  Vizard  2  03102009 06:54 PM 
Could not find plugin to load objects...  halley  Vizard  1  05302006 11:01 AM 