【Ray Tracing in One Weekend 超詳解】 光線追蹤1-3


 

學完了插值,我們來學習在場景里面添加一個立體彩色球(三維插值)

按照慣例,先看效果:

  

 

 

Chapter4: Adding a sphere

我們又一次面臨圖形學的主要任務。

我們需要再次回顧coord1.1的坐標系統,如果有一個球,在眼睛和屏幕中間,那么如何將其繪制到屏幕上

假設我們沿着y負方向垂直往下看:

              diagram 4-1

 

掃描屏幕位置的視線(有眼睛發出指向屏幕特定位置)的生成在上一節講述完畢。

我們的方法是通過逐行掃描屏幕的每一個位置,然后發出對應的視線,檢測視線是否與其他物體相交進而確定屏幕位置的像素值(rgb)。

那么,在此案例中,可能出現的三種視線如上

  line1 視線不與球相交,暢通無阻,看到的應該是背景,所以,在屏幕上對應的位置為背景色

  line2 視線與球體相交,穿過

  line3 視線與球相切

那么,這就比較好辦了

我們只需要推出判斷直線與球是否相交的公式,此題得解

書上的最后的判定公式推錯了,但是書上公式處代碼是對的,我們自己推一遍。

依據符號約定,向量用粗斜體小寫字母,矩陣用粗斜體大寫字母,標量用正常體大寫或小寫字母

 

************************* 數學分割線 ***************************

球體方程:x*x + y*y + z*z = r * r.

如果球心為{hx,hy,hz},那么方程如下:

(x - hx)*(x - hx) + (y - hy)*(y - hy) + (z - hz)*(z - hz) = r*r.

我們設 p = (x,y,z), h = (hx,hy,hz).

則方程表示形式為:p - h·(p - h) = r * r 

我們需要確定我們的光線p(t) = a + t * b. 是否曾經碰撞過這個球體

如果它碰撞過,那么存在系數t滿足方程。

所以我們求下述公式是否滿足

    p(t) - h·(p(t) - h) = r * r 

即:  a + t*b - h·(a + t*h) = r * r 

經過化簡得到:

  t*t* b·b + 2t* b·(a-h) + (a-h)·(a-h) - r * r = 0

 化簡過程也很簡單,你先把a-h組合到一起,然后進行點乘分配律即可

 

************************* End ***************************

 

所有的向量都是已知量,我們要求取 t 是否存在,依此來判定屏幕上的點是否是背景色

所以,二次方程求根法, delt不小於0,說明存在,即視線遇到了球體

 

那么我們假定眼睛仍然在(0,0,0),球心在(0,0,-1),半徑為0.5

看一個橘黃色的球

 

代碼如下:

 

#define LOWPRECISION

#include <fstream>
#include "ray.h"          //https://www.cnblogs.com/lv-anchoret/p/10182002.html
using namespace lvgm;

#define stds std::

bool hit(const ray::vec_type& Center, const precision R, const ray& sight)
{
    ray::vec_type trace = sight.origin() - Center;
    precision a = dot(sight.direction(), sight.direction());
    precision b = 2.0 * dot(trace, sight.direction());
    precision c = dot(trace, trace) - R*R;
    precision delt = b*b - 4. * a*c;
    return delt > 0.;
}


const ray::vec_type lerp4(const ray& sight)
{
    ray::vec_type sphereHeart{ 0.,0.,-1. };

    if (hit(sphereHeart, 0.5, sight))
        return ray::vec_type{ 1.0f,0.6f,0.f };

    ray::vec_type unit = sight.direction().ret_unitization();
    precision _t = 0.5*(unit.y() + 1.);
    return (1.0 - _t)*ray::vec_type{ 1.0f,1.0f,1.0f } +_t * ray::vec_type{ 0.5f,0.7f,1.0f };
}

void build_4_1()
{
    stds ofstream file("graph4-1.ppm");
    size_t W = 400, H = 200;

    if (file.is_open())
    {
        file << "P3\n" << W << " " << H << "\n255\n" << stds endl;
        ray::vec_type eye{ 0.,0.,0. };
        ray::vec_type horizontal{ 4.,0.,0. };
        ray::vec_type vertical{ 0.,2.,0. };
        ray::vec_type start{ -2.,-1.,-1. };
        for (int y = H - 1; y >= 0; --y)
            for (int x = 0; x < W; ++x)
            {
                vec2<precision> para{ precision(x) / W,precision(y) / H };
                ray sight = { eye, start + para.u() * horizontal + para.v() * vertical };
                ray::vec_type color = lerp4(sight);
                int r = int(255.99 * color.r());
                int g = int(255.99 * color.g());
                int b = int(255.99 * color.b());
                file << r << " " << g << " " << b << stds endl;
            }
        stds cout << "complished" << stds endl;
        file.close();
    }
    else
        stds cerr << "open file error" << stds endl;
}

int main()
{
    build_4_1();
}

 

 

當然,你可以把球心的位置前后上下左右移動試試,它在屏幕上會進行對應的平移和放縮

不過,無論你怎么移動,你看到的背面和正面是一樣的,下面我們就來解決區分背面和正面的方法

 

 Chapter5-1: Surface normals and multiple objects.

這一篇,我們先講表面法線,下一篇講剩余部分

我們有了上一章節的知識,現在來實現我們最初的那張圖,就很簡單了。

還記不記得我們在上一篇文章中的二維插值,是根據平面中的二維位置坐標來進行區分和特化的。

那么我們如果要在三維坐標系中像上次那樣,賦予每一個像素點獨一無二的特征,我們也可以采取三維坐標位置,但是它不是很好解決上一章節Chapter4末尾提出的那個問題,所以我們還需要找出能夠表明球體上某一點的另外一個獨一無二的特征且能夠區分出背面。那就是——Surface normals(表面法線)

引用書中一張圖:

    

 

我還是畫一個吧

  

之前就說過,h的三維坐標就是視線到h的向量,p點坐標就是視線向量,因為eye的坐標為(0,0,0),這個應該沒問題吧

好,這就好辦了,關於平面上某一點的法線,就是該點所在的切平面的法向量,而對於球來說,表面某點p的切平面的法向量平行於向量(h->p),所以p - h 即為p點表面法線(向外),p - h,你可以理解為坐標相減得到的向量,也可以理解為兩個向量相減(eye->p,eye->h)得到的向量,結果是一樣的

 

所以,我們目前的任務就是求出p點

即,我們真正要求的是實線

結合

  視線公式  p(t) = a + t * b

  判定相交 t*t* b·b + 2t* b·(a-h) + (a-h)·(a-h) - r * r = 0

求出t系數,就求出了圖中的實線向量

 

代碼:

#define LOWPRECISION

#include <fstream>
#include "ray.h"            //https://www.cnblogs.com/lv-anchoret/p/10182002.html
using namespace lvgm;

#define stds std::

precision hit(const ray::vec_type& Heart, const precision R, const ray& sight)
{
    ray::vec_type trace = sight.origin() - Heart;
    precision a = dot(sight.direction(), sight.direction());
    precision b = 2.0 * dot(trace, sight.direction());
    precision c = dot(trace, trace) - R*R;
    precision delt = b*b - 4. * a*c;
    if (delt < 0.)    return -1.;
    else return (-b - sqrt(delt)) / (2.*a);        //根,正、背面
}

const ray::vec_type lerp5(const ray& sight)
{
    ray::vec_type sphereHeart{ 0.,0.,-1. };
    precision _t = hit(sphereHeart, 0.5, sight);
    if (_t > 0.)        //相交
    {
        ray::vec_type normUnit = (sight.go(_t) - sphereHeart).ret_unitization();    //單位法線 -1~1
        return 0.5*ray::vec_type{ normUnit.x() + 1.f ,normUnit.y() + 1.f,normUnit.z() + 1.f }; //映射到 0~1
    }
    //背景色
    ray::vec_type unit = sight.direction().ret_unitization();
    _t = 0.5*(unit.y() + 1.);
    return (1.0 - _t)*ray::vec_type{ 1.0f,1.0f,1.0f } +_t * ray::vec_type{ 0.5f,0.7f,1.0f };
}

void build_5_1()
{
    stds ofstream file("graph5-1.ppm");
    size_t W = 400, H = 200;

    if (file.is_open())
    {
        file << "P3\n" << W << " " << H << "\n255\n" << stds endl;
        ray::vec_type eye{ 0.,0.,0. };
        ray::vec_type horizontal{ 4.,0.,0. };
        ray::vec_type vertical{ 0.,2.,0. };
        ray::vec_type start{ -2.,-1.,-1. };
        for (int y = H - 1; y >= 0; --y)
            for (int x = 0; x < W; ++x)
            {
                vec2<precision> para{ precision(x) / W,precision(y) / H };
                ray sight = { eye, start + para.u() * horizontal + para.v() * vertical };        //屏幕位置
                ray::vec_type color = lerp5(sight);
                int r = int(255.99 * color.r());
                int g = int(255.99 * color.g());
                int b = int(255.99 * color.b());
                file << r << " " << g << " " << b << stds endl;
            }
        stds cout << "complished" << stds endl;
        file.close();
    }
    else
        stds cerr << "open file error" << stds endl;
}

int main()
{
    build_5_1();
}

 

 效果圖就是開篇那個

 

感謝您的閱讀,生活愉快~

 


免責聲明!

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



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