重新自學(xué)學(xué)習(xí)openGL 之光照投光物

概念

將光投射(Cast)到物體的光源叫做投光物(Light Caster)

分類

  • 定向光(Directional Light)
  • 點(diǎn)光源(Point Light)
  • 聚光(Spotlight)

定向光

當(dāng)一個(gè)光源處于很遠(yuǎn)的地方時(shí)余佃,來自光源的每條光線就會(huì)近似于互相平行甲葬。不論物體和/或者觀察者的位置,看起來好像所有的光都來自于同一個(gè)方向甸怕。當(dāng)我們使用一個(gè)假設(shè)光源處于無限遠(yuǎn)處的模型時(shí),它就被稱為定向光池户,因?yàn)樗乃泄饩€都有著相同的方向识窿,它與光源的位置是沒有關(guān)系的。

定向光非常好的一個(gè)例子就是太陽眠砾。太陽距離我們并不是無限遠(yuǎn),但它已經(jīng)遠(yuǎn)到在光照計(jì)算中可以把它視為無限遠(yuǎn)了托酸。所以來自太陽的所有光線將被模擬為平行光線褒颈,我們可以在下圖看到:


定向光

因?yàn)樗械墓饩€都是平行的,所以物體與光源的相對(duì)位置是不重要的获高,因?yàn)閷?duì)場景中每一個(gè)物體光的方向都是一致的哈肖。由于光的位置向量保持一致,場景中每個(gè)物體的光照計(jì)算將會(huì)是類似的念秧。

我們可以定義一個(gè)光線方向向量而不是位置向量來模擬一個(gè)定向光淤井。著色器的計(jì)算基本保持不變,但這次我們將直接使用光的direction向量而不是通過direction來計(jì)算lightDir向量摊趾。

struct Light{
    vec3 direction;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
...
void main(){
    vec3 lightDir = normalize(-light.direction);
...
}

注意我們首先對(duì)light.direction向量取反币狠。我們目前使用的光照計(jì)算需求一個(gè)從片段至光源的光線方向,但人們更習(xí)慣定義定向光為一個(gè)從光源出發(fā)的全局方向砾层。所以我們需要對(duì)全局光照方向向量取反來改變它的方向漩绵,它現(xiàn)在是一個(gè)指向光源的方向向量了。而且肛炮,記得對(duì)向量進(jìn)行標(biāo)準(zhǔn)化止吐,假設(shè)輸入向量為一個(gè)單位向量是很不明智的。

最終的lightDir向量將和以前一樣用在漫反射和鏡面光計(jì)算中侨糟。

最終結(jié)果如下圖


我們一直將光的位置和位置向量定義為vec3碍扔,但一些人會(huì)喜歡將所有的向量都定義為vec4。當(dāng)我們將位置向量定義為一個(gè)vec4時(shí)秕重,很重要的一點(diǎn)是要將w分量設(shè)置為1.0不同,這樣變換和投影才能正確應(yīng)用。然而,當(dāng)我們定義一個(gè)方向向量為vec4的時(shí)候二拐,我們不想讓位移有任何的效果(因?yàn)樗鼉H僅代表的是方向)服鹅,所以我們將w分量設(shè)置為0.0。
方向向量就會(huì)像這樣來表示:vec4(0.2f, 1.0f, 0.3f, 0.0f)百新。這也可以作為一個(gè)快速檢測光照類型的工具:你可以檢測w分量是否等于1.0企软,來檢測它是否是光的位置向量;w分量等于0.0饭望,則它是光的方向向量澜倦,這樣就能根據(jù)這個(gè)來調(diào)整光照計(jì)算了:

if(lightVector.w == 0.0) // 注意浮點(diǎn)數(shù)據(jù)類型的誤差
  // 執(zhí)行定向光照計(jì)算
else if(lightVector.w == 1.0)

// 根據(jù)光源的位置做光照計(jì)算(與上一節(jié)一樣)
你知道嗎:這正是舊OpenGL(固定函數(shù)式)決定光源是定向光還是位置光源(Positional Light Source)的方法,并根據(jù)它來調(diào)整光照杰妓。

點(diǎn)光源

定向光對(duì)于照亮整個(gè)場景的全局光源是非常棒的,但除了定向光之外我們也需要一些分散在場景中的點(diǎn)光源(Point Light)碘勉。點(diǎn)光源是處于世界中某一個(gè)位置的光源巷挥,它會(huì)朝著所有方向發(fā)光,但光線會(huì)隨著距離逐漸衰減验靡。想象作為投光物的燈泡和火把倍宾,它們都是點(diǎn)光源。


在之前的demo中胜嗓,我們一直都在使用一個(gè)(簡化的)點(diǎn)光源高职。我們?cè)诮o定位置有一個(gè)光源,它會(huì)從它的光源位置開始朝著所有方向散射光線辞州。但是怔锌,以上demo中定義的光源都是不會(huì)衰減的光源。在大部分的3D模擬中变过,我們都希望模擬的光源僅照亮光源附近的區(qū)域而不是整個(gè)場景埃元。

如果在場景中有十個(gè)箱子,我們使用不會(huì)衰減的光源.那么不管箱子距離光源是否很遠(yuǎn),都是會(huì)被照亮的. 如果想讓箱子被照亮的強(qiáng)度隨著光源的遠(yuǎn)近進(jìn)行變化.我們需要在shader中重新定義這樣的衰減公式才可以.

衰減

隨著光線傳播距離的增長逐漸削減光的強(qiáng)度通常叫做衰減(Attenuation)。
隨距離減少光強(qiáng)度的一種方式是使用一個(gè)線性方程媚狰。這樣的方程能夠隨著距離的增長線性地減少光的強(qiáng)度岛杀,從而讓遠(yuǎn)處的物體更暗。然而崭孤,這樣的線性方程通常會(huì)看起來比較假类嗤。在現(xiàn)實(shí)世界中,燈在近處通常會(huì)非常亮辨宠,但隨著距離的增加光源的亮度一開始會(huì)下降非骋怕啵快,但在遠(yuǎn)處時(shí)剩余的光強(qiáng)度就會(huì)下降的非常緩慢了彭羹。所以黄伊,我們需要一個(gè)不同的公式來減少光的強(qiáng)度。

幸運(yùn)的是一些聰明的人已經(jīng)幫我們解決了這個(gè)問題派殷。下面這個(gè)公式根據(jù)片段距光源的距離計(jì)算了衰減值还最,之后我們會(huì)將它乘以光的強(qiáng)度向量:


image.png

在這里d代表了片段距光源的距離墓阀。接下來為了計(jì)算衰減值,我們定義3個(gè)(可配置的)項(xiàng):常數(shù)項(xiàng)Kc拓轻、一次項(xiàng)Kl和二次項(xiàng)Kq斯撮。

  • 常數(shù)項(xiàng)通常保持為1.0,它的主要作用是保證分母永遠(yuǎn)不會(huì)比1小扶叉,否則的話在某些距離上它反而會(huì)增加強(qiáng)度勿锅,這肯定不是我們想要的效果。
  • 一次項(xiàng)會(huì)與距離值相乘枣氧,以線性的方式減少強(qiáng)度溢十。
  • 二次項(xiàng)會(huì)與距離的平方相乘,讓光源以二次遞減的方式減少強(qiáng)度达吞。二次項(xiàng)在距離比較小的時(shí)候影響會(huì)比一次項(xiàng)小很多张弛,但當(dāng)距離值比較大的時(shí)候它就會(huì)比一次項(xiàng)更大了。

由于二次項(xiàng)的存在酪劫,光線會(huì)在大部分時(shí)候以線性的方式衰退吞鸭,直到距離變得足夠大,讓二次項(xiàng)超過一次項(xiàng)覆糟,光的強(qiáng)度會(huì)以更快的速度下降刻剥。這樣的結(jié)果就是,光在近距離時(shí)亮度很高滩字,但隨著距離變遠(yuǎn)亮度迅速降低造虏,最后會(huì)以更慢的速度減少亮度。下面這張圖顯示了在100的距離內(nèi)衰減的效果:


image

你可以看到光在近距離的時(shí)候有著最高的強(qiáng)度麦箍,但隨著距離增長酗电,它的強(qiáng)度明顯減弱,并緩慢地在距離大約100的時(shí)候強(qiáng)度接近0内列。這正是我們想要的撵术。

公式的具體使用

但是,該對(duì)這三個(gè)項(xiàng)設(shè)置什么值呢话瞧?正確地設(shè)定它們的值取決于很多因素:環(huán)境嫩与、希望光覆蓋的距離、光的類型等交排。在大多數(shù)情況下划滋,這都是經(jīng)驗(yàn)的問題,以及適量的調(diào)整埃篓。下面這個(gè)表格顯示了模擬一個(gè)(大概)真實(shí)的处坪,覆蓋特定半徑(距離)的光源時(shí),這些項(xiàng)可能取的一些值同窘。第一列指定的是在給定的三項(xiàng)時(shí)光所能覆蓋的距離玄帕。這些值是大多數(shù)光源很好的起始點(diǎn),它們由Ogre3D的Wiki所提供:

距離 常數(shù)項(xiàng)Kc 一次項(xiàng)Kl 二次項(xiàng)Kq
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

你可以看到想邦,常數(shù)項(xiàng)Kc在所有的情況下都是1.0裤纹。一次項(xiàng)Kl為了覆蓋更遠(yuǎn)的距離通常都很小,二次項(xiàng)Kq甚至更小丧没。嘗試對(duì)這些值進(jìn)行實(shí)驗(yàn)鹰椒,看看它們?cè)谀愕膶?shí)現(xiàn)中有什么效果。在我們的環(huán)境中呕童,32到100的距離對(duì)大多數(shù)的光源都足夠了漆际。

點(diǎn)光源衰減編碼

為了實(shí)現(xiàn)衰減,在片段著色器中我們還需要三個(gè)額外的值:也就是公式中的常數(shù)項(xiàng)夺饲、一次項(xiàng)和二次項(xiàng)灿椅。

