glsl(GPU)矩阵/向量计算产生与CPU不同的结果

时间:2015-09-01 13:42:29

标签: c++ opengl glsl matrix-multiplication

我找不到任何不同行为的文档,所以这只是一个完整性检查,我没有做错任何事......

我在GLSL中创建了一些辅助函数,将float / vec / mat比较作为颜色输出:

注意:非常确定这里没有任何错误,只是包含它以便您确切知道我在做什么......

//returns true or false if floats are eq (within some epsillon)
bool feq(float a, float b)
{
  float c = a-b;
  return (c > -0.05 && c < 0.05);
}

returns true or false if vecs are eq
bool veq(vec4 a, vec4 b)
{
  return
  (
    feq(a.x, b.x) &&
    feq(a.y, b.y) &&
    feq(a.z, b.z) &&
    feq(a.w, b.w) &&
    true
  );
}

//returns color indicating where first diff lies between vecs
//white for "no diff"
vec4 cveq(vec4 a, vec4 b)
{
       if(!feq(a.x, b.x)) return vec4(1.,0.,0.,1.);
  else if(!feq(a.y, b.y)) return vec4(0.,1.,0.,1.);
  else if(!feq(a.z, b.z)) return vec4(0.,0.,1.,1.);
  else if(!feq(a.w, b.w)) return vec4(1.,1.,0.,1.);
  else                    return vec4(1.,1.,1.,1.);
}

//returns true or false if mats are eq
bool meq(mat4 a, mat4 b)
{
  return
  (
    veq(a[0],b[0]) &&
    veq(a[1],b[1]) &&
    veq(a[2],b[2]) &&
    veq(a[3],b[3]) &&
    true
  );
}

//returns color indicating where first diff lies between mats
//white means "no diff"
vec4 cmeq(mat4 a, mat4 b)
{
       if(!veq(a[0],b[0])) return vec4(1.,0.,0.,1.);
  else if(!veq(a[1],b[1])) return vec4(0.,1.,0.,1.);
  else if(!veq(a[2],b[2])) return vec4(0.,0.,1.,1.);
  else if(!veq(a[3],b[3])) return vec4(1.,1.,0.,1.);
  else return vec4(1.,1.,1.,1.);
}

所以我有一个模型垫,一个视图垫和一个项目垫。我在屏幕上渲染一个矩形( 正确投影/转换...),并根据计算的每个步骤与我的on-cpu计算的等价物的匹配程度设置其颜色。

uniform mat4 model_mat;
uniform mat4 view_mat;
uniform mat4 proj_mat;

attribute vec4 position;

varying vec4 var_color;

void main()
{
  //this code works (at least visually)- the rect is transformed as expected
  vec4 model_pos = model_mat * position;
  gl_Position = proj_mat * view_mat * model_pos;

  //this is the test code that does the same as above, but tests its results against CPU calculated equivalents
  mat4 m;

  //test proj
  //compares the passed in uniform 'proj_mat' against a hardcoded rep of 'proj_mat' as printf'd by the CPU
  m[0] = vec4(1.542351,0.000000,0.000000,0.000000);
  m[1] = vec4(0.000000,1.542351,0.000000,0.000000);
  m[2] = vec4(0.000000,0.000000,-1.020202,-1.000000);
  m[3] = vec4(0.000000,0.000000,-2.020202,0.000000);
  var_color = cmeq(proj_mat,m); //THIS PASSES (the rect is white)

  //view
  //compares the passed in uniform 'view_mat' against a hardcoded rep of 'view_mat' as printf'd by the CPU
  m[0] = vec4(1.000000,0.000000,-0.000000,0.000000);
  m[1] = vec4(-0.000000,0.894427,0.447214,0.000000);
  m[2] = vec4(0.000000,-0.447214,0.894427,0.000000);
  m[3] = vec4(-0.000000,-0.000000,-22.360680,1.000000);
  var_color = cmeq(view_mat,m); //THIS PASSES (the rect is white)

  //projview
  mat4 pv = proj_mat*view_mat;

  //proj_mat*view_mat
  //compares the result of GPU computed proj*view against a hardcoded rep of proj*view **<- NOTE ORDER** as printf'd by the CPU
  m[0] = vec4(1.542351,0.000000,0.000000,0.000000);
  m[1] = vec4(0.000000,1.379521,-0.689760,0.000000);
  m[2] = vec4(0.000000,-0.456248,-0.912496,20.792208);
  m[3] = vec4(0.000000,-0.447214,-0.894427,22.360680);
  var_color = cmeq(pv,m); //THIS FAILS (the rect is green)

  //view_mat*proj_mat
  //compares the result of GPU computed proj*view against a hardcoded rep of view*proj **<- NOTE ORDER** as printf'd by the CPU
  m[0] = vec4(1.542351,0.000000,0.000000,0.000000);
  m[1] = vec4(0.000000,1.379521,0.456248,0.903462);
  m[2] = vec4(0.000000,0.689760,21.448183,-1.806924);
  m[3] = vec4(0.000000,0.000000,-1.000000,0.000000);
  var_color = cmeq(pv,m); //THIS FAILS (the rect is green)

  //view_mat_t*proj_mat_t
  //compares the result of GPU computed proj*view against a hardcoded rep of view_t*proj_t **<- '_t' = transpose, also note order** as printf'd by the CPU
  m[0] = vec4(1.542351,0.000000,0.000000,0.000000);
  m[1] = vec4(0.000000,1.379521,-0.456248,-0.447214);
  m[2] = vec4(0.000000,-0.689760,-0.912496,-0.894427);
  m[3] = vec4(0.000000,0.000000,20.792208,22.360680);
  var_color = cmeq(pv,m); //THIS PASSES (the rect is white)
}

