GAMES202作业5

GAMES202实时渲染(5)-实时光线追踪降噪 内容参考闫令琪课程《games202-高质量实时渲染及作业5》、 阅读详情

作业要求

本次作业,我们需要实现一个简单的实时光线追踪的降噪方法。光线追踪的渲染结果,G-Buffer 及其他相关信息会以文件的形式提供给大家。本次作业的重点在于对渲染结果的降噪,而不是如何进行渲染。为了在作业框架中实现上述目标,我们将作业分为三个部分:单帧图像的降噪,计算 motion vector,累积帧间信息。

根据上述作业要求,我们把具体实现分为三个步骤

  1. 单帧降噪
  2. 累加上一帧
  3. 用A-Trous Wavelet加速单帧降噪(提高项)

单帧降噪

首先我们来看一下单帧降噪的效果。

未降噪
在这里插入图片描述
降噪后
在这里插入图片描述
观察上面两幅图我们发现,第二张图的效果明显要比第一张图的好,但是同样的,第二张图片球的边缘相对于第一张要模糊了。由此我们很容易想到,降噪可以通过滤波来做,但是滤波存在一个很明显的问题就是,普通的滤波方式得到的结果是全局模糊的。我们想保留一些细节,在保留边缘的情况下减少噪声,我们不得不提一种特殊的滤波方式:联合双边滤波

联合双边滤波

首先了解一下滤波的原理:高斯滤波以采样距离的远近来决定该采样点对主像素的贡献 ,距离主像素点越近的采样点,贡献到主像素点的颜色的比例就越大。
在这里插入图片描述

我们来看看普通滤波的结果,普通高斯滤波会丢失细节
在这里插入图片描述
我们再看看双边滤波的结果

在这里插入图片描述
上面的结果在保留了边界细节的同时模糊了内容,双边滤波正是做这个的,我们可以通过加入一些限定条件,当达到某些条件的时候,减少某些像素对中心像素的影响,就可以保留想要的细节。
举个例子,我们看下图的山峰边缘,蓝色突变到黑色,他们的颜色变换十分剧烈
在这里插入图片描述
在通过距离判断贡献的基础上,我们加一个条件:颜色差值越大,那么该采样点对中心像素的贡献就越小,这样就可以把距离和颜色一起考虑进去,得到一张颜色相近的位置模糊,边缘处清晰的图片了,公式参考下图
i、j、k、l分别是中心像素点和采样点的x、y坐标
I指的是颜色
在这里插入图片描述
要得到我们期望的结果,只通过距离和颜色的限制是不够的,我们通过多方因素来限制滤波,因此我们引入联合双边滤波。
联合双边滤波可以参考G-buffers进行滤波,就比如通过对比像素点的深度图、Albedo图和法线贴图来对滤波进行限制,这样可以使得滤波的结果保留更多的细节
在这里插入图片描述
作业中给出了公式,在该公式中我们根据distance(距离)、color(颜色)、normal(法线)、plane(综合考虑法线和平面的夹角)四个因素来限制滤波。在这里插入图片描述
在得知了如何计算每个像素点对中心像素点的贡献后,我们参考以下公式,把各个贡献累加起来并且归一化。
在这里插入图片描述
作业中给出了公式的详细计算方法,我们直接套入公式计算

//denoiser.cpp