struct Light {
    vec3 position;  

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

shader 對(duì)點(diǎn)光源的處理

void main(){
   
    
    // 環(huán)境光
    vec3 diffuseT =vec3(texture2D(material.diffuse,v_texture));
    vec3 specularT =vec3(texture2D(material.specular,v_texture));

    vec3 ambient = light.ambient * diffuseT;

    // 漫反射
    vec3 norm = normalize(normal);
    vec3 lightDir = normalize(light.position - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * (diff * diffuseT);

  // 鏡面光
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess*128.0);
    vec3 specular = light.specular * (spec * specularT);

    
    float distance    = length(light.position - FragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance +
                               light.quadratic * (distance * distance));
    
    ambient  *= attenuation;
    diffuse   *= attenuation;
    specular *= attenuation;   
     vec3 result = ambient + diffuse + specular;

    gl_FragColor =vec4(result, 1.0);;

}

給shader綁定以及傳值

//綁定
  weakSelf.bindObject->uniforms[PL_UniformLocationLightPos] = glGetUniformLocation(self.shader.program, "light.position");
        weakSelf.bindObject->uniforms[PL_UniformLocationLightAmbient] = glGetUniformLocation(self.shader.program, "light.ambient");
        weakSelf.bindObject->uniforms[PL_UniformLocationLightSpecular] = glGetUniformLocation(self.shader.program, "light.specular");
        weakSelf.bindObject->uniforms[PL_UniformLocationLightTDiffuse] = glGetUniformLocation(self.shader.program, "light.diffuse");
          weakSelf.bindObject->uniforms[PL_UniformLocationLightConstant] = glGetUniformLocation(self.shader.program, "light.constant");
          weakSelf.bindObject->uniforms[PL_UniformLocationLightLinear] = glGetUniformLocation(self.shader.program, "light.linear");
          weakSelf.bindObject->uniforms[PL_UniformLocationLightQuadratic] = glGetUniformLocation(self.shader.program, "light.quadratic");

傳值
  PL_Light light;
    light.position =  GLKVector3Make(1.2f, 1.0f, 2.0f);
    light.ambient  =GLKVector3Make(0.2,0.2,0.2);
    light.diffuse  =GLKVector3Make(0.5,0.5,0.5);
    light.specular  =GLKVector3Make(1.0,1.0,1.0);
    light.constant = 1.0;
    light.linear = 0.09f;
    light.quadratic = 0.032f;
    glUniform3fv(self.bindObject->uniforms[PL_UniformLocationLightPos], 1, &light.position);
    glUniform3fv(self.bindObject->uniforms[PL_UniformLocationLightAmbient], 1, &light.ambient);
    glUniform3fv(self.bindObject->uniforms[PL_UniformLocationLightSpecular], 1, &light.specular);
    glUniform3fv(self.bindObject->uniforms[PL_UniformLocationLightTDiffuse], 1, &light.diffuse);
    glUniform1fv(self.bindObject->uniforms[PL_UniformLocationLightConstant], 1, &light.constant);
    glUniform1fv(self.bindObject->uniforms[PL_UniformLocationLightLinear], 1, &light.linear);
    glUniform1fv(self.bindObject->uniforms[PL_UniformLocationLightQuadratic], 1, &light.quadratic);

結(jié)果如下


點(diǎn)光源就是一個(gè)能夠配置位置和衰減的光源。它是我們光照工具箱中的又一個(gè)光照類型钞支。

聚光

聚光是位于環(huán)境中某個(gè)位置的光源,它只朝一個(gè)特定方向而不是所有方向照射光線操刀。這樣的結(jié)果就是只有在聚光方向的特定半徑內(nèi)的物體才會(huì)被照亮烁挟,其它的物體都會(huì)保持黑暗。聚光很好的例子就是路燈或手電筒骨坑。

OpenGL中聚光是用一個(gè)世界空間位置撼嗓、一個(gè)方向和一個(gè)切光角(Cutoff Angle)來表示的,切光角指定了聚光的半徑(譯注:是圓錐的半徑不是距光源距離那個(gè)半徑)欢唾。對(duì)于每個(gè)片段且警,我們會(huì)計(jì)算片段是否位于聚光的切光方向之間(也就是在錐形內(nèi)),如果是的話礁遣,我們就會(huì)相應(yīng)地照亮片段斑芜。下面這張圖會(huì)讓你明白聚光是如何工作的:

  • LightDir:從片段指向光源的向量。
  • SpotDir:聚光所指向的方向祟霍。
  • Phi?:指定了聚光半徑的切光角杏头。落在這個(gè)角度之外的物體都不會(huì)被這個(gè)聚光所照亮。
  • Thetaθ:LightDir向量和SpotDir向量之間的夾角沸呐。在聚光內(nèi)部的話θ值應(yīng)該比?值小醇王。

上面的圖需要看的仔細(xì)點(diǎn)
LightDir 指的是圖中黑線
φ指的是 紅線和藍(lán)線之間的夾角,這個(gè)角度需要我們?cè)O(shè)置
lightDir 和 spotDir 相乘 結(jié)果是 θ的余弦值,因此這里我們最好傳入到片段著色器一個(gè)切光角的余弦值進(jìn)行比較. 余弦值越大說明角度越小
SpotDir 是我們傳入的光的方向

LightDir 可以用光源位置和頂點(diǎn)位置求值
vec3 lightDir = normalize(light.position - FragPos);
SpotDir 這個(gè)需要我們直接指定聚光燈的方向
Thetaθ 可以通過上面的LightDirSpotDir 值求得
float theta = dot(lightDir, normalize(-light.direction));
Phi? 需要我們指定.
因此,我們給片段著色器傳入 光的位置放方向以及 Phi? 就可以計(jì)算出 我們需要的聚光效果了