这是我的CPU矢量/矩阵计算(矩阵是col-order [m.x是第一列,而不是第一行]):

fv4 matmulfv4(fm4 m, fv4 v)
{
  return fv4
    { m.x[0]*v.x+m.y[0]*v.y+m.z[0]*v.z+m.w[0]*v.w,
      m.x[1]*v.x+m.y[1]*v.y+m.z[1]*v.z+m.w[1]*v.w,
      m.x[2]*v.x+m.y[2]*v.y+m.z[2]*v.z+m.w[2]*v.w,
      m.x[3]*v.x+m.y[3]*v.y+m.z[3]*v.z+m.w[3]*v.w };
}

fm4 mulfm4(fm4 a, fm4 b)
{
  return fm4
    { { a.x[0]*b.x[0]+a.y[0]*b.x[1]+a.z[0]*b.x[2]+a.w[0]*b.x[3], a.x[0]*b.y[0]+a.y[0]*b.y[1]+a.z[0]*b.y[2]+a.w[0]*b.y[3], a.x[0]*b.z[0]+a.y[0]*b.z[1]+a.z[0]*b.z[2]+a.w[0]*b.z[3], a.x[0]*b.w[0]+a.y[0]*b.w[1]+a.z[0]*b.w[2]+a.w[0]*b.w[3] },
      { a.x[1]*b.x[0]+a.y[1]*b.x[1]+a.z[1]*b.x[2]+a.w[1]*b.x[3], a.x[1]*b.y[0]+a.y[1]*b.y[1]+a.z[1]*b.y[2]+a.w[1]*b.y[3], a.x[1]*b.z[0]+a.y[1]*b.z[1]+a.z[1]*b.z[2]+a.w[1]*b.z[3], a.x[1]*b.w[0]+a.y[1]*b.w[1]+a.z[1]*b.w[2]+a.w[1]*b.w[3] },
      { a.x[2]*b.x[0]+a.y[2]*b.x[1]+a.z[2]*b.x[2]+a.w[2]*b.x[3], a.x[2]*b.y[0]+a.y[2]*b.y[1]+a.z[2]*b.y[2]+a.w[2]*b.y[3], a.x[2]*b.z[0]+a.y[2]*b.z[1]+a.z[2]*b.z[2]+a.w[2]*b.z[3], a.x[2]*b.w[0]+a.y[2]*b.w[1]+a.z[2]*b.w[2]+a.w[2]*b.w[3] },
      { a.x[3]*b.x[0]+a.y[3]*b.x[1]+a.z[3]*b.x[2]+a.w[3]*b.x[3], a.x[3]*b.y[0]+a.y[3]*b.y[1]+a.z[3]*b.y[2]+a.w[3]*b.y[3], a.x[3]*b.z[0]+a.y[3]*b.z[1]+a.z[3]*b.z[2]+a.w[3]*b.z[3], a.x[3]*b.w[0]+a.y[3]*b.w[1]+a.z[3]*b.w[2]+a.w[3]*b.w[3] } };
}

需要注意的一点是,CPU 上的view_mat_t * proj_mat_t匹配 GPU上的proj_mat * view_mat。有谁知道为什么?我已经对CPU上的矩阵进行了测试,并将它们与在线矩阵乘法器的结果进行了比较,看起来是正确的......

我知道GPU在vert着色器和frag着色器之间做了一些事情(我想是这样的,用gl_Position.w划分gl_Position还是什么?)......还有什么别的东西,我没有考虑到这里只是vert着色器?某些事情在某些时候会被自动转换吗?

3 个答案:

答案 0 :(得分:0)

您可能希望考虑GLM进行CPU端Matrix实例化和计算。它有助于减少可能的错误来源。

其次,GPU和CPU不执行相同的计算。用于计算浮点数的IEEE 754标准对于如何执行这些计算以及它们必须在何种程度上准确具有相对严格的标准,但是:

  1. 数字在最低有效位中可能会有所不同(并且根据所使用的具体操作/功能而有所不同)
  2. 一些GPU供应商首先选择不确保严格的IEEE合规性(过去已知Nvidia将Speed优先于严格的IEEE合规性)
  3. 我最后会注意到你的CPU端计算为舍入错误留下了很大的空间,这可能会增加。因此,对这些问题的通常建议是在代码中包含对少量偏差的容差。通常用于检查“平等”的代码。两个浮点数的假设是假设abs(x-y)< 0.000001表示x和y基本相等。当然,具体数字必须根据您的个人用途进行校准。

    当然,您要检查以确保正确传递所有矩阵/制服。

