Shadertoy 教程 Part 9 - 相機的運動

Note: This series blog was translated from Nathan Vaughn's Shaders Language Tutorial and has been authorized by the author. If reprinted or reposted, please be sure to mark the original link and description in the key position of the article after obtaining the author’s consent as well as the translator's. If the article is helpful to you, click this Donation link to buy the author a cup of coffee.
說明:該系列博文翻譯自Nathan Vaughn着色器語言教程。文章已經獲得作者翻譯授權,如有轉載請務必在取得作者譯者同意之後在文章的重點位置標明原文鏈接以及說明。如果你覺得文章對你有幫助,點擊此打賞鏈接請作者喝一杯咖啡。

朋友們,你們好!今天是四月一號愚人節,希望大家沒有被愚弄到😂!歡迎來到Shadertoy第9部分的教程。本次教程中,我們將會學習到如何在場景中移動相機。

初始化

首先,我們新建一個着色器,然後把下面的模板代碼貼進去:

// Rotation matrix around the X axis.
mat3 rotateX(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(1, 0, 0),
        vec3(0, c, -s),
        vec3(0, s, c)
    );
}

// Rotation matrix around the Y axis.
mat3 rotateY(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(c, 0, s),
        vec3(0, 1, 0),
        vec3(-s, 0, c)
    );
}

// Rotation matrix around the Z axis.
mat3 rotateZ(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(c, -s, 0),
        vec3(s, c, 0),
        vec3(0, 0, 1)
    );
}

// Identity matrix.
mat3 identity() {
    return mat3(
        vec3(1, 0, 0),
        vec3(0, 1, 0),
        vec3(0, 0, 1)
    );
}

const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float PRECISION = 0.001;

struct Surface {
    float sd; // signed distance value
    vec3 col; // color
};

Surface sdBox( vec3 p, vec3 b, vec3 offset, vec3 col, mat3 transform)
{
  p = (p - offset) * transform; // apply transformation matrix
  vec3 q = abs(p) - b;
  float d = length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
  return Surface(d, col);
}

Surface sdFloor(vec3 p, vec3 col) {
  float d = p.y + 1.;
  return Surface(d, col);
}

Surface minWithColor(Surface obj1, Surface obj2) {
  if (obj2.sd < obj1.sd) return obj2;
  return obj1;
}

Surface sdScene(vec3 p) {
  vec3 floorColor = vec3(1. + 0.7*mod(floor(p.x) + floor(p.z), 2.0));
  Surface co = sdFloor(p, floorColor);
  co = minWithColor(co, sdBox(p, vec3(1), vec3(0, 0.5, -4), vec3(1, 0, 0), identity()));
  return co;
}

Surface rayMarch(vec3 ro, vec3 rd, float start, float end) {
  float depth = start;
  Surface co; // closest object

  for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
    vec3 p = ro + depth * rd;
    co = sdScene(p);
    depth += co.sd;
    if (co.sd < PRECISION || depth > end) break;
  }
  
  co.sd = depth;
  
  return co;
}

vec3 calcNormal(in vec3 p) {
    vec2 e = vec2(1.0, -1.0) * 0.0005; // epsilon
    return normalize(
      e.xyy * sdScene(p + e.xyy).sd +
      e.yyx * sdScene(p + e.yyx).sd +
      e.yxy * sdScene(p + e.yxy).sd +
      e.xxx * sdScene(p + e.xxx).sd);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;
  vec3 backgroundColor = vec3(0.835, 1, 1);

  vec3 col = vec3(0);
  vec3 ro = vec3(0, 0, 3); // ray origin that represents camera position
  vec3 rd = normalize(vec3(uv, -1)); // ray direction

  Surface co = rayMarch(ro, rd, MIN_DIST, MAX_DIST); // closest object

  if (co.sd > MAX_DIST) {
    col = backgroundColor; // ray didn't hit anything
  } else {
    vec3 p = ro + rd * co.sd; // point on cube or floor we discovered from ray marching
    vec3 normal = calcNormal(p);
    vec3 lightPosition = vec3(2, 2, 7);
    vec3 lightDirection = normalize(lightPosition - p);

    float dif = clamp(dot(normal, lightDirection), 0.3, 1.); // diffuse reflection

    col = dif * co.col + backgroundColor * .2; // Add a bit of background color to the diffuse color
  }

  // Output to screen
  fragColor = vec4(col, 1.0);
}

