今天咱們開始進入正篇html
Chapter 3: Rays, a simple camera, and background字體
對於全部的光線追蹤器,基本都有一個光線類,計算沿光線看到的顏色。ui
咱們的光線是一個矢量運算:spa
p(t) = a + t * b.3d
書中的向量用大寫粗體字表示,但這裏我遵循通常表示法:矩陣爲粗體大寫字母,向量爲粗體小寫字母,而標量爲正常字體code
P是光線指向的目標位置,a是光線的起點,b是光線指向的方向,t則是光線步長。htm
如圖:blog
咱們以下定義ray-classci
/// ray.h // ----------------------------------------------------- // [author] lv // [begin ] 2018.12 // [brief ] the ray-class for the ray-tracing project // from the 《ray tracing in one week》 // ----------------------------------------------------- #ifndef RAY_H #define RAY_H #include <lvgm\type_vec\type_vec.h> //http://www.javashuo.com/article/p-dlyoywqt-gr.html class ray { public: using value_type = lvgm::precision; using vec_type = lvgm::vec3<value_type>; public: ray() :_a{vec_type()} , _b{vec_type()} { } ray(const vec_type& a, const vec_type& b) :_a(a) ,_b(b) { } ray(const ray& r) :_a(r._a) ,_b(r._b) { } inline vec_type origin()const { return _a; } inline vec_type direction()const { return _b; } inline vec_type go(const value_type t)const { return _a + t * _b; } private: vec_type _a; vec_type _b; }; #endif //ray_h
然後咱們來看製做一個光線追蹤器,它的核心在於,發送光線去穿過像素,進而計算出沿着光線方向,什麼顏色被看到了get
咱們看到物體,是外部光照射到物體表面,通過表面反射以後,那部分反射進入眼睛的光被咱們捕捉,從而看到了光來源位置的物體,那麼,咱們假設從眼睛發射一束光,它表明咱們的視線,當它沿着某個方向一直向前,視線會與物體表面相交,那麼,咱們就捕捉到了一個像素。光線追蹤器就是計算視線的一種形式。
爲了更貼切,咱們在此以後將光線統一稱爲視線。
它能夠計算視線與表面交點以及交點處的顏色。
上述內容會在後續的案例講解過程當中逐步理解的。
如今,咱們來了解一下本書採用的默認座標系統
coord 1.1
座標(0,0,0)處爲咱們的眼睛(或者相機),咱們的繪圖區域爲藍色框表明的矩形平面,也就是你觀察圖像的那個屏幕。
按照學OpenGL的老規矩,第一堂固然是線性插值
差值公式:
blended_value = (1-t) * start_value + t * end_value
解釋一下上面的公式:
t 是一個係數,根據狀況肯定,將開始顏色和終止顏色進行比例混合,而後獲得混合色
若是咱們要作一個從白色到藍色根據座標位置肯定混合比例進行插值的矩形彩圖
咱們該如何作呢?
第一步,咱們須要肯定分辨率,假定爲400*200,就用coord1.1的座標體系去作。
第二步,咱們須要肯定開始位置和終止位置,假定從左下角混合到右上角,混合顏色爲白色和藍色。
第三步,咱們須要肯定視線,即肯定從眼睛出發到屏幕的向量
1. 肯定在平面中的位置,從左下角開始每個平面位置均由水平和垂直兩個分向量疊加而成:
diagram 3-1
由座標系統得知,lower-left的座標爲(-2,-1,-1)
若y =(0,0.5,0),x =(1,0,0),則pos =(-1,-0.5,-1)
2.當咱們肯定了pos以後,其實,視線已經肯定好了,由於眼睛的座標爲(0,0,0),pos即爲視線向量
第四步,咱們須要從分辨率到屏幕作一個映射。
看圖還記得咱們的分辨率是400*200嗎,而屏幕的範圍是4*2的矩形
因此,咱們須要作一個映射,和上次同樣,咱們能夠將分辨率下的位置經過除法轉換到標準座標,而後再經過標準座標轉換到屏幕座標
例如一個分辨率下的x的步長爲388,首先經過388/400變爲一個0~1的實數,而後乘以4,便可變爲屏幕步長
而後經過diagram 3-1,肯定屏幕中的位置
第五步,咱們須要肯定比例係數t,咱們能夠選擇x或y方向的其中一個作映射。
由於屏幕座標系不肯定,咱們採用的是4*2的,但不是銘文規定的,因此咱們須要將視線向量(等同於pos座標位置)進行單位化,這樣的話就能夠把每一個座標份量的長度控制在[-1, 1],若是咱們把它+1再除以2,那麼就徹底轉化到[0, 1]了,此時,每一個座標方向的分向量範數均爲[0, 1],它們是由三個座標基共同做用而成的獨一無二的,因此,你能夠採用x的單位化值做爲t,也能夠把y做爲t。
第六步,通過上述一頓操做,咱們終於獲得了屏幕某個點對應的blend_value,顏色混合值(或稱爲插值)
至此,咱們基於位置進行的顏色插值就講解完了
下面是代碼:
#define LOWPRECISION #include <fstream> #include "ray.h" using namespace lvgm; #define stds std:: ray::vec_type lerp(const ray& r) { ray::vec_type unit_dir = r.direction().ret_unitization(); //單位化 ray::value_type t = 0.5*(unit_dir.y() + 1.0); //將y份量映射到[0, 1] //插值公式 白色&藍色 return (1.0 - t)*ray::vec_type(1.0, 1.0, 1.0) + t*ray::vec_type(0.0, 0.0, 1.0); } void build_3_1() { int X = 400, Y = 200; //分辨率 400*200 stds ofstream file("graph3-1.ppm"); if (file.is_open()) { file << "P3\n" << X << " " << Y << "\n255\n"; ray::vec_type left_bottom{ -2.0,-1.0,-1.0 }; //左下角做爲開始位置 ray::vec_type horizontal{ 4.0,0,0 }; //屏幕水平寬度 ray::vec_type vertical{ 0,2.0,0 }; //屏幕垂直高度 ray::vec_type eye{ 0,0,0 }; //眼睛位置 for (int j = Y - 1; j >= 0; --j) for (int i = 0; i < X; ++i) { vec2<ray::value_type> para(ray::value_type(i) / X, ray::value_type(j) / Y); ray r(eye, left_bottom + para.u() * horizontal + para.v() * vertical); ray::vec_type color = lerp(r); //獲得插值顏色(rgb) int ir = int(255.99*color.r()); int ig = int(255.99*color.g()); int ib = int(255.99*color.b()); file << ir << " " << ig << " " << ib << stds endl; } file.close(); } else stds cerr << "load file failed!" << stds endl; stds cout << "complished" << stds endl; } int main() { build_3_1(); }
下面是效果圖(y份量做爲插值公式係數)
固然你也能夠用x份量做爲插值公式係數 t
感謝您的閱讀,生活愉快~