Creator | 2D光影效果及手电筒效果

【本文参与征文活动】

群内大佬贡献的2D光影效果及手电筒效果,小编厚着脸皮要来的转发权

大佬QQ:1099263878
欢迎加入QQ交流群:521643513

大佬比较忙,所以由小编来代笔阐述一下大概的方法,具体的实现,请阅读源码

①使用四叉树检测光源与障碍物之间的碰撞

public getPolygonsByLight(light: cc.Vec2) { let r = cc.rect(light.x - this.radius/2, light.y - this.radius/2, this.radius, this.radius); let eles = this.quadTree.retrieve(r); let polygon: cc.Vec2[][] = []; for(const e of eles) { polygon.push(this.polygons[e.id].points); } console.log(eles.length) return polygon; }

②计算射线(光源和半径)和碰撞障碍物的交叉点

点光源:

`/** 寻找射线和线段的交点 */
private static getIntersectBySegment(ray1: cc.Vec2, ray2: cc.Vec2, seg1: cc.Vec2, seg2: cc.Vec2): Intersect{
let r_px = ray1.x;
let r_py = ray1.y;
let r_dx = ray2.x - ray1.x;
let r_dy = ray2.y - ray1.y;

let s_px = seg1.x;
let s_py = seg1.y;
let s_dx = seg2.x - seg1.x;
let s_dy = seg2.y - seg1.y;

let r_mag = Math.sqrt(r_dx*r_dx+r_dy*r_dy);
let s_mag = Math.sqrt(s_dx*s_dx+s_dy*s_dy);
if(r_dx/r_mag==s_dx/s_mag && r_dy/r_mag==s_dy/s_mag){
    // Unit vectors are the same.
    return null;
}

let T2 = (r_dx*(s_py-r_py) + r_dy*(r_px-s_px))/(s_dx*r_dy - s_dy*r_dx);
let T1 = (s_px+s_dx*T2-r_px)/r_dx;

// Must be within parametic whatevers for RAY/SEGMENT
if(T1<0) return null;
if(T2<0 || T2>1) return null;

// Return the POINT OF INTERSECTION
return {
    x: r_px+r_dx*T1,
    y: r_py+r_dy*T1,
    param: T1
};

}

/** 获得所有射线与多边形的交点 /
public static getIntersect(light: cc.Vec2, polygons: cc.Vec2[][]) {
let rayStart = light;
let rayEnds = this.getRayEnds(polygons);
let intersects: Intersect[] = [];
for(const rayEnd of rayEnds) {
// 这里需要对rayend 进行一次分散, 因为在与多边形交点判断时, 需要判断光线在交点周围的情况
// 这里可以做一次优化, 主要是可以不计算三角函数 牺牲一些精度, 让rayEnd左右移动确定值实现分散
let angle = Math.atan2(rayEnd.y-rayStart.y,rayEnd.x-rayStart.x);
for(let i=-1; i<=1; i++) {
let tmpRayEnd = cc.v2(Math.cos(angle+i
0.00001)+rayEnd.x ,Math.sin(angle + i*0.00001)+rayEnd.y);
let intersect = this.getIntersectByPolygons(polygons, rayStart, tmpRayEnd);
if(!intersect) continue;
// 计算角度 用来对交点进行排序
intersect.angle = angle;
intersects.push(intersect);
}
}
intersects = intersects.sort(function(a,b){
return a.angle-b.angle; // 升序
});

return intersects;

}`

手电筒光源:

`public static binarySearchIntersects(arr: Intersect[], angle: number, findFlag = false) {
let start = 0, end = arr.length-1;
while(end-start > 1){
var idx = Math.floor((start + end) / 2);
if (angle < arr[idx].angle) {
end = idx;
} else if (angle > arr[idx].angle) {
start = idx
} else {
return idx;
}
}
// 没有找到对应的值
return findFlag ? end : start;
}

/** 通过角度剔除交点, 角度是逆时针方向 */
public static getIntersectByAngle(startAngle: number, angleRange: number, light: cc.Vec2, polygons: cc.Vec2[][]) {
let endAngle = startAngle + angleRange;
startAngle *= 0.017453;
endAngle *= 0.017453;
// 本质就是以light为原点搭建坐标系
let rayEnd1 = cc.v2(Math.cos(startAngle), Math.sin(startAngle)).add(light);
let rayEnd2 = cc.v2(Math.cos(endAngle), Math.sin(endAngle)).add(light);

// 算交点
let intersect1 = this.getIntersectByPolygons(polygons, light, rayEnd1);
intersect1.angle = startAngle;
let intersect2 = this.getIntersectByPolygons(polygons, light, rayEnd2);
intersect2.angle = endAngle;

// 获得所有交点
let intersects = this.getIntersect(light, polygons);
// 剔除角度外的交点, 这里可以优化, 因为intersects是有序的, 可以使用二分法查找
// 二分法, 已优化
let start = this.binarySearchIntersects(intersects, startAngle, true);
let end = this.binarySearchIntersects(intersects, endAngle, false);
intersects = intersects.slice(start, end+1);

intersects.unshift({x:light.x, y: light.y, param: 0}, intersect1)
intersects.push(intersect2);

return intersects;

}`

③Graphics 画出(lineTo)光线并填充(fill)

`
/** 绘制light */
public static drawLight(graphics: cc.Graphics, light: cc.Vec2, intersects: Intersect[]) {
graphics.clear();
graphics.moveTo(intersects[0].x, intersects[0].y);
for(let i=1; i<intersects.length; i++) {
let intersect = intersects[i];
graphics.lineTo(intersect.x, intersect.y);
}
graphics.moveTo(intersects[0].x, intersects[0].y);

graphics.fill();

}`

④shader中通过光线和光源的距离来计算光线的透明度

`vec2 st = gl_FragCoord.xy / screen.xy;
float scaleWH = screen.x/screen.y;

vec2 uv = st - lightPos;
float rx = abs(uv.x);
float ry = abs(uv.y);

float radius = length(vec2(rx * scaleWH, ry)); // 算半径
o.a = 1.0 - smoothstep(0.0, maxRadius, radius); // 渐变`

git:
https://github.com/kirikayakazuto/CocosCreator_UIFrameWork/tree/2dLight

更多笔记请关注公众号:

55赞

厉害了大佬!

牛皮牛皮,我自己写了好久但是效果一直不佳

黑白分明的,不只是你的头发

如此优秀,必须mark

6p…

mark.

插眼 插眼!!!

还有什么? 白加黑 么

MARK.

好东西 mark

mark一下

mark 存起来

mark一下

牛的不行不行呀

mark 一下+

mark一下 :grin:

mark~

mark马克马克

mark–