【问题标题】:Simple Progressive Rendering Window for a Path Tracer路径跟踪器的简单渐进式渲染窗口
【发布时间】:2016-09-21 15:17:10
【问题描述】:

希望每个人都做得很好。一直在思考这个问题,最重要的是出于好奇。现在使用 Maya 和 Arnold 有一段时间了。只是为了爱好,主要是简单的渲染来与我的路径跟踪器进行比较。我意识到他们的渲染器有这个非常好的功能,可以让你在渲染时看到图像......渐进式。它似乎从较低的采样和 aa 量开始,然后在自动增加这些参数时重新渲染图像。我想真的很酷。这是在渲染达到最高质量之前显示渲染预览的好方法。这让我非常有兴趣为我正在处理的路径跟踪器做同样的事情。目前它等待整个渲染完成之后它在您的驱动器上保存了一个简单的 ppm 文件。 我现在的问题是......有谁知道这样的事情可以做到吗?我已尽我所能找出,我想出的唯一信息是 OpenGL 以某种方式参与其中。我不想创建与 Maya 相同的东西。只是一个简单的窗口,随着渲染开始并逐渐生成图像更好。 再次......这比其他任何事情都更具好奇心。尽管我认为这真的很酷 谢谢:)

【问题讨论】:

  • Stack Overflow 是针对特定编程问题的。询问“[这个大项目]如何完成”并不具体。
  • 知道了。已经找到一篇关于该问题的文章。现在阅读。感谢您的帮助。

标签: opengl rendering maya raytracing


【解决方案1】:

它不以任何方式仅限于 OpenGL。您将渲染器设计为在单独的线程(可能是多个线程甚至多台机器)中运行,并逐步将部分结果发送到主线程。然后,主线程创建一个窗口,在这些结果进入时显示这些结果。这里没有魔法。

【讨论】:

  • 哦,好的。我正在阅读一篇关于 ScratchaPixel 的文章,似乎自适应采样也以某种方式参与其中。或者这与这个特定问题完全不同?
  • @hecatonchries:它更多的是对渲染方面的优化——您希望在图像中具有较高频率特征的地方追踪更多光线,而在图像更平滑的地方追踪更少。
  • 是的。显然。这是一种非常聪明的降噪方法。正如我在添加 Cook Torrance BRDF 后意识到的那样,我现在得到了更多的噪音。所以我明白了。他们将自适应采样与你刚才描述的过程,它给出了渐进式渲染。对吗?
  • 我感觉阿诺德就是这么做的
  • 无论如何我想我已经掌握了整个过程的要点。再次非常感谢。 :)
【解决方案2】:

预览图像只是蒙特卡罗渲染中的第一轮样本(Arnold 就是这样)。这种“开始嘈杂”,然后“提高质量”不一定是预期的功能。它存在于所有蒙特卡罗渲染器中,因为执行无偏采样的性质意味着您从一些样本开始,其中许多可能不准确(在最终图像中产生噪声)。然后,随着越来越多的样本被发射到场景中(对于每个像素),结果最终将收敛于预期的结果(噪声将减少,不准确的样本贡献越来越少)。

Monte Carlo 渲染将永远进行渲染,但是在一定数量的样本之后,每个贡献都会很小,因此被忽略(确定实际结果)。这就是为什么图像开始有噪声(样本不多,并且有大量不准确的样本),然后随着越来越多的样本用于估计像素颜色而逐渐提高其质量。

渐进式采样是另一种优化,旨在减少收敛到结果所需的时间。即如前所述,在可能贡献更多的区域中发射样本(即采样像素时差异更大,需要更高的精度,因此为该像素计算更多样本)。

P.S 继续关注 Scratch-a-pixel。这是一个很好的资源。

P.P.S 此外,OpenGL 可用于辅助纹理/图像分析(决定对哪些像素进行更多采样等),或用于通过将几何图形绘制到屏幕外缓冲区来加速相交测试(只是我使用过的两种方式过去)。然而,这将取决于实施。默认情况下,OpenGL 不提供任何光线追踪系统所需的内容。

关于显示您的渲染。

首先创建一个与您需要的输出图像尺寸相同的帧缓冲区。这将有效地成为未压缩的 RGB24 或 RGBA32 图像。使用与您所需的显示输出相关的格式(因此可以在有限的延迟下完成复制,并且直接显示不需要转换/处理)。它还将包括另一位元信息,每个像素跟踪像素当前使用的样本数量。这允许结果填充彼此互斥的帧缓冲区。也就是说,您可以在需要的区域中触发更多像素(自适应),并在需要时选择呈现帧缓冲区的内容,同时继续在相同的渲染上下文中对像素进行采样(渐进式)。

应该在主循环的每个循环中保留此帧缓冲区,以便将每个循环的结果累积到帧缓冲区中。计算单个像素的结果,通常是该像素的所有样本的总和除以样本的总数(其他采样方法可能会相应地对样本进行加权),用于像素的标准抖动网格采样。

要向用户呈现此图像,这取决于您使用的 api,但您只需要以与显示图像/位图相同的方式显示帧缓冲区。我个人的做法:

  • 在 openGL 中绘制一个带纹理的四边形,使用帧缓冲区作为纹理(因此您需要每帧使用帧缓冲区的内容更新纹理)。

  • 使用 windows gdi 将 DIB 位图呈现给控件。

  • 输出为未压缩的图像格式(这可以快速完成二进制 PPM,或用于直接复制帧缓冲区内容的 TGA/tiff/未压缩位图)或压缩图像,例如 png 或 jpg。

这里有一些代码,具体实现取决于您选择使用的 api,但希望这是伪代码,足以详细描述正在发生的事情。这是c-esque。

声明/定义。

// number of pixels in the resultant final render image.
unsigned int numberOfPixels = imageWidth * imageHeight;

// number of channels of ouput image (also ray result) RGB 3 channels.
unsiged int channelSize = 3; 

// RGB pixel. Each channel/colour component is in the range 0 <= ... <= 1
struct pixel
{
    float red;
    float green;
    float blue;
};

// framebuffer, 3 channels RGB
pixel frameBuffer[numberOfPixels];

// framebuffer meta data. Number of samples for each pixel.
int pixelSampleCount[numberOfPixels];

然后在你的 init 方法中。为了初始化帧缓冲区,这会将其设置为黑色图像(很重要,因为我们要将第一个样本添加到 0,0,0)。

// your init routine
...   

    for (unsiged int p = 0; p < numberOfPixels; ++p )
    {
        // initialise the framebuffer to black (0,0,0)
        frameBuffer[p].red = 0.0;
        frameBuffer[p].green = 0.0;
        frameBuffer[p].blue = 0.0;

        // set the sample count to 0
        pixelSampleCount[p] = 0;
    }

...

然后在主循环/循环中。

// your main loop
...

// Main loop...each cycle we will cast a single sample for each pixel. Of course you can get as many sample results as you want if you
// intelligently manage the casting (adaptive), ensure each cast knows which pixel it is contributing to, so when it comes to accumulation of
// this sample result, it can be amended to the correct pixel and the correct sample count incremented as you add to the framebuffer.

for ( unsigned int x = 0; x < imageWidth; ++x )
{
    for ( unsigned int y = 0; y < imageHeight; ++y )
    {
         // get the result of the sample for this pixel (e.g cast the ray for this pixel, jittered according to sampling method). Ultimately
         // each sample needs to be different (preferably unique and random) from the previous cycle and will return a different result.
         pixel castResult = GetSampleResult(x, y, ... );    // aka cast the ray and get the resultant 'colour'

         // Get the current pixel from the frame buffer read to ammend it with the new sample/contribution. 
         unsigned int currentPixelIndex = (y * imageWidth) + x;
         pixel& pixelOfSample = frameBuffer[currentPixelIndex];

         // to correctly accumulate this sample, we must first multiply (scale up) each colour component
         // by the number of samples/contributions to this pixel. We can then add the sample result and divide
         // (scale down) the result (sum of all samples now) by the new number of samples.
         pixelOfSample.red = ( (pixelOfSample.red * pixelSampleCount[currentPixelIndex]) + castResult.red ) / ( pixelSampleCount[currentPixelIndex] + 1 );

        // repeat this for the rest of the components in the pixel, i.e for green and blue in this case.
        pixelOfSample.green = ( (pixelOfSample.green * pixelSampleCount[currentPixelIndex]) + castResult.green ) / ( pixelSampleCount[currentPixelIndex] + 1 );
        pixelOfSample.blue = ( (pixelOfSample.blue * pixelSampleCount[currentPixelIndex]) + castResult.blue ) / ( pixelSampleCount[currentPixelIndex] + 1 );

          // increment the sample count for this pixel.
          ++pixelSampleCount[currentPixelIndex];
     }
}

// And then send this to your output gdi/opengl/image output etc.
// For displaying direct in gdi, use BitBlt(...) with SRCCOPY.
// For displaying in OpenGL use glTexture2D(...)

glTexture2D(...);    // for OpenGL
BitBlt(...);         // for win gdi

// The next loop you can simply display the framebuffer (it would look the same as previous cycle) or you can fire a load of rays and then add this to your framebuffer and display that, giving you a different display.

...

N.B GL 和 gdi 都以不同的方式期望图像。因此,您可能需要将图像水平翻转为正确的方向。这取决于您如何在内部存储帧缓冲区以及您使用哪个 api 来显示帧缓冲区。

这有望展示如何编写一个系统,该系统将随着计算的越来越多的细节逐渐显示图像的内容。它适用于任何形式的光线追踪(如前所述,鉴于模拟的性质,蒙特卡洛会产生噪声,有偏差的渲染可能会或可能不会取决于它们的工作方式。通常它不会比图像抗锯齿更多,尽管有偏差的渲染可能会出现噪点)。

【讨论】:

  • 我已经知道采样的东西了。我的问题实际上是关于如何对这样的窗口进行编码。仅此而已。它是一种使用其游戏循环来更新来自cpu线程的像素。或者是其他方式。虽然我有一些理论和想法。但我不知道我是否应该创建一个普通的win32窗口,或者是否存在另一个路径,因为我的路径跟踪器是基于cpu的。而且是的。 ...划一个像素非常好。它们很有帮助。
  • 修改了我的答案,希望能更好地反映您的需求。
  • P.S 答案将在同一个线程中呈现,但是需要应用一些智能才能不限制应用程序的响应能力。当然,您可以轻松地在另一个线程中进行渲染并填充帧缓冲区。
  • 这真的很有帮助。你是独一无二的!仍在解决一些问题和错误,但我会尽快解决。再次感谢您的帮助。
猜你喜欢
  • 2019-09-27
  • 1970-01-01
  • 2020-02-11
  • 1970-01-01
  • 2018-06-05
  • 2013-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多