天天看點

談談Processing 3D世界 五顔色燈光材質(Material)

接下來,讓我們來說說材質和燈光。

顔色

在說燈光前先簡單說一下顔色。

現實世界中有無數種顔色,每一個物體都有它們自己的顔色。我們要做的工作是使用(有限的)數字來模拟真實世界中(無限)的顔色。 當使用RGB模式時,顔色由紅色(Red)、綠色(Green)和藍色(Blue)三個分量組成。每通道可以定義256個灰階。那麼我們便可以顯示 1千6百萬種顔色 :

256*256*256 =  16777216.

談談Processing 3D世界 五顔色燈光材質(Material)

固有色

正因為空間中有了光線,我們才能感受到顔色。當有光源反射到我們眼中時,我們就能察覺到物體的顔色。大自然中有的物體會反射全部的光波,比如白紙。那麼白色就是白紙的固有色;有的物體會吸收藍色和綠色,隻反射紅色的光波,比如紅旗。那麼紅旗的固有色就是紅色。

固有色指物體本身的顔色,即吸收光波的能力。

燈光

ambientLight()

directionalLight()

lightFalloff()

lights()

lightSpecular()

noLights()

normal()

pointLight()

spotLight()

這裡就是Processing全部的燈光函數啦!Processing為我們準備了四種經典的燈光,環境光、平行光、點光,以及聚光燈。 讓我們依次來觀察一下:

環境光(Ambient Lighting)

我們可以把環境光了解為場景中主要光源在大氣中均勻散射産生的無處不在的光源。比如落日會讓所有的東西都渡上一層黃色;夜晚,所有東西都會偏藍。在影片中,環境光對場景中人物的心理塑造很重要。随意在電影中截一張圖,使用吸色管工具取色。就會發現影片中很少有真正純黑(0,0,0)的地方,任何地方或多或少都會有一些環境光。

陽光在陰霾的大氣和地面中均勻散射使所有景物看起來都是冰冷的藍色。

談談Processing 3D世界 五顔色燈光材質(Material)

珠光在屋内四處反射,使屋内的景物看起來暖暖的。

談談Processing 3D世界 五顔色燈光材質(Material)

環境光不會産生陰影,因為環境光在空氣中的散射是無處不在的。是以如果場景中隻有環境光的話(這種情況在現實中幾乎不存在),我們隻能看到物體的平面剪影。

談談Processing 3D世界 五顔色燈光材質(Material)

産生上圖這種現象,是因為這兩個球體的固有色為白色,當藍色的環境光照射球體時,球體隻可能反射回藍色。 那麼計算球體顔色的公式為:

result = objectColor * ambient; // 固有色 * 環境光
       = color(1.0, 1.0, 1.0) * color(0.0, 0.0, 0.65); // 注意這裡也将顔色機關化。0.65 = 0.65*255 = 165.75
       = color(0.0, 0.0, 0.65);
           

由此可知當物體有自己豐富的固有色時(比如貼上紋理,每一個像素的顔色都不同),環境光對物體會産生偏色的效果。當然環境光也可以是白光。

正因如此,環境光不需要方位,因為它無處不在。具體實作也很簡單:

size(100, 100, P3D);
background(0);
noStroke();
ambientLight(51, 102, 126);
translate(20, 50, 0);
sphere(30);
translate(60, 0, 0);
sphere(30);
           

平行光(Directional Lighting)

平行光也稱為定向光,比如太陽光就是一種平行光。當一個光源很遠的時候,來自光源的每條光線接近于平行。這看起來就像所有的光線來自于同一個方向,無論物體和觀察者在哪兒。

談談Processing 3D世界 五顔色燈光材質(Material)

因為所有光線都是平行的,是以光線的位置在哪兒仍然無所謂。我們隻需要比環境光多注意一個燈光方向就行。

directionalLight(r, g, b, rx, ry, rz);

具體實作:

size(100, 100, P3D);
background(0);
noStroke();
directionalLight(51, 102, 126, -1, 0, 0);
translate(20, 50, 0);
sphere(30);
           

點光源(Point Lighting)

好了,終于說道點光源。點光源算是我們日常生活中碰到最多的光源了。燈光、燭光、火光、星光都是點光源。

談談Processing 3D世界 五顔色燈光材質(Material)

點光源與平行光顯著的差別就是,點光源是向周身360°發光的。是以點光源在乎自身所處的位置,卻不在乎方向。而且點光源還有一個重要的屬性-衰減(Attenuation)。簡單來說,就是離光源越近,光線越亮;離光線越遠,光線越暗。 在大多數3D仿真的場景中,我們更希望去模拟一個僅僅能照亮靠近光源點附近場景的光源,而不是照亮整個場景的光源。