運行以上的代碼,我們就能看到一個方塊格子的地板,天空(背景顏色)以及一個紅色的立方體。代碼中包含了我們在上一節教程中學習到的旋轉矩陣。

平移相機

平移相機操作實際上非常基礎。相機當前的位置就是正對着一個立方體,立方體懸浮在空中,距離相機z軸有一段距離。因爲我們這裏採用的是右手座標系統,因此,z軸的負方向是遠離相機的,而正方向則是朝向相機的。

相機所處的位置由ro變量所定義,即射線的源頭。它當前的位置是在vec3(0,0,0)處。如果要讓相機沿着x軸方向移動,我們簡單地調整rox元素即可:

  vec3 ro = vec3(1,0,3);

相機現在移動到了右邊,產生了向左移動的的效果。

同樣,我們調整ro的y元素,使其上下移動

  vec3 ro = vec3(0,1,3);

向上移動相機產生了立方體和地板向下的效果。

可以沿着x軸和y軸對相機進行cossin函數的運動,從而產生旋轉效果。

    vec3 ro = vec3(cos(iTime), sin(iTime) + 0.1, 3);

顯然,當我們的相機緊貼入地板後,看起來會有一點奇怪,所以我們在y軸上添加了0.1個單位,以防出現閃爍的效果。

擺動/旋轉 相機

現在,我們需要保持相機的位置在ro點上,但我們需要對其在上下左右的方向上進行擺動。同時也需要嘗試讓相機能夠朝着所有的方向進行任意地擺動,也就是180度全景旋轉。這些效果都需要運用變換矩陣與射線方向(rd)的計算。我們把攝像機歸位一下先吧:

  vec3 ro = vec3(0, 0, 3);

現在立方體是處在畫布的中間了。目前從側面看我們的場景與下面這張圖中的描繪是相似的:

位置保持不變,但是卻可以沿着不同的角度進行擺動。假設我們需要向上進行擺動,場景圖應該是下圖描繪出來的樣子:

請注意,發射出去的射線的方向也隨着相機的擺動而發生了改變。擺動相機意味着把所有發射的射線做了一定角度的傾斜。

相機的擺動有點像飛行器主軸

相機不僅僅能夠沿着xy和z軸進行平移,同事也可以繞着pitch、yaw和roll軸進行擺動。這意味着相機有6個自由度:三個位置軸和三個旋轉軸:

很幸運,我們可以使用我們之前學到的旋轉矩陣來處理pitch、yaw和roll軸的變化。

“Pitch”被應用到rotateX函數中,“yaw”被應用到rotateY函數中,“roll”被應用到rotateZ函數當中。

如果我們想要相機上下襬動(“pitch”),那麼我們就需要爲射線方向(rd)應用rotateX函數。

  vec3 rd = normalize(vec3(uv, -1));
  rd *= rotateX(0.3);

我們簡單地將一個或者多個旋轉矩陣和射線方向進行了乘法運算,從而對相機進行擺動。這樣做改變了所有的從相機發出的射線方向,從而也改變我們在Shadertoy中看到的物體的景象。

控制擺動角度在-0.5到0.5之間

  vec3 rd = normalize(vec3(uv, -1));
  rd *= rotateX(sin(iTime) * 0.5);

左右擺動相機(yaw),應用 rotateY函數


  vec3 rd = normalize(vec3(uv, -1));
  rd *= rotateY(sin(iTime) * 0.5);

側面擺動相機(roll),需要應用rotateZ函數。是不是像滾筒洗衣機一樣!🐰

  vec3 rd = normalize(vec3(uv, -1));
  rd *= rotateZ(sin(iTime) * 0.5);

360 度全景相機旋轉

我們同樣也可以在PI和負PI之間沿着yaw旋轉,這樣就能完成360度的旋轉了。

  vec3 rd = normalize(vec3(uv, -1));
  rd *= rotateZ(sin(iTime) * 0.5);

