OpenGL ES 入門之旅 -- GLSL光照計算

馮氏光照模型

首先,在現(xiàn)實世界的光照是極其復(fù)雜的,而且會受到諸多因素的影響,這是我們有限的計算能力所無法模擬的。因此OpenGL的光照使用的是簡化的模型,對現(xiàn)實的情況進(jìn)行近似,這樣處理起來會更容易一些。這些光照模型都是基于我們對光的物理特性的理解。其中一個模型被稱為馮氏光照模型(Phong Lighting Model)。馮氏光照模型的主要結(jié)構(gòu)由3個元素組成:環(huán)境(Ambient)光照、漫反射(Diffuse)光照和鏡面(Specular)光照。
下面我們用示例圖來看一下這幾種光照:

光照.png

環(huán)境(Ambient)光照:即使在最黑暗的正常環(huán)境下,世界上依然會存在一些光亮,也就是說物體幾乎永遠(yuǎn)不會是完全黑暗的。環(huán)境光照一般是由室外的太陽通過各種折射來讓我們看到,這樣的光是沒有任何起點,沒有方向的光。主要通過設(shè)置物體顏色來獲取。
漫反射(Diffuse)光照:模擬的是光源對物體方向的影響,一個物體面向光源的時候,朝向光的那個面會亮一點,物體背面和其他面會暗一點。
鏡面(Specular)光照:模擬的是有光澤物體上面出現(xiàn)的亮點。鏡面光照的顏色相比于物體的顏色會更傾向于光的顏色。

光照基礎(chǔ)

OpenGL在處理光照時把光照系統(tǒng)分為三部分:分別是光源、材質(zhì)和光照環(huán)境。光源就是光的來源,可以是前面所說的太陽或者電燈,蠟燭等。材質(zhì)是指接受光照的各種物體的表面,由于物體如何反射光線只由物體表面決定,材質(zhì)特點就決定了物體反射光線的特點。光照環(huán)境是指一些額外的參數(shù),它們將影響最終的光照畫面,比如一些光線經(jīng)過多次反射后,已經(jīng)無法分清它究竟是由哪個光源發(fā)出,這時,指定一個環(huán)境亮度參數(shù),可以使最后形成的畫面更接近于真實情況。
在物理學(xué)中,光線如果射入理想的光滑平面,則反射后的光線是很規(guī)則的(這樣的反射稱為鏡面反射)。光線如果射入粗糙的、不光滑的平面,則反射后的光線是雜亂的(這樣的反射稱為漫反射)?,F(xiàn)實生活中的物體在反射光線時,并不是絕對的鏡面反射或漫反射,但可以看成是這兩種反射的疊加。對于光源發(fā)出的光線,可以分別設(shè)置其經(jīng)過鏡面反射和漫反射后的光線強(qiáng)度。對于被光線照射的材質(zhì),也可以分別設(shè)置光線經(jīng)過鏡面反射和漫反射后的光線強(qiáng)度。這些因素綜合起來,就形成了最終的光照效果。
光照特性
1.發(fā)射光:由物體自身發(fā)光
2.環(huán)境光:就是在環(huán)境中充分散射的光,而且無法分辨光的方向
3.漫反射光:光線來自某個方向,但是在物體上各個方向反射
4.鏡面高光:光線來自一個特定的方向,然后在物體表面上以一個特定的方向反射出去

材質(zhì)屬性
1.泛射材質(zhì):光線直射,反射率較高
2.漫反射材質(zhì):需要考慮光的入射角和反射角的
3.鏡面反射材質(zhì):斑點
4.發(fā)射材質(zhì):物體本身就可以發(fā)光的材質(zhì)

光照計算
1.環(huán)境光的計算
環(huán)境光是不來自任何特定方向的光,在整個場景中經(jīng)典光照模型把它當(dāng)成一個常量,組成一個合適的第一近似值來縮放場景中的光照部分。
環(huán)境光 = 光源的環(huán)境光顏色 * 物體的材質(zhì)顏色

環(huán)境光計算.png

varying vec3 objectColor;
void main()
{
  //?至少有%10的光找到物體所有?面
  float ambientStrength = 0.1;
  //環(huán)境光顏?色
  vec3 ambient = ambientStrength * lightColor;
  //最終顏?色 = 環(huán)境光顏?色 * 物體顏?色
  vec3 result = ambient * objectColor;
  gl_FragColor = vec4(result, 1.0);
}

2.發(fā)射光的計算
如果這個物體本身就是有顏色的,比如說夜明珠,那么這個時候這個光就是這個物體材質(zhì)的顏色
發(fā)射顏色 = 物體的反射材質(zhì)顏色

3.漫反射光的計算
漫反射光是散射在各個方向上的均勻的表面特定光源,漫反射光依賴于表面法線方向和光源方向來計算,但是沒有包含視線方向,它同樣依賴于表面的顏色。

首先來看一下環(huán)境光和漫反射光的比較:
光照比較.png
可以看到環(huán)境光下的蘋果是沒有陰面和陽面的,看到的蘋果感覺不夠逼真,而漫反射光加深了蘋果的真實度,模擬出了在現(xiàn)實生活中的真實環(huán)境。
漫反射光的計算.png
上圖是當(dāng)漫反射光照射到物體表面時:其中N表示法向量,L表示光源,法向量N和光源L之間的夾角決定了光照射的面積。夾角越大照射面積越大。

光線照射到物體表面,決定了物體表面的光照強(qiáng)度,光照強(qiáng)度是光本身強(qiáng)度和光線與物體表面法線夾角cos的乘積

有效光.png
有效光的光照方向是與物體表面法線夾角在0~90度之間的。
漫反射顏色 = 光源的漫反射光顏色 × 物體的漫反射材質(zhì)顏色 × 漫反射因子
漫反射因子.png
漫反射因子DiffuseFactor 是光線與頂點法線向量的點積DiffuseFactor = max(0, dot(N, L))

uniform vec3 lightColor;    //光源色
uniform vec3 lightPo;     //光源位置
uniform vec3 objectColor; //物體?色
uniform vec3 viewPo;  //物體位置
varying vec3 outNormal; //傳?當(dāng)前頂點平面的法向量

//確保法線為單位向量量
vec3 norm = normalize(outNormal); 
//頂點指向光源 單位向量量
vec3 lightDir = normalize(lightPo - FragPo);
 //得到兩向量量的cos值 ?小于0則則為0
float diff = max(dot(norm, lightDir),0.0); 
//得到漫反射收的光源向量量
vec3 diffuse = diff * lightColor;
vec3 result  = diffuse * ojbectColor;
gl_FragColor = vec4(result,1.0);

4.鏡面光照計算
鏡面光是由表面直接反射的高亮光,這個高亮光就像鏡子一樣跟表面材質(zhì)多少有關(guān)。

鏡面光照.png
其中:N表示平面法線,R表示反射光線,@表示視點與反射光的夾角
鏡面反射顏色 = 光源的鏡面光顏色 × 物體的鏡面材質(zhì)顏色 × 鏡面反射因子
鏡面反射因子SpecularFactor = power(max(0,dot(N,R)),shininess)
dot(N,R):H,R的點積幾何意義:平?線與法線夾角的cos值
shiniess : ?光的反光度/發(fā)光值;(值越大反射度越強(qiáng))
一個物體的發(fā)光值越高,反射光的能力越強(qiáng),散射得越少,高光點越小。在下面的圖片里,你會看到不同發(fā)光值對視覺(效果)的影響:
發(fā)光值.png
一般我們不希望鏡面成分過于顯眼,所以我們通常把shiniess指數(shù)設(shè)置為32.

鏡面光的GLSL實現(xiàn)代碼:

//鏡?面強(qiáng)度
float specularStrength = 0.5;
//頂點指向觀察點的單位向量量
vec3 viewDir = normalize(viewPo - FragPo);
//求得光線 在 頂點的反射線(傳?入光源指向頂點的向量量)
vec3 reflectDir = reflect(-lightDir ,outNormal);
// 求得夾?角cos值 取256次冪 注意 pow(float,float)函數(shù)參數(shù)類型 float spec = pow(max(dot(viewDir, reflectDir),0.0),256.0);
vec3 specular = specularStrength * spec * lightColor;

我們都知道光的傳播是會衰減的,所以光照顏色的公式可總結(jié)為:
光照顏色 =(環(huán)境顏色 + 漫反射顏色 + 鏡?反射顏色)* 衰減因子

衰減因子公式.png
衰減因子 = 1.0/(距離衰減常量 + 線性衰減常量 * 距離 + ?次衰減常量 * 距離的平?)

//距離衰減常量量
float constantPara = 1.0f;
//線性衰減常量量
float linearPara = 0.09f;
//?二次衰減因?子
float quadraticPara = 0.032f;
//距離
float LFDistance = length(lightPo - FragPo);
//衰減因?子
float lightWeakPara = 1.0/(constantPara + linearPara * LFDistance + quadraticPara * (LFDistance*LFDistance));

距離衰減常量,線性衰減常量和?次衰減常量均為常量值.

環(huán)境光,漫反射光和鏡面光的強(qiáng)度都會受距離的增大?衰減,只有發(fā)射光和全局環(huán)境光的強(qiáng)度不會受影響.

聚光燈夾角cos值 = power(max(0,dot(單位光源位置,單位光線向量)),聚光燈指數(shù));

單位光線向量是從光源指向頂點的單位向量
聚光燈指數(shù),表示聚光燈的亮度程度
公式解讀:單位光源位置 * 單位光線向量點積的聚光燈指數(shù)次?。

聚光燈有過渡和無過渡處理.png
聚光燈因子 = clamp((外環(huán)的聚光燈角度cos值 - 當(dāng)前頂點的聚光燈角度cos值)/ (外環(huán)的聚光燈角度cos值- 內(nèi)環(huán)聚光燈的角度的cos值),0,1);

//聚光燈過渡計算
//(?些復(fù)雜的計算操作 應(yīng)該讓CPU做,提?效率,不變的量也建議外部傳輸,避免重復(fù)計算)
//內(nèi)錐角cos值
float inCutOff = cos(radians(10.0f)); 
//外錐角cos值
float outCutOff = cos(radians(15.0f)); //聚光朝向
vec3 spotDir = vec3(-1.2f,-1.0f,-2.0f);
//光源指向物體的向量和聚光朝向的 cos值
float theta = dot(lightDir ,normalize(-spotDir)); 
//內(nèi)外錐?角cos差值
float epsilon = inCutOff - outCutOff;

//clamp(a,b,c);若b<a<c 則函數(shù)返回值為a 
//若不是,則返回值最小為b ,最大為c
// (theta - outCutOff)/epsilon 若theta的角度?于內(nèi)錐角 則其值 >=1 
//若theta的?度大于外錐角,則其值<=0 這樣光線就在內(nèi)外錐角之間平滑變化.
float intensity = clamp((theta - outCutOff)/epsilon, 0.0,1.0)

光照顏色的最終公式為:
光照顏色 = 發(fā)射顏色 + 全局環(huán)境顏色 + (環(huán)境顏色 + 漫反射顏色 + 鏡?反射顏色) * 聚光燈效果 * 衰減因子

下面通過一個GLKit繪制金字塔案例來看一下光照的使用:
最終效果圖.png

如果把效果圖里的三角形的頂點全部畫到平面上來,如下圖所示:
頂點圖.png

1.設(shè)置OpenGL ES

@property(nonatomic,strong)EAGLContext *mContext;
//基本Effect 繪圖
@property(nonatomic,strong)GLKBaseEffect *baseEffect;
//額外Effect 輔助線段
@property(nonatomic,strong)GLKBaseEffect *extraEffect;
//頂點緩存區(qū) (頂點,顏色,紋理, 法線...)
@property(nonatomic,strong)AGLKVertexAttribArrayBuffer *vertexBuffer;
//法線位置緩存區(qū)(法線輔助線段也有頂點)
@property(nonatomic,strong)AGLKVertexAttribArrayBuffer *extraBuffer;
//是否繪制法線
@property(nonatomic,assign)BOOL shouldDrawNormals;
//中心點的高 默認(rèn)在(0,0,0)
@property(nonatomic,assign) GLfloat centexVertexHeight;



{
    //三角形-8面
    SceneTriangle triangles[NUM_FACES];
}

設(shè)置GLKitView并設(shè)置上下文

//1.新建OpenGL ES 上下文
self.mContext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
//2.設(shè)置GLKView
GLKView *view = (GLKView *)self.view;
view.context = self.mContext;
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
[EAGLContext setCurrentContext:self.mContext];

2. 設(shè)置金字塔Effect

   //1.金字塔Effect
    self.baseEffect = [[GLKBaseEffect alloc]init];
    self.baseEffect.light0.enabled = GL_TRUE;
    //光的漫射部分 GLKVector4Make(R,G,B,A)
    self.baseEffect.light0.diffuseColor = GLKVector4Make(0.7f, 0.7f, 0.7, 1.0f);
    //世界坐標(biāo)中的光的位置。
    self.baseEffect.light0.position = GLKVector4Make(1.0f, 1.0f, 0.5f, 0.0f);
    
    //2.法線Effect
    self.extraEffect = [[GLKBaseEffect alloc]init];
    self.extraEffect.useConstantColor = GL_TRUE;
    
    //3.調(diào)整模型矩陣,更好的觀察
    //可以嘗試不執(zhí)行這段代碼,改為false
    if (true) {
        
        //圍繞x軸旋轉(zhuǎn)-60度
        //返回一個4x4矩陣進(jìn)行繞任意矢量旋轉(zhuǎn)
        GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(-60.0f), 1.0f, 0.0f, 0.0f);
        
        //圍繞z軸,旋轉(zhuǎn)-30度
        modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix,GLKMathDegreesToRadians(-30.0f), 0.0f, 0.0f, 1.0f);
        
        //圍繞Z方向,移動0.25f
        modelViewMatrix = GLKMatrix4Translate(modelViewMatrix, 0.0f, 0.0f, 0.25f);
        
        //設(shè)置baseEffect,extraEffect 模型矩陣
        self.baseEffect.transform.modelviewMatrix = modelViewMatrix;
        self.extraEffect.transform.modelviewMatrix = modelViewMatrix;    
    }

3.設(shè)置頂點

   //確定圖形的8個面
    triangles[0] = SceneTriangleMake(vertexA, vertexB, vertexD);
    triangles[1] = SceneTriangleMake(vertexB, vertexC, vertexF);
    triangles[2] = SceneTriangleMake(vertexD, vertexB, vertexE);
    triangles[3] = SceneTriangleMake(vertexE, vertexB, vertexF);
    triangles[4] = SceneTriangleMake(vertexD, vertexE, vertexH);
    triangles[5] = SceneTriangleMake(vertexE, vertexF, vertexH);
    triangles[6] = SceneTriangleMake(vertexG, vertexD, vertexH);
    triangles[7] = SceneTriangleMake(vertexH, vertexF, vertexI);
    
    //初始化緩存區(qū)
    self.vertexBuffer = [[AGLKVertexAttribArrayBuffer alloc]initWithAttribStride:sizeof(SceneVertex) numberOfVertices:sizeof(triangles)/sizeof(SceneVertex) bytes:triangles usage:GL_DYNAMIC_DRAW];
    
    self.extraBuffer = [[AGLKVertexAttribArrayBuffer alloc]initWithAttribStride:sizeof(SceneVertex) numberOfVertices:0 bytes:NULL usage:GL_DYNAMIC_DRAW];

    self.centexVertexHeight = 0.0f;

4.開始繪制

-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    //1.
    glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    //2.
    [self.baseEffect prepareToDraw];
    //準(zhǔn)備繪制頂點數(shù)據(jù)
    [self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition numberOfCoordinates:3 attribOffset:offsetof(SceneVertex,position)shouldEnable:YES];
    //準(zhǔn)備繪制光照數(shù)據(jù)
    [self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribNormal numberOfCoordinates:3 attribOffset:offsetof(SceneVertex, normal) shouldEnable:YES];
    [self.vertexBuffer drawArrayWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:sizeof(triangles)/sizeof(SceneVertex)];
    
    //3.是否要繪制光照法線
    if (self.shouldDrawNormals) {
        [self drawNormals];
    }
}

繪制法線

//繪制法線
-(void)drawNormals
{
    
    GLKVector3 normalLineVertices[NUM_LINE_VERTS];
    
    //1.以每個頂點的坐標(biāo)為起點,頂點坐標(biāo)加上法向量的偏移值作為終點,更新法線顯示數(shù)組
    //參數(shù)1: 三角形數(shù)組
    //參數(shù)2:光源位置
    //參數(shù)3:法線顯示的頂點數(shù)組
    SceneTrianglesNormalLinesUpdate(triangles, GLKVector3MakeWithArray(self.baseEffect.light0.position.v), normalLineVertices);
    
    //2.為extraBuffer重新開辟空間
    [self.extraBuffer reinitWithAttribStride:sizeof(GLKVector3) numberOfVertices:NUM_LINE_VERTS bytes:normalLineVertices];
    
    //3.準(zhǔn)備繪制數(shù)據(jù)
    [self.extraBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition numberOfCoordinates:3 attribOffset:0 shouldEnable:YES];
    
    //4.修改extraEffect
    //法線
    /*
     指示是否使用常量顏色的布爾值。
     如果該值設(shè)置為gl_true,然后存儲在設(shè)置屬性的值為每個頂點的顏色值。如果該值設(shè)置為gl_false,那么你的應(yīng)用將使glkvertexattribcolor屬性提供每頂點顏色數(shù)據(jù)。默認(rèn)值是gl_false。
     */
    self.extraEffect.useConstantColor = GL_TRUE;
    //設(shè)置光源顏色為綠色,畫頂點法線
    self.extraEffect.constantColor = GLKVector4Make(0.0f, 1.0f, 0.0f, 1.0f);
    //準(zhǔn)備繪制-綠色的法線
    [self.extraEffect prepareToDraw];
    //繪制線段
    [self.extraBuffer drawArrayWithMode:GL_LINES startVertexIndex:0 numberOfVertices:NUM_NORMAL_LINE_VERTS];
    
    //設(shè)置光源顏色為黃色,并且畫光源線
    //Red+Green =Yellow
    self.extraEffect.constantColor = GLKVector4Make(1.0f, 1.0f, 0.0f, 1.0f);
    
    //準(zhǔn)備繪制-黃色的光源方向線
    [self.extraEffect prepareToDraw];
    
    //(NUM_LINE_VERTS - NUM_NORMAL_LINE_VERTS) = 2 .2點確定一條線
    [self.extraBuffer drawArrayWithMode:GL_LINES startVertexIndex:NUM_NORMAL_LINE_VERTS numberOfVertices:2];
}

//更新法向量
-(void)updateNormals
{
    //更新每個點的平面法向量
    SceneTrianglesUpdateFaceNormals(triangles);
    [self.vertexBuffer reinitWithAttribStride:sizeof(SceneVertex) numberOfVertices:sizeof(triangles)/sizeof(SceneVertex) bytes:triangles];
}

更新中心頂點

- (IBAction)changeCenterVertexHeight:(UISlider *)sender {
  
    self.centexVertexHeight = sender.value;
}

-(void)setCentexVertexHeight:(GLfloat)centexVertexHeight
{
    _centexVertexHeight = centexVertexHeight;
    
    //更新頂點 E
    SceneVertex newVertexE = vertexE;
    newVertexE.position.z = _centexVertexHeight;
    
    triangles[2] = SceneTriangleMake(vertexD, vertexB, newVertexE);
    triangles[3] = SceneTriangleMake(newVertexE, vertexB, vertexF);
    triangles[4] = SceneTriangleMake(vertexD, newVertexE, vertexH);
    triangles[5] = SceneTriangleMake(newVertexE, vertexF, vertexH);
    
    //更新法線
    [self updateNormals];
}

在實際的開發(fā)中,我們在繪圖時是不會把法線繪制在圖形中的,所以繪制法線部分代碼可以忽略。


GLKit繪制光照金字塔.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容