我编写了一个基本程序,可以加载模型并将其渲染到屏幕上。我正在使用GLSL来适当地转换模型,但是在用我能想到的模型矩阵,视图矩阵,逆,转置等的每个组合旋转它们之后,法线似乎总是不正确的。模型矩阵只是使用glm围绕y轴旋转:
angle += deltaTime;
modelMat = glm::rotate(glm::mat4(), angle, glm::vec3(0.f, 1.f, 0.f));
我当前的顶点着色器代码(我多次修改了法线):
#version 150 core
uniform mat4 projMat;
uniform mat4 viewMat;
uniform mat4 modelMat;
in vec3 inPosition;
in vec3 inNormal;
out vec3 passColor;
void main()
{
gl_Position = projMat * viewMat * modelMat * vec4(inPosition, 1.0);
vec3 normal = normalize(mat3(inverse(modelMat)) * inNormal);
passColor = normal;
}
我的片段着色器:
#version 150 core
in vec3 passColor;
out vec4 outColor;
void main()
{
outColor = vec4(passColor, 1.0);
}
我确信已经将均匀变量正确地传递给着色器,因为模型本身可以正确转换,如果我进行定向照明等计算,则初始法线是正确的。
我已经创建了旋转模型的GIF,抱歉质量低: http://i.imgur.com/LgLKHCb.gif?1
最令我困惑的是法线在多个轴上的旋转方式,我认为在一个轴上乘以一个简单的旋转矩阵时不会发生这种情况。
修改
我在下面添加了一些客户端代码。
这是缓冲区绑定模型的位置,Mesh
类(vao
是GLuint
,在类中定义):
GLuint vbo[3];
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(normals? (uvcoords? 3 : 2) : (uvcoords? 2 : 1), vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, vcount * 3 * sizeof(GLfloat), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
if(normals)
{
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, vcount * 3 * sizeof(GLfloat), normals, GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_TRUE, 0, 0);
glEnableVertexAttribArray(1);
}
if(uvcoords)
{
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glBufferData(GL_ARRAY_BUFFER, vcount * 2 * sizeof(GLfloat), uvcoords, GL_STATIC_DRAW);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(2);
}
glBindVertexArray(0);
glGenBuffers(1, &ib);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, icount * sizeof(GLushort), indices, GL_STATIC_DRAW);
这是在Material
类中使用简单的readf()加载到内存后编译着色器的地方:
u32 vertexShader = glCreateShader(GL_VERTEX_SHADER);
u32 fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vertexShader, 1, (const GLchar**)&vsContent, 0);
glCompileShader(vertexShader);
if(!validateShader(vertexShader)) return false;
glShaderSource(fragmentShader, 1, (const GLchar**)&fsContent, 0);
glCompileShader(fragmentShader);
if(!validateShader(fragmentShader)) return false;
programHandle = glCreateProgram();
glAttachShader(programHandle, vertexShader);
glAttachShader(programHandle, fragmentShader);
glBindAttribLocation(programHandle, 0, "inPosition");
glBindAttribLocation(programHandle, 1, "inNormal");
//glBindAttribLocation(programHandle, 2, "inUVCoords");
glLinkProgram(programHandle);
if(!validateProgram()) return false;
validateShader(GLuint)
和validateProgram()
函数:
bool Material::validateShader(GLuint shaderHandle)
{
char buffer[2048];
memset(buffer, 0, 2048);
GLsizei len = 0;
glGetShaderInfoLog(shaderHandle, 2048, &len, buffer);
if(len > 0)
{
Logger::log("ve::Material::validateShader: Failed to compile shader - %s", buffer);
return false;
}
return true;
}
bool Material::validateProgram()
{
char buffer[2048];
memset(buffer, 0, 2048);
GLsizei len = 0;
glGetProgramInfoLog(programHandle, 2048, &len, buffer);
if(len > 0)
{
Logger::log("ve::Material::validateProgram: Failed to link program - %s", buffer);
return false;
}
glValidateProgram(programHandle);
GLint status;
glGetProgramiv(programHandle, GL_VALIDATE_STATUS, &status);
if(status == GL_FALSE)
{
Logger::log("ve::Material::validateProgram: Failed to validate program");
return false;
}
return true;
}
每个Material
个实例都有std::map
个Mesh
个,并按以下方式呈现:
void Material::render()
{
if(loaded)
{
glUseProgram(programHandle);
for(auto it = mmd->uniforms.begin(); it != mmd->uniforms.end(); ++it)
{
GLint loc = glGetUniformLocation(programHandle, (const GLchar*)it->first);
switch(it->second.type)
{
case E_UT_FLOAT3: glUniform3fv(loc, 1, it->second.f32ptr); break;
case E_UT_MAT4: glUniformMatrix4fv(loc, 1, GL_FALSE, it->second.f32ptr); break;
default: break;
}
}
for(Mesh* m : mmd->objects)
{
GLint loc = glGetUniformLocation(programHandle, "modelMat");
glUniformMatrix4fv(loc, 1, GL_FALSE, &m->getTransform()->getTransformMatrix()[0][0]);
m->render();
}
}
}
it->second.f32ptr
将是指向float
或&some_vec3[0]
的{{1}}指针。
我在渲染之前手动上传模型的变换矩阵,但是(这只是一个旋转矩阵,&some_mat4[0][0]
类(由Mesh :: getTransform()返回)只会执行glm :: rotation(),因为我在尝试找出问题所在。)
最后,Transform
呈现代码:
Mesh
我认为这是所有必要的代码,但如果需要我可以发布更多。
答案 0 :(得分:1)
你的正常矩阵计算是错误的。正确的法线矩阵将是模型或模型视图矩阵的左上3x3子矩阵的逆的转置(取决于您想要进行光照计算的空间)。
你所做的只是反转完整的4x4矩阵并采用左上角的3x3子矩阵,这是完全错误的。
你应该计算transpose(inverse(mat3(modelMat)))
,但是你真的不应该在着色器中这样做,而是将它与CPU上的模型矩阵一起计算,以避免让GPU计算相当昂贵的矩阵求逆每个顶点。
答案 1 :(得分:1)
只要您的转换仅包含旋转,平移和均匀缩放,您只需将转换的旋转部分应用于法线即可。
一般来说,只需要使用常规的3x3线性变换矩阵,而不需要将矩阵扩展到4x4的平移部分,它就是需要应用于法线的转置逆矩阵。
对于旋转和均匀缩放,反转置与原始矩阵相同。因此,只有在应用其他类型的变换(如非均匀缩放或剪切变换)时,才需要对矩阵进行反转和转置的矩阵运算。
答案 2 :(得分:1)
显然,如果网格的顶点法线不正确,则会出现奇怪的旋转伪影。在我的例子中,我在3D建模程序(Blender)中将网格在X轴上转换了90度,因为Blender使用z轴作为其垂直轴,而我的程序使用y轴作为垂直轴。但是,我在导出脚本中用于在Blender中转换/旋转网格的方法没有正确地转换法线,而只是顶点的位置。没有任何先前的转换,程序按预期工作。我最初通过比较对称物体中的归一化位置和法线来发现法线是不正确的(我使用了具有平滑法线的立方体),并且看到法线被旋转。感谢@derhass和@Solkar指导我的答案。
但是,如果有人还想贡献,我想知道为什么法线在乘以单轴旋转矩阵时不会在一个轴上旋转,即使它们不正确。