天天看点

谈谈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标签页在上一节有。这里就不再贴一遍了。