cocos creator探照灯效果实现
时间:2022-07-22
本文章向大家介绍cocos creator探照灯效果实现,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
介绍
探照灯效果就是指整个场景或者图片都是黑的,只有灯光照射的地方才是亮的。实现方式有很多种,我们这里用shader来实现,主要原因就是用shader来实现,效率更高,效果更好,并且拓展性更强一些。下面是一个探照灯效果:
探照灯实现
光照原理
我们知道在程序中颜色由RGB三个数值来决定,比如红光的RGB值是255、0、0,但在GLSL语言中表示颜色的最大值是1.0,而且通常用一个vec4来表示,所以红光就是:vec4(1.0, 0.0, 0.0, 1.0); 第四个参数是alpha值,这里我们都设置为1。
这里我们再说一下现实中我们看到物体有各式各样的颜色,并不是它们本身是这个颜色,而是它所能反射光的颜色。太阳光中是包含所有颜色光的,而一个物体能反射什么颜色的光与物体本身粒子特性有关,决定了它能反射光的波长范围,而其他范围的光则被吸收,所以我们就看到了各种颜色的物体。如下图所示:
利用这个原理,我们就可以通过shader来控制目标的颜色显示来模拟光照了,比如我们设定一个白色光照:vec4(1.0, 1.0, 1.0, 1.0)。该向量与片段着色器中的颜色相乘,因为每个值都是1,所以物体颜色并不会改变。当我们设置为红光vec4(1.0, 0.0, 0.0, 1.0)时,物体就会发红。
我们再控制光照的范围是一个圆形,那么就是一个探照灯效果了。
着色器实现
- 下面是着色器的实现过程,不是完整代码,只有代码片段,熟悉GLSL语言的同学应该可以看懂,不了解的同学可以直接到文末获取完整代码再回头过来学习亦可。
- 首先是光照范围逻辑,根据两点距离和光照中心点来判断是否在光照范围内,光照范围内的点为目标颜色,范围外的点则设置成黑色:
// 计算当前点是否在光照范围内
float dist = sqrt(pow(fragPos.x - light_center.x, 2.0) + pow(fragPos.y - light_center.y, 2.0));
// 光照半径
float light_radius = 0.2;
// 根据点到光源圆心的距离判断光照范围
if (dist > light_radius) {
// 超出光照范围则为黑色
gl_FragColor = vec4(0, 0, 0, 1.0);
} else {
gl_FragColor = o;
}
运行效果:
- 此时一个大体的光照范围效果就有了,但是不太自然,光照的边缘没有过渡,并且有锯齿存在,下面我们通过插值来把光照的边缘增加过渡效果:
// 通过插值让光照过度平滑
float r = (1.0 - smoothstep(light_radius, light_radius + 0.1, dist));
gl_FragColor = vec4(o.r * r, o.g * r, o.b * r, 1.0);
- smoothstep函数接收三个参数,第一个是范围的最小值,第二个是范围的最大值,第三个参数是需要计算的目标值x,x小于最小值则返回0,大于最大值返回1,其余返回0到1的插值。上面代码里的范围是光照半径及半径加0.1的范围内进行插值来实现光线边缘的过渡效果。修改完再看,效果已经不错了:
- 最后我们通过代码来设置light_center参数就能实现光源的移动了:
// Touch move事件函数
public touchMove(event: cc.Event) {
let touch: cc.Touch = event.touch;
// 根据屏幕坐标获得-1 ~ 1 的范围坐标,传递给着色器使用
let x = touch.getLocation().x / (cc.winSize.width / 2) - 1;
let y = touch.getLocation().y / (cc.winSize.height / 2) - 1;
let center = cc.v2(x, y);
this._materi.effect.setProperty("light_center", center);
}
- 细心的朋友可能发现,如果你的场景或者目标图片不是正方形,那么光照的范围可能就不是个正圆了,而是一个椭圆。这是因为OpenGL中的坐标范围是从-1到1,但宽高长度并不一定是等比的。所以我们在计算圆的范围时,可以把场景或者图片的宽高比传入进来,然后计算圆范围时进行修正就可以了:
// 计算当前点是否在光照范围内, 增加宽高比的修正,wh_ratio是传入的目标宽高比。
float dist = sqrt(pow(fragPos.x * wh_ratio - light_center.x * wh_ratio, 2.0) + pow(fragPos.y - light_center.y, 2.0));
进一步优化
漫反射环境光
- 大家知道,无论我们在多么漆黑的屋子里,我们总是能隐约看见物体轮廓的,这就是因为有漫反射的环境光造成的。物体表面都不是光滑的,都是凹凸不平的,总会把光源的光线,反射到各个方向,其它物体接收到反射光后会再次反射,反复循环,这就是漫反射,我们也叫环境光。这就是为什么我们在没有光源的屋子里也能看见东西的原因,漫反射原理如下图:
- 在我们的例子中光线没有照到的地方漆黑一片,有时我们也需要一些漫反射造成的环境光。真实的漫反射算法非常复杂,要通过法向量来运算,我们这里只是一个2D的纹理贴图,所以我们就把环境光设置成一个平均的常数就可以了,在光线没有照到的逻辑中加入环境光逻辑:
// 设置环境光强度为0.1
float ambient_strength = 0.1;
// 通过插值让光照过度平滑
float r = (1.0 - smoothstep(light_radius, light_radius + 0.1, dist));
// 该判断用于保证漫反射的环境光有效
if (r <= ambient_strength) {
r = ambient_strength;
}
gl_FragColor = vec4(o.r * r, o.g * r, o.b * r, 1.0);
- 这时运行效果就和教程开头的效果基本一样了。
光照强度和光源颜色
- 前面的实现过程中我们对在光照范围内的目标颜色没有干预,保证了目标的固有色。但有时我们也需要改变一些,比如上面讲到了环境光,没有光照的地方并不是漆黑一片了,那么反过来,有光照的地方就一定是亮的吗?有时我们也需要调整一下光照的亮度和光照的颜色。
- 我们先看光照的亮度,这个比较简单,逻辑和环境光强度一样,我们再定义一个光源强度的参数就可以了:
// 光照强度,我们修改为0.8
float result = 0.8;
// 根据点到光源圆心的距离判断光照范围
if (dist > light_radius) {
// 设置环境光强度为0.1
float ambient_strength = 0.1;
// 通过插值让光照过度平滑
float r = (1.0 - smoothstep(light_radius, light_radius + 0.1, dist)) * result;
// 该判断用于保证漫反射的环境光有效
if (r <= ambient_strength) {
r = ambient_strength;
}
gl_FragColor = vec4(o.r * r, o.g * r, o.b * r, 1.0);
} else {
gl_FragColor = vec4(o.r * result, o.g * result, o.b * result, 1.0);
}
- 我们将光照部分和参与插值运算的光照边缘过渡部分都乘上光照强度就可以实现我们的目标效果了。(不过这里需要注意的一个问题是,光照强度result如果小于环境光强度ambient_strength会是什么效果呢?大家可以试一下,并尝试修改。)
- 接下来我们增加光源颜色的支持,根据教程开头讲的光照原理,我们可以直接把之前我们例子中的目标颜色乘以一个光源颜色就可以了,光源颜色我们就用上面说过的红光:
// 光源颜色,红光
vec4 light_color = vec4(1, 0, 0, 1);
// 光照强度,我们修改为0.8
float result = 0.8;
// 根据点到光源圆心的距离判断光照范围
if (dist > light_radius) {
// 设置环境光强度为0.1
float ambient_strength = 0.1;
// 通过插值让光照过度平滑
float r = (1.0 - smoothstep(light_radius, light_radius + 0.1, dist)) * result;
// 该判断用于保证漫反射的环境光有效
if (r <= ambient_strength) {
r = ambient_strength;
}
gl_FragColor = vec4(o.r * r, o.g * r, o.b * r, 1.0) * light_color;
} else {
gl_FragColor = vec4(o.r * result, o.g * result, o.b * result, 1.0) * light_color;
}
- 运行效果如下:
最后
如果有其它问题可以关注我的公众号给我留言。完整代码地址如下:https://github.com/yue19870813/cocos_demo/tree/master/CocosDemo_2_3_3/assets/case/searchlight
- 对X86汇编的理解与入门
- BZOJ 2748: [HAOI2012]音量调节【二维dp,枚举】
- POJ 3264 Balanced Lineup【线段树区间查询求最大值和最小值】
- HDU 2289 Cup【高精度,二分】
- BZOJ 1083: [SCOI2005]繁忙的都市【Kruscal最小生成树裸题】
- [快学Python3]二分查找[策略优化版本]
- 微服务与SOA架构(4)
- 移动测试Appium之API手册
- BZOJ 1088: [SCOI2005]扫雷Mine【思维题,神奇的模拟+枚举】
- 浅谈关于特征选择算法与Relief的实现
- HDU 5752 Sqrt Bo【枚举,大水题】
- 移动测试 Appium源码初探
- UESTC 1599 wtmsb【优先队列+排序】
- BZOJ 1029: [JSOI2007]建筑抢修【优先队列+贪心策略】
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 学以致用C++设计模式 之 “备忘录模式”
- Mybatis学习笔记(三)关联查询以及相关属性
- 学以致用C++设计模式 之 “装饰者模式”
- 学以致用C++设计模式 之 “责任链模式”
- 学以致用C++设计模式 之 “命令模式”
- 学以致用C++设计模式 之 “中介模式”
- 【自然语言处理】利用LDA对希拉里邮件进行主题分析
- 学以致用C++设计模式 之 “代理模式”
- 学以致用C++设计模式 “模板方法模式”
- 学以致用C++设计模式 “抽象工厂模式”
- 学以致用C++设计模式 之 “工厂模式”
- 六大原则不熟?那你学什么设计模式?来来来,赶紧来!
- 精品:TCP连接的建立和终止
- python--几种快速排序的实现以及运行时间比较
- TCP/IP详解 -奠基篇