本文参考
http://learnopengl-cn.readthedocs.org/zh/latest/02%20Lighting/05%20Light%20casters/
这里是最后一种光 了,前面学习了 平行光、定点光,这一次来学习 聚光灯。
聚光灯 是朝某一个方向 照射的光,而且有一个固定的半径 ,在这个半径之内,物体才受到光照影响,这个范围之外的物体就不会被光照影响到。
聚光灯在现实生活中的例子有:手电筒、路灯等。 转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn
( 游戏中的手电筒 就是聚光灯 )
简单的来说,聚光灯就是指定一个圈,在这个圈里面就计算光照,在这个圈外面就不计算光照。
我简单的画了一张图来解释转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn
仍然以前面的 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 ):转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn
"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");
//传入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://blog.csdn.net/huutu http://www.thisisgame.com.cn
测试结果
测试工程下载:
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;"
"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);
测试项目下载:
http://pan.baidu.com/s/1mh31fUK