仔細看相機的後方,我們會發現地板上有一個發光的區域。這個點就是光的位置,它目前的位置是vec3(2,2,7)。因爲z軸的位置設置在相機後面,當你翻轉相機之後,就會看到光。

你或許會認爲這個光斑是一個四月的愚人節的玩笑,但它確實是漫反射的結果,這個我們在第六章課程中提到過。

 float dif = clamp(dot(normal, lightDirection), 0.3, 1.);
 col = dif * co.col + backgroundColor * .2;

因爲我們是基於漫反射和表面的法線給地板着色的,如果你要移除太陽斑點,你就需要在計算地板的顏色時候忽略光照的效果。

如果光線是被擋在相機的後面,這也許不是個問題。但如果你需要一個場景,裏面有地板,並且相機會全景的轉動,那麼你就會想要移除所有的光斑了。

移除太陽光點或者太陽光照的思路是考慮給每個場景分配一個ID。首先通過光線步進算法得到一個採樣點,然後檢查這個點是否在地板上,最後通過ID區分地板和其他的物體,從而達到移除光照的效果。

  // Rotation matrix around the X axis.
mat3 rotateX(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(1, 0, 0),
        vec3(0, c, -s),
        vec3(0, s, c)
    );
}

// Rotation matrix around the Y axis.
mat3 rotateY(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(c, 0, s),
        vec3(0, 1, 0),
        vec3(-s, 0, c)
    );
}

// Rotation matrix around the Z axis.
mat3 rotateZ(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(c, -s, 0),
        vec3(s, c, 0),
        vec3(0, 0, 1)
    );
}

// Identity matrix.
mat3 identity() {
    return mat3(
        vec3(1, 0, 0),
        vec3(0, 1, 0),
        vec3(0, 0, 1)
    );
}

const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float PRECISION = 0.001;

struct Surface {
    float sd; // signed distance value
    vec3 col; // color
    int id; // identifier for each surface/object
};

/*
Surface IDs:
1. Floor
2. Box
*/

Surface sdBox( vec3 p, vec3 b, vec3 offset, vec3 col, mat3 transform)
{
  p = (p - offset) * transform;
  vec3 q = abs(p) - b;
  float d = length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
  return Surface(d, col, 2);
}

Surface sdFloor(vec3 p, vec3 col) {
  float d = p.y + 1.;
  return Surface(d, col, 1);
}

Surface minWithColor(Surface obj1, Surface obj2) {
  if (obj2.sd < obj1.sd) return obj2;
  return obj1;
}

Surface sdScene(vec3 p) {
  vec3 floorColor = vec3(.5 + 0.3*mod(floor(p.x) + floor(p.z), 2.0));
  Surface co = sdFloor(p, floorColor);
  co = minWithColor(co, sdBox(p, vec3(1), vec3(0, 0.5, -4), vec3(1, 0, 0), identity()));
  return co;
}

Surface rayMarch(vec3 ro, vec3 rd, float start, float end) {
  float depth = start;
  Surface co; // closest object

  for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
    vec3 p = ro + depth * rd;
    co = sdScene(p);
    depth += co.sd;
    if (co.sd < PRECISION || depth > end) break;
  }
  
  co.sd = depth;
  
  return co;
}

vec3 calcNormal(in vec3 p) {
    vec2 e = vec2(1.0, -1.0) * 0.0005; // epsilon
    return normalize(
      e.xyy * sdScene(p + e.xyy).sd +
      e.yyx * sdScene(p + e.yyx).sd +
      e.yxy * sdScene(p + e.yxy).sd +
      e.xxx * sdScene(p + e.xxx).sd);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;
  vec3 backgroundColor = vec3(0.835, 1, 1);

  vec3 col = vec3(0);
  vec3 ro = vec3(0, 0, 3); // ray origin that represents camera position
  
  const float PI = 3.14159265359;
  vec3 rd = normalize(vec3(uv, -1));
  rd *= rotateY(sin(iTime * 0.5) * PI); // 0.5 is used to slow the animation down

  Surface co = rayMarch(ro, rd, MIN_DIST, MAX_DIST); // closest object

  if (co.sd > MAX_DIST) {
    col = backgroundColor; // ray didn't hit anything
  } else {
    vec3 p = ro + rd * co.sd; // point on cube or floor we discovered from ray marching
    vec3 normal = calcNormal(p);
            
    // check material ID        
    if( co.id == 1 ) // floor
    {
        col = co.col;
    } else {
      // lighting
      vec3 lightPosition = vec3(2, 2, 7);
      vec3 lightDirection = normalize(lightPosition - p);

      // color
      float dif = clamp(dot(normal, lightDirection), 0.3, 1.); // diffuse reflection
      col = dif * co.col + backgroundColor * .2; // Add a bit of background color to the diffuse color
    }
  }

  // Output to screen
  fragColor = vec4(col, 1.0);
}

