GLSL 中的正常旋转

Posted

技术标签:

【中文标题】GLSL 中的正常旋转【英文标题】:Normal Rotation in GLSL 【发布时间】:2014-06-22 13:43:52 【问题描述】:

我编写了一个加载模型并将其渲染到屏幕的基本程序。我正在使用 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 类中(vaoGLuint,在类中定义):

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::mapMeshs,并按如下方式呈现:

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 将是指向&some_vec3[0]&some_mat4[0][0]float 指针。 我在渲染之前手动上传了模型的转换矩阵,但是(这只是一个旋转矩阵,Transform 类(由 Mesh::getTransform() 返回)只会做一个 glm::rotation() 因为我试图弄清楚解决问题)。

最后,Mesh 渲染代码:

if(loaded)

    glBindVertexArray(vao);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib);
    glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, 0);

我认为这是所有必要的代码,但如果需要我可以发布更多。

【问题讨论】:

【参考方案1】:

您的法线矩阵计算是错误的。正确的法线矩阵将是模型或模型视图矩阵左上角 3x3 子矩阵的逆矩阵的转置(取决于您要进行照明计算的空间)。

您所做的只是将完整的 4x4 矩阵反转并取其左上角的 3x3 子矩阵,这是完全错误的。

你应该计算transpose(inverse(mat3(modelMat))),但你真的不应该在着色器中这样做,而是将它与 CPU 上的模型矩阵一起计算,以避免让 GPU 计算非常昂贵的矩阵求逆每个顶点。

【讨论】:

感谢您的回复,但我仍然不知所措。我将法线更改为vec3 normal = normalize(transpose(inverse(mat3(modelMat))) * inNormal);,但仍然有类似的结果,但法线不正确 @user3764686:嗯,你想让你的法线在什么空间?从你的问题来看,这并不是很清楚。还不清楚您的输入法线是否正确。而且您还必须在 FS 中重新规范化它们,因为线性插值不会保留长度。 抱歉不清楚,我已将输出颜色设置为等于法线以确定它们是否正确旋转,但主要问题中的动画图像说明了会发生什么。我不知道这是否正确,但我认为颜色(正常值)不应该在球体周围垂直移动,尽管我使用模型空间或视图空间。 另外,即使我的法线最初不正确,由于旋转矩阵,它们不应该仍然围绕 y 轴均匀旋转吗?我也在乘法后对法线进行归一化。对不起,如果我的假设不正确,如果我仍然不清楚,请告诉我。现在,我想我只想说我想要模型空间中的法线(顺便说一下,视图矩阵现在是恒定的)。谢谢你。 (原始消息太长,无法发表评论) @user3764686:即使您的视图矩阵是恒定的,它仍可能包含一些旋转,可能会导致您看到的效果。但我不确定。您至少应该看到原始图像和我给出的公式之间的不同行为。至于重新归一化:您在乘法之后进行归一化(应该如此),但您不会在片段着色器中对它们进行重新归一化(您也应该这样做,因为它们不会在插值期间保持归一化)。跨度> 【参考方案2】:

只要您的变换仅包含旋转、平移和统一缩放,您就可以简单地将变换的旋转部分应用于法线。

一般情况下,需要将转置逆矩阵应用于法线,只使用常规的 3x3 线性变换矩阵,没有将矩阵扩展到 4x4 的平移部分。

对于旋转和均匀缩放,逆转置与原始矩阵相同。因此,仅当您应用其他类型的变换(如非均匀缩放或剪切变换)时,才需要矩阵运算来反转和转置矩阵。

【讨论】:

【参考方案3】:

显然,如果网格的顶点法线不正确,则会出现奇怪的旋转伪影。在我的例子中,我在我的 3D 建模程序 (Blender) 中将网格在 X 轴上转换了 90 度,因为 Blender 使用 z 轴作为其垂直轴,而我的程序使用 y 轴作为垂直轴。但是,我在导出脚本中用于在 Blender 中变换/旋转网格的方法没有正确变换法线,而只是变换顶点的位置。没有任何先前的转换,程序按预期工作。我最初通过比较对称对象中的归一化位置和法线(我使用具有平滑法线的立方体)发现法线不正确,并看到法线被旋转。感谢@derhass 和@Solkar 引导我找到答案。

但是,如果有人仍然想贡献,我想知道为什么法线在乘以单轴旋转矩阵时不会在一个轴上旋转,即使它们不正确。

【讨论】:

以上是关于GLSL 中的正常旋转的主要内容,如果未能解决你的问题,请参考以下文章

GLSL:用旋转矢量旋转?

glsl 着色器 - 颜色混合,正常模式(如在 Photoshop 中)

GLSL 镜面光照旋转

顶点着色器 glsl qt 中的纹理映射

GLSL 2D 旋转矩阵无法按预期工作

OpenGL/GLSL/GLM - Skybox 像第三人称一样旋转