Buffer2D<Float3> Denoiser::Filter(const FrameInfo &frameInfo) {
    int height = frameInfo.m_beauty.m_height;
    int width = frameInfo.m_beauty.m_width;
    Buffer2D<Float3> filteredImage = CreateBuffer2D<Float3>(width, height);
    int kernelRadius = 32;
#pragma omp parallel for
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            // TODO: Joint bilateral filter
            //filteredImage(x, y) = frameInfo.m_beauty(x, y);
            // 
            //定义Filter范围
            int x_start = std::max(0, x - kernelRadius);
            int x_end = std::min(width-1,x+kernelRadius);
            int y_start = std::max(0,y-kernelRadius);
            int y_end = std::min(height - 1, y + kernelRadius);

            //获取Filter中心点的数据
            auto center_position = frameInfo.m_position(x, y);
            auto center_normal = frameInfo.m_normal(x, y);
            auto center_color = frameInfo.m_beauty(x, y);
            
            //先定义 最终颜色值和权重总和方便归一化
            Float3 result_color;
            auto total_weight = 0.0f;
            for (int i = x_start; i < x_end; i++) {
                for (int j = y_start; j < y_end; j++) {
                    //对采样点进行分析
                    auto current_position = frameInfo.m_position(i, j);
                    auto current_normal = frameInfo.m_normal(i, j);
                    auto current_color = frameInfo.m_beauty(i, j);

                    //套公式算
                    // Distance
                    auto D = Dot((center_position - current_position),
                                 (center_position - current_position)) /
                             (2.0f * m_sigmaCoord * m_sigmaCoord);
                    // Color
                    auto C = Dot((center_color - current_color),
                                 (center_color - current_color)) /
                             (2.0f * m_sigmaColor * m_sigmaColor);
                    // Normal
                    ////SafeAcos和acos(x)
                    ///一样,不过如果x超出范围的话,它会夹紧最近的可用值。
                    //和标准c函数acos(x)一致,这个值返回0到pi。
                    auto N = SafeAcos(Dot(center_normal, current_normal)); 
                    N *= N;
                    //这里遇到一个非常奇怪的问题,下面直接除结果是正确的,但是我改成N/=(2 * m_sigmaNormal * m_sigmaNormal)就会报错,希望有大佬能解答
                    N/(2 * m_sigmaNormal * m_sigmaNormal);

                    // Plane
                    auto P = Dot(center_normal,(current_position - center_position)) *
                             Dot(center_normal,
                                 (current_position - center_position)) /
                             (2 * m_sigmaPlane * m_sigmaPlane);

                    float weight = std::exp(-D - C - N - P);
                    total_weight += weight;
                    result_color += current_color * weight;
                }
            }
            //filteredImage(x, y) = frameInfo.m_beauty(x, y);
            //filteredImage(x, y) =(result_color.x / total_weight, result_color.y / total_weight,result_color.z / total_weight);
            //归一化
            filteredImage(x, y) = result_color / total_weight;
        }
    }
    return filteredImage;
}

上面通过采样周围的像素并计算各个像素对中心像素的贡献来滤波得出单帧也就是当前帧的颜色值,相比于1spp的光追结果,降噪后的结果清晰了很多,但是时间是原来的好几倍。

累加上一帧

单帧降噪的结果非常的不错,但是只经过单帧降噪不能消除所有的早点,降噪说白了还是把早点的颜色按一定比例分给旁边的像素,所以一次滤波会出现“不干净”的结果,由此我们可以再对画面进行一次滤波,不过这次滤波我们不再是求周围像素点对中心点的贡献了,我们计算上一帧对应位置对这一帧某个像素点的贡献。
假如上一帧是没有噪声的,我们令最终颜色为80%上一帧颜色+20%当前帧滤波后颜色,这样可以得到一个非常好的效果,这便是基于时间上的滤波Temporal。

对时间的滤波Temporal

Temporal的核心思想是运用了递归,当前帧的前一帧一定是已经滤波好了的。我们假设每一帧的上一帧都是没有噪声的,那么得到的这一帧结果会比只做一次当前帧滤波的结果好很多,接着这一帧的结果作为下一帧的前一帧继续递归,会得到一个非常好的结果。

我们详细讲解Temporal的原理。

motion vector

在Temporal里,我们需要用motion vector 来记录上一帧对应物体的位置。

motion vector是用来记录某个物体这一帧相对上一帧的运动情况的
在这里插入图片描述我们可以从motion vector里找到该像素上一帧的已经滤波好了的对应位置的像素,然后把上一帧已经滤波过的结果加权作用到这一帧上。

这被称为时间上的复用。

要用motion vector做到当前帧某个位置找到前一帧的某个位置,就需要用到G-Buffers中的一些数据
在这里插入图片描述
我们可以用G-Buffer存储世界坐标coord也可以用MVPE(E是Viewport)矩阵来逆变换位置来找到该点的世界坐标,通过乘一个Translate的逆矩阵来得到上一帧的世界坐标,再通过记录上一帧的MVP矩阵得到上一帧的对应屏幕位置。在这里插入图片描述
该方法和光流法有一定区别,该方法可以用G-Buffers的信息获取一个准确的结果,但光流法只通过两帧的结果进行分析,不使用G-Buffers。

下面得出利用motion vector的公式

~代表没有滤波的结果

-代表已滤波的结果

  • 首先要对当前帧进行滤波处理
  • 然后最后该帧的结果其实是前一帧的已经滤波的结果和这一帧已经滤波的结果的混合

在这里插入图片描述
我们用Temporal降噪后的结果
在这里插入图片描述
在这里插入图片描述
这里补充一点:
第二张图是降噪后的图片,看起来比第一张亮很多,其实这种理解是不对的,因为降噪过程并不会导致能量增加,导致这种问题的原因是第一张图的噪点的能量实际上是非常高的,往往他的亮度超过了1,也就是说第二张图的能量来自于噪点能量的均分,所以能量并没有增加。

计算当前像素在上一帧的对应点

我们要把上一帧的结果累加到这一帧,第一步首先得先找出我们要算的点在上一帧的对应点。
在这里插入图片描述
我们可以根据下面这个公式来找这一帧某个像素的对应点
在这里插入图片描述
首先把我们要算的当前帧的某个像素的世界位置通过乘Model的逆矩阵变回到局部空间,再乘上一帧的MVP矩阵,便可以知道,该像素点上一帧的屏幕位置了。
我们可以通过motion vector来存储各个物体或者场景上一帧的MVPl矩阵来分别计算上一帧的对应点。
由于作业里发生移动的只有小球本身,所以我们默认只需要一个motion vector记录小球上一帧的Model矩阵便可。

 Matrix4x4 M =frameInfo.m_matrix[id];
 auto world_position = frameInfo.m_position(x, y);
 Matrix4x4 preWorldToScreen =
        m_preFrameInfo.m_matrix[m_preFrameInfo.m_matrix.size() - 1];

我们可以直接从作业框架调取当前帧的Model矩阵、当前帧的WorldPosition已经Pi-1Vi1矩阵,所以我们要计算的只有上一帧的Model矩阵和这一帧Model的逆矩阵。

void Denoiser::Reprojection(const FrameInfo &frameInfo) {
    int height = m_accColor.m_height;
    int width = m_accColor.m_width;
    Matrix4x4 preWorldToScreen =
        m_preFrameInfo.m_matrix[m_preFrameInfo.m_matrix.size() - 1];
    Matrix4x4 preWorldToCamera =
        m_preFrameInfo.m_matrix[m_preFrameInfo.m_matrix.size() - 2];
#pragma omp parallel for
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            // TODO: Reproject
            m_valid(x, y) = false;
            m_misc(x, y) = Float3(0.f);

            //判断id是否合理
            int id = frameInfo.m_id(x, y);
            if (id==-1) {
                continue;
            }
            //已知Pi-1Vi-1也就是上面的preWorldToScreen
            //我们只需要求Mi-1和Mi^(-1)即可
            //Mi^(-1)
            Matrix4x4 M_i_inv = Inverse(frameInfo.m_matrix[id]);
            //Mi-1
            Matrix4x4 M_pre_i = m_preFrameInfo.m_matrix[id];

            auto world_position = frameInfo.m_position(x, y);
            auto local_position = M_i_inv(world_position, Float3::EType::Point);
            auto pre_world_postion = M_pre_i(local_position, Float3::EType::Point);
            auto pre_screen_position =
                preWorldToScreen(pre_world_postion, Float3::EType::Point);
            //一定要加上这个判断,因为不确定该点对应上一帧的位置是否在屏幕外
            if (pre_screen_position.x<0||pre_screen_position.x>=width||pre_screen_position.y<0||pre_screen_position.y>=height) {
                continue;
            } 
            else {
                int pre_id = m_preFrameInfo.m_id(pre_screen_position.x,pre_screen_position.y);
                if (id == pre_id) {
                    m_valid(x, y) = true;
                    //m_misc是临时颜色缓冲区、m_accColor是上一帧的颜色缓冲区
                    m_misc(x, y) =
                        m_accColor(pre_screen_position.x, pre_screen_position.y);
                }
            }

        }
    }
    //需要交换缓冲区,不然下一帧需要同时对m_accColor进行读写,这可能会导致数据的不一致或竞争
    std::swap(m_misc, m_accColor);
}

有几点需要注意的是:
我们需要在找上一帧对应位置的时候考虑几个问题

  1. 如果该像素点的内容在上一帧对应位置被遮挡了
  2. 如果该像素点的内容在上一帧在屏幕外

我们先看第一个问题, 如果该像素点的内容在上一帧对应位置被遮挡了,最终颜色有80%的贡献还来自上一帧的话,会出现残影
就比如下图,这一帧墙壁的位置出现了箱子的残影,因为墙壁没有动,他上一帧对应像素点的内容是移动过程中的箱子,我们仍取80%上一帧的贡献就会出现箱子的残影

在这里插入图片描述
在实时渲染里面,相比于噪声,其实残影给玩家带来的体验会更差,所以我们选择对像素点进行处理,如果上一帧的对应点和这一帧不是一个物体,那我们不再累加上一帧的颜色,上面代码直接通过id来判断,如果不是通过物体则跳过该循环。

对于第二个问题,我的措施是只有对应点在屏幕空间内才会进行颜色的累加,否则跳过该循环。

计算累加后的颜色

在实现上面Reprojection函数来找到上一帧对应点后,我们开始计算累加上一帧的当前帧的最终颜色,根据下面的流程来计算在这里插入图片描述
我们来看看课程给出的公式
在这里插入图片描述
在判断这一帧和前一帧对应点都是同个物体的时候,需要把前一帧对应位置的颜色Clamp到这一帧来,简单来说,就是我们计算这一帧对应点附近一个范围的均值和方差,用此范围来限制上一帧对应点的颜色值,这是降低比如这一帧颜色突变了或者是上一帧有很亮的噪点这种问题的影响

在这里插入图片描述

void Denoiser::TemporalAccumulation(const Buffer2D<Float3> &curFilteredColor) {
    int height = m_accColor.m_height;
    int width = m_accColor.m_width;
    int kernelRadius = 3;
#pragma omp parallel for
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            // TODO: Temporal clamp
            Float3 color = m_accColor(x, y);
            // TODO: Exponential moving average
            float alpha = 1.0f;
            if (m_valid(x,y)) {
                alpha = m_alpha;

                //根据采样范围,计算均值和标准差,目的是把上一帧颜色clamp到这一帧
                int x_start = std::max(0, x - kernelRadius);
                int x_end = std::min(width - 1, x + kernelRadius);
                int y_start = std::max(0, y - kernelRadius);
                int y_end = std::min(height - 1, y + kernelRadius);

                Float3 mean_value(0.0f);
                Float3 variance(0.0f);

                for (int i=x_start;i<x_end;i++) {
                    for (int j=y_start;j<y_end;j++) {
                        mean_value += curFilteredColor(i, j);
                        variance += Sqr(curFilteredColor(i, j) - curFilteredColor(x, y));
                    }
                }
                int sum = (kernelRadius * 2 + 1) * (kernelRadius * 2 + 1);
                //均值
                mean_value /= (float)(sum);
                //标准差
                variance = SafeSqrt(variance/(float)(sum));
                color = Clamp(color, mean_value - variance * m_colorBoxK,
                              mean_value + variance * m_colorBoxK);
            }
            m_misc(x, y) = Lerp(color, curFilteredColor(x, y), alpha);
        }
    }
    std::swap(m_misc, m_accColor);
}

这里我们采样7×7范围的点进行均值和方差的计算,再用这个结果约束上一帧对应点的颜色。

在上面Reprojection和TemporalAccumulation代码里面其实有一个小细节不易于理解,就是框架里给出的是在代码末尾用swap来交换缓冲区,其实我们也可以不这么做。

//denoiser.cpp

Reprojection(const FrameInfo &frameInfo)
{
//..
//删除std::swap(m_misc, m_accColor);
}

TemporalAccumulation(const Buffer2D<Float3> &curFilteredColor)
{
//..
Float3 color =  m_misc(x, y);
//..
m_accColor(x, y) = Lerp(color, curFilteredColor(x, y), alpha);
//..
//删除std::swap(m_misc, m_accColor);
}

其实可以这么理解,原框架中m_misc是临时颜色缓冲区,m_accColor是上一帧的颜色缓冲区,按我们的思想,用m_misc存储当前点需要累加的上一帧颜色,用color获取这个值,最后把当前帧累加后的最终结果更新到m_accColor作为下一帧的上一帧颜色缓冲,以此递归循环。
但是原框架用了swap来交换缓冲区,第一次交换缓冲区后m_misc存储的是上一帧的颜色,m_accColor则存当前点需要累加的上一帧颜色。因此在TemporalAccumulation里,color读取m_accColor的值其实也是在读取当前点需要累加的上一帧颜色,m_misc存的值是最终颜色。最后交换缓冲区后和我们写法结果是一样的。
交换缓冲区的目的其实是为了避免同时对m_accColor缓冲区的读写,同时读写可能会导致数据的不一致或竞争。

接下来我们来对比结果。
未经过Temporal的第19帧
在这里插入图片描述

经过Temporal的第19帧
在这里插入图片描述
上图中,经过Temporal的第19帧很明显会比未经过累加处理的第19帧效果要好,但出现了一个问题,就是有很明显的残影。
这里残影出现的问题原因很明显,当前帧出现残影的点内容是墙壁,该点对应上一帧的点内容其实是球体, 按理来说我们判断过,如果不是同一物体颜色应该不会累加。
出现这个问题说明通过id判断这一帧在上一帧对应点是否为同一物体是不准确的。
我们来看看id到底是什么

//main.cpp

Buffer2D<float> id =
        ReadFloatImage((inputDir / ("ID_" + std::to_string(idx) + ".exr")).str());

在main里存在上面代码,说明该场景id是由这个inputDir路径下的文件导入的,我们看看这个文件
在这里插入图片描述
我们打开这张图可以发现,图中颜色并没有区分球和墙体,也就是说导入的球和墙体的id是一样的
在这里插入图片描述
这就是导致了不管前一帧对应位置内容是球还是墙体,该值都会被累加到最终结果中,最终导致残影的出现。

用A-Trous Wavelet加速单帧降噪

作业中的单帧降噪中每个点都会进行一个64×64的采样操作,这无疑是巨费时间的,我们通过A-Trous Wavelet的方式来减小这个采样数量级。
A-Trous Wavelet采取的是多趟间隔采样的思想。该方法总共进行5次采样,每次都只采样一5×5的区域,每次采样间隔是2的i次方,i是采样趟数,参考下图在这里插入图片描述
该方法进行采样总次数为555也就是125次采样,和64*64的4096次采样不在一个数量级,得到的效果却相差无几。
参照第一次Filter的结果,我们写出FIlter的ATrousWaveletFilter重载

Buffer2D<Float3> Denoiser::ATrousWaveletFilter(const FrameInfo &frameInfo) {
    int height = frameInfo.m_beauty.m_height;
    int width = frameInfo.m_beauty.m_width;
    Buffer2D<Float3> filteredImage = CreateBuffer2D<Float3>(width, height);
    int kernelRadius = 32;
#pragma omp parallel for
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            // TODO: Joint bilateral filter
            // filteredImage(x, y) = frameInfo.m_beauty(x, y);
            //
            //定义Filter范围
            

            //获取Filter中心店的数据
            auto center_position = frameInfo.m_position(x, y);
            auto center_normal = frameInfo.m_normal(x, y);
            auto center_color = frameInfo.m_beauty(x, y);

            Float3 result_color;
            auto total_weight = 0.0f;
            for (int pass= 0; pass < 5; pass++) {
                for (int x_start = -2; x_start <= 2;x_start++) {
                    for (int y_start = -2; y_start <= 2;y_start++) {
                        int i = x + std::pow(2, pass) * x_start;
                        int j = y + std::pow(2, pass) * y_start;
                        auto current_position = frameInfo.m_position(i, j);
                        auto current_normal = frameInfo.m_normal(i, j);
                        auto current_color = frameInfo.m_beauty(i, j);

                        // Distance
                        auto D = Dot((center_position - current_position),
                                     (center_position - current_position)) /
                                 (2.0f * m_sigmaCoord * m_sigmaCoord);
                        // Color
                        auto C = Dot((center_color - current_color),
                                     (center_color - current_color)) /
                                 (2.0f * m_sigmaColor * m_sigmaColor);
                        // Normal
                        ////SafeAcos和acos(x)
                        ///一样,不过如果x超出范围的话,它会夹紧最近的可用值。
                        //和标准c函数acos(x)一致,这个值返回0到pi。
                        auto N = SafeAcos(Dot(center_normal, current_normal));
                        N *= N;
                        N / (2 * m_sigmaNormal * m_sigmaNormal);

                        // Plane
                        auto P =
                            Dot(center_normal, (current_position - center_position)) *
                            Dot(center_normal, (current_position - center_position)) /
                            (2 * m_sigmaPlane * m_sigmaPlane);

                        float weight = std::exp(-D - C - N - P);
                        total_weight += weight;
                        result_color += current_color * weight;
                    }
                }
            }
            // filteredImage(x, y) = frameInfo.m_beauty(x, y);
            // filteredImage(x, y) =(result_color.x / total_weight, result_color.y /
            // total_weight,result_color.z / total_weight);
            filteredImage(x, y) = result_color / total_weight;
        }
    }
    return filteredImage;
}

加速前的降噪
在这里插入图片描述
加速后的降噪
在这里插入图片描述
加速后的效果噪声明显要比加速前的多,但是其速度提升了将近10倍。

