Daily Pathtracer!安利下不錯的Pathtracer學習資料


0x00 前言

最近看到了我司大網紅aras-p(Aras Pranckevičius)的博客開了一個很有趣的新系列《Daily Pathtracer~》,來實現一個簡單的ToyPathTracer。
屏幕快照 2018-05-29 下午11.12.31.png
除了使用C++,aras還使用了兩種不同的C#運行時來實現,即.NET Standard和Xamarin/Mono。效率上自然C++要強於.NET Standard,而.NET Standard的表現要強於Xamarin/Mono。

除此之外,aras當然還會用到Unity來實現,比較有意思的是Unity的實現中使用了2018中的Burst來提升性能,而結果甚至有點小驚人呢,之后可以再說。

而aras的這個小巧的ToyPathTracer事實上和最近在知乎上小火的小書《Ray Tracing in One Weekend》還有些關系。其實這是一套3本中的第一本書,另外還有兩本,分別叫《Ray Tracing: the Next Week》和《Ray Tracing: The Rest Of Your Life》。

屏幕快照 2018-05-29 下午9.54.28.png
嗯,真是一入碼門深似海。而圖形學作為程序員的三大浪漫之一,真是誠不我欺也。

當然,這本書的作者Peter Shirley已經將此書作為PDF免費放出了(此處有地址:https://drive.google.com/drive/folders/14yayBb9XiL16lmuhbYhhvea8mKUUK77W),但是各位如果有興趣的話建議還是買一下吧(seriously, just buy that minibook)。

所以前言部分先安利的,是這套小書。

0x01 Unity中實現PathTracer

aras的這一系列博客很長,目錄摘錄在下面。

我的這篇小文作為安利文,也就不詳細介紹了每一篇的內容了,相信有興趣的小伙伴都會去看的。

但是其中有一篇文章還是蠻有趣的,因為對比了不同語言、不同運行時的性能差異,而且還演示了一下Unity2018中的Burst compiler對C#代碼性能的提升。所以稍微介紹下這部分吧。

當然,首先可以看到,場景是作為硬編碼寫死的:

static Sphere[] s_SpheresData = {
    new Sphere(new float3(0,-100.5f,-1), 100),
    new Sphere(new float3(2,0,-1), 0.5f),
    new Sphere(new float3(0,0,-1), 0.5f),
    new Sphere(new float3(-2,0,-1), 0.5f),
    new Sphere(new float3(2,0,1), 0.5f),
    new Sphere(new float3(0,0,1), 0.5f),
    new Sphere(new float3(-2,0,1), 0.5f),
    new Sphere(new float3(0.5f,1,0.5f), 0.5f),
    new Sphere(new float3(-1.5f,1.5f,0f), 0.3f),
    #if DO_BIG_SCENE
    new Sphere(new float3(4,0,-3), 0.5f), new Sphere(new float3(3,0,-3), 0.5f), new Sphere(new float3(2,0,-3), 0.5f), new Sphere(new float3(1,0,-3), 0.5f), new Sphere(new float3(0,0,-3), 0.5f), new Sphere(new float3(-1,0,-3), 0.5f), new Sphere(new float3(-2,0,-3), 0.5f), new Sphere(new float3(-3,0,-3), 0.5f), new Sphere(new float3(-4,0,-3), 0.5f),
    new Sphere(new float3(4,0,-4), 0.5f), new Sphere(new float3(3,0,-4), 0.5f), new Sphere(new float3(2,0,-4), 0.5f), new Sphere(new float3(1,0,-4), 0.5f), new Sphere(new float3(0,0,-4), 0.5f), new Sphere(new float3(-1,0,-4), 0.5f), new Sphere(new float3(-2,0,-4), 0.5f), new Sphere(new float3(-3,0,-4), 0.5f), new Sphere(new float3(-4,0,-4), 0.5f),
    new Sphere(new float3(4,0,-5), 0.5f), new Sphere(new float3(3,0,-5), 0.5f), new Sphere(new float3(2,0,-5), 0.5f), new Sphere(new float3(1,0,-5), 0.5f), new Sphere(new float3(0,0,-5), 0.5f), new Sphere(new float3(-1,0,-5), 0.5f), new Sphere(new float3(-2,0,-5), 0.5f), new Sphere(new float3(-3,0,-5), 0.5f), new Sphere(new float3(-4,0,-5), 0.5f),
    new Sphere(new float3(4,0,-6), 0.5f), new Sphere(new float3(3,0,-6), 0.5f), new Sphere(new float3(2,0,-6), 0.5f), new Sphere(new float3(1,0,-6), 0.5f), new Sphere(new float3(0,0,-6), 0.5f), new Sphere(new float3(-1,0,-6), 0.5f), new Sphere(new float3(-2,0,-6), 0.5f), new Sphere(new float3(-3,0,-6), 0.5f), new Sphere(new float3(-4,0,-6), 0.5f),
    new Sphere(new float3(1.5f,1.5f,-2), 0.3f),
    #endif // #if DO_BIG_SCENE        
};

static Material[] s_SphereMatsData = {
    new Material(Material.Type.Lambert,     new float3(0.8f, 0.8f, 0.8f), new float3(0,0,0), 0, 0),
    new Material(Material.Type.Lambert,     new float3(0.8f, 0.4f, 0.4f), new float3(0,0,0), 0, 0),
    new Material(Material.Type.Lambert,     new float3(0.4f, 0.8f, 0.4f), new float3(0,0,0), 0, 0),
    new Material(Material.Type.Metal,       new float3(0.4f, 0.4f, 0.8f), new float3(0,0,0), 0, 0),
    new Material(Material.Type.Metal,       new float3(0.4f, 0.8f, 0.4f), new float3(0,0,0), 0, 0),
    new Material(Material.Type.Metal,       new float3(0.4f, 0.8f, 0.4f), new float3(0,0,0), 0.2f, 0),
    new Material(Material.Type.Metal,       new float3(0.4f, 0.8f, 0.4f), new float3(0,0,0), 0.6f, 0),
    new Material(Material.Type.Dielectric,  new float3(0.4f, 0.4f, 0.4f), new float3(0,0,0), 0, 1.5f),
    new Material(Material.Type.Lambert,     new float3(0.8f, 0.6f, 0.2f), new float3(30,25,15), 0, 0),
    #if DO_BIG_SCENE
    new Material(Material.Type.Lambert, new float3(0.1f, 0.1f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.2f, 0.2f, 0.2f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.3f, 0.3f, 0.3f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.4f, 0.4f, 0.4f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.5f, 0.5f, 0.5f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.6f, 0.6f, 0.6f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.7f, 0.7f, 0.7f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.8f, 0.8f, 0.8f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.9f, 0.9f, 0.9f), new float3(0,0,0), 0, 0),
    new Material(Material.Type.Metal, new float3(0.1f, 0.1f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.2f, 0.2f, 0.2f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.3f, 0.3f, 0.3f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.4f, 0.4f, 0.4f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.5f, 0.5f, 0.5f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.6f, 0.6f, 0.6f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.7f, 0.7f, 0.7f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.8f, 0.8f, 0.8f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.9f, 0.9f, 0.9f), new float3(0,0,0), 0, 0),
    new Material(Material.Type.Metal, new float3(0.8f, 0.1f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.8f, 0.5f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.8f, 0.8f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.4f, 0.8f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.1f, 0.8f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.1f, 0.8f, 0.5f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.1f, 0.8f, 0.8f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.1f, 0.1f, 0.8f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.5f, 0.1f, 0.8f), new float3(0,0,0), 0, 0),
    new Material(Material.Type.Lambert, new float3(0.8f, 0.1f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.8f, 0.5f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.8f, 0.8f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.4f, 0.8f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.1f, 0.8f, 0.1f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.1f, 0.8f, 0.5f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.1f, 0.8f, 0.8f), new float3(0,0,0), 0, 0), new Material(Material.Type.Lambert, new float3(0.1f, 0.1f, 0.8f), new float3(0,0,0), 0, 0), new Material(Material.Type.Metal, new float3(0.5f, 0.1f, 0.8f), new float3(0,0,0), 0, 0),
    new Material(Material.Type.Lambert, new float3(0.1f, 0.2f, 0.5f), new float3(3,10,20), 0, 0),
    #endif
};

當然需要來判斷是否相交的:

static bool HitWorld(Ray r, float tMin, float tMax, ref Hit outHit, ref int outID, ref SpheresSoA spheres)
{
    outID = spheres.HitSpheres(ref r, tMin, tMax, ref outHit);
    return outID != -1;
}

光線的追蹤:

static float3 Trace(Ray r, int depth, ref int inoutRayCount, ref SpheresSoA spheres, NativeArray<Material> materials, ref uint randState, bool doMaterialE = true)
{
    Hit rec = default(Hit);
    int id = 0;
    ++inoutRayCount;
    if (HitWorld(r, kMinT, kMaxT, ref rec, ref id, ref spheres))
    {
        Ray scattered;
        float3 attenuation;
        float3 lightE;
        var mat = materials[id];
        var matE = mat.emissive;
        ...

物體表面如何處理接收到的光線呢?可以看這里:

static bool Scatter(Material mat, Ray r_in, Hit rec, out float3 attenuation, out Ray scattered, out float3 outLightE, ref int inoutRayCount, ref SpheresSoA spheres, NativeArray<Material> materials, ref uint randState)
{
    outLightE = new float3(0, 0, 0);
    if (mat.type == Material.Type.Lambert)
    {
        // random point inside unit sphere that is tangent to the hit point
        float3 target = rec.pos + rec.normal + MathUtil.RandomUnitVector(ref randState);
        scattered = new Ray(rec.pos, normalize(target - rec.pos));
        attenuation = mat.albedo;

        // sample lights
        ...

最后,在Unity中使用了burst,並分發了任務:

[ComputeJobOptimization]
struct TraceRowJob : IJobParallelFor
{
    public int screenWidth, screenHeight, frameCount;
    [NativeDisableParallelForRestriction] public NativeArray<UnityEngine.Color> backbuffer;
    public Camera cam;

    [NativeDisableParallelForRestriction] public NativeArray<int> rayCounter;
    [NativeDisableParallelForRestriction] public SpheresSoA spheres;
    [NativeDisableParallelForRestriction] public NativeArray<Material> materials;

    public void Execute(int y)
    {
        int backbufferIdx = y * screenWidth;
        float invWidth = 1.0f / screenWidth;
        float invHeight = 1.0f / screenHeight;
        float lerpFac = (float)frameCount / (float)(frameCount + 1);
        ...

通過UnityProfiler,我們可以看到經過Burst優化后的運行狀態:
rt-cs-unity-timeline-burst.png

不過讓我感到比較吃驚的是,在Unity2018中使用C#+Burst時的效率竟然超過了C++的實現,看來Burst的效率還是很驚人的。

下面是aras那里的結論:

  • .NET Core is about 2x slower than vanilla C++.
  • Mono (with default settings) is about 3x slower than .NET Core.
  • IL2CPP is 2x-3x faster than Mono, which is roughly .NET Core performance level.
  • Unity’s Burst compiler can get our C# code faster than vanilla C++. Note that right now Burst is very early tech, I expect it will get even better performance later on.

當然,aras的博客中有更詳細的對比數據。

所以這部分安利的,是aras的博客(http://aras-p.info/blog/2018/03/28/Daily-Pathtracer-Part-0-Intro/)。

0x02 小結

當然,aras除了寫了博客之外,也開源了這個ToyPathTracer(也許先有了ToyPathTracer才有了這系列的博客也未可知)。

所以各位可以去github上clone一份代碼,除了可以分別在windows和mac上編譯c++、c#的實現,也可以直接在unity中使用。
屏幕快照 2018-05-29 下午10.43.52.png

所以最后安利一下這個工程(https://github.com/aras-p/ToyPathTracer)。

怎么樣,是不是還挺有趣的?

Ref

《Ray Tracing in One Weekend》
https://drive.google.com/drive/folders/14yayBb9XiL16lmuhbYhhvea8mKUUK77W

《Daily Pathtracer》
http://aras-p.info/blog/2018/03/28/Daily-Pathtracer-Part-0-Intro/

《ToyPathTracer》
https://github.com/aras-p/ToyPathTracer

-EOF-
最后打個廣告,歡迎支持我的書《Unity 3D腳本編程》

歡迎大家關注我的公眾號慕容的游戲編程:chenjd01


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM