# Strange normal mapping artifacts - OpenGL / GLSL

I have been following a few tutorials to learn some graphics programming with OpenGL, and recently implemented Normal Mapping.

It works well for the most part, especially for objects with normal-mapped textures. However, I get some strange problems with some models when I give them a try - weird triangular artifacts show up. The lighting looks good for the most part, but these artifacts really ruin it.

One such example would be this - you can see the chassis of the car is quite affected. Other parts appear fine. In other cases (I'm sure it's somehow related) the model appears split in half and one side is lit completely wrong.

I'm confident that it's to do with how I've written my vertex shader but as far as I can see, everything is 'correct' with regards to the tutorials I've followed. I'm hoping someone can take a look and let me know if I'm missing something - maths is not my strong suit and if it's an issue with the matrix math or anything like that I'm unlikely to spot it :/

Strangely enough, if I change the line mat3 TBN = transpose(mat3(T, B, N)); to simply mat3 TBN = (mat3(T, B, N)); the strange artifacts go away however the lighting goes strangely smooth, like so:

#version 330

layout (location=0) in vec3 aPosition;
layout (location=1) in vec3 aNormal;
layout (location=2) in vec2 aTexCoord;
layout (location=3) in vec3 aTangent;
layout (location=4) in vec4 aJointWeights;
layout (location=5) in ivec4 aJointIndices;

const int MAX_WEIGHTS = 4;
const int MAX_JOINTS = 150;

out VS_OUT{
vec2 texCoord;
vec3 fragPos;
vec3 tangentViewPos;
vec3 tangentFragPos;
vec3 vertexNormal;
mat3 TBN;
} vs_out;

uniform vec3 viewPos;
uniform mat4 projectionMatrix;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 jointMatrices[MAX_JOINTS];

void main()
{
vec4 initPos = vec4(0);
vec4 initNormal = vec4(0);
vec4 initTangent = vec4(0);

int count = 0;
for (int i = 0; i < MAX_WEIGHTS; i++){
float weight = aJointWeights[i];
if (weight > 0){
count++;
int jointIndex = aJointIndices[i];

vec4 tmpPos = jointMatrices[jointIndex] * vec4(aPosition, 1.0);
initPos += weight * tmpPos;

vec4 tmpNormal = jointMatrices[jointIndex] * vec4(aNormal, 0.0);
initNormal += weight * tmpNormal;

vec4 tmpTangent = jointMatrices[jointIndex] * vec4(aTangent, 0.0);
initTangent += weight * tmpTangent;
}
}
if (count == 0){
initPos = vec4(aPosition, 1.0);
initNormal = vec4(aNormal, 0.0);
initTangent = vec4(aTangent, 0.0);
}

vs_out.fragPos = vec3(modelMatrix * vec4(initPos.xyz, 1.0));
vs_out.texCoord = aTexCoord;

mat3 normalMatrix = transpose(inverse(mat3(modelMatrix)));
vec3 T = normalize(vec3(normalMatrix * initTangent.xyz));
vec3 N = normalize(vec3(normalMatrix * initNormal.xyz));
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
if (dot(cross(N, T), B) < 0.0)
T = T * -1.0;

mat3 TBN = transpose(mat3(T, B, N));
vs_out.tangentViewPos = TBN * viewPos;
vs_out.tangentFragPos = TBN * vs_out.fragPos;

vs_out.TBN = TBN;
vs_out.vertexNormal = vec3(normalMatrix * initNormal.xyz);

gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(initPos.xyz, 1.0);
}


And here's the code I use for the fragment shader:

#version 330

in VS_OUT{
vec2 texCoord;
vec3 fragPos;
vec3 tangentViewPos;
vec3 tangentFragPos;
vec3 vertexNormal;
mat3 TBN;
} fs_in;

layout (location = 0) out vec4 fragColor;
layout (location = 1) out vec4 brightColor;