利用此種方法,地板的和光線的作用會看起來有點不一樣了,但是光斑卻不見了。


通過給每個表面,材質或者物體一個id,在執行完光線步進算法之後,我們就能追蹤到哪個物體被擊中。這種方法在給物體單獨上色的時候非常有用。

理解 iMouse

Shadertoy 爲我們提供了一系列全局變量,你可以在你的着色器代碼中使用他們更好的與屏幕交互。新建一個着色器,點擊左上角的箭頭,旁邊的文字是Shader inputs。你就能看到一個全局變量的列表。

下面就這我們可以在Shadertoy着色器中使用的全局變量:

Shader Inputs
uniform vec3      iResolution;           // 視口分辨率viewport resolution (in pixels)
uniform float     iTime;                 // 着色器運行時間 shader playback time (in seconds)
uniform float     iTimeDelta;            // 渲染時間 render time (in seconds)
uniform int       iFrame;                // 着色器運行幀率 shader playback frame
uniform float     iChannelTime[4];       // 通道運行時間 channel playback time (in seconds)
uniform vec3      iChannelResolution[4]; // 通道分辨率channel resolution (in pixels)
uniform vec4      iMouse;                // 鼠標像素座標mouse pixel coords. xy: current (if MLB down), zw: click
uniform samplerXX iChannel0..3;          // 輸入通道input channel. XX = 2D/Cube
uniform vec4      iDate;                 // 年,月,日(year, month, day, time in seconds)
uniform float     iSampleRate;           // 聲音樣本sound sample rate (i.e., 44100)

其中,iMouse保存了鼠標點擊畫布上時的位置信息。這個變量是vec4類型的,他裏面保存了你點擊鼠標左鍵時的四個信息。

  vec4 mouse = iMouse;

mouse.xy = 最後一次鼠標按下時的位置 mouse position during last button down 
abs(mouse.zw) = 最後一次鼠標點擊的位置 mouse position during last button click
sign(mouze.z) = 鼠標被按下 button is down (positive if down)
sign(mouze.w) = 點擊了鼠標 button is clicked (positive if clicked)

mouse click 表示你點擊鼠標之後立即發生的事情,mouse down 事件表示你按下鼠標並且一直按住它的時候發生的事情。

在Inigo Quilez 的這篇教程中,展示瞭如何使用存儲在iMouse中的每一塊數據。當你在場景中點擊任意地方,會出現一個白色的圈圈。如果你按住鼠標不放然後移動鼠標,一條黃色線會在兩個圈圈之間出現。一旦你鬆開,黃色的線就會消失。

我們在這篇教程中唯一關心的是鼠標的座標位置。我們在這裏做了一個小小的示例,向你展示如何使的鼠標在畫布中移動圓圈。我們看看下面的代碼吧:

  float sdfCircle(vec2 uv, float r, vec2 offset) {
  float x = uv.x - offset.x;
  float y = uv.y - offset.y;
  
  float d = length(vec2(x, y)) - r;
  
  return step(0., -d);
}

vec3 drawScene(vec2 uv, vec2 mp) {
  vec3 col = vec3(0);
  float blueCircle = sdfCircle(uv, 0.1, mp);
  col = mix(col, vec3(0, 1, 1), blueCircle);
  
  return col;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = fragCoord/iResolution.xy - 0.5; // <-0.5,0.5>
  uv.x *= iResolution.x/iResolution.y; // fix aspect ratio
  
  // mp = mouse position of the last click
  vec2 mp = iMouse.xy/iResolution.xy - 0.5; // <-0.5,0.5>
  mp.x *= iResolution.x/iResolution.y; // fix aspect ratio

  vec3 col = drawScene(uv, mp);

  // Output to screen
  fragColor = vec4(col,1.0);
}