最后来解释一下为什么可以跳着采样(沿用202和101课上的说法,没学过信号处理,不太清楚本质的原因)
首先我们要知道连个概念

  1. 越大的采样面积会溢出越多的低频信息
  2. 采样其实是在搬移频谱
    在这里插入图片描述
    蓝色部分是第一趟采样,去除了高频信息,而隔一定距离进行采样可以理解为移动了原理的频谱,使得频谱之间恰好不想交,所以刚好没有走样,可以参考下面这幅图理解
    在这里插入图片描述

在这里插入图片描述

参考文章:
GAMES202-作业5:实时光线追踪降噪

games202:五,基于物理的材质:Microfacet、Kulla-Conty估计、LTC、NPR 今天讲我们熟悉的PBR啦,PBR实际包含材质、光照、相机(透镜成像)、光线传播等等,一般说起来就是指材质。 阅读详情

相关推荐

RenderDoc抓取WebGL帧被禁了?别慌,手把手教你用旧版RenderDoc+Chrome调试GAMES202作业

本文针对最新版RenderDoc禁止WebGL抓帧的问题,提供了使用旧版RenderDoc配合Chrome调试GAMES202作业的详细解决方案。通过获取特定版本RenderDoc、配置Chrome启动参数等步骤,帮助开发者继续实现WebGL帧的抓取和分析,确保图形学学习与开发的顺利进行。

weixin_33737774的博客 430

[MVP viewport 相机详解]实时渲染里的光线追踪 - games202实时渲染课后作业5

回顾基于蒙特卡罗中的rayTracing,大致看一下其计算量: 当图像目标设置为768768,然后每个像素sample32次(蒙特卡罗的经典方式),每次光线进入到下一层的概率为0.25,那么平均castray会被计算多少次呢?32 * (10.25 + 2 * 0.125 + 3 0.0625 + …) 大概就是0.7532 = 24次,也就76876824的castRay的计算,花费的时间大概为23min,效果为:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YWO9Zm6

cxy_hust的博客 912

GAMES202homework:主要是学习闫老师的GAMES202高质量实时渲染完成的大作业

GAMES202homework 主要是学习闫老师的GAMES202高质量实时渲染完成的大作业 RenderDoc调试webgl: 用到了webgl所以需要webgl抓帧调试,但是当前shader的调试暂时不支持~ homework0 作业0主要是实现的Blinn-Phong 着色模型 ,最主要是的环境搭建测试熟悉202的框架等等; homework1 作业1主要是在Blinn-Phong 着色模型基础上实现阴影,通过shadow map实现硬阴影、Soft 阴影PCF算法过滤、软硬阴影PCSS;

GAMES202——作业5 实时光线追踪降噪(联合双边滤波、多帧的投影与积累、À-Trous Wavelet 加速单帧降噪)

根据上面的式子,能够求出世界坐标系在上一帧的屏幕坐标的位置。如果在屏幕内,判断是否为同一个物体,如果不是就不能采用上一帧的信息,否则会造成拖影现象。如果不存在,说明该像素不能以上一帧进行参考,将α设置为1,只用自己这一帧。由于没有学过信号与系统,这里我的简单理解是离得越远,点的贡献就越小,那么在远的地方就选一个点来代表其附近区域的贡献。这里实现比较简单,直接根据给出的联合双边滤波核的公式就能实现。采用加速降噪后,并使用帧的投影与累积生成的结果。采用加速降噪后,并使用帧的投影与累积生成的结果。

weixin_50780569的博客 854

games202:games202作业

游戏202 games202作业

G2O (General Graph Optimization)入门及简单使用

g2o全称是General Graph Optimization,也就是图优化,我们在做SLAM后端或者更加常见的任何优化问题(曲线拟合)都可以使用G2O进行处理。

晓晨的博客 1万+

Games202作业5(完结)

也就是我们需要在像素(i,j)的四周进行采样,然后将采样的权重加到一起,然后所有的权重和像素的乘积也加到一起,然后相除,得到最终滤波后的(i,j)像素期望的结果。首先我们需要求出motion vector来判断上一帧的像素位置来补充当前帧的对应像素的信息。所有作业代码(作业2被我弄不见了....)其实就是膨胀卷积原理,间隔采样。也就是针对图像空间进行降噪。首先我们需要计算均值和方差。

m0_57958061的博客 448

Arnold Denoise流程

