我正在从太空进行地球的行星模拟并停留在正常的测绘阶段。我在互联网上浏览了一堆教程并编写了逻辑来计算切线,将它们传递给顶点着色器并计算TBN空间中的法线,然后使用法线来计算使用phong模型的光照。一切似乎都正常,但在行星的南极,我得到了三角形缺失,因此出现了黑点:
void Sphere::generateMesh(){
float deltaPhi = (float)PI/stacks;
float deltaTheta = (2 * (float)PI)/slices;
for(float phi = 0; phi < PI ; phi += deltaPhi){
for(float theta = 0; theta < 2*PI - 0.001; theta += deltaTheta){
float x1 = -sinf(phi) * sinf(theta) * radius;
float y1 = -cosf(phi) * radius;
float z1 = -sinf(phi) * cosf(theta) * radius;
float u1 = float( atan2(x1, z1) / (2 * PI) + 0.5 );
float v1 = float( -asin(y1) / (float)PI + 0.5 );
float x2 = -sinf(theta + deltaTheta) * sinf(phi) * radius;
float y2 = -cosf(phi) * radius;
float z2 = -sinf(phi) * cosf(theta + deltaTheta) * radius;
float u2 = float( atan2(x2, z2) / (2 * PI) + 0.5 );
float v2 = float( -asin(y2) / ((float)PI) + 0.5 );
float x3 = -sinf(theta + deltaTheta) * sinf(phi + deltaPhi) * radius;
float y3 = -cosf(phi + deltaPhi) * radius;
float z3 = -sinf(phi + deltaPhi) * cosf(theta + deltaTheta) * radius;
float u3 = float( atan2(x3, z3) / (2 * (float)PI) + 0.5 );
float v3 = float( -asin(y3) / (float)PI + 0.5 );
float x4 = -sinf(theta) * sinf(phi + deltaPhi) * radius;
float y4 = -cosf(phi + deltaPhi) * radius;
float z4 = -sinf(phi + deltaPhi) * cosf(theta) * radius;
float u4 = float( atan2(x4, z4) / (2 * (float)PI) + 0.5 );
float v4 = float( -asin(y4) / (float)PI + 0.5 );
Vec3f p1(x1, y1, z1);
Vec3f uv1(u1, v1, 0);
Vec3f p2(x2, y2, z2);
Vec3f uv2(u2, v2, 0);
Vec3f p3(x3, y3, z3);
Vec3f uv3(u3, v3, 0);
Vec3f p4(x4, y4, z4);
Vec3f uv4(u4, v4, 0);
//addTriangle(x1, y1, z1, u1, v1,
// x2, y2, z2, u2, v2,
// x3, y3, z3, u3, v3);
//addTriangle(x1, y1, z1, u1, v1,
// x3, y3, z3, u3, v3,
// x4, y4, z4, u4, v4);
addTriangle(p1, uv1, p2, uv2, p3, uv3);
addTriangle(p1, uv1, p3, uv3, p4, uv4);
}
}
}
addTriangle方法将三角形添加到它们的数据结构中,而这些数据结构又用于将顶点缓冲区对象中的顶点,法线,切线和纹理坐标传递给着色器。该方法看起来像这样:
void Object::addTriangle(Vec3f v1, Vec3f u1, Vec3f v2, Vec3f u2, Vec3f v3, Vec3f u3){
//add triangle normally
addTriangle(v1, v2, v3);
//TODO integrate uv coordinates in face
//add extra texture coordinates to the uv vector
uv.push_back(u1);
uv.push_back(u2);
uv.push_back(u3);
Vec3f e1 = v2 - v1;
Vec3f e2 = v3 - v1;
float deltaU1 = u2.x - u1.x;
float deltaV1 = u2.y - u1.y;
float deltaU2 = u3.x - u1.x;
float deltaV2 = u3.y - u1.y;
float f = 1.f / (deltaU1 * deltaV2 - deltaU2 * deltaV1);
Vec3f tangent;
tangent = f * (deltaV2 * e1 - deltaV1 * e2);
auto tangentIter = find(tangents.begin(), tangents.end(), tangent);
// if tangent not in the vector, then add it and set iter to the
// vector's tail.
if (tangentIter == tangents.end()){
tangents.push_back(tangent);
tangentIter = tangents.end() - 1;
}
GLuint tangentIndex = tangentIter - tangents.begin();
face* face = faces.back();
face->t = tangentIndex;
vector<Vec3f> triangleVertices;
triangleVertices.push_back(v1);
triangleVertices.push_back(v2);
triangleVertices.push_back(v3);
for (unsigned i = 0; i < triangleVertices.size(); i++){
Vec3f v = triangleVertices.at(i);
if (sharedTangents.find(v) == sharedTangents.end()){
sharedTangents.insert(pair<Vec3f, Vec3f>(v, tangent));
}
else{
Vec3f& vt = sharedTangents.at(v);
vt += tangent;
}
}
}
在初始化缓冲区时,我调用此方法来计算每个顶点的切线:
void Object::calculateVertexTangents(){
vector<Vec3f> vertexTangentList;
//find the mean of all the tangents which are shared by the vertex,
//and normalize and add them to the vertex tangent vector
for (unsigned i = 0; i < points.size(); i++){
Vec3f vt = sharedTangents.at(points.at(i));
vt.normalize();
vertexTangentList.push_back(vt);
}
outVertexTangents = new float[faces.size() * 3 * 3];
Vec3f vt;
int index = 0;
for (unsigned i = 0; i < faces.size(); i++){
for (unsigned j = 0; j < 3; j++){
vt = vertexTangentList.at((*faces.at(i))[j]);
outVertexTangents[index] = vt.x;
outVertexTangents[index + 1] = vt.y;
outVertexTangents[index + 2] = vt.z;
index += 3;
}
}
}
最后我的顶点和片段着色器代码就在这里: 顶点着色器:
#version 400 core
in vec3 vPosition;
in vec2 vTexCoord;
in vec3 vNormal;
in vec3 vTangent;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform vec4 lightPosition;
out vec2 texCoord;
out vec3 lightVec;
out vec3 eyeVec;
out vec3 halfVec;
out vec3 vPos;
void main(){
mat4 modelView = view * model;
mat4 normalMatrix = transpose(inverse(modelView));
vec3 n = normalize ( ( normalMatrix * vec4( vNormal, 0.0 ) ).xyz );
vec3 t = normalize ( ( normalMatrix * vec4( vTangent, 0.0 ) ).xyz );
vec3 b = cross (n, t);
vec4 vertexInEye = modelView * vec4(vPosition, 1.0);
vPos = vertexInEye.xyz;
vec3 lightDir = normalize( lightPosition.xyz - vertexInEye.xyz );
vec3 temp;
temp.x = dot (lightDir, t);
temp.y = dot (lightDir, b);
temp.z = dot (lightDir, n);
lightVec = normalize(temp);
temp.x = dot (vertexInEye.xyz, t);
temp.y = dot (vertexInEye.xyz, b);
temp.z = dot (vertexInEye.xyz, n);
eyeVec = normalize(temp);
vertexInEye = normalize(vertexInEye);
vec3 halfVector = normalize(vertexInEye.xyz + lightDir);
temp.x = dot (halfVector, t);
temp.y = dot (halfVector, b);
temp.z = dot (halfVector, n);
halfVec = temp ;
texCoord = vTexCoord;
gl_Position = projection * modelView * vec4(vPosition, 1.0);
}
Fragment Shader:
#version 400 core
uniform vec4 lightColor;
uniform vec4 diffuseColor;
uniform sampler2D mySampler;
uniform sampler2D night;
uniform sampler2D clouds;
uniform sampler2D specMap;
uniform sampler2D bumpMap;
in vec3 vPos;
in vec3 lightVec;
in vec3 eyeVec;
in vec3 halfVec;
in vec2 texCoord;
out vec4 frag_color;
void main(){
vec3 normal = 2.0 * texture(bumpMap, texCoord).rgb - 1.0;
//normal.z = 1 - normal.x * normal.x - normal.y * normal.y;
normal = normalize ( normal );
vec4 spec = vec4(1.0, 0.941, 0.898, 1.0);
vec4 specMapColor = texture2D(specMap, texCoord);
vec3 L = lightVec;
vec3 N = normal;
vec3 Emissive = normalize(-vPos);
vec3 R = reflect(-L, N);
float dotProd = max(dot(R, Emissive), 0.0);
vec4 specColor = spec * pow(dotProd,6.0) * 0.6;
float diffuse = max(dot(N, L), 0.0);
vec2 cloud_color = texture2D( clouds, texCoord).rg;
vec3 day_color = (texture2D( mySampler, texCoord ).rgb * diffuse + specColor.rgb * specMapColor.g) * (1 - cloud_color.r) + cloud_color.r * diffuse;
vec3 night_color = texture2D( night, texCoord ).rgb * (1 - cloud_color.r) * 0.5;
vec3 color = day_color;
if(dot(N, L) < 0.1)
color = mix(night_color, day_color, (diffuse + 0.1) * 5.0);
frag_color = vec4(color, 1.0);
}
如果有人能够指出我在计算切线方面做错了什么,我将非常感激?如果这些信息有所帮助,当我只用顶点法线和phong阴影渲染球体时,我会得到一个完美的行星。
编辑: 这是我刚刚使用纹理着色而不是使用切线空间法线贴图时的屏幕截图: