OpenGL ES 学习教程(10.3):Spot Light(聚光灯)

发表于2017-11-22
评论0 5.1k浏览

这篇介绍最后一种光,前面学习了平行光、定点光,这一次来学习聚光灯。聚光灯是朝某一个方向照射的光,而且有一个固定的半径 ,在这个半径之内,物体才受到光照影响,这个范围之外的物体就不会被光照影响到。


聚光灯在现实生活中的例子有:手电筒、路灯等。

( 游戏中的手电筒 就是聚光灯 )


简单的来说,聚光灯就是指定一个圈,在这个圈里面就计算光照,在这个圈外面就不计算光照。

我简单的画了一张图来解释




仍然以前面的 Lighting Map (高光贴图) 工程来进行修改,去除 Diffuse 和 Specular ,只保留 Ambient 来便于测试。


首先在 片段着色器中修改 Light ,添加 position ( 聚光灯位置 ) 、 spotdirection ( 聚光灯指向 ) 、cutOff ( 聚光灯范围 指定角度的 cos 值 )。

如下 ( GLProgram_Cube.h Line 108 ):

"struct Light"  //替换光的RGB分量强度  
"{"  
"       vec3 position;" //聚光灯 位置;  
"       vec3 spotdirection;" //聚光灯 指向,就像路灯总是指向地面,我这里是指向屏幕里面;  
"       vec3 ambient;"  
"       float cutOff;"  //聚光灯范围 cos(角度);  
"};"  

然后计算片段 指向 光源 的向量;

计算 (片段-光源) 向量 与 聚光灯指向向量的角度的余弦值. 
如果计算出来的值比 指定的聚光灯范围 cos(角度) 的值更大,说明角度更小,在聚光灯范围内,就照亮,否则就黑的。

如下 ( GLProgram_Cube.h Line 120 ):

"void main()"  
"{"  
"   vec3 lightDir=normalize(m_light.position - out_fragpos);" //计算片段 指向 光源 的向量;  
"   float theta = dot(lightDir,normalize(-m_light.spotdirection));" //计算 (片段-光源) 向量 与 聚光灯指向向量的角度的余弦值.  这个灯光的方向 是指向屏幕里面的,所以这里用了负值!!  
"   if(theta > m_light.cutOff)"    //如果计算出来的值比 指定的聚光灯范围 cos(角度) 的值更大,说明角度更小,在聚光灯范围内,就照亮,否则就黑的。  
"   {"  
    "   vec3 ambient=vec3(texture2D(m_diffusetexture,m_outUV))*m_light.ambient;"//环境光;  
    "   gl_FragColor=vec4(ambient,1.0);"  
"   }"  
"   else"  
"   {"  
    "   gl_FragColor=vec4(0.0,0.0,0.0,1.0);"  
"   }"  
"}"  


然后获取属性 ( GLProgram_Cube.h Line 150 ):

m_lightspotdirection = glGetUniformLocation(m_programId, "m_light.spotdirection");  
m_lightAmbient = glGetUniformLocation(m_programId, "m_light.ambient");  
m_lightpos = glGetUniformLocation(m_programId, "m_light.position");  
m_lightCutOff = glGetUniformLocation(m_programId, "m_light.cutOff");  

然后设置属性值 ( MyApp.h Line 111 ):
//传入light;  
glUniform3f(m_programCube.m_lightpos, cameraX, cameraY, 10.0f);//传入灯光的位置  
glUniform3f(m_programCube.m_lightAmbient, 1.0f, 1.0f, 1.0f);  
glUniform3f(m_programCube.m_lightspotdirection, cameraX, cameraY, -1.0f); //灯光朝向;  
float cos = glm::cos(glm::radians(12.0f)); //话说我这个相机的距离 ,和这个Cube的角度,不到30度;  
glUniform1f(m_programCube.m_lightCutOff, cos);  

我们计算一下,我这里创建了 9 个箱子,X 轴范围是 -3 到 3 。聚光灯的位置是 (0,0,10) ,聚光灯是朝向 ( 0,0,-1 ) 。

