C++ 使用四元数进行切线空间法线映射-问题I';我有
受crytek关于使用四元数在四元数中存储较小顶点的切线空间的演示的启发,我得出了一个合乎逻辑的结论,即如果可以使用四元数存储切线空间,那么也可以在顶点之间映射四元数,并使用它们直接旋转法线。这将消除对切线空间向量进行重新正交化或重建其中一个向量的需要,并将删除每片段矩阵向量乘法,将其全部替换为单个四元数向量乘法 我尝试在我的OpenGL应用程序中实现它,使用我自制的四元数类,但我遇到了一些问题。我知道我的四元数可以从一个矩阵中构造出来,将四元数乘以一个向量,得到与矩阵乘以向量相同的结果——我已经在cpu端成功地做到了这一点。然而,一旦我开始在GLSL中与他们合作,一切都会变得混乱 非常有趣的是,我可以分辨出法线贴图的模式,所以我认为我的思路是正确的。不幸的是,我的颜色似乎乱了套 这是我在glsl中使用的四元数数学:C++ 使用四元数进行切线空间法线映射-问题I';我有,c++,opengl,glsl,quaternions,normals,C++,Opengl,Glsl,Quaternions,Normals,受crytek关于使用四元数在四元数中存储较小顶点的切线空间的演示的启发,我得出了一个合乎逻辑的结论,即如果可以使用四元数存储切线空间,那么也可以在顶点之间映射四元数,并使用它们直接旋转法线。这将消除对切线空间向量进行重新正交化或重建其中一个向量的需要,并将删除每片段矩阵向量乘法,将其全部替换为单个四元数向量乘法 我尝试在我的OpenGL应用程序中实现它,使用我自制的四元数类,但我遇到了一些问题。我知道我的四元数可以从一个矩阵中构造出来,将四元数乘以一个向量,得到与矩阵乘以向量相同的结果——我已
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);
其中,四元视图是由视图矩阵生成的四元数。这可能是我的问题,因为生成这个四元数的代码假设矩阵是正交的
最后,我们计算片段着色器中的凹凸法线:
vec3 calcBumpedNormal(void)
{
vec4 qtangent = normalize(vQtangent);
vec3 normal = texture2D(texNormal, vTexCoord).xyz;
normal = (normal * 2) - 1;
return normalize(rotateVector(qtangent, normal));
};
下面是我如何从3个向量3计算四元数(如何从tbn向量得到四元数):
下面是我如何从mat4计算四元数(如何从视图matix获得四元视图):
我知道这两种方法都不适用于非正交矩阵
但是,只有法线的x和y存储在法线缓冲区中,我使用sqrt技巧在光通过片段着色器中重建z。因为这些法线将位于视图空间中,所以z分量始终为正值
不幸的是,我的结果是错误的,我不知道去哪里找。我能分辨出法线贴图的模式,所以有些东西必须是正确的
如果有人告诉我我的问题可能在哪里,或者他们自己有这样做的经验,任何建议都将不胜感激。如果您仅在顶点着色器中使用逐顶点四元数(通过将灯光和摄影机向量转换为切线空间),您的代码工作正常吗?如果仅当您尝试在像素着色器中旋转法线时它才会中断,那么您的问题是四元数插值(如果没有,那么我只是浪费了20分钟) 问题 四元数与所选惯用手的正交法线矩阵不是1:1的关系(我假设你的惯用手很好,但你应该验证一下)。如果将每个四元数分量乘以
-1
,将得到相同的变换
现在,您的fromma3
总是生成一个具有正W
分量的四元数。想象一下插值是如何沿着(0.99,0,0,0.1)
和(-0.99,0,0.1)
之间的边进行的。X
组件将一直穿过其轴,从而为您带来各种着色问题
解决方案
您必须确保任何四元数插值(QI)都发生在属于同一半球的四元数之间,即点(q1,q2)>0
。很容易看出,对于我提到的示例四元数,该检查是如何失败的,如果将第二个四元数乘以-1
,它是如何工作的
棘手的部分是,确保QI正确性可能需要分割边和添加新顶点,因此最好在导出器端完成,而不是在模型加载期间。请看一下代码以供参考
结论
出于实际原因,我不建议你去那里,除非你非常坚持。相反,您可以在顶点着色器中愉快地使用四元数。如果你有一本GPU Pro 3手册,你可以在那里找到,详细解释同样的问题(以及解决方案)。你的应用程序会在公共存储库中提供吗?:)恐怕不是这样,但是如果你想查看整个项目的源代码,你可以抓住它。另外,“因为这些法线应该在视图空间中,所以z分量总是正的”是不正确的。例如,如果你站在你右边的一堵墙旁边,沿着墙看,然后稍微向左看,你仍然可以看到这堵墙,但是它的法线在你的参照系中会有-ve z。有些人像你一样重建z,却没有注意到(有时很微妙的)照明缺陷。这里有一个关于不同方法的讨论(有点陈旧):我看不出这里的代码有任何错误(也许你可以用你已经做的修复来更新它)。你能不能也发布视图四元数的构造代码?查看一些屏幕截图也可能会有所帮助。
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);
}
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);
}