Normal mapping can have better effects when dealing with the details.
Then we use Normal Mapping.
This tutorial has a very clear structure. We will add it to our previous mesh.
We get a texture and its normal map from the Internet.


There are some additional attributes: normal, tangent and bitangent. Previously, we have only 5 attributes and now we have 14 attributes.
We have to add those values and we need a new array to store all the values.
static float quadVertices3[(SIZE_WATER-1)*(SIZE_WATER-1)*3*2*14] = {0.0f};
It is clearly demonstrated how to get tangent and bitangent in the tutorial.
Now we have to reuse the wdata array. And we need to load these data according to its indices which is a little troublesome.
// positions
glm::vec3 npos1(wdata[data_index], wdata[data_index+1], wdata[data_index+2]);
glm::vec3 npos2(wdata[data_index+5], wdata[data_index+5+1], wdata[data_index+5+2]);
glm::vec3 npos3(wdata[data_index+SIZE_WATER*5], wdata[data_index+SIZE_WATER*5+1], wdata[data_index+SIZE_WATER*5+2]);
glm::vec3 npos4(wdata[data_index+(SIZE_WATER+1)*5], wdata[data_index+(SIZE_WATER+1)*5+1], wdata[data_index+(SIZE_WATER+1)*5+2]);
The texture coordinates are easy to handle.
// texture coordinates
glm::vec2 nuv1(0.0f, 0.0f);
glm::vec2 nuv2(1.0f, 0.0f);
glm::vec2 nuv3(0.0f, 1.0f);
glm::vec2 nuv4(1.0f, 1.0f);
Normal here is the same.
glm::vec3 nm2(0.0f, 0.0f, 1.0f);
How to compute tangent and bitangent are also well demonstrated in the tutorial which are
the cross products.
for(long i = 0; i < (SIZE_WATER-1)*(SIZE_WATER-1)*3*2*14; i = i +14*6)
{
if((data_index/5) % SIZE_WATER == SIZE_WATER-1)
data_index += 5;
/* -------------------- tangent ---------------------------------------*/
// positions
glm::vec3 npos1(wdata[data_index], wdata[data_index+1], wdata[data_index+2]);
glm::vec3 npos2(wdata[data_index+5], wdata[data_index+5+1], wdata[data_index+5+2]);
glm::vec3 npos3(wdata[data_index+SIZE_WATER*5], wdata[data_index+SIZE_WATER*5+1], wdata[data_index+SIZE_WATER*5+2]);
glm::vec3 npos4(wdata[data_index+(SIZE_WATER+1)*5], wdata[data_index+(SIZE_WATER+1)*5+1], wdata[data_index+(SIZE_WATER+1)*5+2]);
// texture coordinates
glm::vec2 nuv1(0.0f, 0.0f);
glm::vec2 nuv2(1.0f, 0.0f);
glm::vec2 nuv3(0.0f, 1.0f);
glm::vec2 nuv4(1.0f, 1.0f);
// calculate tangent/bitangent vectors of both triangles
glm::vec3 ntangent1, nbitangent1;
glm::vec3 ntangent2, nbitangent2;
// new tri 1
glm::vec3 nedge1 = npos2 - npos1;
glm::vec3 nedge2 = npos2 - npos4;
glm::vec2 ndeltaUV1 = nuv2 - nuv1;
glm::vec2 ndeltaUV2 = nuv2 - nuv4;
GLfloat nf = 1.0f / (ndeltaUV1.x * ndeltaUV2.y - ndeltaUV2.x * ndeltaUV1.y);
ntangent1.x = nf * (ndeltaUV2.y * nedge1.x - ndeltaUV1.y * nedge2.x);
ntangent1.y = nf * (ndeltaUV2.y * nedge1.y - ndeltaUV1.y * nedge2.y);
ntangent1.z = nf * (ndeltaUV2.y * nedge1.z - ndeltaUV1.y * nedge2.z);
ntangent1 = glm::normalize(ntangent1);
nbitangent1.x = nf * (-ndeltaUV2.x * nedge1.x + ndeltaUV1.x * nedge2.x);
nbitangent1.y = nf * (-ndeltaUV2.x * nedge1.y + ndeltaUV1.x * nedge2.y);
nbitangent1.z = nf * (-ndeltaUV2.x * nedge1.z + ndeltaUV1.x * nedge2.z);
nbitangent1 = glm::normalize(nbitangent1);
// new tri 2
nedge1 = npos3 - npos1;
nedge2 = npos3 - npos4;
ndeltaUV1 = nuv3 - nuv1;
ndeltaUV2 = nuv3 - nuv4;
nf = 1.0f / (ndeltaUV1.x * ndeltaUV2.y - ndeltaUV2.x * ndeltaUV1.y);
ntangent2.x = nf * (ndeltaUV2.y * nedge1.x - ndeltaUV1.y * nedge2.x);
ntangent2.y = nf * (ndeltaUV2.y * nedge1.y - ndeltaUV1.y * nedge2.y);
ntangent2.z = nf * (ndeltaUV2.y * nedge1.z - ndeltaUV1.y * nedge2.z);
ntangent2 = glm::normalize(ntangent2);
nbitangent2.x = nf * (-ndeltaUV2.x * nedge1.x + ndeltaUV1.x * nedge2.x);
nbitangent2.y = nf * (-ndeltaUV2.x * nedge1.y + ndeltaUV1.x * nedge2.y);
nbitangent2.z = nf * (-ndeltaUV2.x * nedge1.z + ndeltaUV1.x * nedge2.z);
nbitangent2 = glm::normalize(nbitangent2);
/* -----------------------------------------------------------*/
update2(data_index, i, ntangent1, nbitangent1);
update2(data_index+(SIZE_WATER+1)*5, i+14, ntangent1, nbitangent1);
update2(data_index+1*5, i+14*2, ntangent1, nbitangent1);
update2(data_index, i+14*3, ntangent2, nbitangent2);
update2(data_index+(SIZE_WATER+1)*5, i+14*4, ntangent2, nbitangent2);
update2(data_index+SIZE_WATER*5, i+14*5, ntangent2, nbitangent2);
Vertex shader used in tutorial.
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in vec3 aTangent;
layout (location = 4) in vec3 aBitangent;
out VS_OUT {
vec3 FragPos;
vec2 TexCoords;
vec3 TangentLightPos;
vec3 TangentViewPos;
vec3 TangentFragPos;
} vs_out;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform vec3 lightPos;
uniform vec3 viewPos;
void main()
{
vs_out.FragPos = vec3(model * vec4(aPos, 1.0));
vs_out.TexCoords = aTexCoords;
mat3 normalMatrix = transpose(inverse(mat3(model)));
vec3 T = normalize(normalMatrix * aTangent);
vec3 N = normalize(normalMatrix * aNormal);
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
mat3 TBN = transpose(mat3(T, B, N));
vs_out.TangentLightPos = TBN * lightPos;
vs_out.TangentViewPos = TBN * viewPos;
vs_out.TangentFragPos = TBN * vs_out.FragPos;
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
Fragment shader used in the tutorial. Blinn-Phong Illumination model.


Code:
#version 330 core
out vec4 FragColor;
in VS_OUT {
vec3 FragPos;
vec2 TexCoords;
vec3 TangentLightPos;
vec3 TangentViewPos;
vec3 TangentFragPos;
} fs_in;
uniform sampler2D diffuseMap;
uniform sampler2D normalMap;
uniform vec3 lightPos;
uniform vec3 viewPos;
void main()
{
// obtain normal from normal map in range [0,1]
vec3 normal = texture(normalMap, fs_in.TexCoords).rgb;
// transform normal vector to range [-1,1]
normal = normalize(normal * 2.0 - 1.0); // this normal is in tangent space
// get diffuse color
vec3 color = texture(diffuseMap, fs_in.TexCoords).rgb;
// ambient
vec3 ambient = 0.1 * color;
// diffuse
vec3 lightDir = normalize(fs_in.TangentLightPos - fs_in.TangentFragPos);
float diff = max(dot(lightDir, normal), 0.0);
vec3 diffuse = diff * color;
// specular
vec3 viewDir = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
vec3 reflectDir = reflect(-lightDir, normal);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);
vec3 specular = vec3(0.2) * spec;
FragColor = vec4(ambient + diffuse + specular, 1.0);
}








