學(xué)習(xí)OpenGL ES之高級(jí)光照

本系列所有文章目錄

獲取示例代碼


基本光照中為大家介紹了環(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 Directionlightstruct 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查看雹锣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末网沾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蕊爵,更是在濱河造成了極大的恐慌辉哥,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件攒射,死亡現(xiàn)場離奇詭異醋旦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)匆篓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門浑度,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鸦概,你說我怎么就攤上這事箩张。” “怎么了窗市?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵先慷,是天一觀的道長。 經(jīng)常有香客問我咨察,道長论熙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任摄狱,我火速辦了婚禮脓诡,結(jié)果婚禮上无午,老公的妹妹穿的比我還像新娘。我一直安慰自己祝谚,他們只是感情好宪迟,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著交惯,像睡著了一般次泽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上席爽,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天意荤,我揣著相機(jī)與錄音,去河邊找鬼只锻。 笑死玖像,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的炬藤。 我是一名探鬼主播御铃,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沈矿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咬腋,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對情侶失蹤羹膳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后根竿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陵像,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年寇壳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了醒颖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壳炎,死狀恐怖泞歉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匿辩,我是刑警寧澤腰耙,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站铲球,受9級(jí)特大地震影響挺庞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜稼病,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一选侨、第九天 我趴在偏房一處隱蔽的房頂上張望掖鱼。 院中可真熱鬧,春花似錦援制、人聲如沸锨用。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽增拥。三九已至,卻和暖如春寻歧,著一層夾襖步出監(jiān)牢的瞬間掌栅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工码泛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猾封,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓噪珊,卻偏偏與公主長得像晌缘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子痢站,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容