我正在尝试出于研究目的而实现梯度域路径跟踪器。为了实现此步骤,我首先需要一个有效的Path跟踪器。到目前为止,我一直在创建一个,但是结果是错误的,我将向您解释原因。一些概念:

我正在处理在采样之前生成的路径。我的意思是,算法的第一步在于计算特定像素(x,y)的路径。此路径将在场景内执行一些反弹操作,如果终止于灯光,则将被认为是有效的。

首先:以下是struct的定义(稍后将进行声明和初始化) ,其中包含将光线从相机投射到场景中所需的一些场景信息。

struct RenderData
{
    vec3 E;
    vec3 p1;
    vec3 dx;
    vec3 dy;
};


通过InitializeScene方法初始化:

void InitializeScene(){ // setup virtual screen plane
  vec3 E( 2, 8, -26 ); //Eye position
  vec3 V( 0, 0, 1 ); //LookAt vector
  float d = 0.5f, ratio = SCRWIDTH / SCRHEIGHT, focal = 18.0f;
  vec3 p1(E + V * focal + vec3(-d * ratio * focal, d * focal, 0)); // top-left screen corner in SCREEN SPACE
  vec3 p2(E + V * focal + vec3(d * ratio * focal, d * focal, 0)); // top-right screen corner
  vec3 p3(E + V * focal + vec3(-d * ratio * focal, -d * focal, 0)); // bottom-left screen corner
  mat4 M = rotate( mat4( 1 ), r, vec3( 0, 1, 0 ) );
  p1 = vec3(M * vec4(p1, 1.0f)); //rotating the above points
  p2 = vec3(M * vec4(p2, 1.0f));
  p3 = vec3(M * vec4(p3, 1.0f));
  renderData.dx = (p2 - p1) / (float)SCRWIDTH;
  renderData.dy = (p3 - p1) / (float)SCRHEIGHT;
  renderData.E = vec3(M * vec4(E, 1.0f));
  renderData.p1 = p1;
}


上面的代码是让您了解我如何初始化场景的方法。
我还具有存储有关路径信息的结构:

struct PathVert {
   vec3 p; vec3 n; //hit point and normal of the surface hit 
};

struct Path {
   PathVert verts[MAX_DEPTH]; //maxDepth is 15 for now
   int vertCount;
   int x, y; //which pixel this path is referring to
};


因此,我开始考虑接一个像素。

for (int y = 0; y < SCRHEIGHT; y++) for (int x = 0; x < SCRWIDTH; x++)
{     
   Path path;
   if(generatePath(x,y, path)){
      Sample(path);
   }
}


generatePath()方法确实跟踪进入场景的路径并检查其命中的所有顶点。您将看到使用了checkIfRayIntersectSomething(t)方法,它只是在我的框架中实现的伪方法,我省略了其长度的原因。我用它来检查我的射线是否击中了场景中的某物,如果发生了,它会用到该物体的距离来更新“ t”。注意:灯光本身不被视为物体。因此,我还有一个checkRayLightIntersection(hitLightPoint)来检查与灯光的交点,如果有的话,hitLightPoint会更新为我所点击的灯光上的点。
灯光是2D曲面。

Vec lightPos = Vec(5, 15, 2); //hard coded position of the light


正如所说的,光是一个表面,但恰好是一个正方形表面,其四个角度分别为:

Vec P1 = Vec(lightPos.x - 20, lightPos.y, lightPos.z + 20);
Vec P2 = Vec(lightPos.x + 20, lightPos.y, lightPos.z + 20);
Vec P3 = Vec(lightPos.x + 20, lightPos.y, lightPos.z - 20);
Vec P4 = Vec(lightPos.x - 20, lightPos.y, lightPos.z - 20);


我知道很大,所以第一个问题取决于这个方面,那么大的角度是否正确?

但是让我们看一下主要方法。在这里,您可以看到GeneratePath方法:BSDFDiffuseReflectionCosineWeighted()只是计算新的方向,进行测试并可以正常工作。最后剩下的是用于计算像素最终颜色的Sample方法。

