虚幻4渲染编程(光线追踪篇)【第二卷:一天入门光线追踪】

本文详细介绍了在虚幻4引擎中实现实时光线追踪的过程,从环境搭建到抗锯齿、Diffuse Lighting等高级功能,展示了如何利用GPU加速光线追踪计算。

MY BLOG DIRECTORY:

小IVan:专题概述及目录

INTRODUCTION:

光线追踪和光栅比起来有很多天生优势,实时光线追踪也是游戏未来发展的大方向。这一节将会实现“Ray Tracing in a Weekend”的内容,不过与“Ray Tracing in a Weekend”不同的是,我们将会在虚幻4中实现它,并且使用GPU来加速计算。

在开始本系列之前,如果没有光线追踪基础建议先阅读Ray Tracing in a weekend,Two Weekend 和last life这三本书。

Ray Tracing in One Weekend (Ray Tracing Minibooks Book 1) eBook: Peter Shirley: Kindle Store

這些是基礎知識,下面我會在Unreal中自己搭建一個簡陋的光錐渲染器,這些完成后再進入RTX。


MAIN CONTENT:

【1】在Unreal中使用GPU做光线追踪的环境搭建

我们还是使用插件的形式来搭建我们的代码,虚幻的插件和unity的有本质不同,虚幻的插件其实是一个新的模块。

我们新建一个名为RayTracing的插件,然后在插件里新建一个RayTracing.usf和一个RayRender.h,一个RayRender.cpp

v2-fe279cfcab53f31a6300811d4d796092_b.jpg

对RayTracing.build.cs做如下修改

v2-19d0cdd6a427f149da4863a86190a4e8_b.jpg

对RayTracing.uplugin做如下修改:

v2-c730a3c5b417204672e7beebc589a2ba_b.jpg

然后在RayRender.h文件中,敲入如下代码

v2-f2b20ff176cbc7347ceca21c3ff070e6_b.jpg

在RayRender.cpp中实现MainRayRender

v2-a6950670302938813dba09698de5ea70_b.jpg

然后在RayTracing.usf中敲入如下代码

v2-8b77885afc4919c03924136361cc8b8f_b.jpg

然后新建一个场景,做一个BP派生自RayRender

v2-b830e7078303576bd19fc3d541610355_b.jpg

然后把它拖进去,在BP_RayRender中做如下设置

v2-d804c1d4ed0650cd43fb2ae10b3a5a9f_b.jpg

OK现在完成了最基本的环境搭建。


【2】加入ComputeShader

在RayRender.cpp中我们要建一个ComputeShader。这个CS将负责RayTracing的大部分计算。代码如下:

v2-1f3d265e4c7d2557d6551f4e059a05b9_b.jpg


【3】使用ComputeShader来输出计算结果

在MainRayRender函数前面我们增加一个函数

v2-6a9754d72d064bbcafbaefb71b82514b_b.jpg

这个函数负责被逻辑线程的MainRayRender函数调用,然后它负责在渲染线程控制CS,在这个函数的末尾有个SaveArrayToTexture函数,我们需要在RayTracing_RenderThread前实现它。如下图所示:

v2-11616f56ec29b0d0e4732523925f1f44_b.jpg

最后是我们的MainRayRender函数的实现。

v2-d92c841803de479d78015a729e890616_b.jpg

然后在编辑器启动游戏点下R键之后,你将会在项目的Save目录下看到CS计算的结果

v2-8133c20111138e3fd8e0bb5f0d84cf05_b.jpg


【4】在ComputeShader中加入RayTracing的逻辑完成天空的绘制

使用CS的时候需要注意的是,uv的v方向是向下的

v2-802743530f94688717cd7dea3e168e31_b.jpg

再改一下输出图片的分辨率(为了好看)

然后我们继续在RayTracing.usf敲入如下代码:

v2-57857629fb5697eb5dd36e6535dce964_b.jpg

首先我们定义了一个光线的结构体,并且为这个结构体配套了一个point_at_paramerter的函数。然后定义了一个渲染场景的函数,最后在MainCS里定义了我们的摄像机。

v2-d9c3a503a2ac8483af0183ae870f6368_b.jpg

在编辑器中运行点R键,你将会在项目的Save目录的ScreenShot里找到输出结果

v2-39b3c6ea91e5a3a8229a882c1058e84a_b.jpg


【5】向RayTracing中加入一个球体

我们先来使用数学方法建一个球体要判断光线是否与球相交,只需要判断射线方程和球面方程是否有交点即可

v2-4bfa27c811c22bb78309a37ed7b154cb_b.jpg

(电脑上写的字巨丑,见谅)

所以我们定义一个hit_sphere函数如下

v2-89cfa6ad3834600f6ca0bd93202f1f7b_b.jpg

我们的RenderSceneColor也要做相应变化,如果光线打到了球面上,则绘制红色。然后我们就可以得到如下的图:

v2-509d39acdb7637bf9374bd33f480e414_b.jpg


【6】计算表面法线

因为表面是球形的,所以法线就是球星到光线和表面的相交点方向的向量

v2-a1d17db44287c877eb5e5f330d7a896b_b.jpg
v2-81df3855cded00a2b8462abfcca2a75d_b.jpg
v2-7e315a629c236f2d67e190933cb6bd68_b.jpg

【7】多物体计算

v2-697e36c2a251d88c26bf620630db8ad5_b.jpg

如果想得到一个场景肯定不可能只绘制一个东西,这时候我们需要绘制多个物体。这时候我们就需要在Shader里维护一个绘制列表。同时为了更方便绘制,我把摄像机的坐标系重新调整了一下。

v2-418d37a7b213d74758b70f7c0692c72b_b.jpg

先定义一个结构体用来储存光追的结果

v2-39fa95fff32072fd41d0554840c4b545_b.jpg

然后定义物体结构体和物体绘制列表结构

        struct hitable_Sphere
{
    float3 center;
    float radius;
	void InitHitableObject(float3 centerlocation, float radiusvalue)
    {
        center = centerlocation;
        radius = radiusvalue;
    }
    bool hit(in Ray r, in float t_min, float t_max, out hit_record rec)
    {
        float3 oc = r.origin - center;
        float a = dot(r.direction, r.direction);
        float b = 2 * dot(oc, r.direction);
        float c = dot(oc, oc) - radius * radius;
        float discrimninant = b * b - 4 * a * c;
	
        if (discrimninant >= 0)
        {
            float temp = (-b - sqrt(discrimninant)) / (2.0f * a);
            if (temp < t_max && temp > t_min)
            {
                rec.t = temp;
                rec.p = r.point_at_paramerter(rec.t);
                rec.normal = (rec.p - center) / radius;
                return true;
            }
            temp = (-b + sqrt(discrimninant)) / (2.0f * a);
            if (temp < t_max && temp > t_min)
            {
                rec.t = temp;
                rec.p = r.point_at_paramerter(rec.t);
                rec.normal = (rec.p - center) / radius;
                return true;
            }
        }
        return false;
    }
};

struct hitablelist
{
    hitable_Sphere Sphere_001;
    hitable_Sphere Sphere_002;

    bool hit(in Ray rayline, in float t_min, in float t_max, out hit_record rec)
    {
        hit_record temp_rec;
        bool hit_anything = false;
        float closest_so_far = t_max;
		
        Sphere_001.InitHitableObject(float3(0, -1.0f, 0), 0.5f);
        if (Sphere_001.hit(rayline, t_min, closest_so_far, temp_rec))
        {
            hit_anything = true;
            closest_so_far = temp_rec.t;
            rec = temp_rec;
        }
		
        Sphere_002.InitHitableObject(float3(0, -1.0f, 20.5f), 20.0f);
        if (Sphere_002.hit(rayline, t_min, closest_so_far, temp_rec))
        {
            hit_anything = true;
            closest_so_far = temp_rec.t;
            rec = temp_rec;
        }

        return hit_anything;
    }
};
      

最后是我们的场景渲染函数和MainCS

v2-3de051b70c43ca92b0f2b8ec0be5ffa6_b.jpg

【8】抗锯齿

可以看到我们渲染出来的图锯齿还是很重的

v2-9273a760cf3e0d52850eca176b2d3a44_b.jpg

我们可以对这个地方进行超采样来缓解。首先把Camera的逻辑封装一下

v2-3025c260edcf5cc9f4c0cb21be9bb7da_b.jpg

主函数也需要做相应修改

v2-86900c4d0aa499693126993500a47c0c_b.jpg

于是最后我们得到了平滑的图像

v2-204c6242b8d5d33c1d479ecc64c4ed2b_b.jpg

这里我采样了2048次(其实不用这么多次),对于GPU来说小Case啦。我1060的垃圾卡都能瞬间做完运算并输出图片。

【9】DiffuseLighting

我们有了法线就能做Lighting啦。

我们可以假设一种非常简单的光照模型,光打到一个物体上,一部分能量被吸收,一部分能量反弹,我们对每条光线做迭代即可

v2-e722963655cbf4926155fee00d846f99_b.jpg
v2-1f4db28496e4dc8c6badc92a2e01d51c_b.jpg

最后可以得到的输出结果

v2-f76af13c5bf53b8af931122245c38fba_b.jpg

可以看到我们不需要可以渲染AO影子什么的,这些东西自己就有了。影子噪点非常多我们还需要对其进行降噪处理。

修改下Hit的最小距离,将其修改为0.001

v2-6807f0e087996f94251c63a4d854d3f8_b.jpg

于是奇怪的bug纹就消失了

v2-5dd35c71f6ff8d0fb2721a6488c6b62f_b.jpg
v2-5686e8b717a9a4dde9d07be19f6104e6_b.jpg

SUMMARY AND OUTLOOK:

先暂时写到这里吧,后面的全反射和折射,透射什么的有时间再写。到这里我们就可以得到一个简单的光纤追踪渲染器了。不过没有加速结构,没有反弹循环等等,但是这并不影响我们对光线追踪的理解。

Enjoy it。


NEXT:

YivanLee:虚幻4渲染编程(光线追踪篇)【第三卷:三角形求交】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值