pointLight(r, g, b, x, y, z);

點光源的實作:

size(100, 100, P3D);
background(0);
noStroke();
pointLight(51, 102, 126, 35, 40, 36);
translate(80, 50, 0);
sphere(30);
           

衰減

随着距離漸遠,光線會逐漸減弱,這是光線在媒體傳播過程中的特性。但是如果我們隻是簡單的線性減弱光源,稍微精緻一點的場合就無法蒙混過關了。在真實世界,通常光在近處時非常亮,但是一個光源的亮度,開始的時候減少的非常快,之後随着距離的增加,減少的速度會慢下來。我們需要一種不同的方程來減少光的亮度。

來看看牛逼的人寫出來的公式(咱們大普羅塞斯也是用的這個公式):

談談Processing 3D世界 五顔色燈光材質(Material)

這裡的d代表物體表面到光源的距離。為了計算衰減值,我們定義3個(可配置)項:常數項Kc,一次項Kl和二次項Kq。

  • 常數項(constant)通常是1.0,它的作用是保證分母永遠不會比1小。
  • 一次項用于與距離值相乘,通常稱為線性衰減(linear)。
  • 二次項用于與距離的平方相乘,這通常稱為二次衰減(quadratic)。二次項在距離比較近的時候比一次項更小,但是當距離遠到時候,比一次項更大。有時我們隻用線性衰減就可以實作效果了。

這裡下圖展示了100以内範圍,二次衰減的效果。

談談Processing 3D世界 五顔色燈光材質(Material)

選擇正确的值

但是,我們把這三個項設定為什麼值呢?正确的值的設定由很多因素決定:環境、你希望光所覆寫的距離範圍、光的類型等。大多數場合,這是經驗的問題,也要适度調整。下面的表格展示一些各項的值,它們模拟現實(某種類型的)光源,覆寫特定的半徑(距離)。第一欄定義一個光的距離,它覆寫所給定的項。這些值是大多數光的良好開始,它是來自Ogre3D的維基的禮物:

距離 常數項 一次項 二次項
7 1.0 0.7 1.8
13 1.0 0.35 0.44
20 1.0 0.22 0.20
32 1.0 0.14 0.07
50 1.0 0.09 0.032
65 1.0 0.07 0.017
100 1.0 0.045 0.0075
160 1.0 0.027 0.0028
200 1.0 0.022 0.0019
325 1.0 0.014 0.0007
600 1.0 0.007 0.0002
3250 1.0 0.0014 0.000007

Processing的衰減辦法

大普羅塞斯中為我們提供的衰減函數是:  lightFalloff(constant, linear, quadratic);

現在我們應該一目了然了。普羅塞斯把計算都替我們隐藏了。 具體的實作:

size(100, 100, P3D);
noStroke();
background(0);
lightFalloff(1.0, 0.001, 0.0);
pointLight(150, 250, 150, 50, 50, 50);
beginShape();
vertex(0, 0, 0);
vertex(100, 0, -100);
vertex(100, 100, -100);
vertex(0, 100, 0);
endShape(CLOSE);
           

聚光燈(Spot Lighting)

我們都見過探照燈,例如召喚蝙蝠俠那個..好吧,我們都見過電筒。聚光燈的光照有一定的角度,大于光照角度外的物體不會被照射。

談談Processing 3D世界 五顔色燈光材質(Material)

Processing用于實作聚光燈的函數是: 