struct Material {
vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
float alphaTest;

float opacity;

sampler2D diffuseMap;
int hasDiffuseMap;

sampler2D normalMap;
int hasNormalMap;

sampler2D specularMap;
int hasSpecularMap;

sampler2D exponentMap;
int hasExponentMap;

sampler2D parallaxMap;
int hasParallaxMap;
float parallaxHeightScale;

vec2 uvMultiplier;
};

struct Light {
vec3 position;

vec3 ambient;
vec3 diffuse;
vec3 specular;
};

struct DirectionalLight{
vec3 direction;

vec3 ambient;
vec3 diffuse;
vec3 specular;
};

struct PointLight{
vec3 position;

float constant;
float linear;

vec3 ambient;
vec3 diffuse;
vec3 specular;
};

struct SpotLight{
vec3 position;
vec3 direction;

float cutOff;
float outerCutOff;

float constant;
float linear;

vec3 ambient;
vec3 diffuse;
vec3 specular;
};

struct Environment{
samplerCube cubeMap;
samplerCube depthMap;
float farPlane;
};

#define POINT_LIGHTS
uniform PointLight pointLights[POINT_LIGHTS];
uniform int numPointLights;
uniform DirectionalLight dirLight;

uniform Material material;
uniform Environment environment;

uniform int blinn;
uniform int halfLambert;
uniform int fresnel;

uniform vec3 viewPos;

vec3 gridSamplingDisk[20] = vec3[]
(
vec3(1, 1,  1), vec3( 1, -1,  1), vec3(-1, -1,  1), vec3(-1, 1,  1),
vec3(1, 1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1),
vec3(1, 1,  0), vec3( 1, -1,  0), vec3(-1, -1,  0), vec3(-1, 1,  0),
vec3(1, 0,  1), vec3(-1,  0,  1), vec3( 1,  0, -1), vec3(-1, 0, -1),
vec3(0, 1,  1), vec3( 0, -1,  1), vec3( 0, -1, -1), vec3( 0, 1, -1)
);

float fZero = pow( (1.0f-(1.0f/1.31f))/ (1.0f+(1.0f/1.31f)), 2);

vec4 dMap;
vec3 sMap;
vec3 nMap;

float specularExponent;

vec3 fragToLight = fragPos - lightPos;
float currentDepth = length(fragToLight);

float bias = 0.10;
int samples = 20;
float viewDistance = length(viewPos - fragPos);
float diskRadius = (1.0 + (viewDistance / environment.farPlane)) / 35.0;
for(int i = 0; i < samples; ++i)
{
float closestDepth = texture(environment.depthMap, fragToLight + gridSamplingDisk[i] * diskRadius).r;
closestDepth *= environment.farPlane;
if(currentDepth - bias > closestDepth)
}

}

float calcFresnel(vec3 v, vec3 h, float f0){
float base = 1-dot(v, h);
float exp = pow(base, 5);
return exp + f0 * (1.0 - exp);
}

vec4 calcDirLight(DirectionalLight light, vec3 normal, vec3 viewDir){
vec3 lightDir = normalize((fs_in.TBN * -light.direction) - fs_in.tangentFragPos);
float diff = max(dot(lightDir, normal), 0.0);

vec3 halfwayDir = normalize(lightDir + viewDir);
vec3 reflectDir = reflect(-lightDir, normal);

float spec = pow(max(dot(normal, halfwayDir), 0.0), specularExponent);

vec3 ambient = light.ambient;
vec3 diffuse = light.diffuse * diff;
vec3 specular = light.specular * spec * sMap;

return vec4(ambient + diffuse + specular, 1.0) * dMap;
}

vec4 calcPointLight(PointLight light, vec3 normal, vec3 fPos, vec3 vPos){
vec3 tangentLightPos = (fs_in.TBN * light.position);
vec3 lightDir = normalize(tangentLightPos - fPos);

float diff = max(dot(lightDir, normal), 0.0);

vec3 viewDir = normalize(vPos - fPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), specularExponent);

