iOS开发之OpenGL ES(四) —— OpenGL ES光照效果计算

前言

这篇文章主要是为了了解在OpenGL ES中,光照的一些基本概念以及该如何进行光照计算,方便以后在用到光照时,能够理解每一步是如何计算出来的。

什么是光照?

现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是以目前我们所拥有的处理能力无法模拟的。

  • OpenGL的光照仅仅使用了简化的模型并基于对现实的估计来进行模拟,这样处理起来会更容易一些,而且看起来也差不多一样。这些光照模型都是基于我们对光的物理特性的理解。
  • 其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)、漫反射(Diffuse)和镜面


如上图所示
环境光照(Ambient Lighting):即使在黑暗的情况下,世界上也仍然有一些光亮(月亮、一个来自远处的光),所以物体永远不会是完全黑暗的。我们使用环境光照来模拟这种情况,也就是无论如何永远都给物体一些颜色。
漫反射光照(Diffuse Lighting):模拟一个发光物对物体的方向性影响(Directional Impact)。它是冯氏光照模型最显著的组成部分。面向光源的一面比其他面会更亮。
镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色,相比于物体的颜色更倾向于光的颜色。

光照计算

1.环境光的计算

环境光 = 光源的环境光颜色 * 物体的材质颜⾊
环境光的GLSL代码实现如下:

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.发射光的计算

发射颜色 = 物体的反射材质颜色

3.漫反射光照计算

光照强度是光本身强度和光线与物体表面法线夹角cos的乘积.
如下图所示,有效的光照方向是与物体表面法线夹角在0~90度之间的


