學(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)境提供的微弱光亮。對(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 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)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評(píng)論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評(píng)論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評(píng)論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評(píng)論 0 291
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評(píng)論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評(píng)論 2 380

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