請注意我們獲取鼠標的位置非常類似我們獲取UV的座標位置。通過下面這種方式,歸一化座標:

  vec2 mp = iMouse.xy/iResolution.xy // range is between 0 and 1

通過減去0.5,會將鼠標的座標歸一化到0和1,我這裏將鼠標座標歸一化 -0.5到0.5之間。

  vec2 mp = iMouse.xy/iResolution.xy - 0.5 // range is between -0.5 and 0.5

用鼠標平移相機

現在我們知道了如何使用iMouse全局變量,讓我們應用到相機裏面去吧。用鼠標來控制光源ro的位置,從而移動相機:

  vec2 mouse = iMouse.xy / iResolution.xy - 0.5; // <-0.5,0.5>
  vec3 ro = vec3(mouse.x, mouse.y, 3); // 當你點擊畫布並且拖動鼠標,光源會隨着鼠標移動 ray origin will move as you click on the canvas and drag the mouse

如果你點擊畫布,拖動鼠標,你會發現相機在-0.5和0.5的範圍區間沿着x軸和y軸移動。畫布的中心是點(0,0),同時可以讓相機位置回到畫布的中心:

如果你想平移得更遠,你可以對他們進行乘法運算:

  vec2 mouse = iMouse.xy / iResolution.xy - 0.5; // <-0.5,0.5>
  vec3 ro = vec3(2. * mouse.x, 2. * mouse.y, 3);


用鼠標擺動相機

我們可以通過改變theta的值來擺動我們的相機,theta就是我們通過旋轉矩陣(例如:ratateXrotateY以及rotateZ)處理後得到的角度。請確保你不再使用鼠標來控制光源了。否則,你會發現很怪異的現象。

沿着yaw軸左右擺動相機:

  vec2 mouse = iMouse.xy / iResolution.xy - 0.5; // <-0.5,0.5>
  vec3 rd = normalize(vec3(uv, -1)); // ray direction
  rd *= rotateY(mouse.x); // apply yaw

目前,我們的mouse.x 的範圍是在-0.5到0.5之間,如果我們把範圍改成π到-π之間,那麼我們的代碼就看起來更爲合理了。要做到這點,我們使用mix函數,它生來就是用來做線性差值的,所以對於重新設定區間值再合適不過了。我們將mouse.x的區間 <-0.5, 0.5> 改爲 <-π, π>.

vec2 mouse = iMouse.xy / iResolution.xy - 0.5; // <-0.5,0.5>
vec3 rd = normalize(vec3(uv, -1)); // ray direction
rd *= rotateY(mix(-PI, PI, mouse.x)); // apply yaw with a 360 degree range

現在我們可以360度旋轉相機了。

你可能會想如何使用mouse.y呢,我們可以用這個值上下襬動相機。意味着我們可以利用rotateX函數。

  vec2 mouse = iMouse.xy / iResolution.xy - 0.5; // <-0.5,0.5>
  vec3 rd = normalize(vec3(uv, -1)); // ray direction
  rd *= rotateX(mouse.y); // apply pitch

這樣就可以讓相機在-0.5到0.5之間上下襬動。

如果你想要用鼠標用mouse.x改變yaw軸角度,並且同時用mouse.y改變pitch軸的角度,那麼我們需要將這兩個矩陣結合在一起:

  vec2 mouse = iMouse.xy / iResolution.xy - 0.5; // <-0.5,0.5>
  vec3 rd = normalize(vec3(uv, -1));
  rd *= rotateY(mouse.x) * rotateX(mouse.y); // apply yaw and pitch

現在你可以在我們的場景中自由地旋轉擺動相機了!通過擺動相機,可以手動解決我們在3D場景中的一些困難了。在Unity或者Blender這種軟件當中自帶了一個非常強大的3D相機。完整的代碼如下:

  // Rotation matrix around the X axis.
