OpenGL ES 3.0(七)顏色和光照

1本橙、概述

前面幾篇關(guān)于OpenGLES的文章:

OpenGL ES 2.0 顯示圖形(上)

OpenGL ES 2.0 顯示圖形(下)

OpenGL ES 3.0(一)綜述

OpenGL ES 3.0(二)GLSL與著色器

OpenGL ES 3.0(三)紋理

OpenGL ES 3.0(四)矩陣變換

OpenGL ES 3.0(五)坐標(biāo)系

OpenGL ES 3.0(六)攝像機(jī)

現(xiàn)實(shí)世界中有無(wú)數(shù)種顏色讨彼,每一個(gè)物體都有它們自己的顏色。開(kāi)發(fā)中需要使用(有限的)數(shù)值來(lái)模擬真實(shí)世界中(無(wú)限)的顏色,所以并不是所有現(xiàn)實(shí)世界中的顏色都可以用數(shù)值來(lái)表示的夺巩。但仍能通過(guò)數(shù)值來(lái)表現(xiàn)出非常多的顏色,甚至你可能都不會(huì)注意到與現(xiàn)實(shí)的顏色有任何的差異。顏色可以數(shù)字化的將顏色由紅色渤早、綠色和藍(lán)色三個(gè)分量組成,它們通常被縮寫(xiě)為RGB瘫俊。僅僅用這三個(gè)值就可以組合出任意一種顏色鹊杖。

在現(xiàn)實(shí)生活中看到某一物體的顏色并不是這個(gè)物體真正擁有的顏色,而是它所反射的顏色扛芽。換句話說(shuō)骂蓖,那些不能被物體所吸收的顏色就是肉眼能夠感知到的物體的顏色。例如川尖,太陽(yáng)光能被看見(jiàn)的白光其實(shí)是由許多不同的顏色組合而成的登下。如果將白光照在一個(gè)藍(lán)色的玩具上,這個(gè)藍(lán)色的玩具會(huì)吸收白光中除了藍(lán)色以外的所有子顏色,不被吸收的藍(lán)色光被反射到眼中被芳,讓這個(gè)玩具看起來(lái)是藍(lán)色的畔濒。下圖顯示的是一個(gè)珊瑚紅的玩具侵状,它以不同強(qiáng)度反射了多個(gè)顏色绽左。

顏色原理

可以看到,白色的陽(yáng)光實(shí)際上是所有可見(jiàn)顏色的集合闯团,物體吸收了其中的大部分顏色。它僅反射了代表物體顏色的部分,被反射顏色的組合就是所感知到的顏色隔心。

這些顏色反射的定律被直接地運(yùn)用在圖形領(lǐng)域尚胞。當(dāng)在OpenGL ES中創(chuàng)建一個(gè)光源時(shí)笼裳,希望給光源一個(gè)顏色躬柬。比如可以將光源設(shè)置成光源設(shè)置為白色。當(dāng)把光源的顏色與物體的顏色值相乘橄碾,所得到的就是這個(gè)物體所反射的顏色史汗,也就是所感知到的顏色。


vec3 lightColor = vec3(1.0f, 1.0f, 1.0f);

vec3 toyColor = vec3(1.0f, 0.5f, 0.31f);

vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);

可以看到玩具的顏色吸收了白色光源中很大一部分的顏色怜森,但它根據(jù)自身的顏色值對(duì)紅、綠恐疲、藍(lán)三個(gè)分量都做出了一定的反射培己。這也表現(xiàn)了現(xiàn)實(shí)中顏色的工作原理省咨。由此,可以定義物體的顏色為物體從一個(gè)光源反射各個(gè)顏色分量的大小 穷缤。如果用綠色光源(0,1,0)來(lái)照射玩具,那么只有綠色分量能被反射和感知到章喉,紅色和藍(lán)色都不能被感知到。這樣做的結(jié)果是秸脱,一個(gè)珊瑚紅的玩具突然變成了深綠色∽卜矗或者一個(gè)深橄欖綠色的光源(0.33f, 0.42f, 0.18f) 打到上面的玩具上最后會(huì)產(chǎn)生如下顏色:


vec3 lightColor = vec3(0.33f, 0.42f, 0.18f);

vec3 toyColor = vec3(1.0f, 0.5f, 0.31f);

vec3 result = lightColor * toyColor; // = (0.33f, 0.21f, 0.06f);

這和現(xiàn)實(shí)世界中的顏色規(guī)律是相符合的。

2搪花、光照?qǐng)鼍?/h1>

這一節(jié)主要?jiǎng)?chuàng)建一個(gè)之后要用的光照?qǐng)鼍八北悖奖愫竺娴挠懻撔硎ΑJ紫刃枰粋€(gè)物體來(lái)作為被投光的對(duì)象,將使用前面文章中提到的立方體箱子咧擂。還需要一個(gè)物體來(lái)代表光源在3D場(chǎng)景中的位置逞盆。簡(jiǎn)單起見(jiàn),依然使用一個(gè)立方體來(lái)代表光源松申。

這里創(chuàng)建一個(gè)光源類云芦,與前面的箱子基本類似。首先需要一個(gè)頂點(diǎn)著色器來(lái)繪制箱子贸桶。與之前的頂點(diǎn)著色器相比舅逸,容器的頂點(diǎn)位置是保持不變的,但這邊為了突出要討論的內(nèi)容皇筛,把與之無(wú)關(guān)的紋理坐標(biāo)去掉琉历,將會(huì)使用前面文章討論的頂點(diǎn)著色器的精簡(jiǎn)版,同時(shí)新建:


// Ligjt.kt
private val vertexShaderCode =

            "#version 300 es \n" +

                    " layout (location = 0) in vec3 aPos;" +

                    "uniform mat4 model;" +

                    "uniform mat4 view;" +

                    "uniform mat4 projection;" +

                    "void main() {" +

                    " gl_Position = projection * view * model * vec4(aPos, 1.0);" +

                    "}"

片段著色器直接輸出一個(gè)光源即箱子的顏色。


// Ligjt.kt

private val fragmentShaderCode = (

            "#version 300 es \n " +

                    "#ifdef GL_ES\n" +

                    "precision highp float;\n" +

                    "#endif\n" +

                    "out vec4 FragColor; " +

                    "void main() {" +

                    "  FragColor = vec4(1.0) ;" +

                    "}")

這邊需要額外注意的是這邊開(kāi)放兩個(gè)屬性水醋,一個(gè)是光源位置善已,一個(gè)是光源顏色。用來(lái)提供給其他物體進(jìn)行參考离例。


// Ligjt.kt
var mLightColor: Array<Float> = arrayOf(1f, 1f, 1f)

var mLightPosition: Array<Float> = arrayOf(0.2f,0.3f, -1f)

同時(shí)要注意, 在draw()方法中清除顏色和深度的指令要拿出來(lái)悉稠,在MyGLRenderer.kt中進(jìn)行調(diào)用宫蛆。不然刷新時(shí)候 Ligjt類的draw()方法會(huì)把其他物體渲染的對(duì)象給清除掉。


// Ligjt.kt

fun draw() {

//GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)

}

//MyGLRenderer.kt

override fun onDrawFrame(unused: GL10) {

        ...

        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)

        mLight!!.draw()

        mTriangle!!.draw()

    }

同時(shí)創(chuàng)建另外一個(gè)普通物體類Triangle的猛,來(lái)進(jìn)行之后的光影效果實(shí)現(xiàn)耀盗。其需要傳入Light類來(lái)進(jìn)行之后的使用,同時(shí)物體的位置通過(guò)view矩陣來(lái)進(jìn)行表示卦尊,在(0.0叛拷,0.0,-4.0)上岂却。


//Triangle.kt

class Triangle(context: Context, light: Light) {

        ...

        private val mLight: Light

        ...

        init {

            mLight = light

            ...

        }

            ...

        fun draw() {

            ...

        Matrix.setIdentityM(mViewMatrix, 0)

        Matrix.translateM(mViewMatrix, 0, 0f,0f,-4.0f)

            ...

        }

}

光照?qǐng)鼍?/div>

3忿薇、光照模型

現(xiàn)實(shí)世界的光照是極其復(fù)雜的裙椭,而且會(huì)受到諸多因素的影響,在有限的計(jì)算能力所無(wú)法模擬的署浩。因此OpenGL ES的光照使用的是簡(jiǎn)化的模型揉燃,對(duì)現(xiàn)實(shí)的情況進(jìn)行近似。這些光照模型都是基于對(duì)光的物理特性的理解筋栋。其中一個(gè)模型被稱為馮氏光照模型(Phong Lighting Model)炊汤。馮氏光照模型的主要結(jié)構(gòu)由3個(gè)分量組成:環(huán)境(Ambient)、漫反射(Diffuse)和鏡面(Specular)光照弊攘。下面這張圖展示了這些光照分量看起來(lái)的樣子:

馮氏光照模型

① 環(huán)境光照:即使在黑暗的情況下抢腐,世界上通常也仍然有一些光亮(月亮、遠(yuǎn)處的光)襟交,所以物體幾乎永遠(yuǎn)不會(huì)是完全黑暗的迈倍。為了模擬這個(gè),我們會(huì)使用一個(gè)環(huán)境光照常量婿着,它永遠(yuǎn)會(huì)給物體一些顏色授瘦。

② 漫反射光照:模擬光源對(duì)物體的方向性影響(Directional Impact)。它是馮氏光照模型中視覺(jué)上最顯著的分量竟宋。物體的某一部分越是正對(duì)著光源提完,它就會(huì)越亮。

③ 鏡面光照:模擬有光澤物體上面出現(xiàn)的亮點(diǎn)丘侠。鏡面光照的顏色相比于物體的顏色會(huì)更傾向于光的顏色徒欣。

4、環(huán)境光照

光通常都不是來(lái)自于同一個(gè)光源蜗字,而是來(lái)自于物體周圍分散的很多光源打肝,即使它們可能并不是那么顯而易見(jiàn)。光的一個(gè)屬性是挪捕,它可以向很多方向發(fā)散并反彈粗梭,從而能夠到達(dá)不是非常直接臨近的點(diǎn)。所以级零,光能夠在其它的表面上反射断医,對(duì)一個(gè)物體產(chǎn)生間接的影響∽嗉停考慮到這種情況的算法叫做全局照明算法鉴嗤,但是這種算法既開(kāi)銷高昂又極其復(fù)雜。鑒于移動(dòng)端的性能有限序调,將會(huì)使用一個(gè)簡(jiǎn)化的全局照明模型醉锅,即環(huán)境光照。正如在上一節(jié)所討論的发绢,使用一個(gè)很小的常量(光照)顏色硬耍,添加到物體片段的最終顏色中垄琐,這樣子的話即便場(chǎng)景中沒(méi)有直接的光源也能看起來(lái)存在有一些發(fā)散的光。

把環(huán)境光照添加到場(chǎng)景里就可以用光的顏色乘以一個(gè)很小的常量環(huán)境因子默垄,再乘以物體的顏色此虑,然后將最終結(jié)果作為片段的顏色:


// Triangle.kt
private val fragmentShaderCode = (

            "#version 300 es \n " +

                    "#ifdef GL_ES\n" +

                    "precision highp float;\n" +

                    "#endif\n" +

                    "out vec4 FragColor; " +

                    "uniform vec3 lightColor; " +

                    "uniform vec3 objectColor; " +

                    "void main() {" +

                    // ambient

                    "  float ambientStrength = 0.2;" +

                    "  vec3 ambient = ambientStrength * lightColor;" 

                    "  vec3 result = ambient * objectColor;" +

                    "  FragColor = vec4(result, 1.0);" +

                    "}")

環(huán)境光照效果如下圖:

環(huán)境光照

5、漫反射光照

環(huán)境光照是周圍環(huán)境對(duì)物體的統(tǒng)一的光照影響口锭。漫反射光照使物體上與光線方向越接近的片段能從光源處獲得更多的亮度朦前。原理如下圖:

漫反射光照原理

圖左上方有一個(gè)光源,它所發(fā)出的光線落在物體的一個(gè)面上鹃操。需要測(cè)量這個(gè)光線是以什么角度接觸到這個(gè)面的韭寸。如果光線垂直于物體表面,這束光對(duì)物體的影響會(huì)最大化也就是說(shuō)更亮荆隘。為了測(cè)量光線和片段的角度恩伺,使用一個(gè)叫做法向量(Normal Vector)的東西,它是垂直于所要表示的面的一個(gè)向量椰拒,上圖中黃色箭頭晶渠。而這兩個(gè)向量之間的角度能夠通過(guò)點(diǎn)乘計(jì)算出來(lái)。

在前面討論關(guān)于變換那一篇文章里燃观,討論過(guò)兩個(gè)單位向量的夾角越小褒脯,它們點(diǎn)乘的結(jié)果越傾向于1。當(dāng)兩個(gè)向量的夾角為90度的時(shí)候缆毁,點(diǎn)乘會(huì)變?yōu)?番川。這同樣適用于,越大脊框,光對(duì)片段顏色的影響就應(yīng)該越小颁督。注意,為了得到兩個(gè)向量夾角的余弦值浇雹,使用的是單位向量即長(zhǎng)度為1的向量沉御,所以需要確保所有的向量都是標(biāo)準(zhǔn)化的,否則點(diǎn)乘返回值還要用兩個(gè)向量的長(zhǎng)度進(jìn)行修正才能得到向量夾角的余弦值昭灵。

點(diǎn)乘返回一個(gè)標(biāo)量嚷节,可以用它計(jì)算光線對(duì)片段顏色的影響。不同片段朝向光源的方向的不同虎锚,這些片段被照亮的情況也不同。所以衩婚,計(jì)算漫反射光照需要兩個(gè)向量

①法向量:一個(gè)垂直于頂點(diǎn)表面的向量窜护。

②定向的光線:作為光源的位置與物體各個(gè)表面位置之間向量差的方向向量。為了計(jì)算這個(gè)光線非春,需要光的位置向量和片段的位置向量柱徙。

5.1 法向量

法向量是一個(gè)垂直于頂點(diǎn)表面的單位向量缓屠。由于頂點(diǎn)本身并沒(méi)有表面,它只是空間中一個(gè)獨(dú)立的點(diǎn)护侮,但可以利用它周圍的頂點(diǎn)來(lái)計(jì)算出這個(gè)頂點(diǎn)的表面敌完。這邊使用一個(gè)小技巧,使用叉乘對(duì)立方體所有的頂點(diǎn)計(jì)算法向量羊初,但是由于3D立方體不是一個(gè)復(fù)雜的形狀滨溉,所以可以簡(jiǎn)單地把法線數(shù)據(jù)手工添加到頂點(diǎn)數(shù)據(jù)中。更新后的頂點(diǎn)數(shù)據(jù)數(shù)組如下:


// Triangle.kt
var vertices3D = floatArrayOf(

                // ---- 位置 ----    ---- 頂點(diǎn)法向量 ----

                -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,

                0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,

                0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,

                0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,

                -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,

                -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,

                -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,

                0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,

                0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,

                0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,

                -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,

                -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,

                -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,

                -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f,

                -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,

                -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,

                -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f,

                -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,

                0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,

                0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,

                0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,

                0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,

                0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,

                0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,

                -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,

                0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,

                0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,

                0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,

                -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,

                -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,

                -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,

                0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,

                0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,

                0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,

                -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,

                -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f)

5.2 計(jì)算漫反射光照

由于向頂點(diǎn)數(shù)組添加了額外的數(shù)據(jù)长赞,所以需要更新光照的頂點(diǎn)著色器:


// Triangle.kt
private val vertexShaderCode =

            "#version 300 es \n" +

                    "layout (location = 0) in vec3 aPos;" +

                    "layout (location = 1) in vec3 aNormal;" +

                    "uniform mat4 model;" +

                    "uniform mat4 view;" +

                    "uniform mat4 projection;" +

                    "out vec3 FragPos; " +

                    "out vec3 Normal; " +

                    "void main() {" +

                    "  gl_Position = projection * view * model * vec4(aPos, 1.0);" +

                    "  FragPos = vec3(view * vec4(aPos, 1.0));" +

                    "  Normal = aNormal;" +

                    "}"

其中aNormal是前面頂點(diǎn)矩陣傳入的每個(gè)頂點(diǎn)的原始法向量晦攒。 由于需要在世界空間中進(jìn)行所有的光照計(jì)算,因此需要一個(gè)在世界空間中的頂點(diǎn)位置得哆「眨可以通過(guò)把頂點(diǎn)位置屬性乘以視圖矩陣來(lái)把它變換到世界空間坐標(biāo)。這個(gè)在頂點(diǎn)著色器中完成贩据,然后傳輸給片段著色器栋操,這個(gè)數(shù)據(jù)就是上面的FragPos。

下面需要對(duì)片段著色器進(jìn)行修改:


// Triangle.kt
private val fragmentShaderCode = (

            "#version 300 es \n " +

                    "#ifdef GL_ES\n" +

                    "precision highp float;\n" +

                    "#endif\n" +

                    "in vec3 Normal;" +

                    "in vec3 FragPos;" +

                    "out vec4 FragColor; " +

                    "uniform vec3 lightPos; " +

                    "uniform vec3 lightColor; " +

                    "uniform vec3 objectColor; " +

                    "void main() {" +

                    ...

                    // diffuse

                    "  vec3 norm = normalize(Normal);" +

                    "  vec3 lightDir = normalize(lightPos - FragPos);" +

                    "  float diff = max(dot(norm, lightDir), 0.0);" +

                    "  vec3 diffuse = diff * lightColor;" +

                    ...

                    "  vec3 result = ( diffuse) * objectColor;" +

                    "  FragColor = vec4(result, 1.0);" +

                    "}")

首先需要獲取標(biāo)準(zhǔn)化后的法向量norm和光線向量lightDir饱亮。下一步矾芙,對(duì)norm和lightDir向量進(jìn)行點(diǎn)乘,計(jì)算光源對(duì)當(dāng)前片段實(shí)際的漫發(fā)射影響近尚。結(jié)果值再乘以光的顏色蠕啄,得到漫反射分量。兩個(gè)向量之間的角度越大戈锻,漫反射分量就會(huì)越小歼跟。如果兩個(gè)向量之間的角度大于90度,點(diǎn)乘的結(jié)果就會(huì)變成負(fù)數(shù)格遭,這樣會(huì)導(dǎo)致漫反射分量變?yōu)樨?fù)數(shù)哈街。為此,使用max函數(shù)返回兩個(gè)參數(shù)之間較大的參數(shù)拒迅,從而保證漫反射分量不會(huì)變成負(fù)數(shù)骚秦。負(fù)數(shù)顏色的光照是沒(méi)有定義的,所以最好避免它璧微。有了環(huán)境光分量和漫反射分量作箍,把它們相加,然后把結(jié)果乘以物體的顏色前硫,來(lái)獲得片段最后的輸出顏色胞得。

其輸出效果如下:

環(huán)境和漫反射光照

5.3 法向量矯正

現(xiàn)在已經(jīng)把法向量從頂點(diǎn)著色器傳到了片段著色器∫俚纾可是阶剑,目前片段著色器里的計(jì)算都是在世界空間坐標(biāo)中進(jìn)行的跃巡。所以,應(yīng)該把法向量也轉(zhuǎn)換為世界空間坐標(biāo)牧愁。但是這不是簡(jiǎn)單地把它乘以一個(gè)模型矩陣就能搞定的素邪。首先,法向量只是一個(gè)方向向量猪半,不能表達(dá)空間中的特定位置兔朦。同時(shí),法向量沒(méi)有齊次坐標(biāo)办龄,即頂點(diǎn)位置中的w分量烘绽。這意味著,位移不應(yīng)該影響到法向量俐填。因此安接,如果打算把法向量乘以一個(gè)模型矩陣,就要從矩陣中移除位移部分英融,只選用模型矩陣左上角3×3的矩陣對(duì)于法向量盏檐,只希望對(duì)它實(shí)施縮放和旋轉(zhuǎn)變換。其次驶悟,如果模型矩陣執(zhí)行了不等比縮放胡野,頂點(diǎn)的改變會(huì)導(dǎo)致法向量不再垂直于表面了。因此痕鳍,不能用這樣的模型矩陣來(lái)變換法向量硫豆。下面的圖展示了應(yīng)用了不等比縮放的模型矩陣對(duì)法向量的影響:

法向量變形

每當(dāng)應(yīng)用一個(gè)不等比縮放時(shí),不會(huì)破壞法線笼呆,因?yàn)榉ň€的方向沒(méi)被改變熊响,僅僅改變了法線的長(zhǎng)度,而這很容易通過(guò)標(biāo)準(zhǔn)化來(lái)修復(fù)诗赌,但是法向量不會(huì)再垂直于對(duì)應(yīng)的表面汗茄,這樣光照就會(huì)被破壞。修復(fù)這個(gè)問(wèn)題需要使用一個(gè)為法向量專門(mén)定制的模型矩陣铭若。這個(gè)矩陣稱之為法線矩陣(Normal Matrix)洪碳,它使用了一些線性代數(shù)的操作來(lái)移除對(duì)法向量錯(cuò)誤縮放的影響。

法線矩陣被定義為——觀察矩陣左上角的逆矩陣的轉(zhuǎn)置矩陣叼屠。

在頂點(diǎn)著色器中瞳腌,可以使用inverse和transpose函數(shù)自己生成這個(gè)法線矩陣,這兩個(gè)函數(shù)對(duì)所有類型矩陣都有效镜雨。注意還要把被處理過(guò)的矩陣強(qiáng)制轉(zhuǎn)換為3×3矩陣纯趋,來(lái)保證它失去了位移屬性以及能夠乘以vec3的法向量。


// Triangle.kt

private val vertexShaderCode =

                ...

                    "void main() {" +

                       ...

                    "  Normal = mat3(transpose(inverse(view))) * aNormal;" +

                    "}"

在漫反射光照部分,光照表現(xiàn)并沒(méi)有問(wèn)題吵冒,這是因?yàn)闆](méi)有對(duì)物體本身執(zhí)行任何縮放操作,所以并不是必須要使用一個(gè)法線矩陣西剥,僅僅讓模型矩陣乘以法線也可以痹栖。可是瞭空,如果進(jìn)行了不等比縮放揪阿,使用法線矩陣去乘以法向量就是必不可少的了。即使是對(duì)于著色器來(lái)說(shuō)咆畏,逆矩陣也是一個(gè)開(kāi)銷比較大的運(yùn)算南捂,因此,只要可能就應(yīng)該避免在著色器中進(jìn)行逆矩陣運(yùn)算旧找,它們必須為場(chǎng)景中的每個(gè)頂點(diǎn)都進(jìn)行這樣的處理溺健。這邊為了方便實(shí)現(xiàn)效果直接在GPU中進(jìn)行處理,但是對(duì)于一個(gè)對(duì)效率有要求的應(yīng)用來(lái)說(shuō)钮蛛,在繪制之前最好用CPU計(jì)算出法線矩陣鞭缭,然后通過(guò)uniform把值傳遞給著色器。

6魏颓、鏡面光照

和漫反射光照一樣岭辣,鏡面光照也是依據(jù)光的方向向量和物體的法向量來(lái)決定的,但是它也依賴于觀察方向甸饱。鏡面光照是基于光的反射特性沦童。如果反射光直射入眼此時(shí)的光照強(qiáng)度就是最大的,而隨著角度的偏離叹话,光照強(qiáng)度漸漸變小偷遗。

鏡面光照原理

通過(guò)反射法向量周圍光的方向來(lái)計(jì)算反射向量。然后計(jì)算反射向量和視線方向的角度差渣刷,如果夾角越小鹦肿,那么鏡面光的影響就會(huì)越大。它的作用效果就是辅柴,去看光被物體所反射的那個(gè)方向的時(shí)候箩溃,會(huì)看到一個(gè)高光。

觀察向量是鏡面光照附加的一個(gè)變量碌嘀,可以使用觀察者世界空間位置和片段的位置來(lái)計(jì)算它涣旨。之后,計(jì)算鏡面光強(qiáng)度股冗,用它乘以光源的顏色霹陡,再將它加上環(huán)境光和漫反射分量。選擇在世界空間進(jìn)行光照計(jì)算,也可以在觀察空間進(jìn)行光照計(jì)算烹棉。在觀察空間計(jì)算的好處是攒霹,觀察者的位置總是(0,0,0),所以這樣直接就獲得了觀察者位置浆洗。為了得到觀察者的世界空間坐標(biāo)催束,簡(jiǎn)單地使用攝像機(jī)對(duì)象的位置坐標(biāo)代替,即它就是觀察者伏社,由于這邊沒(méi)有修改相機(jī)位置抠刺,所以視角位置是世界坐標(biāo)的原點(diǎn)(0,0,0)。所以把另一個(gè)uniform viewPos添加到片段著色器摘昌,把相應(yīng)的攝像機(jī)位置(0,0,0)坐標(biāo)傳給片段著色器:


private val fragmentShaderCode = (

                    ...

                    "uniform vec3 viewPos; " +

                    "void main() {" +

                    ...

                    // specular

                    "  float specularStrength = 0.5;" +

                    "  vec3 viewDir = normalize(viewPos - FragPos);" +

                    "  vec3 reflectDir = reflect(-lightDir, norm);" +

                    // pow函數(shù)第二個(gè)參數(shù)一定要帶小數(shù)位速妖,不然會(huì)崩潰

                    "  float spec = pow(max(dot(viewDir, reflectDir), 0.0f), 128.0);" +

                    "  vec3 specular = specularStrength * spec * lightColor; " +

                    "  vec3 result = ( ambient + diffuse + specular ) * objectColor;" +

                    "  FragColor = vec4(result, 1.0);" +

                    "}")

specularStrength 是鏡面強(qiáng)度變量,給鏡面高光一個(gè)中等亮度顏色聪黎,讓它不要產(chǎn)生過(guò)度的影響罕容。

viewDir是視角向量,reflectDir是反射光向量挺举。其中l(wèi)ightDir是光線向量杀赢,需要注意的是對(duì)lightDir向量進(jìn)行了取反。reflect函數(shù)要求第一個(gè)向量是從光源指向片段位置的向量湘纵,但是lightDir當(dāng)前正好相反脂崔,是從片段指向光源,由先前計(jì)算lightDir向量時(shí)梧喷,減法的順序決定砌左。為了保證得到正確的reflect向量,通過(guò)對(duì)lightDir向量取反來(lái)獲得相反的方向铺敌。第二個(gè)參數(shù)要求是一個(gè)法向量汇歹,即前面提供的已標(biāo)準(zhǔn)化的norm向量。之后的兩行代碼是在計(jì)算鏡面光強(qiáng)分量偿凭。先計(jì)算視線方向與反射方向的點(diǎn)乘产弹,并確保它不是負(fù)值,然后取它的32次冪弯囊。這個(gè)128是高光的反光度痰哨。一個(gè)物體的反光度越高,反射光的能力越強(qiáng)匾嘱,散射得越少斤斧,高光點(diǎn)就會(huì)越小。在下面的圖片里霎烙,是不同反光度的視覺(jué)效果影響:

反光度

最后是將3個(gè)光的分量累加起來(lái)作用與物體中的最終形態(tài)撬讽。

環(huán)境蕊连、漫反射、鏡面光照

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末游昼,一起剝皮案震驚了整個(gè)濱河市甘苍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烘豌,老刑警劉巖羊赵,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異扇谣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)闲昭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)罐寨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人序矩,你說(shuō)我怎么就攤上這事鸯绿。” “怎么了簸淀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵瓶蝴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我租幕,道長(zhǎng)舷手,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任劲绪,我火速辦了婚禮男窟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贾富。我一直安慰自己歉眷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布颤枪。 她就那樣靜靜地躺著汗捡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪畏纲。 梳的紋絲不亂的頭發(fā)上扇住,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音霍骄,去河邊找鬼台囱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛读整,可吹牛的內(nèi)容都是我干的簿训。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼强品!你這毒婦竟也來(lái)了膘侮?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤的榛,失蹤者是張志新(化名)和其女友劉穎琼了,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體夫晌,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雕薪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晓淀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片所袁。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖凶掰,靈堂內(nèi)的尸體忽然破棺而出燥爷,到底是詐尸還是另有隱情,我是刑警寧澤懦窘,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布前翎,位于F島的核電站,受9級(jí)特大地震影響畅涂,放射性物質(zhì)發(fā)生泄漏港华。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一毅戈、第九天 我趴在偏房一處隱蔽的房頂上張望苹丸。 院中可真熱鬧,春花似錦苇经、人聲如沸赘理。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)商模。三九已至,卻和暖如春蜘澜,著一層夾襖步出監(jiān)牢的瞬間施流,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工鄙信, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞪醋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓装诡,卻偏偏與公主長(zhǎng)得像银受,于是被迫代替她去往敵國(guó)和親践盼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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