bool GeneratePath(int x, int y, Path &path){
   path.x = x;
   path.y = y;
   path.vertCount = 0;

   vec3 P = renderData.p1 + renderData.dx * ((float)(x) + Rand(1)) +  renderData.dy * ((float)(y) + Rand(1));
   vec3 O = renderData.E + vec3(Rand(0.4f) - 0.2f, Rand(0.4f) - 0.2f, Rand(0.4f) - 0.2f);
   vec3 D = normalize(P - O); //direction of the first ray, the one from the camera towards the pixel we are considering

   for (int depth = 1; depth <= MAXDEPTH; depth++){
    float t;
    Vec hitLightPoint;
    PathVert vert;
    if (!checkIfRayIntersectSomething(t)){
        //we didn't find any object.. but we still may have found the light which is an object non represented in the scene
        //the depth check avoids me rendering the light as a white plane
        if (depth > 1 && checkRayLightIntersection(O, D, hitLightPoint)){
            //update the vertex since we realized it's the light
            vert.p = hitLightPoint;
            vert.n = Vec(0, -1, 0);//cause the light is pointing down
            path.verts[depth - 1] = vert;
            path.vertCount++;
            return true; //light hit, path completed    
        }
        return false; //nothing hit, path non valid
    }
    //otherwise I got a hit into the scene
    vert.p = O + D * t; //reach the hitPoint
    vert.n = methodToFindTheNormal();
    vert.color = CalculateColor(vert.p); //according to the material properties (only diffuse objects so far)
    path.verts[depth - 1] = vert;
    path.vertCount++;

    //since I have the light, and a path terminates when it hits the light, I have to check out also if my ray hits this light,
    //and if does, I have to check whether it first hits the light or the object just calculated above
    //moreover with the "depth > 1" check, I avoid again rendering the light which otherwise would be visible as a white plane

    if (depth > 1 && checkRayLightIntersection(O, D, hitLightPoint)){
        float distFromObj = length(vert.p);
        float distFromLight = length(hitLightPoint);
        if (distFromLight < distFromObj){
            //update the vertex since we realized it's the light
            vert.p = hitLightPoint;
            vert.n = Vec(0, -1, 0);
            vert.color = Vec(1, 1, 1);// TODO light color? or light emission?

            path.verts[depth - 1] = vert;
            return true; //light hit, path completed
        }
    }
    if (depth == MAXDEPTH) return false;
       Vec newDir = BSDFDiffuseReflectionCosineWeighted(vert.n, D);//explained later
       D = newDir;
       O = vert.p;
   }
   return false;
}


16SPP的结果是:



您可以看到结果不是很糟糕,但是有一个主要问题:阴影消失了。尝试了多种组合但没有改进。算法本身存在错误。你能帮我理解为什么吗?提前谢谢。

编辑:这是我的目标参考文献:



编辑2:如果深度> 1,则发生此情况省略检查:



编辑3:修复distFromLight和distFromObj之后,我得到以下结果:



@ trichoplax建议的两个问题之一可能是阴影太柔和。所以我试图减小灯的尺寸,增加他的补偿能力。结果不好,显示在这里:



此外,根据要求,我可以发布更多代码:

Vec Sampling(Path &path){

   Vec color(1, 1, 1);

   for (int vert = 0; vert < path.vertCount - 1; vert++) { //considers the last vertex as the light
      const PathVert &currVert = path.verts[vert];
      const PathVert &nextVert = path.verts[vert + 1];
      Vec wo = (nextVert.p - currVert.p).norm();
      double cosTheta = fabs(wo.dot(currVert.n));
      float PDF = cosTheta/PI;
      if (cosTheta <= 1e-6) return Vec();
      //considering only DIFFUSE objects
      color = color.mult(currVert.color * (cosTheta / M_PI) / PDF);
   }
   return color.mult(Vec(10.0f, 10.0f, 10.0f)); //multiplication for the light emission?
}


相反,checkIfRayIntersectSomething方法是框架本身的一部分(它只是另一个名称),它不是我做的,但是已经过测试并且可以正常工作。

编辑4:尝试渲染一小部分spp数量较高的场景。在右下角尝试512 spp,根据目标图像应在其中出现阴影。结果表明阴影不存在:


编辑5:

我特此发布用于渲染目标图像的代码,它确实同时使用递归结构的Sampling()和GeneratePath()方法。

bool checkRayLightIntersection(Vec O, Vec D, Vec & hitLightPoint){
  //getting the 4 corners of my light
  Vec P1 = Vec(lightPos.x - 20, lightPos.y, lightPos.z + 10);
  Vec P2 = Vec(lightPos.x + 20, lightPos.y, lightPos.z + 10);
  Vec P3 = Vec(lightPos.x + 20, lightPos.y, lightPos.z - 10);
  Vec P4 = Vec(lightPos.x - 20, lightPos.y, lightPos.z - 10);

  //the majority of the methods first find out where the ray intersects the plane that the rectangle lies on Ax + By + Cz + D = 0
  //in our case the equation of that plane is easy -> D = 20
  // answers.google.com/answers/threadview?id=18979

  float t = -(-O.y + lightPos.y) / (-D.y);
  if (t > 0){
    Vec hitPoint = O + D * t;
    Vec V1 = (P2 - P1).norm();
    Vec V2 = (P3 - P2).norm();
    Vec V3 = (P4 - P3).norm();
    Vec V4 = (P1 - P4).norm();
    Vec V5 = (hitPoint - P1).norm();
    Vec V6 = (hitPoint - P2).norm();
    Vec V7 = (hitPoint - P3).norm();
    Vec V8 = (hitPoint - P4).norm();

    if (V1.dot(V5) > 0.0 && V2.dot(V6) > 0.0 && V3.dot(V7) > 0.0 && V4.dot(V8) > 0.0){
        hitLightPoint = hitPoint;
        return true;
    }
  }
  return false;
}


}

编辑6:Trichoplax是正确的,我在计算distanceFromObject和distanceFromLight时遇到问题。解决此问题后,我们意识到出现了某种阴影(如上图所示)。然后,他建议,如此宽广的光线可能已经冲走了阴影,事实如此。通过减小光的大小并使用大量的SPP,我们意识到存在阴影并且可见:http://imgur.com/y88zgAZ。作为一个缺点,较少的光线到达光线的方式会返回具有高噪声的图像,但这是另一种问题,我为此提出了另一个问题。感谢@trichoplax和@julien的帮助

评论

我可以看到阴影。因此,我不同意您的分析。但是,二次光的反射率可能太高。您可以尝试使用更简单的场景吗?

@joojaa使用目标参考图像编辑了说明

使用更简单的场景可以帮助您调试效果。对我来说,好像那里的阴影只是您的场景没有经过色彩校正,并且反弹的能量太多了。

@joojaa我刚刚发布了一些代码,因为我使用的是复杂得多的框架。这就是为什么我避免渲染更简单的场景的原因,因为这需要我付出一些努力才能找出场景加载器的工作方式。好的,您可以明智地在代码上建议我可以更改的颜色以正确显示场景吗?

看到更新后的屏幕截图(第3次修改),它是否仍以16spp渲染?由于只有很小的区域光,并且没有双向路径跟踪或下一个事件估计,因此预计会有很多噪声。您是否尝试过更高的数字,例如1000spp?

#1 楼

问题似乎是无意识的透明表面

尽管图像有颗粒感,但可以很清楚地估计出所有较暗的区域是由于表面背向光线,而不是由于阴影所致在面向光的表面上。因此似乎确实存在问题,并且缺少阴影的原因不仅仅是由于大面积光线显示的柔和阴影效果不佳,或者反射过多的表面提供了过多的环境光。光线以某种方式穿过您的表面。

此问题的可能原因

由于您分别检查了光线和物体的相交处,因此可能发生的关键点检查错误的地方

if (distFromLight < distFromObj)


如果此检查为true(即,如果灯光比最近的物体更近),则正确地终止路径并返回灯光颜色朝那个方向)。因此,问题可能出在您正在比较的两个变量之一。如果它们都是正确的,那么您会看到阴影。

这些是在前面的行中计算的

float distFromObj = length(vert.p);
float distFromLight = length(hitLightPoint);


如朱利安·古埃特(Julien Guertault)所指出的那样答案是,这些计算可能是多余的。

length(vert.p)应该仍然可以作为t使用,以前在vert.p的计算中使用了11行。这不仅避免了不必要的计算,而且消除了潜在的错误源。除非checkIfRayIntersectSomething()出现问题,否则该长度很可能是正确的。

除非存在更细微的问题,否则这仅将length(hitLightPoint)当作错误来源。能够消除由length()计算出的hitLightPointcheckRayLightIntersection()出现问题的可能性将非常有用。

我们需要查看代码以提供帮助

length()checkIfRayIntersectSomething()checkRayLightIntersection()当前未包含在您的问题代码中,因此我们无法对其进行分析。如果您能够共享这些内容,这将有助于缩小问题的根源。

检查这是否是真正问题的简单方法

您还可以验证是否通过渲染将消除其他可能性的场景(阴影太柔和而无法区分,或阴影被场景其余部分反射的过多阴影冲走),对问题的这种分析是正确的。如果通过表面的光是问题的根源,那么即使从表面到光的路径不存在,它也将适用。因此,渲染一个简单的场景,该场景的灯光下面有一个平面,下面有一个平面,该平面之间的间隙足够大,很容易看到。较低的平面完全被较高的平面遮挡,因此不应该接收任何光线(没有柔和的阴影,也没有来自其他地方的反射光)。如果较低的平面点亮,那么您就知道这确实是问题所在,您可以在这里集中精力。

如果较低的平面没有点亮,那么这不是问题所在,还是那里光栅尺的问题是一个问题,为了使问题出现,可能需要使遮挡平面与光线的距离不同。您可以将现有场景用作发生此问题的估计距离的来源,并且可以使用倾斜平面来查看单个图像中各种物距与光距之比的效果。

共享这样简单的场景所产生的图像可能有助于我们更准确地识别问题。


我注意到,在对您提到的问题的评论中,您没有自己生成场景的简单方法。也许已经有许多更简单的场景以相同的文件格式公开可用,您可以用来测试此场景?

如果没有,听起来您唯一可以控制的就是灯光。在这种情况下,您可以尝试将灯放到地下。如果表面不透明,则场景应统一为黑色。如果尽管有遮挡的表面仍在照亮表面,则您可能会以这种方式看到证据,当周围有很多次反射光反弹时,很难辨别。

评论


$ \ begingroup $
首先,您的回答非常有用!您正确的位置,distFromLight和distFromObj的位置计算错误。我修好了它。某些问题已得到改善,但问题尚未解决。我张贴了一张显示新结果的图片。我以为错误可能是由于光线太大而导致阴影太柔和,因此我尝试降低光线的尺寸。无论如何增加光的功率,但在这种情况下效果都不好(发布了第二张图片)。希望对您有帮助。同时,我正在研究.obj加载器,以简化场景。
$ \ endgroup $
–塔塔
16年4月10日在17:15

$ \ begingroup $
较小的光线意味着更多的光线将永远无法到达光线,因此您需要增加光线的数量才能获得更好的图像质量。这也会增加渲染时间。取而代之的是使光线尽可能大(一个无限大或非常大的平面)。这样,如果问题是透明的表面,那么您应该会看到更多的证据,并具有额外的好处,即最大可能的光线会减少图像的颗粒感。
$ \ endgroup $
– trichoplax
16年4月10日在22:45

$ \ begingroup $
我又添加了最后一段,关于将灯光放到地下作为测试(请参见编辑后的答案),如果您无法更改场景但能够移动灯光,则可能会有所帮助。
$ \ endgroup $
– trichoplax
16-4-10在22:49



$ \ begingroup $
好吧,我听了你的建议。我试图将灯光放在场景下,但没有任何照明。没有光穿过表面,因此表面不透明。至少我们可以排除这种可能性。因此可能是阴影太柔和或被场景其余部分反射的过多光冲走了。您如何建议我继续?
$ \ endgroup $
–塔塔
16年4月11日在13:06

$ \ begingroup $
您是否尝试过渲染图像的一小部分?例如,图像的右下角十六分之一包含目标图像中的强阴影。仅渲染该小图像,就可以使每个像素使用更多数量的样本,以提供更清晰的图像并更多地了解正在发生的事情。
$ \ endgroup $
– trichoplax
16年4月11日在14:03

#2 楼


有这么大的灯光正确吗?


我没有看到大面积的灯光有任何问题。也就是说,这还取决于场景的规模。如果光线比它大,阴影会更弥漫,就像在阴云密布的天空下。


阴影消失了。[...]能帮助我理解为什么?


我还没有发现任何明显的错误,但是有两个对我来说似乎很可疑的元素。

if (depth > 1 && checkRayLightIntersection(O, D, hitLightPoint)) {
    // ...
    return true; //light hit, path completed
}


尽管据我所知,这部分似乎是正确的,但我将尝试确保没有引入偏差的错误。对于每次弹跳,它都会检查是否有光线首先击中光线,如果是,光线就会跳出循环:如果代码不正确并且碰巧碰到光线的次数比正常情况多,阴影就会消失。

此外,在第一个条件中:

return false; //nothing hit, path non valid


及其相关循环:

for (int y = 0; y < SCRHEIGHT; y++) for (int x = 0; x < SCRWIDTH; x++)
{     
  Path path;
   if(generatePath(x,y, path)){
      Sample(path);
   }
}


在平均像素颜色时,请确保仍将没有影响的路径考虑在内,否则也会引入偏差。

如joojaa在评论中所说,您应该尝试使用更简单的场景:一些球体或立方体,没有纹理。您也可以将跳动次数减少到一到两次,以仅看到直接照明。如有必要,甚至可以先使用恒定的圆顶而不是明亮的区域。


其他说明:

struct Path {
   PathVert verts[MAX_DEPTH]; //maxDepth is 15 for now
   int vertCount;
   int x, y; //which pixel this path is referring to
};


您需要存储x和y吗?

float distFromObj = length(vert.p);
float distFromLight = length(hitLightPoint);


是否还没有两个可以比较的t

评论


$ \ begingroup $
首先,感谢您的回答:)因此,检查射线与正方形光之间的交点的方法是正确的。我已经在询问它导致我找不到的小错误的原因。该方法和相关解决方案都可以在这里找到:stackoverflow.com/questions/36180741/…我还编辑了问题,添加了一张图片,该图片显示了如果我忽略了深度> 1检查的结果。也许它可以帮助您了解问题所在。.再次感谢
$ \ endgroup $
–塔塔
16-4-7在19:52