漫反射颜色 = 光源的漫反射颜色* 物体的漫反射材质颜色 * 漫反射因子(DiffuseFactor)
漫反射因子(DiffuseFactor) = max(0,dot(N,L) //是光线 与顶点法线向量的点积
GLSL代码如下所示:

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 * objectColor;//上一步的结果*物体的漫反射材质颜色
gl_FragColor = vec4(result,1.0);

4.镜面光计算

镜面反射颜色 = 光源的镜面光的颜色 * 物体的镜面材质颜色 * 镜面因子
镜面因子(SpecularFactor) = power(max(0,dot(N,H)),shininess)

  • H :视线向量E 与 光线向量L 的半向量
  • dot(N,H):H,N的点积⼏几何意义,平⽅方线与法线夹角的cos值
  • shiniess : ⾼光的反光度
  • 如下图所示:

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;

5.衰减因子计算

attenuation = 1.0 / (1.0 + a * dist + b * dist * dist)
衰减因子 = 1.0/(距离衰减常量 + 线性衰减常量 * 距离 + 二次衰减常量 * 距离的平⽅)
环境光,漫反射光和镜⾯光的强度都会受距离的增⼤而衰减,只有发射光和全局环境光的强度不会受影响
GLSL代码如下:

//距离衰减常量
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值
聚光灯夹⻆cos值 = power(max(0,dot(单位光源位置,单位光线向量)),聚光灯指数);

  • 单位光线向量是从光源指向顶点的单位向量
  • 聚光灯指数,表示聚光灯的亮度程度
  • 公式解读:单位光源位置 * 单位光线向量点积 的 聚光灯指数次⽅。

聚光灯分为无过渡处理和有过渡处理。如下图所示:


增加过渡计算的聚光灯因子公式:

聚光灯因⼦ = clamp((外环的聚光灯角度cos值 - 当前顶点的聚光灯⻆度cos值)/ (外环的聚光灯角度cos值- 内环聚光灯的⻆度的cos值),0,1);
可以从下图进行理解这个公式:


GLSL代码实现:

//(⼀些复杂的计算操作 应该让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);

6.光照计算综合公式

基本公式:
光照颜色 = (环境颜色 + 漫反射颜色 + 镜面反射颜色) * 衰减因子
终极公式:
光照颜⾊ = 发射颜色 + 全局环境颜色 + (环境颜色 + 漫反射颜⾊ + 镜⾯反射颜⾊色) * 聚光灯效果 * 衰减因⼦
分为平面光和点光源

平面光计算

如下图所示为平面光示意图:


平面光计算的GLSL代码实现:

//环境因⼦
float ambientStrength = 0.3; 
//镜⾯强度
float specularStrength = 2.0; 
//反射强度
float reflectance = 256.0;
//平行光方向
vec3 paraLightDir = normalize(vec3(-0.2,-1.0,-0.3));
//环境光
vec3 ambient = ambientStrength * texture(Texture ,outTexCoord).rgb;
//漫反射
vec3 norm = normalize(outNormal);
//当前顶点⾄光源的单位向量 
vec3 lightDir = normalize(lightPo - FragPo); 
float diff = max(dot(norm ,paraLightDir),0.0);
vec3 diffuse = diff *lightColor*texture(Texture ,outTexCoord).rgb;
//镜面反射
vec3 viewDir = normalize(viewPo - FragPo);
vec3 reflectDir = reflect(-paraLightDir ,outNormal);
float spec = pow(max(dot(viewDir, reflectDir),0.0),reflectance);
vec3 specular = specularStrength * spec * texture(specularTexture ,outTexCoord).rgb;
//最终光照颜色
vec3 res = ambient + diffuse + specular;
FragColor = vec4(res,1.0);
点光源计算

如下图所示为点光源示意图:


点光源计算的GLSL代码实现:

//环境因⼦
float ambientStrength = 0.3; 
//镜⾯强度
float specularStrength = 2.0; 
//反射强度
float reflectance = 256.0;
//常量
float constantPara = 1.0f;
//线性部分因数
float linearPara = 0.09f;
//二次项部分因数
float quadraticPara = 0.032f;
//环境光
vec3 ambient = ambientStrength * texture(Texture ,outTexCoord).rgb;
//漫反射
vec3 norm = normalize(outNormal);
vec3 lightDir = normalize(lightPo - FragPo); //当前顶点⾄光源的单位向量
//点光源
float diff = max(dot(norm ,lightDir),0.0); //光源与法线夹⻆
vec3 diffuse = diff * lightColor * texture(Texture ,outTexCoord).rgb;
//镜⾯反射
vec3 viewDir = normalize(viewPo - FragPo);
vec3 reflectDir = reflect(-lightDir ,outNormal);
float spec = pow(max(dot(viewDir, reflectDir),0.0),reflectance);
vec3 specular = specularStrength * spec * texture(specularTexture ,outTexCoord).rgb;
//光线衰弱
float LFDistance = length(lightPo - FragPo);
float lightWeakPara = 1.0/(constantPara + linearPara * LFDistance + quadraticPara * (LFDistance*LFDistance));
vec3 res = (ambient + diffuse + specular)*lightWeakPara;
FragColor = vec4(res,1.0);

总结

以上就是按步骤来,然后最终完成光照计算的所有过程。在OpenGL ES中光照计算是很复杂的一部分内容了,重点是需要理解每一个步骤,然后能够在使用到光照的时候能够灵活计算就可以了。
GLKit 实现光照效果Demo
GLSL 实现光照效果Demo:待更新

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容

  • 首先需要从Apple 获取开发人员ID证书并将其安装到Mac的钥匙串中,注意:必须是开发人员ID证书,只有申请证书...
    Sultan阅读 3,546评论 0 0
  • 总述 从总体上来讲它是容纳一个纹理的容器。这个类写得太别的散,让人一眼看不出什么主旨要义来,可能是因为作者像让这个...
    Stroman阅读 1,167评论 2 3
  • 我是一个二宝妈妈,为了更好地照顾俩个孩子,我选择了做全职妈妈。虽然生活也算安定,家庭也算和睦,但总觉点什么。 ...
    NgYan阅读 287评论 1 2
  • 昨天忘记更文了,我干嘛去了?想起来了,我更我的小说去了。每天一百字的文,居然还是漏了。 来说点啥? 刚刚在路上还在...
    洛塔锁姑娘阅读 74评论 0 1