那么可以算的 这 9 个箱子 ,在聚光灯的 30度范围之内。 

所以我这里取了 12度 来作为测试。


测试结果:


测试工程下载:http://pan.baidu.com/s/1jHpYiSe


上面的聚光灯效果太硬,边缘分界太明显,我们可以在边缘处加一个慢慢变暗的效果。

在上面工程基础上,我们给聚光灯添加一个外边框范围 outerCutOff 。

然后通过计算片段位置 与 内边框 和外边框的距离远近,来调整边缘处灯光的强弱。


首先添加外边框范围 outerCutOff ( GLProgram_Cube.h Line106 )

"struct Light"  //替换光的RGB分量强度  
"{"  
"       vec3 position;" //聚光灯 位置;  
"       vec3 spotdirection;" //聚光灯 指向,就像路灯总是指向地面,我这里是指向屏幕里面;  
"       vec3 ambient;"  
"       float cutOff;"  //聚光灯范围 cos(角度) 内圈;  
"       float outerCutOff;" //聚光灯范围 cos(角度) 外圈;  
"};"  
"uniform Light m_light;"  

然后计算 内圈和外圈之间的光照亮度变化 ( GLProgram_Cube.h Line119 ) copy
"void main()"  
"{"  
"   vec3 lightDir=normalize(m_light.position - out_fragpos);" //计算片段 指向 光源 的向量;  
"   float theta = dot(lightDir,normalize(-m_light.spotdirection));" //计算 (片段-光源) 向量 与 聚光灯指向向量的角度的余弦值.  这个灯光的方向 是指向屏幕里面的,所以这里用了负值!!  
"   float epsion = m_light.cutOff - m_light.outerCutOff;" //cos(内圈与聚光灯方向角度) - cos(外圈与聚光灯方向角度);  
"   float autenuation = clamp( (theta - m_light.outerCutOff) / epsion ,0.0,1.0 );"//然后用cos(片段与聚光灯方向角度) - cos(外圈与聚光灯方向角度)。这个意思就是,当片段已经到达 内圈边缘的时候,autenuation=(内圈角度-外圈角度) / (内圈角度-外圈角度) ==1 ,受到100% 光照。然后如果片段已经到达外圈边缘的时候,autenuation = (外圈角度-外圈角度) / (内圈角度-外圈角度) ==0,就没有光照了。片段越靠近外圈边缘,光照越弱,直到到达了外圈边缘,就没有了光照。  
"   vec3 ambient=vec3(texture2D(m_diffusetexture,m_outUV))*m_light.ambient;"//环境光;  
"   ambient = ambient * autenuation;"  
"   gl_FragColor=vec4(ambient,1.0);"  
"}"  

这里使用了 clamp 来限定值范围为 ( 0.0 , 1.0 )

autenuation 是这样算的:

用cos(片段与聚光灯方向角度) - cos(外圈与聚光灯方向角度)。

这个意思就是,当片段已经到达 内圈边缘的时候,autenuation=(内圈角度-外圈角度) / (内圈角度-外圈角度) ==1 ,受到100% 光照。然后如果片段已经到达外圈边缘的时候,autenuation = (外圈角度-外圈角度) / (内圈角度-外圈角度) ==0,就没有光照了。片段越靠近外圈边缘,光照越弱,直到到达了外圈边缘,就没有了光照。


然后传入值 ( MyApp.h Line118 )

float cutoffCos = glm::cos(glm::radians(12.0f)); //话说我这个相机的距离 ,和这个Cube的角度,不到30度;  
float outcutoffCos = glm::cos(glm::radians(20.0f));  
glUniform1f(m_programCube.m_lightCutOff, cutoffCos);  
glUniform1f(m_programCube.m_lightOuterCutOff, outcutoffCos);  

这里选定 12度 为内圈, 20度为外圈,测试效果如下图:


测试项目下载:http://pan.baidu.com/s/1mh31fUK 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引