所以我們要做的就是計(jì)算LightDir向量和SpotDir向量之間的點(diǎn)積(還記得它會(huì)返回兩個(gè)單位向量夾角的余弦值嗎?)崭添,并將它與切光角?
值對(duì)比寓娩。

手電筒

手電筒(Flashlight)是一個(gè)位于觀察者位置的聚光,通常它都會(huì)瞄準(zhǔn)玩家視角的正前方〖椋基本上說寞埠,手電筒就是普通的聚光,但它的位置和方向會(huì)隨著玩家的位置和朝向不斷更新排嫌。

從上面的分析中,聚光需要 光源的位置和方向以及切光角.我們可以重新定義光照為

struct Light{
    vec3 position;
    vec3 direction;
    float cutOff;
    
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    
    float constant;
    float linear;
    float quadratic;
};
uniform Light light;

傳值

 FL_Light light;
    light.position =  GLKVector3Make(0.0f, 0.0f, 3.0f);
    light.ambient  =GLKVector3Make(0.2,0.2,0.2);
    light.diffuse  =GLKVector3Make(0.5,0.5,0.5);
    light.specular  =GLKVector3Make(1.0,1.0,1.0);
    light.constant = 1.0;
    light.linear = 0.09f    ;
    light.quadratic = 0.032f;
//     0.0f, 0.0f, 3.0f
    const float YAW         = -90.0f;
    const float PITCH       =  0.0f;
    GLKVector3 front;
    front.x = cos(radians(YAW)) * cos(radians(PITCH));
    front.y = sin(radians(PITCH));
    front.z = sin(radians(YAW)) * cos(radians(PITCH));
    front = GLKVector3Normalize(front);
    light.direction = front;
    light.cutOff = cos(12.5*M_PI/180);
    glUniform3fv(self.bindObject->uniforms[FL_UniformLocationLightPos], 1, &light.position);
    glUniform3fv(self.bindObject->uniforms[FL_UniformLocationLightAmbient], 1, &light.ambient);
    glUniform3fv(self.bindObject->uniforms[FL_UniformLocationLightSpecular], 1, &light.specular);
    glUniform3fv(self.bindObject->uniforms[FL_UniformLocationLightTDiffuse], 1, &light.diffuse);
    glUniform1fv(self.bindObject->uniforms[FL_UniformLocationLightConstant], 1, &light.constant);
    glUniform1fv(self.bindObject->uniforms[FL_UniformLocationLightLinear], 1, &light.linear);
    glUniform1fv(self.bindObject->uniforms[FL_UniformLocationLightQuadratic], 1, &light.quadratic);
    glUniform3fv(self.bindObject->uniforms[FL_UniformLocationLightDirection], 1, &light.direction);
    glUniform1fv(self.bindObject->uniforms[FL_UniformLocationLightCutOff], 1, &light.cutOff);

從前面的知識(shí)我們知道,我們不需要給切光角傳入一個(gè)角度值,而用角度值計(jì)算了一個(gè)余弦值畸裳,將余弦結(jié)果傳遞到片段著色器中。這樣做的原因是在片段著色器中淳地,我們會(huì)計(jì)算LightDir和SpotDir向量的點(diǎn)積怖糊,這個(gè)點(diǎn)積返回的將是一個(gè)余弦值而不是角度值,所以我們不能直接使用角度值和余弦值進(jìn)行比較颇象。為了獲取角度值我們需要計(jì)算點(diǎn)積結(jié)果的反余弦伍伤,這是一個(gè)開銷很大的計(jì)算。所以為了節(jié)約一點(diǎn)性能開銷遣钳,我們將會(huì)計(jì)算切光角對(duì)應(yīng)的余弦值扰魂,并將它的結(jié)果傳入片段著色器中。由于這兩個(gè)角度現(xiàn)在都由余弦角來表示了蕴茴,我們可以直接對(duì)它們進(jìn)行比較而不用進(jìn)行任何開銷高昂的計(jì)算劝评。

接下來就是計(jì)算θ值,并將它和切光角?對(duì)比倦淀,來決定是否在聚光的內(nèi)部:

float theta = dot(lightDir, normalize(-light.direction));

if(theta > light.cutOff) 
{       
  // 執(zhí)行光照計(jì)算
}
else  // 否則蒋畜,使用環(huán)境光,讓場景在聚光之外時(shí)不至于完全黑暗
  color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);

我們首先計(jì)算了lightDir和取反的direction向量(取反的是因?yàn)槲覀兿胱屜蛄恐赶蚬庠炊皇菑墓庠闯霭l(fā))之間的點(diǎn)積撞叽。記住要對(duì)所有的相關(guān)向量標(biāo)準(zhǔn)化姻成。

你可能奇怪為什么在if條件中使用的是 > 符號(hào)而不是 < 符號(hào)。theta不應(yīng)該比光的切光角更小才是在聚光內(nèi)部嗎愿棋?這并沒有錯(cuò)科展,但不要忘記角度值現(xiàn)在都由余弦值來表示的。一個(gè)0度的角度表示的是1.0的余弦值糠雨,而一個(gè)90度的角度表示的是0.0的余弦值才睹,你可以在下圖中看到:


image

你現(xiàn)在可以看到,余弦值越接近1.0甘邀,它的角度就越小砂竖。這也就解釋了為什么theta要比切光值更大了。切光值目前設(shè)置為12.5的余弦鹃答,約等于0.9978乎澄,所以在0.9979到1.0內(nèi)的<var style="box-sizing: border-box; font-style: normal; font-family: "Courier New", Courier, monospace; color: rgb(34, 34, 119);">theta</var>值才能保證片段在聚光內(nèi),從而被照亮测摔。

運(yùn)行結(jié)果如圖
shader 編碼
precision lowp float;

attribute vec3 beginPostion; ///開始位置
attribute vec2 a_texture;  //紋理貼圖
attribute vec3 a_normal; //法向量

uniform mat4 u_mvpMatrix;
uniform mat4 u_model;
uniform mat4  u_inverModel;

varying lowp vec3 normal;
varying lowp vec3 FragPos;
varying lowp vec2 v_texture;

void main(){
    gl_Position =u_mvpMatrix *u_model* vec4(beginPostion, 1.0);
    FragPos = vec3(u_model * vec4(beginPostion, 1.0));
    normal =  mat3(u_inverModel) * a_normal;;
    v_texture = a_texture;
}
precision mediump float;

uniform vec3 viewPos;

varying lowp vec3 normal;
varying lowp vec3 FragPos;

varying lowp vec2 v_texture;

struct Light{
    vec3 position;
    vec3 direction;
    float cutOff;
    
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    
    float constant;
    float linear;
    float quadratic;
};
uniform Light light;

struct Material{
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};
uniform Material material;

void main()
{
    vec3 lightDir = normalize(light.position - FragPos);
    
    // check if lighting is inside the spotlight cone
    float theta = dot(lightDir, normalize(-light.direction));
    
    if(theta > light.cutOff) // remember that we're working with angles as cosines instead of degrees so a '>' is used.
    {
        // ambient
        vec3 ambient = light.ambient * texture2D(material.diffuse, v_texture).rgb;
        
        // diffuse
        vec3 norm = normalize(normal);
        float diff = max(dot(norm, lightDir), 0.0);
        vec3 diffuse = light.diffuse * diff * texture2D(material.diffuse, v_texture).rgb;
        
        // specular
        vec3 viewDir = normalize(viewPos - FragPos);
        vec3 reflectDir = reflect(-lightDir, norm);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
        vec3 specular = light.specular * spec * texture2D(material.specular, v_texture).rgb;
        
        // attenuation
        float distance    = length(light.position - FragPos);
        float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
        
        // ambient  *= attenuation; // remove attenuation from ambient, as otherwise at large distances the light would be darker inside than outside the spotlight due the ambient term in the else branche
        diffuse   *= attenuation;
        specular *= attenuation;
        
        vec3 result = ambient + diffuse + specular;
        gl_FragColor = vec4(result, 1.0);
    }
    else
    {
        // else, use ambient light so scene isn't completely dark outside the spotlight.
        gl_FragColor = vec4(light.ambient * texture2D(material.diffuse, v_texture).rgb, 1.0);
    }
}

平滑/軟化邊緣

上邊實(shí)現(xiàn)的聚光燈看起來有點(diǎn)假,主要是因?yàn)榫酃庥幸蝗τ策呏眉谩.?dāng)一個(gè)片段遇到聚光圓錐的邊緣時(shí)解恰,它會(huì)完全變暗,沒有一點(diǎn)平滑的過渡浙于。一個(gè)真實(shí)的聚光將會(huì)在邊緣處逐漸減少亮度护盈。

為了創(chuàng)建一種看起來邊緣平滑的聚光,我們需要模擬聚光有一個(gè)內(nèi)圓錐(Inner Cone)和一個(gè)外圓錐(Outer Cone)羞酗。我們可以將內(nèi)圓錐設(shè)置為上一部分中的那個(gè)圓錐腐宋,但我們也需要一個(gè)外圓錐,來讓光從內(nèi)圓錐逐漸減暗檀轨,直到外圓錐的邊界胸竞。

為了創(chuàng)建一個(gè)外圓錐,我們只需要再定義一個(gè)余弦值來代表聚光方向向量和外圓錐向量(等于它的半徑)的夾角参萄。然后卫枝,如果一個(gè)片段處于內(nèi)外圓錐之間,將會(huì)給它計(jì)算出一個(gè)0.0到1.0之間的強(qiáng)度值讹挎。如果片段在內(nèi)圓錐之內(nèi)校赤,它的強(qiáng)度就是1.0,如果在外圓錐之外強(qiáng)度值就是0.0筒溃。

我們可以用下面這個(gè)公式來計(jì)算這個(gè)值:


這里?(Epsilon)是內(nèi)(?)和外圓錐(γ)之間的余弦值差(?=??γ)马篮。最終的I值就是在當(dāng)前片段聚光的強(qiáng)度。

我們可以這樣理解


基本模型

這里在 θ 在r~? 之間移動(dòng),值正好是 0~1 ,正好是余弦值的變化.
r~? 是內(nèi)邊距到外邊距的變化根據(jù)光照需要逐漸變?nèi)?br> 而余弦值正好符號(hào)上述條件, 在0~1 之間值隨著 θ的變大正好是變小的 .因此我們可以用余弦值來表示光照強(qiáng)度變化

我們?cè)倥e例說明下,看下列實(shí)例:

θ θ(角度) ?(內(nèi)光切) ?(角度) γ(外光切) γ(角度) ? I
0.87 30 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.87 - 0.82 / 0.09 = 0.56
0.9 26 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.9 - 0.82 / 0.09 = 0.89
0.97 14 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.97 - 0.82 / 0.09 = 1.67
0.83 34 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0 .83 - 0.82 / 0.09 = 0.11
0.64 50 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0 .64 - 0.82 / 0.09 = -2.0
0.966 15 0.9978 12.5 0.953 17.5 0.966 - 0.953 = 0.0448 0.966 - 0.953 / 0.0448 = 0.29

你可以看到怜奖,我們基本是在內(nèi)外余弦值之間根據(jù)θ
插值

我們現(xiàn)在有了一個(gè)在聚光外是負(fù)的浑测,在內(nèi)圓錐內(nèi)大于1.0的,在邊緣處于兩者之間的強(qiáng)度值了烦周。如果我們正確地約束(Clamp)這個(gè)值,在片段著色器中就不再需要if-else了怎顾,我們能夠使用計(jì)算出來的強(qiáng)度值直接乘以光照分量:

precision lowp float;

attribute vec3 beginPostion; ///開始位置
attribute vec2 a_texture;  //紋理貼圖
attribute vec3 a_normal; //法向量

uniform mat4 u_mvpMatrix;
uniform mat4 u_model;
uniform mat4  u_inverModel;

varying lowp vec3 normal;
varying lowp vec3 FragPos;
varying lowp vec2 v_texture;

void main(){
    gl_Position =u_mvpMatrix *u_model* vec4(beginPostion, 1.0);
    FragPos = vec3(u_model * vec4(beginPostion, 1.0));
    normal =  mat3(u_inverModel) * a_normal;;
    v_texture = a_texture;
}

 // spotlight (soft edges)
        float theta = dot(lightDir, normalize(-light.direction));
        float epsilon = (light.cutOff - light.outerCutOff);
        float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
        diffuse  *= intensity;
        specular *= intensity;

注意我們使用了clamp函數(shù)读慎,它把第一個(gè)參數(shù)約束(Clamp)在了0.0到1.0之間。這保證強(qiáng)度值不會(huì)在[0, 1]區(qū)間之外槐雾。

最終效果如圖

光滑 shader 編碼
precision mediump float;

uniform vec3 viewPos;

varying lowp vec3 normal;
varying lowp vec3 FragPos;

varying lowp vec2 v_texture;

struct Light{
    vec3 position;
    vec3 direction;
    float cutOff;
    float outerCutOff;
    
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    
    float constant;
    float linear;
    float quadratic;
};
uniform Light light;

struct Material{
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};
uniform Material material;

void main()
{
        // ambient
        vec3 ambient = light.ambient * texture2D(material.diffuse, v_texture).rgb;
        
        // diffuse
        vec3 norm = normalize(normal);
        vec3 lightDir = normalize(light.position - FragPos);
        float diff = max(dot(norm, lightDir), 0.0);
        vec3 diffuse = light.diffuse * diff * texture2D(material.diffuse, v_texture).rgb;
        
        // specular
        vec3 viewDir = normalize(viewPos - FragPos);
        vec3 reflectDir = reflect(-lightDir, norm);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
        vec3 specular = light.specular * spec * texture2D(material.specular, v_texture).rgb;
        
        
        // spotlight (soft edges)
        float theta = dot(lightDir, normalize(-light.direction));
        float epsilon = (light.cutOff - light.outerCutOff);
        float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
        diffuse  *= intensity;
        specular *= intensity;
        
        // attenuation
        float distance    = length(light.position - FragPos);
        float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
        
        // ambient  *= attenuation; // remove attenuation from ambient, as otherwise at large distances the light would be darker inside than outside the spotlight due the ambient term in the else branche
        diffuse   *= attenuation;
        specular *= attenuation;
        
        vec3 result = ambient + diffuse + specular;
        gl_FragColor = vec4(result, 1.0);
   
}

源碼地址 對(duì)應(yīng)的demo是OpenGLZeroStudyDemo(7)-光照

參考博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末夭委,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子募强,更是在濱河造成了極大的恐慌株灸,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件擎值,死亡現(xiàn)場離奇詭異慌烧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鸠儿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門屹蚊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來厕氨,“玉大人,你說我怎么就攤上這事汹粤∶” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵嘱兼,是天一觀的道長国葬。 經(jīng)常有香客問我,道長芹壕,這世上最難降的妖魔是什么汇四? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮哪雕,結(jié)果婚禮上船殉,老公的妹妹穿的比我還像新娘。我一直安慰自己斯嚎,他們只是感情好利虫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堡僻,像睡著了一般糠惫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钉疫,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天硼讽,我揣著相機(jī)與錄音,去河邊找鬼牲阁。 笑死固阁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的城菊。 我是一名探鬼主播备燃,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼凌唬!你這毒婦竟也來了并齐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤客税,失蹤者是張志新(化名)和其女友劉穎况褪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體更耻,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡测垛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秧均。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赐纱。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脊奋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疙描,到底是詐尸還是另有隱情诚隙,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布起胰,位于F島的核電站久又,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏效五。R本人自食惡果不足惜地消,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畏妖。 院中可真熱鬧脉执,春花似錦、人聲如沸戒劫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迅细。三九已至巫橄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茵典,已是汗流浹背湘换。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留统阿,地道東北人彩倚。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像扶平,于是被迫代替她去往敵國和親帆离。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354