繁体   English   中英

将四元数用于切线空间法线贴图-我遇到的问题

[英]Using quaternions for tangent space normal mapping - Problems I'm having

受crytek关于使用四元数在较小顶点的四元数中存储切线空间的演示的启发,我得出了合理的结论:如果您可以使用四元数存储切线空间,那么您也可以将四元数束缚在顶点之间并使用它们直接旋转法线。 这样就无需重新正交化切空间矢量,或重建其中一个切线空间矢量,并且将切出每个片段的矩阵矢量乘法,并用一个四元数矢量乘法将其全部替换。

我尝试使用自制的四元数类在OpenGL应用程序中实现它,但遇到了一些问题。 我知道我的四元数可以从矩阵构造,将四元数乘以向量,并得到与将矩阵乘以向量相同的结果-我在CPU方面已经成功做到了。 但是,一旦我开始在GLSL中使用它们,一切都会变得一团糟。

值得注意的是,我实际上可以辨别法线贴图的模式,因此我认为自己走在正确的轨道上。 不幸的是,我的颜色似乎一团糟。

这是我在glsl中使用的四元数数学:

vec4 multQuat(vec4 q1, vec4 q2)
{
    return vec4(
        (q1.w * q2.y) + (q1.y * q2.w) + (q1.x * q2.z) - (q1.z * q2.x),
        (q1.w * q2.z) + (q1.z * q2.w) + (q1.y * q2.x) - (q1.x * q2.y),
        (q1.w * q2.w) - (q1.x * q2.x) - (q1.y * q2.y) - (q1.z * q2.z),
        (q1.w * q2.x) + (q1.x * q2.w) + (q1.z * q2.y) - (q1.y * q2.z)
        );
}

vec3 rotateVector(vec4 quat, vec3 vec)
{
    return vec + 2.0 * cross(quat.xyz, cross(quat.xyz, vec) + (quat.w * vec));
}

这是从顶点着色器传递的方式:

vQtangent = multQuat(inQtangent, quatView);

其中quatView是由视图矩阵构成的四元数。 这可能是我的问题,因为生成此四元数的代码假定矩阵是正交的。

最后,我们在片段着色器中计算凹凸法线:

vec3 calcBumpedNormal(void)
{
    vec4 qtangent = normalize(vQtangent);
    vec3 normal = texture2D(texNormal, vTexCoord).xyz;
    normal = (normal * 2) - 1;
    return normalize(rotateVector(qtangent, normal));
};

这是我如何从3个vec3计算四元数的方法(如何从tbn向量中获取四元数):

inline static quat fromMat3(const vec3& col0, const vec3& col1, const vec3& col2)
{
    /* warning - this only works when the matrix is orthogonal and special orthogonal */

    float w = sqrtf(1.0f + col0.x + col1.y + col2.z) / 2.0f;

    return quat(
        (col1.z - col2.y) / (4.0f * w),
        (col2.x - col0.z) / (4.0f * w),
        (col0.y - col1.x) / (4.0f * w),
        w);
}

这是我如何从mat4计算四元数的方法(如何从matix视图中获取quatView):

inline static quat fromMat4(const mat4& mat)
{
    /* warning - this only works when the matrix is orthogonal and special orthogonal */

    float w = sqrtf(1.0f + mat.m[0][0] + mat.m[1][1] + mat.m[2][2]) / 2.0f;

    return quat(
        (mat.m[1][2] - mat.m[2][1]) / (4.0f * w),
        (mat.m[2][0] - mat.m[0][2]) / (4.0f * w),
        (mat.m[0][1] - mat.m[1][0]) / (4.0f * w),
        w);
}

我知道这两种方法都不适用于非正交矩阵。

但是,只有法线的x和y存储在法线缓冲区中,我使用sqrt技巧在光通道片段着色器中重建z。 由于这些法线是在视图空间中的,因此z分量始终为正。

不幸的是,我的结果不正确,而且我不知道在哪里看。 我可以辨别法线贴图的模式,所以有些事情是正确的。

如果有人让我知道我的问题可能在哪里,或者如果他们有自己做过的经验,那么任何建议都将不胜感激。

如果仅在顶点着色器中使用逐顶点四元数(通过将光和相机矢量转换为切线空间),代码是否可以正常工作? 如果仅当您尝试旋转像素着色器中的法线时才中断,那么您的问题是四元数插值(如果没有,那么我就浪费了20分钟)。

问题

四元数与选定惯用性的正交法线矩阵不具有1:1关系(我认为您的惯用性很好,但您应该验证这一点)。 如果将每个四元数分量乘以-1 ,则将获得相同的变换。

现在,您的fromMat3始终生成具有正W分量的四元数。 想象一下插值如何沿着(0.99,0,0,0.1)(-0.99,0,0,0.1)之间的边缘进行。 X组件将一直沿其轴行进,从而给您带来各种阴影问题。

您必须确保在属于同一半球的四元数之间发生任何四元数插值(QI),即dot(q1,q2) > 0 很容易看出,对于我提到的示例四元数,此检查如何失败;如果将第二四元数乘以-1 ,它将如何正常工作。

棘手的部分是,要确保QI正确性可能需要分割边缘并添加新的顶点,因此最好在导出器端而不是在模型加载期间进行。 请参阅KRI网格导出器代码以供参考。

结论

我不建议您出于实际原因去那里,除非您非常执着。 相反,您可以在顶点着色器中愉快地使用四元数。 如果您有使用过GPU Pro 3的书,则可以在那找到我关于四元数的文章 ,详细解释同样的问题(以及解决方案)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM