OpenGL ES 学习教程(10.3):Spot Light(聚光灯)
这篇介绍最后一种光,前面学习了平行光、定点光,这一次来学习聚光灯。聚光灯是朝某一个方向照射的光,而且有一个固定的半径 ,在这个半径之内,物体才受到光照影响,这个范围之外的物体就不会被光照影响到。
聚光灯在现实生活中的例子有:手电筒、路灯等。
( 游戏中的手电筒 就是聚光灯 )
简单的来说,聚光灯就是指定一个圈,在这个圈里面就计算光照,在这个圈外面就不计算光照。
我简单的画了一张图来解释
仍然以前面的 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 个箱子 ,在聚光灯的 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