参考:https://support.solidangle.com/display/ARP/Removing+Noise+Workflow Arnold生来不是一个以单帧渲染速度见长的渲染器,但这并不能作为一个简单场景渲染时间长达几个小时的借口。 个人认为高品质生产级渲染结果中,Unbiased renderer在渲染时最耗计算时间的其实就是降噪,以至于降噪专门成了一门灯光师必备的技术

机器猫的CG空间 5084

GAMES101-作业5

Render部分,这里的公式其实是经历了三次变换:光栅化空间,正交空间,屏幕空间,具体原理参照这篇博客的翻译,讲的很清楚 for (int j = 0; j < scene.height; ++j) { for (int i = 0; i < scene.width; ++i) { // generate primary ray directionS float x=(2*(float(i)+0.5)

weixin_45455414的博客 2597

PSCAD学习笔记(1)Project Settings(重点snapshot设置)

若不勾选,则横坐标为实际时间2-7s,故障显示在第3s发生。图片中时间坐标轴显示为0-5s,故障时刻显示为第1s,即0-1s展示系统稳态,1-5s展示系统发生故障后的暂态响应,如何通过快照文件实现?勾选该项后,使用快照文件启动仿真会将仿真起始时间强制设置为0s,反之不勾选则依据快照文件保存的时间轴继续(即坐标为实际时间)。如果已有的快照文件保存的是0s至5s的仿真内容,则仿真运行时间从5s开始,到10s结束。如果已有的快照文件保存的是0s至2s的仿真内容,则仿真运行时间从2s开始,到7s结束;

gwenh的博客 7343

四阶龙格库塔法求解一次常微分方程组(python实现)

之前在博客发布了关于使用四阶龙格库塔方法求解一次常微分方程组的文章,由于代码缺少具体的验证,部分朋友可能存在疑问,因此这里打算再重新写一篇博客来验证一下程序的正确性,另外,这里是使用python语言来实现的。

HelloWorldTM的博客 8543

[GAMES202]作业1

本文系列为介绍games202中作业1的任务介绍,部分框架代码介绍以及示例答案代码。硬阴影基于PCF的软阴影,和基于PCSS的软阴影实现。本文主要介绍其中硬阴影的作业部分。

刘好念的博客 1323

无线通信技术及其在机器人中的应用

本文系统阐述了无线通信技术在机器人领域的应用框架与技术选型。首先构建了无线通信的三层理论架构:物理层解决信号传输问题,链路层处理接入控制,网络层实现组网管理。随后详细分析了主流无线技术的特性与适用场景:BLE适用于低功耗传感器节点,Wi-Fi适合高带宽视频传输,5G专网满足工业AGV的广域覆盖需求,星闪技术凭借20μs超低时延和4096台并发连接能力,为高端机器人应用提供突破性解决方案。通过鄂尔多斯隆基5G专网等典型案例,验证了5G在工业AGV规模化部署中的优势,并展望了通感算智融合、星闪+5G协同等未来发

weiyvyy的博客 1114

GAMES202-高质量实时渲染笔记01-ShadowMapping

ShadowMapping 一共做两次计算,第一次从灯光出发,记录各点最近深度形成shadowmap。第二次从摄像机出发,利用shadowmap进行阴影判断。但是会出现自遮挡现象,以及走样现象 因为shadowmap有像素分别率,从某个像素看去,覆盖过去的深度,就是一个像素大小常值深度。 橘色值方片,就是一个常值深度,随后后面像素被遮挡。 解决办法:通过夹角判断忽略一小段距离。 secondDepth shadowmapping 还记录次小深度,利用最小和次小深度的中间深度,来做阴影投射。

weixin_44953347的博客 308

openwrt 固件定制详解:各类默认参数修改及应用添加

本文以friendlywrt22-rk3568发行版为例, 介绍如**何定制**一款**openwrt固件**,定制特性包括网络参数、登陆密码、开机界面(banner)、代理arp、opkg源、hostname、应用默认配置参数修改、增加任意文件(不限于应用程序及其库)等, 最后给出了一个完整的实操实例。

十年通信老兵的技术修炼之路——从3G到5G,从地面基站到卫星通信。 1542

Unity WebView 插件⭐️(十八)3D WebView 原生2D模式 — Native 2D Mode

Unity中的 浏览器插件:3D WebView,利用好该插件我们可以自己使用Unity打造出一个自定义的多样化浏览器。可以用于2D显示,也可以用于3D空间场景中使用,在PC端、手机端和VR设备等都可以有一个非常好的效果。一起来浏览器插件:3D WebView的专栏学习使用吧!

努力前行,总会成为自己心中的那道光 7640

GAMES202 作业3

总览 本轮作业中,我们需要在一个光源为方向光,材质为漫反射 (Diffuse) 的场景中,完成屏幕空间下的全局光照效果(两次反射)。 为了在作业框架中实现上述效果,基于我们需要的信息不同我们会分三阶段着色,每个阶段都有相对应的任务。 第一次着色负责计算 Shadow Map 所需的深度值并保存到贴图中。 第二次着色负责计算屏幕空间中,每个像素对应的世界坐标系下位置、世界坐标系下法线、漫反射反射率和可见性信息并最终保存到对应贴图中。 第三次着色基于之前得到的场景几何信息 (像素对应的位置,法线),场景与光源

关宅 1819

1. SAP Business Application Studio 里创建一个基于 CAP 模型的最简单的 OData 服务

本文基于 SAP Cloud Application Programming 编程模型的 OData 服务开发教程的第一步,简述了 SAP Business Technology Platform 里 Business Application Studio 开发工具的打开方式,创建 Dev Space 以及 CAP 项目的创建步骤,以及如何通过 schema.cds 和 service.cds 分别创建 OData 模型和 OData 服务,最后通过 ports preview 打开 OData 服务预览页

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1798

GAMES202-高质量实时渲染(Assignment 2)

使用PRT技术进行预计算(针对BRDF是diffuse的情况)

lzh804121985的博客 1179

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

GAMES202 PCSS软阴影算法细节解析

在LearnOpenGL框架的基础上实现了一遍GAMES202的PCF+PCSS软阴影,之前学习GAMES202时一些没弄清楚的问题顺便搞清楚了。注:本文中代码和shader均在笔者中实现,因此有一些细节可能和GAMES202作业框架不一致,且对202框架中的一些错误进行了修正。

聚焦游戏和图形引擎开发技术 2621

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

GAMES202作业2

确定查询方向:你需要一个三维方向向量,通常是通过法线、反射向量或视线方向等计算出来的。这是着色点的“查询方向”,用于在 cubemap 中查找光照。这个方向向量 ((x, y, z)) 指向环境中的某个方向。我们要找到与这个方向对应的 cubemap 面和该面上具体的二维坐标。找到对应的Cubemap面根据这个方向向量 ((x, y, z)) 的符号和大小,可以判断这个方向向量是指向 cubemap 的哪个面。例如:如果 (x) 分量最大且为正,说明该方向指向 cubemap 的 +X 面(右侧)

m0_57958061的博客 1244

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Games202作业1 Unity(更新完毕)

介绍 作业1在Unity上的复现, 实现了基于的硬阴影, PCF, PCSS 代码下载: https://github.com/Eagle104fred/Games202_Homework1 注意事项 阴影的shader必须写在阴影的接受物体上例如墙壁等地方, 和模型本身的shader没啥关系 注意深度像机的参数设置 实现过程 1.构建shadowMap以及硬阴影 新建unity场景后建立一个Camera, 也可以在下面创建一个光源给模型打光不过不影响阴影效果。 新建一个产生阴影的Object和一个

Eagle的博客 5383

GAMES202作业0-环境搭建过程&解决遇到的问题

展示了作业0框架搭建的过程,以及解决了一些搭建框架遇到的问题。

qq_41835314的博客 4716

GAMES202作业1-万字分析代码框架&帮助更好理解框架内容

还算比较全面的介绍了GAMES202作业1作业框架的内容,详细的注释了自己对于每一部分的理解。

qq_41835314的博客 4146

GAMES 202 作业2 Part2

第七步 实时球谐光照计算 歪打正着 function getRotationPrecomputeL(precompute_L, rotationMatrix){ result = []; for(xx = 0;xx < 3;xx++) { result[xx] = []; rr = []; for(yy =0;yy < 9;yy ++) { result[xx][yy] = precompute_L[yy][xx]; rr[yy] = rotationMatri

关宅 1179

GAMES202 作业1

下面是两种随机采样方式的对比 这样乍得一看,两种随机采样方式得到的结果似乎差别并不明显,但其实不然。 可以看到,uniform的模式下,阴影的过渡部分有明显的层级,而泊松则平滑许多 ...

weixin_45455414的博客 986
上一篇: GAMES202作业4
下一篇: MVP矩阵变换详解
_GR
_GR
博客等级 码龄5年 76粉丝 12原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值