<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      代碼改變世界

      用JavaScript玩轉計算機圖形學(一)光線追蹤入門

      2010-03-29 00:05  Milo Yip  閱讀(83999)  評論(114)    收藏  舉報

      系列簡介

      記得小時候讀過一本關于計算機圖形學(computer graphics, CG)的入門書,從此就愛上了CG。本系列希望,采用很多人認識的JavaScript語言去分享CG,令更多人有機會接觸,并愛上CG。

      本系列的特點之一,是讀者能在瀏覽器里直接執行代碼,也可重覆修改代碼測試。透過這種互動,也許能更深刻體會內容。讀者只要懂得JavaScript(因為JavaScript很簡單,學過Java/C/C++/C#之類的語言也應沒問題)和一點點線性代數(linear algebra)就可以了。

      筆者在大學期間并沒有修讀CG課程,雖然看過相關書籍,始終未親手做過全域光照的渲染器,本文也作為個人的學習分享。此外,筆者也差不多十年沒接觸JavaScript,希望各位不吝賜教。

      本文簡介

      多數程序員聽到3D CG,就會聯想到Direct3D、OpenGL等API。事實上,這些流行的API主要為實時渲染(real-time rendering)而設,一般采用光柵化(rasterization)方式,渲染大量的三角形(或其他幾何圖元種類(primitive types))。這種基于光柵化的渲染系統,只支持局部光照(local illumination)。換句話說,渲染幾何圖形的一個像素時,光照計算只能取得該像素的資訊,而不能訪問其他幾何圖形資訊。理論上,陰影(shadow)、反射(reflection)、折射(refraction)等為全局光照(global illumination)效果,實際上,柵格化渲染系統可以使用預處理(如陰影貼圖(shadow mapping)、環境貼圖(environment mapping))去模擬這些效果。

      全局光照計算量大,一般也沒有特殊硬件加速(通常只使用CPU而非GPU),所以只適合離線渲染(offline rendering),例如3D Studio Max、Maya等工具。其中一個支持全局光照的方法,稱為光線追蹤(ray tracing)。光線追蹤能簡單直接地支持陰影、反射、折射,實現起來亦非常容易。本文的例子里,只用了數十行JavaScript代碼(除canvas外不需要其他特殊插件和庫),就能實現一個支持反射的光線追蹤渲染器。光線追蹤可以用來學習很多計算機圖形學的課題,也許比學習Direct3D/OpenGL更容易。現在,先介紹點理論吧。

      光線追蹤

      光柵化渲染,簡單地說,就是把大量三角形畫到屏幕上。當中會采用深度緩沖(depth buffer, z-buffer),來解決多個三角形重疊時的前后問題。三角形數目影響效能,但三角形在屏幕上的總面積才是主要瓶頸。

      光線追蹤,簡單地說,就是從攝影機的位置,通過影像平面上的像素位置(比較正確的說法是取樣(sampling)位置),發射一束光線到場景,求光線和幾何圖形間最近的交點,再求該交點的著色。如果該交點的材質是反射性的,可以在該交點向反射方向繼續追蹤。光線追蹤除了容易支持一些全局光照效果外,亦不局限于三角形作為幾何圖形的單位。任何幾何圖形,能與一束光線計算交點(intersection point),就能支持。

      上圖(來源)顯示了光線追蹤的基本方式。要計算一點是否在陰影之內,也只須發射一束光線到光源,檢測中間有沒有障礙物而已。不過光源和陰影留待下回分解。

      初試畫板

      光線追蹤的輸出只是一個影像(image),所謂影像,就是二維顏色數組。

      要在瀏覽器內,用JavaScript生成一個影像,目前可以使用HTML 5的<canvas>。但現時Internet Explorer(直至版本8)還不支持<canvas>,其他瀏覽器如Chrome、Firefox、Opera等就可以。

      以下是一個簡單的實驗,把每個象素填入顏色,左至右越來越紅,上至下越來越綠。

       

      左邊的canvas定義如下:

      <canvas width="256" height="256" id="testCanvas"></canvas>
      

      修改代碼試試看

      • 把第三個pixels[i++] = 0 改為255 (即藍色全開)
      • 把第四個pixels[i++] = 255 改為128 (alpha=128)
      • 可以只修改兩個for循環里面的代碼,畫一個國際象棋棋盤么?

      這實驗說明,從canvas取得的影像資料canvas.getImageData(...).data是個一維數組,該數組每四個元素代表一個象素(按紅, 綠, 藍, alpha排列),這些象素在影像中從上至下、左至右排列。

      解決實驗平臺的技術問題后,可開始從基礎類別開始實現。

      基礎類

      筆者使用基于物件(object-based)的方式編寫JavaScript。

      三維向量

      三維向量(3D vector)可謂CG里最常用型別了。這里三維向量用Vector3類實現,用(x, y, z)表示。 Vector3亦用來表示空間中的點(point),而不另建類。先看代碼:

      Vector3 = function(x, y, z) { this.x = x; this.y = y; this.z = z; };
      
      Vector3.prototype = {
          copy : function() { return new Vector3(this.x, this.y, this.z); },
          length : function() { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); },
          sqrLength : function() { return this.x * this.x + this.y * this.y + this.z * this.z; },
          normalize : function() { var inv = 1/this.length(); return new Vector3(this.x * inv, this.y * inv, this.z * inv); },
          negate : function() { return new Vector3(-this.x, -this.y, -this.z); },
          add : function(v) { return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z); },
          subtract : function(v) { return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z); },
          multiply : function(f) { return new Vector3(this.x * f, this.y * f, this.z * f); },
          divide : function(f) { var invf = 1/f; return new Vector3(this.x * invf, this.y * invf, this.z * invf); },
          dot : function(v) { return this.x * v.x + this.y * v.y + this.z * v.z; },
          cross : function(v) { return new Vector3(-this.z * v.y + this.y * v.z, this.z * v.x - this.x * v.z, -this.y * v.x + this.x * v.y); }
      };
      
      Vector3.zero = new Vector3(0, 0, 0);
      

      這些類方法(如normalize、negate、add等),如果傳回Vector3類對象,都會傳回一個新建構的Vector3。這些三維向量的功能很簡單,不在此詳述。注意multiply和divide是與純量(scalar)相乘和相除。

      Vector3.zero用作常量,避免每次重新構建。值得一提,這些常量必需在prototype設定之后才能定義。

      光線

      所謂光線(ray),從一點向某方向發射也。數學上可用參數函數(parametric function)表示:

      \mathbf{r}(t) = \mathbf{o} + t\mathbfw0obha2h00, t \geq 0

      當中,o即發謝起點(origin),d為方向。在本文的例子里,都假設d為單位向量(unit vector),因此t為距離。實現如下:

      Ray3 = function(origin, direction) { this.origin = origin; this.direction = direction; }
      
      Ray3.prototype = {
          getPoint : function(t) { return this.origin.add(this.direction.multiply(t)); }
      };
      

      球體

      球體(sphere)是其中一個最簡單的立體幾何圖形。這里只考慮球體的表面(surface),中心點為c、半徑為r的球體表面可用等式(equation)表示:

      \left \| \mathbf{x} - \mathbf{c} \right \| = r

      如前文所述,需要計算光線和球體的最近交點。只要把光線x = r(t)代入球體等式,把該等式求解就是交點。為簡化方程,設v=o - c,則:

      \begin{align*} \left\| \mathbf{v} +t\mathbfw0obha2h00 \right\| &= r \\ \left\| \mathbf{v} +t\mathbfw0obha2h00 \right\|^2 &= r^2 \\ (\mathbf{v}+t\mathbfw0obha2h00) \cdot (\mathbf{v}+t\mathbfw0obha2h00) - r^2 &= 0 \\ \mathbf{v}^2 + 2t(\mathbfw0obha2h00 \cdot \mathbf{v}) +t^2 \mathbfw0obha2h00^2 - r^2 &= 0 \\ (\mathbfw0obha2h00^2)t^2 + (2\mathbfw0obha2h00 \cdot \mathbf{v})t + (\mathbf{v}^2 - r^2) &= 0 \end{align*}

      因為d為單位向量,所以二次方的系數可以消去。 t的二次方程式的解為

      \begin{align*} t&=\frac{-2\mathbfw0obha2h00 \cdot \mathbf{v}\pm \sqrt{(2\mathbfw0obha2h00 \cdot \mathbf{v})^2 - 4(\mathbf{v}^ 2 - r^2)} }{2} \\ &= -\mathbfw0obha2h00 \cdot \mathbf{v}\pm \sqrt{(\mathbfw0obha2h00 \cdot \mathbf{v})^2 - (\mathbf {v}^2 - r^2)} \end{align*}

      若根號內為負數,即相交不發生。另外,由于這里只需要取最近的交點,因此正負號只需取負號。代碼實現如下:

      Sphere = function(center, radius) { this.center = center; this.radius = radius; };
      
      Sphere.prototype = {
          copy : function() { return new Sphere(this.center.copy(), this.radius.copy()); },
      
          initialize : function() {
              this.sqrRadius = this.radius * this.radius;
          },
      
          intersect : function(ray) {
              var v = ray.origin.subtract(this.center);
              var a0 = v.sqrLength() - this.sqrRadius;
              var DdotV = ray.direction.dot(v);
      
              if (DdotV <= 0) {
                  var discr = DdotV * DdotV - a0;
                  if (discr >= 0) {
                      var result = new IntersectResult();
                      result.geometry = this;
                      result.distance = -DdotV - Math.sqrt(discr);
                      result.position = ray.getPoint(result.distance);
                      result.normal = result.position.subtract(this.center).normalize();
                      return result;
                  }
              }
      
              return IntersectResult.noHit;
          }
      };
      

      實現代碼時,盡快用最少的運算剔除沒相交的情況(Math.sqrt是比較慢的函數)。另外,預計算了球體半徑r的平方,此為一個優化。

      這里用到一個IntersectResult類,這個類只用來記錄交點的幾何物件(geometry)、距離(distance)、位置(position)和法向量(normal)。 IntersectResult.noHit的geometry為null,代表光線沒有和任何幾何物件相交。

      IntersectResult = function() {
          this.geometry = null;
          this.distance = 0;
          this.position = Vector3.zero;
          this.normal = Vector3.zero;
      };
      
      IntersectResult.noHit = new IntersectResult();
      

      攝影機

      攝影機在光線追蹤系統里,負責把影像的取樣位置,生成一束光線。

      由于影像的大小是可變的(多少像素寬x多少像素高),為方便計算,這里設定一個統一的取樣座標(sx, sy),以左下角為(0,0),右上角為(1 ,1)。

      從數學角度來說,攝影機透過投影(projection),把三維空間投射到二維空間上。常見的投影有正投影(orthographic projection)、透視投影(perspective projection)等等。這里首先實現透視投影。 ]]>

      透視攝影機

      透視攝影機比較像肉眼和真實攝影機的原理,能表現遠小近大的觀察方式。透視投影從視點(view point/eye position),向某個方向觀察場景,觀察的角度范圍稱為視野(field of view, FOV)。除了定義觀察的向前(forward)是那個方向,還需要定義在影像平面中,何謂上下和左右。為簡單起見,暫時不考慮寬高不同的影像,FOV同時代表水平和垂直方向的視野角度。

      上圖顯示,從攝影機上方顯示的幾個參數。 forward和right分別是向前和向右的單位向量。

      因為視點是固定的,光線的起點不變。要生成光線,只須用取樣座標(sx, sy)計算其方向d。留意FOV和s的關系為:

      \tan \frac{FOV}{2} = s

      把sx從[0, 1]映射到[-1,1],就可以用right向量和s,來計算r向量,代碼如下:

      PerspectiveCamera = function(eye, front, up, fov) { this.eye = eye; this.front = front; this.refUp = up; this.fov = fov; };
      
      PerspectiveCamera.prototype = {
          initialize : function() {
              this.right = this.front.cross(this.refUp);
              this.up = this.right.cross(this.front);
              this.fovScale = Math.tan(this.fov * 0.5 * Math.PI / 180) * 2;
          },
      
          generateRay : function(x, y) {
              var r = this.right.multiply((x - 0.5) * this.fovScale);
              var u = this.up.multiply((y - 0.5) * this.fovScale);
              return new Ray3(this.eye, this.front.add(r).add(u).normalize());
          }
      };
      

      代碼中fov為度數,轉為弧度才能使用Math.tan()。另外,fovScale預先乘了2,因為sx映射到[-1,1]每次都要乘以2。 sy和sx的做法一樣,把兩個在影像平面的向量,加上forward向量,就成為光線方向d。因之后的計算需要,最后把d變成單位向量。

      渲染測試

      寫了Vector3、Ray3、Sphere、IntersectResult、Camera五個類之后,終于可以開始渲染一點東西出來!

      基本的做法是遍歷影像的取樣座標(sx, sy),用Camera把(sx, sy)轉為Ray3,和場景(例如Sphere)計算最近交點,把該交點的屬性轉為顏色,寫入影像的相對位置里。

      把不同的屬性渲染出來,是CG編程里經常用的測試和調試手法。筆者也是用此方法,修正了一些錯誤。

      渲染深度

      深度(depth)就是從IntersectResult取得最近相交點的距離,因深度的范圍是從零至無限,為了把它顯示出來,可以把它的一個區間映射到灰階。這里用[0, maxDepth]映射至[255, 0],即深度0的像素為白色,深度達maxDepth的像素為黑色。

      // renderDepth.htm
      function renderDepth(canvas, scene, camera, maxDepth) {
          // 從canvas取得imgdata和pixels,跟之前的代碼一樣
          // ...
      
          scene.initialize();
          camera.initialize();
      
          var i = 0;
          for (var y = 0; y < h; y++) {
              var sy = 1 - y / h;
              for (var x = 0; x < w; x++) {
                  var sx = x / w;            
                  var ray = camera.generateRay(sx, sy);
                  var result = scene.intersect(ray);
                  if (result.geometry) {
                      var depth = 255 - Math.min((result.distance / maxDepth) * 255, 255);
                      pixels[i    ] = depth;
                      pixels[i + 1] = depth;
                      pixels[i + 2] = depth;
                      pixels[i + 3] = 255;
                  }
                  i += 4;
              }
          }
      
          ctx.putImageData(imgdata, 0, 0);
      }

       

      這里的觀看方向是,正X軸向右,正Y軸向上,正Z軸向后。

      修改代碼試試看

      • 改變球體的位置
      • 改變球體的半徑
      • 改變fov(PerspectiveCamera最后的參數)
      • 改變maxDepth(renderDepth最后的參數)
      • 改變攝影機的方向,例如向左轉一點點(記得要是單位向量啊!可以用new Vector(...).normalize())

      渲染法向量

      相交測試也計算了幾何物件在相交位置的法向量,這里也可把它視覺化。法向量是一個單位向量,其每個元素的范圍是[-1, 1]。把單位向量映射到顏色的常用方法為,把(x, y, z)映射至(r, g, b),范圍從[-1, 1]映射至[0, 255]。

      // renderNormal.htm
      function renderNormal(canvas, scene, camera) {
          // ...
                  if (result.geometry) {
                      pixels[i    ] = (result.normal.x + 1) * 128;
                      pixels[i + 1] = (result.normal.y + 1) * 128;
                      pixels[i + 2] = (result.normal.z + 1) * 128;
                      pixels[i + 3] = 255;
                  }
          // ...
      }
      

       

      球體上方的法向量是接近(0, 1, 0),所以是淺綠色(0.5, 1, 0.5)。

      修改代碼試試看

      • 從球體的正上方往下看

      材質

      渲染深度和法向量只為測試和調試,要顯示物件的"真實"顏色,需要定義該交點向某方向(如往視點的方向)發出的光的顏色,稱之為幾個圖形的材質(material )。

      材質的接口為function sample(ray, posiiton, normal) ,傳回顏色Color的對象。這是個極簡陋的接口,臨時做一些效果出來,有機會再詳談。

      顏色

      顏色在CG里最簡單是用紅、綠、藍三個通道(color channel)。為實現簡單的Phong材質,還加入了對顏色的簡單操作。

      Color = function(r, g, b) { this.r = r; this.g = g; this.b = b };
      
      Color.prototype = {
          copy : function() { return new Color(this.r, this.g, this.b); },
          add : function(c) { return new Color(this.r + c.r, this.g + c.g, this.b + c.b); },
          multiply : function(s) { return new Color(this.r * s, this.g * s, this.b * s); },
          modulate : function(c) { return new Color(this.r * c.r, this.g * c.g, this.b * c.b); }
      };
      
      Color.black = new Color(0, 0, 0);
      Color.white = new Color(1, 1, 1);
      Color.red = new Color(1, 0, 0);
      Color.green = new Color(0, 1, 0);
      Color.blue = new Color(0, 0, 1);
      

      這Color類很像Vector3類,值得留意的是,顏色有調制(modulate)操作,其意義為兩個顏色中每個顏色通道相乘。

      格子材質

      CG世界里,國際象棋棋盤是最常見的測試用紋理(texture)。這里不考慮紋理貼圖(texture mapping)的問題,只憑(x, z)坐標計算某位置發出黑色或白色的光(黑色的光不叫光吧,哈哈)。

      CheckerMaterial = function(scale, reflectiveness) { this.scale = scale; this.reflectiveness = reflectiveness; };
      
      CheckerMaterial.prototype = {
          sample : function(ray, position, normal) {
              return Math.abs((Math.floor(position.x * 0.1) + Math.floor(position.z * this.scale)) % 2) < 1 ? Color.black : Color.white;
          }
      };
      

      代碼中scale的意義為1坐標單位有多少個格子,例如scale=0.1即一個格子的大小為10x10。

      Phong材質

      這里實現簡單的Phong材質,因為未有光源系統,只用全域變量設置一個臨時的光源方向,并只計算漫射(diffuse)和鏡射(specular)。

      PhongMaterial = function(diffuse, specular, shininess, reflectiveness) {
          this.diffuse = diffuse;
          this.specular = specular;
          this.shininess = shininess;
          this.reflectiveness = reflectiveness;
      };
      
      // global temp
      var lightDir = new Vector3(1, 1, 1).normalize();
      var lightColor = Color.white;
      
      PhongMaterial.prototype = {
          sample: function(ray, position, normal) {
              var NdotL = normal.dot(lightDir);
              var H = (lightDir.subtract(ray.direction)).normalize();
              var NdotH = normal.dot(H);
              var diffuseTerm = this.diffuse.multiply(Math.max(NdotL, 0));
              var specularTerm = this.specular.multiply(Math.pow(Math.max(NdotH, 0), this.shininess));
              return lightColor.modulate(diffuseTerm.add(specularTerm));
          }
      };
      

      Phong的內容不在此述。

      渲染材質

      修改之前的渲染代碼,當碰到相交時,就向幾何對象取得material屬性,并調用sample方法函數取得顏色。

      // rayTrace.htm
      function rayTrace(canvas, scene, camera) {
          // ...
                  if (result.geometry) {
                      var color = result.geometry.material.sample(ray, result.position, result.normal);
                      pixels[i] = color.r * 255;
                      pixels[i + 1] = color.g * 255;
                      pixels[i + 2] = color.b * 255;
                      pixels[i + 3] = 255;
                  }
          // ...
      }
      

       

      修改代碼試試看

      • 改變fov,有了格子地板效果應該很明顯
      • 改變CheckerMaterial的scale
      • 把原來紅色的球改為綠色
      • 把原來紅色的球改為黃色
      • 改變shininess(PhongMaterial最后一個參數)

      多個幾何物件

      只渲染一個幾何物件太乏味,這節再加入一個無限平面,和介紹如何組合多個幾何物件。

      平面

      一個(無限)平面(Plane)在數學上可用等式定義:

      \mathbf{n} \cdot \mathbf{x} = d

      n為平面的法向量,d為空間原點至平面的最短距離。光線和平面的相交計算很簡單,這里不詳述了。

      Plane = function(normal, d) { this.normal = normal; this.d = d; };
      
      Plane.prototype = {
          copy : function() { return new plane(this.normal.copy(), this.d); },
      
          initialize : function() {
              this.position = this.normal.multiply(this.d);
          },
          
          intersect : function(ray) {
              var a = ray.direction.dot(this.normal);
              if (a >= 0)
                  return IntersectResult.noHit;
      
              var b = this.normal.dot(ray.origin.subtract(this.position));
              var result = new IntersectResult();
              result.geometry = this;
              result.distance = -b / a;
              result.position = ray.getPoint(result.distance);
              result.normal = this.normal;
              return result;
          }
      };
      

      并集

      把多個幾何物件結合起來,可以使用集(set)的概念。這里最容易實現的操作,就是并集(union),即光線要找到一組幾個圖形的最近交點。無需改其他代碼,只加入一個Union類就可以:

      Union = function(geometries) { this.geometries = geometries; };
      
      Union.prototype = {
          initialize: function() {
              for (var i in this.geometries)
                  this.geometries[i].initialize();
          },
          
          intersect: function(ray) {
              var minDistance = Infinity;
              var minResult = IntersectResult.noHit;
              for (var i in this.geometries) {
                  var result = this.geometries[i].intersect(ray);
                  if (result.geometry && result.distance < minDistance) {
                      minDistance = result.distance;
                      minResult = result;
                  }
              }
              return minResult;
          }
      };
      

      可以看到,這里利用Javascript的多型(polymorphism)的特性,完全不用修改原來的代碼,就可以擴展功能。

      如前所述,這里只考慮幾何幾何圖形的表面。如果考慮幾何圖形是實心的,就可以用構造實體幾何(constructive solid geometry, CSG)方法,提供并集、交集、補集等操作。容后再談。

      反射

      以上實現的,也只是局部照明。只要再加入一點點代碼,就可以實現反射。

      下圖說明反射向量的計算方法:

      把d投射到n上(因n是單位向量,只需要點乘即可),就可以計算d在n上的長度,把d減去這長度兩倍的法向量,就是反射向量r。數學上可寫成:

      \mathbf{r} = \mathbfw0obha2h00 - 2(\mathbf{d \cdot n})\bf{n}"

      一般材質并非完全反射(鏡子除外),因此這里為材質加上一個反射度(reflectiveness)的屬性。反射的功能很簡單,只要在碰到反射度非零的材質,就繼續向反射方向追蹤,并把結果按反射度來混合。例如一個材質的反射度為25%,則它傳回的顏色是75%本身顏色,加上25%反射傳回來的顏色。

      另外,不斷反射會做成大量的運算,甚至乎永遠不能停止(考慮攝影機在兩個鏡子中間)。因此要限制反射的次數。含反射功能的光線追蹤代碼如下:

      function rayTraceRecursive(scene, ray, maxReflect) {
          var result = scene.intersect(ray);
          
          if (result.geometry) {
              var reflectiveness = result.geometry.material.reflectiveness;
              var color = result.geometry.material.sample(ray, result.position, result.normal);
              color = color.multiply(1 - reflectiveness);
              
              if (reflectiveness > 0 && maxReflect > 0) {
                  var r = result.normal.multiply(-2 * result.normal.dot(ray.direction)).add(ray.direction);
                  ray = new Ray3(result.position, r);
                  var reflectedColor = rayTraceRecursive(scene, ray, maxReflect - 1);
                  color = color.add(reflectedColor.multiply(reflectiveness));
              }
              return color;
          }
          else
              return Color.black;
      }
      
      function rayTraceReflection(canvas, scene, camera, maxReflect) {
          // 從canvas取得imgdata和pixels,跟之前的代碼一樣
          // ...
      
          scene.initialize();
          camera.initialize();
      
          var i = 0;
          for (var y = 0; y < h; y++) {
              var sy = 1 - y / h;
              for (var x = 0; x < w; x++) {
                  var sx = x / w;
                  var ray = camera.generateRay(sx, sy);
                  var color = rayTraceRecursive(scene, ray, maxReflect);
                  pixels[i++] = color.r * 255;
                  pixels[i++] = color.g * 255;
                  pixels[i++] = color.b * 255;
                  pixels[i++] = 255;
              }
          }
      
          ctx.putImageData(imgdata, 0, 0);
      }
      

       

      修改代碼試試看

      • 改變一個球的reflectiveness,試試0、1及之間的數值
      • 改變maxReflect(rayTraceReflection最后一個參數)
      • 加入更多的球體(可用for循環啊……不過小心渲染時間太長)

      結語

      能體會到計算機圖形學的有趣之處么?百多行簡單的JavaScript代碼,就繪畫出像真的影像,那種滿足感實非筆墨所能形容。

      本文實現了一個簡單的光線追蹤渲染器,支持球體、平面、Phong材質、格子材質、多重反射等功能。讀者可以下載這組代碼,加入不同的擴展,也可以嘗試翻譯做熟悉的編程語言。很多光線追蹤用到的計算機圖形技術,也可以應用到實時圖形編程里,例如光源和材質的計算,基本上可以簡易翻譯做實時圖形的著色器(shader)編程。

      游戲里采用光柵化渲染技術已有二十年以上,這幾年的硬件發展,使其他渲染方法也能用于實時應用。光線追蹤和其他類似的方法,有個當今重要優點,就是能高度平行化。采樣之間并沒有依賴性,例如256x256=65536個采樣,理論上,可使用65536個機器/核心獨立執行追蹤,那么完成時間只是最慢的一個取樣所需的時間。

      筆者希望繼續撰寫這系列,例如包括以下內容:

      • 其他幾何圖形(長方體、柱體、三角形、曲面、高度場、等值面、……)
      • 光源(方向光源、點光源、聚光燈、陰影、ambient occlusion)
      • 材質(Phong-Blinn、Oren-Nayar、Torrance-Sparrow、折射、 Fresnel、BRDF、BSDF……)
      • 紋理(紋理座標、采樣、Perlin noise)
      • 攝影機模型(正投射、全景、景深)
      • 成像流程(漸進渲染、反鋸齒、后期處理)
      • 優化方法(場景剖分、低階優化)
      • 其他全局光照渲染方法

      祈望得到大家的意見反饋。

      參考

      更新

      • 2010年3月31日,網友HouSisong把本文代碼以C++實現,并完全保留了原設計,代碼可於他的博文下載。
      主站蜘蛛池模板: 国产区一区二区现看视频| 精品久久久中文字幕人妻| 精品无码久久久久国产电影| 深夜精品免费在线观看| 中文字幕第一页亚洲精品| 国产精品中文字幕av| 精精国产xxx在线观看| 国产亚洲精品成人aa片新蒲金| 国产一区国产二区在线视频| 成人区人妻精品一区二区| 色 亚洲 日韩 国产 综合| 万荣县| 国产精品视频一区二区三区无码| 国产av一区二区麻豆熟女| 中文字幕免费一二三区乱码| 国产v亚洲v天堂a无码99| 国产午夜福利精品视频| 一区二区三区四区亚洲综合| 一区二区三区放荡人妻| 国产精品大片中文字幕| 国产内射xxxxx在线| 国产在线视频不卡一区二区| 无码国产69精品久久久久网站| 灵璧县| 永久免费的av在线电影网| 亚洲国产成人综合自在线| 99re视频在线| 成人av午夜在线观看| 无码一级视频在线| 久久96热在精品国产高清| awww在线天堂bd资源在线| 欧洲码亚洲码的区别入口| 国产乱精品一区二区三区| 日本高清www无色夜在线视频 | 久久精品免视看国产成人| 婷婷丁香五月激情综合 | 丰满人妻被黑人猛烈进入| 网友自拍视频一区二区三区| 亚洲 制服 丝袜 无码| 桃花岛亚洲成在人线AV| 1精品啪国产在线观看免费牛牛|