float distance = length(tangentLightPos - fPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +

vec3 ambient = light.ambient;
vec3 diffuse = diff * light.diffuse;
vec3 specular = light.specular * spec * sMap; // * light.specular * sMap * frs;

ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;

vec3 lighting = (ambient + (1 - shadow) * (diffuse + specular)) * dMap.rgb;

return vec4(lighting, dMap.a);
}

vec2 parallaxMapping(vec2 texCoords, vec3 viewDir){
const float minLayers = 8.0;
const float maxLayers = 32.0;

float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));

float layerDepth = 1.0 / numLayers;
float currentLayerDepth = 0.0;

vec2 P = viewDir.xy * material.parallaxHeightScale;
vec2 deltaTexCoords = P / numLayers;

vec2 currentTexCoords = texCoords;
float currentDepthValue = texture(material.parallaxMap, currentTexCoords).r;

while (currentLayerDepth < currentDepthValue){
currentTexCoords -= deltaTexCoords;
currentDepthValue = texture(material.parallaxMap, currentTexCoords).r;
currentLayerDepth += layerDepth;
}

vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
float afterDepth = currentDepthValue - currentLayerDepth;
float beforeDepth = texture(material.parallaxMap, prevTexCoords).r - currentLayerDepth + layerDepth;

float weight = afterDepth / (afterDepth - beforeDepth);
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);

return finalTexCoords;
}

void main()
{
vec2 ourTexCoords;
vec3 viewDir = normalize(fs_in.tangentViewPos - fs_in.tangentFragPos);

if (material.hasParallaxMap){
ourTexCoords = parallaxMapping(fs_in.texCoord, viewDir);
if(ourTexCoords.x > 1.0 || ourTexCoords.y > 1.0 || ourTexCoords.x < 0.0 || ourTexCoords.y < 0.0)
}else{
ourTexCoords = fs_in.texCoord * material.uvMultiplier;
}

float matOpacity;
if (material.hasDiffuseMap){
dMap = texture(material.diffuseMap, ourTexCoords);
matOpacity = dMap.a;
}else{
dMap = vec4(material.diffuse.rgb, 0.0);
matOpacity = material.opacity;
}

if (material.hasSpecularMap){
sMap = texture(material.specularMap, ourTexCoords).rgb;
}else{
sMap = material.specular.rgb;
}

vec3 normal;
if (material.hasNormalMap){
nMap = texture(material.normalMap, ourTexCoords).rgb;
normal = normalize(nMap * 2.0 - 1.0);
}else{
normal = normalize(fs_in.vertexNormal);
}

if (material.hasExponentMap){
specularExponent = 255 - (texture(material.exponentMap, ourTexCoords).r * 255);
}else{
specularExponent = material.shininess;
}

vec4 result = calcDirLight(dirLight, normal, viewDir);
for (int i = 0; i < numPointLights; i++){
result += calcPointLight(pointLights[i], normal, fs_in.tangentFragPos, fs_in.tangentViewPos);
}

fragColor = vec4(result.rgb, matOpacity);

float brightness = dot(fragColor.rgb, vec3(0.2126, 0.7152, 0.0722));
if (brightness > 1){
brightColor = vec4(fragColor.rgb, 1.0);
}else{
brightColor = vec4(0, 0, 0, 1);
}
}


I appreciate any help in advance and hope that we can figure it out :)

• Make sure that you actually have tangent and binormal data in the shader by outputting them as colour components. color = vec4((tangent.rgb+vec3(1.0))*vec3(2.0), 1.0) – Ocelot Aug 31 '19 at 19:32
• Good suggestion - I've been using RenderDoc to visualize that and I can definitely see that those things are in there. – Dimitri Velovski Aug 31 '19 at 23:34
• Do they look correct? – Ocelot Sep 2 '19 at 2:30
• If you are using Assimp to load your model, ensure you are telling it on load to compute tangents. – Ian Young Sep 2 '19 at 10:32
• Your tangent space might be incorrect - can you show us how you're calculating your tangent vectors as input to this shader? In particular, it looks like you might have a line of symmetry down the middle of the car, resulting in flipped tangents on one half - can you confirm this based on your UV unwrap? – DMGregory Sep 3 '19 at 2:23