獲取示例代碼
在基本光照中為大家介紹了環(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)境提供的微弱光亮。對于環(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ì)對光進(jìn)行鏡面反射,如果你的眼睛剛好在光線的反射光附近袜瞬,那么你將看到強(qiáng)烈的光照怜俐。傳統(tǒng)的Phong光照模型就是先計(jì)算光線相對于當(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
曲線帘营。
通過上圖我們可以看出來票渠,對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查看雹锣。