spotLight(r, g, b,      // 燈光顔色           x, y, z,      // 燈光位置           dx, dy, dz,   // 燈光方向           angle,        // 光切角           concentration // 邊緣軟化           )

具體實作:

size(100, 100, P3D); 
int concentration = 600;  // Try 1 -> 10000
background(0); 
noStroke(); 
spotLight(51, 102, 126, 50, 50, 400, 
          0, 0, -1, PI/16, concentration); 
translate(80, 50, 0); 
sphere(30);
           

好了,基礎燈光我們說完了。進階燈光處理我們稍後在說,在這之前我們來看看材質。

材質(Material)

ambient() emissive() shiniess() specular()

Processing的材質函數就這四個。研究了一下沒研究出個是以然?。。。 這四個函數實際上都是設定一個Phone(馮氏)材質球的算法參數。它們不能獨立使用,需要配合着一起使用。

談談Processing 3D世界 五顔色燈光材質(Material)
  • ambient(rgb)用于設定材質的光源反射能力,實際上就是控制材質的固有色。
  • emissive(rgb)用于設定材質的自發光能力,有時我們希望物體在黑暗中能自己發出光源。比如幽靈的眼眸。
  • shiniess(shine)用于設定材質的高光發散或緊縮的屬性,shine是一個幂的指數,值一般在1.0-10.0之間。
  • speclar(rgb)用于設定材質的鏡面反射能力,鏡面反射是指光源射向物體後反射出來的光源,鏡子反光就是最好的例子。它同散射的差別在于,隻有一定的角度才能看見反射光。  

但問題是,這些函數不能引入貼圖,那有個毛用呀?僅僅一個texture()隻能設定一個固有基本色貼圖,姑且當你是給ambient()貼上了吧。那emissive的自發光貼圖,speclar的鏡面貼圖不能使用設定這些屬性有個毛用呀。别告訴我就隻能設定基本材質球。。。。感覺像是未完成的東西。 後面等我有空再研究研究,也許有人知道也說不定。。 實在不行隻好從着色器下手。

下面是燈光應用執行個體: 

談談Processing 3D世界 五顔色燈光材質(Material)
// 視窗屬性
int w = 720;
int h = 480;

// 定義頂點
PVector[] ver;
PVector[] face;
PVector[][] uv;
PVector[] cubesPos;
PImage tex;

// 攝影機屬性
PVector cameraPos, cameraTarget, up, cameraFront;
float focalLength; // 焦距
float fov, aspect, zNear, zFar;
float yaw, pitch;  // 偏航、俯仰

float[] angle = new float[8];

void settings() {
  //fullScreen();
  size(w, h, P3D);
}

void setup() {
  initVertices(); // 初始化頂點資料
  initLocation(); // 初始化網格位置
  initTexture();  // 初始化紋理
  initCamera();   // 初始化攝影機
  
  // 其他
  noStroke();
  for (int i = 0; i < 8; i++) {
    angle[i] = random(2*PI); 
  }
}

void draw() {
  // 清楚緩沖區
  background(0);
  // 事件處理
  do_movement();
  
  // 設定攝影機
  cameraTarget = PVector.add(cameraPos, cameraFront);
  camera(cameraPos.x,    cameraPos.y,    cameraPos.z,
         cameraTarget.x, cameraTarget.y, cameraTarget.z,
         up.x,           up.y,           up.z);
  perspective(fov, aspect, zNear, zFar);
  
  // 繪制evn
  ambientLight(1, 10, 35);
  sphere(2500.0);
  
  // 設定燈光
  ambientLight(20, 50, 126);
  directionalLight(75, 75, 75, 0, 0, -1);
  lightFalloff(1.0, 0.005, 0.0);
  PVector pointPos = new PVector(350, 200, 200); 
  pointLight(255, 170, 30, pointPos.x, pointPos.y, pointPos.z);
  pointLight(255, 170, 30, pointPos.x, pointPos.y, pointPos.z);
  // 檢視點光源位置
  //pushMatrix();
  //translate(pointPos.x, pointPos.y, pointPos.z);
  //box(10);
  //popMatrix();
  
  // 繪制圖形
  for (int n = 0; n < cubesPos.length; n++) {
    
    pushMatrix();
      // 配置設定模型網格位置
      translate(cubesPos[n].x, cubesPos[n].y, cubesPos[n].z);
      
      pushMatrix();
        // 單個模型矩陣變換
        translate(width/2, height/2, -100.0);
        rotateX(angle[n]);
        rotateY(angle[n]*0.3);
        rotateZ(angle[n]*0.05);
        randomSeed(n*100);
        angle[n] += 0.005 * random(0, 1);
        scale(200);
        
        // 繪制單個網格物體
        //fill(255, 127, 39);
        for (int i = 0; i < face.length; i++) {
          beginShape();
          texture(tex);
          //     x                    , y                    , z                    , u         , v
          vertex(ver[int(face[i].x)].x, ver[int(face[i].x)].y, ver[int(face[i].x)].z, uv[i][0].x, uv[i][0].y);
          vertex(ver[int(face[i].y)].x, ver[int(face[i].y)].y, ver[int(face[i].y)].z, uv[i][1].x, uv[i][1].y);
          vertex(ver[int(face[i].z)].x, ver[int(face[i].z)].y, ver[int(face[i].z)].z, uv[i][2].x, uv[i][2].y);
          endShape(TRIANGLES);
        }
      popMatrix();
    popMatrix();
  }
  
  // 列印資訊
  text("w、s、a、d          - Move the camera.\nPress the mouse - turn the camera.", 10,10);
}
           

Camera标簽頁和data标簽頁在上一節有。這裡就不再貼一遍了。