答案 1 :(得分:0)

确定。我找到了答案。 在单个着色器中对矩阵运算没有什么特别之处。 ,但是,你应该注意几件事情:

:1:OpenGL(GLSL)使用 column-major 矩阵。因此,构建一个可在数学上下文中直观表示的矩阵:

 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16

你会在GLSL内使用:

mat4 m = mat4(
  vec4( 1, 5, 9,13),
  vec4( 2, 6,10,14),
  vec4( 3, 7,11,15),
  vec4( 4, 8,12,16),
);

:2:如果您改为在CPU上使用 row-major 矩阵,请务必设置&#34;转置&#34;将矩阵制服上传到着色器时标记为true,如果您正在使用 col-major 矩阵,请确保将其设置为 false

只要你知道这两件事,你就应该好好去。

我上面的特殊问题是我在CPU实现中从 row-major 切换到 col-major 并且没有彻底完成确保在我的所有CPU矩阵操作中都考虑了实现。

具体来说,这是我现在正确的mat4乘法实现,假设col-major矩阵

fm4 mulfm4(fm4 a, fm4 b)
{
  return fm4
    { { a.x[0]*b.x[0] + a.y[0]*b.x[1] + a.z[0]*b.x[2] + a.w[0]*b.x[3], a.x[1]*b.x[0] + a.y[1]*b.x[1] + a.z[1]*b.x[2] + a.w[1]*b.x[3], a.x[2]*b.x[0] + a.y[2]*b.x[1] + a.z[2]*b.x[2] + a.w[2]*b.x[3], a.x[3]*b.x[0] + a.y[3]*b.x[1] + a.z[3]*b.x[2] + a.w[3]*b.x[3] },
      { a.x[0]*b.y[0] + a.y[0]*b.y[1] + a.z[0]*b.y[2] + a.w[0]*b.y[3], a.x[1]*b.y[0] + a.y[1]*b.y[1] + a.z[1]*b.y[2] + a.w[1]*b.y[3], a.x[2]*b.y[0] + a.y[2]*b.y[1] + a.z[2]*b.y[2] + a.w[2]*b.y[3], a.x[3]*b.y[0] + a.y[3]*b.y[1] + a.z[3]*b.y[2] + a.w[3]*b.y[3] },
      { a.x[0]*b.z[0] + a.y[0]*b.z[1] + a.z[0]*b.z[2] + a.w[0]*b.z[3], a.x[1]*b.z[0] + a.y[1]*b.z[1] + a.z[1]*b.z[2] + a.w[1]*b.z[3], a.x[2]*b.z[0] + a.y[2]*b.z[1] + a.z[2]*b.z[2] + a.w[2]*b.z[3], a.x[3]*b.z[0] + a.y[3]*b.z[1] + a.z[3]*b.z[2] + a.w[3]*b.z[3] },
      { a.x[0]*b.w[0] + a.y[0]*b.w[1] + a.z[0]*b.w[2] + a.w[0]*b.w[3], a.x[1]*b.w[0] + a.y[1]*b.w[1] + a.z[1]*b.w[2] + a.w[1]*b.w[3], a.x[2]*b.w[0] + a.y[2]*b.w[1] + a.z[2]*b.w[2] + a.w[2]*b.w[3], a.x[3]*b.w[0] + a.y[3]*b.w[1] + a.z[3]*b.w[2] + a.w[3]*b.w[3] } };
}

再次,上面的实现是针对列主要的矩阵。这意味着a.x是矩阵的第一个,而不是行。

答案 2 :(得分:0)

  

需要注意的一件事是CPU上的view_mat_t * proj_mat_t   匹配GPU上的proj_mat * view_mat。有谁知道为什么?

原因是两个矩阵A,B: A * B =(B&#39; * A&#39;)&#39; ,其中&#39;表示转置操作。正如您自己已经指出的那样,您的数学代码(以及流行的数学库,如GLM)使用矩阵的行主要表示,而OpenGL(默认情况下)使用列主要表示。这意味着矩阵A,

    (a b c)
A = (d e f)
    (g h i)
你的CPU数学库中的

作为[a,b,c,d,e,f,g,h,i]存储在内存中,而在GLSL着色器中定义,它将被存储为[a,d, g,b,e,h,c,f,i]。因此,如果您使用glUniformMatrix3fv上传GLM矩阵的数据[a,b,c,d,e,f,g,h,i]并将转置参数设置为GL_FALSE,那么矩阵就是你将在GLSL中看到

     (a d g)
A' = (b e h)
     (c f i)

是转置的原始矩阵。已经意识到改变行主要和列主要之间的矩阵数据的解释导致原始矩阵的转置版本,您现在可以解释为什么突然矩阵乘法反过来工作。 CPU上的view_mat_t和proj_mat_t被解释为view_mat_t&#39;和proj_mat_t&#39;在您的GLSL着色器中,将预先计算的view_mat_t * proj_mat_t上传到着色器将导致与单独上传两个矩阵然后计算proj_mat_t * view_mat_t相同的结果。