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

馮氏光照模型

首先,在現實世界的光照是極其複雜的,並且會受到諸多因素的影響,這是咱們有限的計算能力所沒法模擬的。所以OpenGL的光照使用的是簡化的模型,對現實的狀況進行近似,這樣處理起來會更容易一些。這些光照模型都是基於咱們對光的物理特性的理解。其中一個模型被稱爲馮氏光照模型(Phong Lighting Model)。馮氏光照模型的主要結構由3個元素組成:環境(Ambient)光照、漫反射(Diffuse)光照和鏡面(Specular)光照。 下面咱們用示例圖來看一下這幾種光照:數組

光照.png
環境(Ambient)光照:即便在最黑暗的正常環境下,世界上依然會存在一些光亮,也就是說物體幾乎永遠不會是徹底黑暗的。環境光照通常是由室外的太陽經過各類折射來讓咱們看到,這樣的光是沒有任何起點,沒有方向的光。主要經過設置物體顏色來獲取。 漫反射(Diffuse)光照:模擬的是光源對物體方向的影響,一個物體面向光源的時候,朝向光的那個面會亮一點,物體背面和其餘面會暗一點。 鏡面(Specular)光照:模擬的是有光澤物體上面出現的亮點。鏡面光照的顏色相比於物體的顏色會更傾向於光的顏色。

光照基礎

OpenGL在處理光照時把光照系統分爲三部分:分別是光源、材質和光照環境。光源就是光的來源,能夠是前面所說的太陽或者電燈,蠟燭等。材質是指接受光照的各類物體的表面,因爲物體如何反射光線只由物體表面決定,材質特色就決定了物體反射光線的特色。光照環境是指一些額外的參數,它們將影響最終的光照畫面,好比一些光線通過屢次反射後,已經沒法分清它到底是由哪一個光源發出,這時,指定一個環境亮度參數,可使最後造成的畫面更接近於真實狀況。緩存

在物理學中,光線若是射入理想的光滑平面,則反射後的光線是很規則的(這樣的反射稱爲鏡面反射)。光線若是射入粗糙的、不光滑的平面,則反射後的光線是雜亂的(這樣的反射稱爲漫反射)。現實生活中的物體在反射光線時,並非絕對的鏡面反射或漫反射,但能夠當作是這兩種反射的疊加。對於光源發出的光線,能夠分別設置其通過鏡面反射和漫反射後的光線強度。對於被光線照射的材質,也能夠分別設置光線通過鏡面反射和漫反射後的光線強度。這些因素綜合起來,就造成了最終的光照效果。bash

光照特性 1.發射光:由物體自身發光 2.環境光:就是在環境中充分散射的光,並且沒法分辨光的方向 3.漫反射光:光線來自某個方向,可是在物體上各個方向反射 4.鏡面高光:光線來自一個特定的方向,而後在物體表面上以一個特定的方向反射出去ide

材質屬性 1.泛射材質:光線直射,反射率較高 2.漫反射材質:須要考慮光的入射角和反射角的 3.鏡面反射材質:斑點 4.發射材質:物體自己就能夠發光的材質函數

光照計算ui

1.環境光的計算atom

環境光是不來自任何特定方向的光,在整個場景中經典光照模型把它當成一個常量,組成一個合適的第一近似值來縮放場景中的光照部分。 環境光 = 光源的環境光顏色 * 物體的材質顏色 spa

環境光計算.png

varying vec3 objectColor;
void main()
{
  //⾄至少有%10的光找到物體全部⾯面
  float ambientStrength = 0.1;
  //環境光顏⾊色
  vec3 ambient = ambientStrength * lightColor;
  //最終顏⾊色 = 環境光顏⾊色 * 物體顏⾊色
  vec3 result = ambient * objectColor;
  gl_FragColor = vec4(result, 1.0);
}
複製代碼

2.發射光的計算3d

若是這個物體自己就是有顏色的,好比說夜明珠,那麼這個時候這個光就是這個物體材質的顏色 發射顏色 = 物體的反射材質顏色code

3.漫反射光的計算

漫反射光是散射在各個方向上的均勻的表面特定光源,漫反射光依賴於表面法線方向和光源方向來計算,可是沒有包含視線方向,它一樣依賴於表面的顏色。 首先來看一下環境光和漫反射光的比較:

光照比較.png
能夠看到環境光下的蘋果是沒有陰面和陽面的,看到的蘋果感受不夠逼真,而漫反射光加深了蘋果的真實度,模擬出了在現實生活中的真實環境。
漫反射光的計算.png
上圖是當漫反射光照射到物體表面時:其中N表示法向量,L表示光源,法向量N和光源L之間的夾角決定了光照射的面積。夾角越大照射面積越大。

光線照射到物體表面,決定了物體表面的光照強度,光照強度是光自己強度和光線與物體表面法線夾角cos的乘積

有效光.png
有效光的光照方向是與物體表面法線夾角在0~90度之間的。

漫反射顏色 = 光源的漫反射光顏色 × 物體的漫反射材質顏色 × 漫反射因子

漫反射因子.png
漫反射因子DiffuseFactor 是光線與頂點法線向量的點積 DiffuseFactor = max(0, dot(N, L))

uniform vec3 lightColor;    //光源色
uniform vec3 lightPo;     //光源位置
uniform vec3 objectColor; //物體⾊色
uniform vec3 viewPo;  //物體位置
varying vec3 outNormal; //傳⼊當前頂點平面的法向量

//確保法線爲單位向量量
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.鏡面光照計算 鏡面光是由表面直接反射的高亮光,這個高亮光就像鏡子同樣跟表面材質多少有關。

鏡面光照.png
其中:N表示平面法線,R表示反射光線,@表示視點與反射光的夾角

鏡面反射顏色 = 光源的鏡面光顏色 × 物體的鏡面材質顏色 × 鏡面反射因子

鏡面反射因子SpecularFactor = power(max(0,dot(N,R)),shininess)

dot(N,R):H,R的點積幾何意義:平⽅線與法線夾角的cos值

shiniess : ⾼光的反光度/發光值;(值越大反射度越強)

一個物體的發光值越高,反射光的能力越強,散射得越少,高光點越小。在下面的圖片裏,你會看到不一樣發光值對視覺(效果)的影響:

發光值.png
通常咱們不但願鏡面成分過於顯眼,因此咱們一般把shiniess指數設置爲32.

鏡面光的GLSL實現代碼:

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

咱們都知道光的傳播是會衰減的,因此光照顏色的公式可總結爲:

光照顏色 =(環境顏色 + 漫反射顏色 + 鏡⾯反射顏色)* 衰減因子

衰減因子公式.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));
複製代碼

距離衰減常量,線性衰減常量和⼆次衰減常量均爲常量值.

環境光,漫反射光和鏡面光的強度都會受距離的增大⽽衰減,只有發射光和全局環境光的強度不會受影響.

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

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

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

//聚光燈過渡計算
//(⼀些複雜的計算操做 應該讓CPU作,提⾼效率,不變的量也建議外部傳輸,避免重複計算)
//內錐角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)); 
//內外錐⻆角cos差值
float epsilon = inCutOff - outCutOff;

//clamp(a,b,c);若b<a<c 則函數返回值爲a 
//若不是,則返回值最小爲b ,最大爲c
// (theta - outCutOff)/epsilon 若theta的角度⼩於內錐角 則其值 >=1 
//若theta的⻆度大於外錐角,則其值<=0 這樣光線就在內外錐角之間平滑變化.
float intensity = clamp((theta - outCutOff)/epsilon, 0.0,1.0)
複製代碼

光照顏色的最終公式爲:

光照顏色 = 發射顏色 + 全局環境顏色 + (環境顏色 + 漫反射顏色 + 鏡⾯反射顏色) * 聚光燈效果 * 衰減因子

下面經過一個GLKit繪製金字塔案例來看一下光照的使用:

最終效果圖.png

若是把效果圖裏的三角形的頂點所有畫到平面上來,以下圖所示:

頂點圖.png

1.設置OpenGL ES

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



{
    //三角形-8面
    SceneTriangle triangles[NUM_FACES];
}
複製代碼

設置GLKitView並設置上下文

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

2. 設置金字塔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);
    //世界座標中的光的位置。
    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.調整模型矩陣,更好的觀察
    //能夠嘗試不執行這段代碼,改成false
    if (true) {
        
        //圍繞x軸旋轉-60度
        //返回一個4x4矩陣進行繞任意矢量旋轉
        GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(-60.0f), 1.0f, 0.0f, 0.0f);
        
        //圍繞z軸,旋轉-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);
        
        //設置baseEffect,extraEffect 模型矩陣
        self.baseEffect.transform.modelviewMatrix = modelViewMatrix;
        self.extraEffect.transform.modelviewMatrix = modelViewMatrix;    
    }
複製代碼

3.設置頂點

//肯定圖形的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);
    
    //初始化緩存區
    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];
    //準備繪製頂點數據
    [self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition numberOfCoordinates:3 attribOffset:offsetof(SceneVertex,position)shouldEnable:YES];
    //準備繪製光照數據
    [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.以每一個頂點的座標爲起點,頂點座標加上法向量的偏移值做爲終點,更新法線顯示數組
    //參數1: 三角形數組
    //參數2:光源位置
    //參數3:法線顯示的頂點數組
    SceneTrianglesNormalLinesUpdate(triangles, GLKVector3MakeWithArray(self.baseEffect.light0.position.v), normalLineVertices);
    
    //2.爲extraBuffer從新開闢空間
    [self.extraBuffer reinitWithAttribStride:sizeof(GLKVector3) numberOfVertices:NUM_LINE_VERTS bytes:normalLineVertices];
    
    //3.準備繪製數據
    [self.extraBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition numberOfCoordinates:3 attribOffset:0 shouldEnable:YES];
    
    //4.修改extraEffect
    //法線
    /*
     指示是否使用常量顏色的布爾值。
     若是該值設置爲gl_true,而後存儲在設置屬性的值爲每一個頂點的顏色值。若是該值設置爲gl_false,那麼你的應用將使glkvertexattribcolor屬性提供每頂點顏色數據。默認值是gl_false。
     */
    self.extraEffect.useConstantColor = GL_TRUE;
    //設置光源顏色爲綠色,畫頂點法線
    self.extraEffect.constantColor = GLKVector4Make(0.0f, 1.0f, 0.0f, 1.0f);
    //準備繪製-綠色的法線
    [self.extraEffect prepareToDraw];
    //繪製線段
    [self.extraBuffer drawArrayWithMode:GL_LINES startVertexIndex:0 numberOfVertices:NUM_NORMAL_LINE_VERTS];
    
    //設置光源顏色爲黃色,而且畫光源線
    //Red+Green =Yellow
    self.extraEffect.constantColor = GLKVector4Make(1.0f, 1.0f, 0.0f, 1.0f);
    
    //準備繪製-黃色的光源方向線
    [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];
}
複製代碼

在實際的開發中,咱們在繪圖時是不會把法線繪製在圖形中的,因此繪製法線部分代碼能夠忽略。

GLKit繪製光照金字塔.png
相關文章
相關標籤/搜索