mat3 rotateX(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(1, 0, 0),
        vec3(0, c, -s),
        vec3(0, s, c)
    );
}

// Rotation matrix around the Y axis.
mat3 rotateY(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(c, 0, s),
        vec3(0, 1, 0),
        vec3(-s, 0, c)
    );
}

// Rotation matrix around the Z axis.
mat3 rotateZ(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(c, -s, 0),
        vec3(s, c, 0),
        vec3(0, 0, 1)
    );
}

// Identity matrix.
mat3 identity() {
    return mat3(
        vec3(1, 0, 0),
        vec3(0, 1, 0),
        vec3(0, 0, 1)
    );
}

const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float PRECISION = 0.001;

struct Surface {
    float sd; // signed distance value
    vec3 col; // color
    int id; // identifier for each surface/object
};

/*
Surface IDs:
1. Floor
2. Box
*/

Surface sdBox( vec3 p, vec3 b, vec3 offset, vec3 col, mat3 transform)
{
  p = (p - offset) * transform;
  vec3 q = abs(p) - b;
  float d = length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
  return Surface(d, col, 2);
}

Surface sdFloor(vec3 p, vec3 col) {
  float d = p.y + 1.;
  return Surface(d, col, 1);
}

Surface minWithColor(Surface obj1, Surface obj2) {
  if (obj2.sd < obj1.sd) return obj2;
  return obj1;
}

Surface sdScene(vec3 p) {
  vec3 floorColor = vec3(.5 + 0.3*mod(floor(p.x) + floor(p.z), 2.0));
  Surface co = sdFloor(p, floorColor);
  co = minWithColor(co, sdBox(p, vec3(1), vec3(0, 0.5, -4), vec3(1, 0, 0), identity()));
  return co;
}

Surface rayMarch(vec3 ro, vec3 rd, float start, float end) {
  float depth = start;
  Surface co; // closest object

  for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
    vec3 p = ro + depth * rd;
    co = sdScene(p);
    depth += co.sd;
    if (co.sd < PRECISION || depth > end) break;
  }

  co.sd = depth;

  return co;
}

vec3 calcNormal(in vec3 p) {
    vec2 e = vec2(1.0, -1.0) * 0.0005; // epsilon
    return normalize(
      e.xyy * sdScene(p + e.xyy).sd +
      e.yyx * sdScene(p + e.yyx).sd +
      e.yxy * sdScene(p + e.yxy).sd +
      e.xxx * sdScene(p + e.xxx).sd);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;
  vec3 backgroundColor = vec3(0.835, 1, 1);

  vec3 col = vec3(0);
  vec3 ro = vec3(0, 0, 3); // ray origin that represents camera position
  
  vec2 mouse = iMouse.xy / iResolution.xy - 0.5; // <-0.5,0.5>
  vec3 rd = normalize(vec3(uv, -1)); // ray direction
  rd *= rotateY(mouse.x) * rotateX(mouse.y); // apply yaw and pitch


  Surface co = rayMarch(ro, rd, MIN_DIST, MAX_DIST); // closest object

  if (co.sd > MAX_DIST) {
    col = backgroundColor; // ray didn't hit anything
  } else {
    vec3 p = ro + rd * co.sd; // point on cube or floor we discovered from ray marching
    vec3 normal = calcNormal(p);

    // check material ID        
    if( co.id == 1 ) // floor
    {
        col = co.col;
    } else {
      // lighting
      vec3 lightPosition = vec3(2, 2, 7);
      vec3 lightDirection = normalize(lightPosition - p);

      // color
      float dif = clamp(dot(normal, lightDirection), 0.3, 1.); // diffuse reflection
      col = dif * co.col + backgroundColor * .2; // Add a bit of background color to the diffuse color
    }
  }

  // Output to screen
  fragColor = vec4(col, 1.0);
}

總結

本篇教程中,我們學習瞭如何在六個自由的角度移動相機。我們會學會如何沿着x、y和z軸平移先相機,也學習瞭如何沿着yaw、pitch和roll軸擺動我們的相機。利用我們今天學會的知識,你可以在3D場景中調試bug以及做一些動畫交互。

資源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章