獲取示例代碼
在基本光照中為大家介紹了環(huán)境光和漫反射光構(gòu)成的基本光照模型。本文將為大家介紹Blinn-Phong光照模型,通過環(huán)境光,漫反射光和高光渲染出更加真實(shí)的物體。
Blinn-Phong光照模型分為三個(gè)部分,環(huán)境光,漫反射光,高光(也可以理解為鏡面反射光),將這三種光和物體本來的顏色融合,就可以計(jì)算出最終的顏色了。下面我們先來介紹這三種光的物理含義。
環(huán)境光
真實(shí)世界里,就算在晚上,物體也不一定會(huì)處于完全黑暗狀態(tài),總會(huì)有一些微弱的光源照亮物體,比如月光,被燈光照亮的天空等等,為了模擬這種情況,我們使用環(huán)境光這一概念來表達(dá)周圍環(huán)境提供的微弱光亮。對(duì)于環(huán)境光我們可以使用環(huán)境光顏色ambientColor
和強(qiáng)度ambientIndensity
來表示。ambientColor
乘以ambientIndensity
就是環(huán)境光產(chǎn)生的顏色。
如果你處于大森林中又恰巧烏云蔽月,應(yīng)該就不用計(jì)算環(huán)境光了。
漫反射光
表面粗糙的物體會(huì)將光反射到各個(gè)方向,無論我們從哪個(gè)方向觀察它,都會(huì)看到一樣的光照效果。我們把這些光稱為漫反射光。
白光是由各種不同顏色(波長)的光組成的。如果我們看到物體是紅色,就表明這個(gè)物體把除了紅光外的其他光都吸收了。我們可以用向量乘法輕松的來表達(dá)這一物理現(xiàn)象。
物體顏色 = (1.0, 0.0, 0.0) // r,g,b
白光 = (1.0, 1.0, 1.0) // r,g,b
白光照射物體后 = (1.0, 1.0, 1.0) * (1.0, 0.0, 0.0) = (1.0 * 1.0, 1.0 * 0.0, 1.0 * 0.0) = (1.0, 0.0, 0.0)
有了這個(gè)公式,我們可以隨意調(diào)整物體的顏色和光的顏色,然后通過它計(jì)算出最終色。除了顏色我們還需要計(jì)算接受到的光照強(qiáng)度,這個(gè)在基本光照中已有提及。我們通過光線和法線的夾角來計(jì)算接受到的光照強(qiáng)度。強(qiáng)度乘以上面計(jì)算出的最終色就是漫反射光產(chǎn)生的顏色。
高光
表面光滑的物體,比如汽車的烤漆,光滑的金屬,會(huì)對(duì)光進(jìn)行鏡面反射,如果你的眼睛剛好在光線的反射光附近,那么你將看到強(qiáng)烈的光照。傳統(tǒng)的Phong光照模型就是先計(jì)算光線相對(duì)于當(dāng)前法線的反射光,然后將視線向量和反射光向量點(diǎn)乘來計(jì)算觀察到的反射光強(qiáng)度。但是這種算法在視線和反射光夾角大于90度時(shí)效果不佳,所以本文采用Blinn-Phong模型來計(jì)算反射光強(qiáng)度。
我們先求解出視線向量和光線向量的半向量H,就是將視線向量和光線向量規(guī)范化后相加再規(guī)范化。然后將H和法向量N點(diǎn)乘來計(jì)算高光強(qiáng)度
specularStrength
。計(jì)算出強(qiáng)度后,我們會(huì)使用一個(gè)參數(shù)smoothness
再次處理強(qiáng)度,specularStrength = pow(specularStrength, smoothness)
,smoothness
越大,高光就會(huì)使平面顯得越光滑。紅色是
pow(cos, 100)
曲線,綠色是cos
曲線。
通過上圖我們可以看出來,對(duì)cos
進(jìn)行冪運(yùn)算,指數(shù)越大,曲線邊緣下降越快,而且越窄。我們看到的光滑表面一般都具有較窄和快速衰減的高光,所以smoothness
取較大值時(shí),可以模擬光滑的表面。
Fragment Shader
了解完原理后,接下來我們來看看新的Fragment Shader。
precision highp float;
// 平行光
struct Directionlight {
vec3 direction;
vec3 color;
float indensity;
float ambientIndensity;
};
struct Material {
vec3 diffuseColor;
vec3 ambientColor;
vec3 specularColor;
float smoothness; // 0 ~ 1000 越高顯得越光滑
};
varying vec3 fragNormal;
varying vec2 fragUV;
varying vec3 fragPosition;
uniform float elapsedTime;
uniform Directionlight light;
uniform Material material;
uniform vec3 eyePosition;
uniform mat4 normalMatrix;
uniform mat4 modelMatrix;
uniform sampler2D diffuseMap;
void main(void) {
vec3 normalizedLightDirection = normalize(-light.direction);
vec3 transformedNormal = normalize((normalMatrix * vec4(fragNormal, 1.0)).xyz);
// 計(jì)算漫反射
float diffuseStrength = dot(normalizedLightDirection, transformedNormal);
diffuseStrength = clamp(diffuseStrength, 0.0, 1.0);
vec3 diffuse = diffuseStrength * light.color * material.diffuseColor * light.indensity;
// 計(jì)算環(huán)境光
vec3 ambient = vec3(light.ambientIndensity) * material.ambientColor;
// 計(jì)算高光
vec4 eyeVertexPosition = modelMatrix * vec4(fragPosition, 1.0);
vec3 eyeVector = normalize(eyePosition - eyeVertexPosition.xyz);
vec3 halfVector = normalize(normalizedLightDirection + eyeVector);
float specularStrength = dot(halfVector, transformedNormal);
specularStrength = pow(specularStrength, material.smoothness);
vec3 specular = specularStrength * material.specularColor * light.color * light.indensity;
// 最終顏色計(jì)算
vec3 finalColor = diffuse + ambient + specular;
gl_FragColor = vec4(finalColor, 1.0);
}
首先我們定義了兩個(gè)結(jié)構(gòu)體struct Directionlight
和struct Material
,來描述平行光照和物體材質(zhì)。下面是他們中成員變量的定義。
Directionlight
-
vec3 direction;
描述光照方向,和之前的lightDirection
含義一致。 -
vec3 color;
光的顏色 -
float indensity;
光照強(qiáng)度,推薦值0~1,大于1的值可能會(huì)使物體大面積曝光過度。 -
float ambientIndensity;
環(huán)境光強(qiáng)度,可以放到結(jié)構(gòu)體外,不是每個(gè)燈光必須的參數(shù)。
Material
-
vec3 diffuseColor;
物體的顏色,可以使用diffuse貼圖來代替diffuseColor。 -
vec3 ambientColor;
環(huán)境光顏色,可以放到結(jié)構(gòu)體外,如果你想所有的物體共享相同的環(huán)境光顏色的話。 -
vec3 specularColor;
高光顏色,我們可以為高光指定顏色,或者讓高光的顏色和燈光顏色相同。 -
float smoothness;
平滑度,從0到1000。
然后我們用這兩個(gè)結(jié)構(gòu)定義燈光和材質(zhì)的uniform
。
uniform Directionlight light;
uniform Material material;
接下來分別計(jì)算三種光照顏色。
漫反射
// 計(jì)算漫反射
float diffuseStrength = dot(normalizedLightDirection, transformedNormal);
diffuseStrength = clamp(diffuseStrength, 0.0, 1.0);
vec3 diffuse = diffuseStrength * light.color * material.diffuseColor * light.indensity;
normalizedLightDirection
是反向并規(guī)范后的光照向量,transformedNormal
是變換后的法線,利用他們的點(diǎn)乘計(jì)算出強(qiáng)度diffuseStrength
,再將光照顏色,材質(zhì)顏色,漫反射強(qiáng)度和光照強(qiáng)度相乘就得出了最終漫反射的顏色diffuse
。
環(huán)境光
vec3 ambient = vec3(light.ambientIndensity) * material.ambientColor;
環(huán)境光強(qiáng)度乘以環(huán)境光顏色即是最終的環(huán)境光顏色ambient
。
高光
// 計(jì)算高光
vec4 worldVertexPosition = modelMatrix * vec4(fragPosition, 1.0);
vec3 eyeVector = normalize(eyePosition - worldVertexPosition.xyz);
vec3 halfVector = normalize(normalizedLightDirection + eyeVector);
float specularStrength = dot(halfVector, transformedNormal);
specularStrength = pow(specularStrength, material.smoothness);
vec3 specular = specularStrength * material.specularColor * light.color * light.indensity;
先計(jì)算出頂點(diǎn)在世界坐標(biāo)的位置worldVertexPosition
,然后求出視線的向量eyeVector
,通過視線向量和光線向量求解出半向量halfVector
。接著使用半向量halfVector
和法向量transformedNormal
進(jìn)行點(diǎn)乘計(jì)算出高光強(qiáng)度specularStrength
,最后把高光強(qiáng)度specularStrength
進(jìn)行冪運(yùn)算,調(diào)整高光效果。將高光強(qiáng)度,高光顏色,光照顏色,光照強(qiáng)度相乘,計(jì)算出高光顏色specular
。
在最后一步中,如果你希望你的高光顏色只受
material.specularColor
控制,可以把light.color
從公式中移除。這取決于你想要的效果。
最終,我們通過簡單的相加計(jì)算出最終顏色。
// 最終顏色計(jì)算
vec3 finalColor = diffuse + ambient + specular;
gl_FragColor = vec4(finalColor, 1.0);
OC代碼
在OC代碼中,我們在ViewController
里增加了表示光照和材質(zhì)的結(jié)構(gòu)體,以及變量。
typedef struct {
GLKVector3 direction;
GLKVector3 color;
GLfloat indensity;
GLfloat ambientIndensity;
} Directionlight;
typedef struct {
GLKVector3 diffuseColor;
GLKVector3 ambientColor;
GLKVector3 specularColor;
GLfloat smoothness; // 0 ~ 1000 越高顯得越光滑
} Material;
@property (assign, nonatomic) Directionlight light;
@property (assign, nonatomic) Material material;
在viewDidLoad
中進(jìn)行了初始化。
Directionlight defaultLight;
defaultLight.color = GLKVector3Make(1, 1, 1); // 白色的燈
defaultLight.direction = GLKVector3Make(1, -1, 0);
defaultLight.indensity = 1.0;
defaultLight.ambientIndensity = 0.1;
self.light = defaultLight;
Material material;
material.ambientColor = GLKVector3Make(1, 1, 1);
material.diffuseColor = GLKVector3Make(0.1, 0.1, 0.1);
material.specularColor = GLKVector3Make(1, 1, 1);
material.smoothness = 300;
self.material = material;
你可以任意調(diào)整顏色來修改渲染結(jié)果,或者運(yùn)行程序,使用內(nèi)置的UI調(diào)整各個(gè)變量的值。我增加了一些Slider來調(diào)整這些參數(shù)。
#pragma mark - Arguments Adjust
- (IBAction)smoothnessAdjust:(UISlider *)sender {
Material _material = self.material;
_material.smoothness = sender.value;
self.material = _material;
}
- (IBAction)indensityAdjust:(UISlider *)sender {
Directionlight _light = self.light;
_light.indensity = sender.value;
self.light = _light;
}
- (IBAction)lightColorAdjust:(UISlider *)sender {
GLKVector3 yuv = GLKVector3Make(1.0, (cos(sender.value) + 1.0) / 2.0, (sin(sender.value) + 1.0) / 2.0);
Directionlight _light = self.light;
_light.color = [self colorFromYUV:yuv];
if (sender.value == sender.maximumValue) {
_light.color = GLKVector3Make(1, 1, 1);
}
self.light = _light;
sender.backgroundColor = [UIColor colorWithRed:_light.color.r green:_light.color.g blue:_light.color.b alpha:1.0];
}
- (IBAction)ambientColorAdjust:(UISlider *)sender {
GLKVector3 yuv = GLKVector3Make(1.0, (cos(sender.value) + 1.0) / 2.0, (sin(sender.value) + 1.0) / 2.0);
Material _material = self.material;
_material.ambientColor = [self colorFromYUV:yuv];
if (sender.value == sender.maximumValue) {
_material.ambientColor = GLKVector3Make(1, 1, 1);
}
self.material = _material;
sender.backgroundColor = [UIColor colorWithRed:_material.ambientColor.r green:_material.ambientColor.g blue:_material.ambientColor.b alpha:1.0];
}
- (IBAction)diffuseColorAdjust:(UISlider *)sender {
GLKVector3 yuv = GLKVector3Make(1.0, (cos(sender.value) + 1.0) / 2.0, (sin(sender.value) + 1.0) / 2.0);
Material _material = self.material;
_material.diffuseColor = [self colorFromYUV:yuv];
if (sender.value == sender.maximumValue) {
_material.diffuseColor = GLKVector3Make(1, 1, 1);
}
if (sender.value == sender.minimumValue) {
_material.diffuseColor = GLKVector3Make(0.1, 0.1, 0.1);
}
self.material = _material;
sender.backgroundColor = [UIColor colorWithRed:_material.diffuseColor.r green:_material.diffuseColor.g blue:_material.diffuseColor.b alpha:1.0];
}
- (IBAction)specularColorAdjust:(UISlider *)sender {
GLKVector3 yuv = GLKVector3Make(1.0, (cos(sender.value) + 1.0) / 2.0, (sin(sender.value) + 1.0) / 2.0);
Material _material = self.material;
_material.specularColor = [self colorFromYUV:yuv];
if (sender.value == sender.maximumValue) {
_material.specularColor = GLKVector3Make(1, 1, 1);
}
self.material = _material;
sender.backgroundColor = [UIColor colorWithRed:_material.specularColor.r green:_material.specularColor.g blue:_material.specularColor.b alpha:1.0];
}
- (GLKVector3)colorFromYUV:(GLKVector3)yuv {
float Cb, Cr, Y;
float R ,G, B;
Y = yuv.x * 255.0;
Cb = yuv.y * 255.0 - 128.0;
Cr = yuv.z * 255.0 - 128.0;
R = 1.402 * Cr + Y;
G = -0.344 * Cb - 0.714 * Cr + Y;
B = 1.772 * Cb + Y;
return GLKVector3Make(MIN(1.0, R / 255.0), MIN(1.0, G / 255.0), MIN(1.0, B / 255.0));
}
在渲染代碼中,將燈光和材質(zhì)傳遞給Shader。
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
[super glkView:view drawInRect:rect];
[self.objects enumerateObjectsUsingBlock:^(GLObject *obj, NSUInteger idx, BOOL *stop) {
[obj.context active];
[obj.context setUniform1f:@"elapsedTime" value:(GLfloat)self.elapsedTime];
[obj.context setUniformMatrix4fv:@"projectionMatrix" value:self.projectionMatrix];
[obj.context setUniformMatrix4fv:@"cameraMatrix" value:self.cameraMatrix];
[obj.context setUniform3fv:@"eyePosition" value:self.eyePosition];
[obj.context setUniform3fv:@"light.direction" value:self.light.direction];
[obj.context setUniform3fv:@"light.color" value:self.light.color];
[obj.context setUniform1f:@"light.indensity" value:self.light.indensity];
[obj.context setUniform1f:@"light.ambientIndensity" value:self.light.ambientIndensity];
[obj.context setUniform3fv:@"material.diffuseColor" value:self.material.diffuseColor];
[obj.context setUniform3fv:@"material.ambientColor" value:self.material.ambientColor];
[obj.context setUniform3fv:@"material.specularColor" value:self.material.specularColor];
[obj.context setUniform1f:@"material.smoothness" value:self.material.smoothness];
[obj draw:obj.context];
}];
}
注意傳遞struct
給Shader的寫法,我們需要為struct中每一個(gè)成員單獨(dú)傳遞值。除了燈光和材質(zhì)外,我還傳遞了eyePosition
給Shader,這是攝像機(jī)的位置,用來計(jì)算視線向量。
到此,一個(gè)Blinn-Phong光照模型就構(gòu)建完了。現(xiàn)在我們可以將上篇文章中未介紹的mtl文件在此做一個(gè)說明了。mtl文件保存了物體材質(zhì)的信息,基本的結(jié)構(gòu)如下。
newmtl mtlName
Ns 94.117647
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
d 1.0
illum 2
map_Kd XXX
map_Ks XXX
map_Ka XXX
map_Bump XXX
map_d XXX
-
newmtl xxx
表示材質(zhì)的名稱,在obj文件中可以通過名稱來引用材質(zhì)。 -
Ns 94.117647
高光調(diào)整參數(shù),類似于我們的smoothness
-
Ka 1.000000 1.000000 1.000000
環(huán)境光顏色 -
Kd 0.640000 0.640000 0.640000
漫反射顏色 -
Ks 0.500000 0.500000 0.500000
高光顏色 -
d 1.0
溶解度,為0時(shí)完全透明,1完全不透明。 -
illum 2
光照模式,0 禁止光照, 1 只有環(huán)境光和漫反射光,2 所有光照啟用。 -
map_XX
map開頭的都是各種顏色的貼圖,如果有值,就是使用貼圖來代替純色,map_Bump
表示法線貼圖,在后面會(huì)有文章詳細(xì)介紹。
了解到他們的含義后,我們就可以很輕易的將他們運(yùn)用到Blinn-Phong光照模型中了。
本文主要介紹了平行光的Blinn-Phong光照模型。如果是點(diǎn)光源呢?其實(shí)只要通過點(diǎn)光源的位置和頂點(diǎn)位置計(jì)算出光線向量,剩下的計(jì)算都是一樣的。我將在分支chapter18-point
實(shí)現(xiàn)點(diǎn)光源的效果,如果你有興趣,可以前去clone查看。