纯shader实现雷达扫描效果(three.js实战13)
Posted 点燃火柴
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了纯shader实现雷达扫描效果(three.js实战13)相关的知识,希望对你有一定的参考价值。
1. demo效果
2. 实现要点
2.1 绘制雷达边框
这一步其实就是绘制一个圆,输入一个屏幕坐标st,原型坐标还有半径,使用distance求出坐标上任意点到圆心的距离,将这个距离接近圆半径的点绘制
float drawCircle(vec2 st,vec2 center,float radius) {
float pct = distance(st,center);
float line_width = 0.01;
return smoothstep(radius-line_width,radius,pct)-smoothstep(radius,radius+line_width,pct);
}
2.2 绘制雷达扫描起始线
这一步主要是绘制一个线段,这个线段的起点是圆心,终点是圆上0度角的位置,在绘制点通过旋转矩阵将屏幕坐标st旋转起来,来实现扫描起始线旋转的效果,有一点需要注意,为了实现顺时针旋转,旋转矩阵传入的值需要取负
//二维旋转矩阵
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),sin(_angle),cos(_angle));
}
//绘制指定两点间的直线
float line_segment_with_two_point (vec2 st,vec2 start,vec2 end,float line_width){
vec2 line_vector_from_end = normalize(vec2(start.x,start.y) - vec2(end.x,end.y));//结束点指向起始点的向量
vec2 line_vector_from_start = -line_vector_from_end;//起始点指向结束点的向量
vec2 st_vector_from_end = st - vec2(end.x,end.y); //结束点指向画布中任意点的向量
vec2 st_vector_from_start = st - vec2(start.x,start.y);//起始点指向画布中任意点的向量
float proj1 = dot(line_vector_from_end,st_vector_from_end);
float proj2 = dot(line_vector_from_start,st_vector_from_start);
if(proj1>0.0&&proj2>0.0){//通过点乘结果>0判断是否同相,过滤掉线段两头超出部分
//屏幕上任意点到直线的垂直距离
float dist = sin(acos(dot(line_vector_from_end,st_vector_from_end)/length(st_vector_from_end)))*length(st_vector_from_end);
return pow(1.0-smoothstep(0.0,line_width,dist),6.0);
}
}
float flowingLine(vec2 st,float line_width,float radius){
//u_time前加负号使之变为顺时针旋转
st = rotate2d( -u_time*1.5) * st;
float x = cos(0.0)*radius;
float y = sin(0.0)*radius;
return line_segment_with_two_point (st,vec2(x,y),vec2(0.0),line_width);
}
2.3 绘制扫描光和被扫描物体
这一步稍微麻烦一些,首先绘制扫描光思路与绘制扫描线起始点一样,要创建一个四分之一圆的渐变扇形,绘制这个扇形前也通过旋转矩阵使屏幕坐标旋转起来,以实现扫描光旋转,要注意旋转矩阵的入参要与上一步中的一致,否则旋转速度不一致;
接下来绘制物体,因为物体不需要跟着扫描光旋转,所以要使用未改变的屏幕坐标,绘制物体后使用mix函数将物体与上一次的结果累加
//绘制小点模拟被扫描物体
float smallPoint(vec2 st,vec2 position,vec2 vector,float radius){
// 从原点指向物体的向量
vec2 positionVector = position - vec2(0.0);
//雷达扫描光起始线与指向物体的向量间的夹角
float angle = acos(dot(positionVector,vector)/(length(positionVector)*length(vector)));
float dist = distance(st,position);
return (1.0 -step(radius*sin(angle/2.0-PI/16.0),dist));
}
//绘制雷达扫描光及被扫描物体
float sectorFlowingLight(vec2 oriSt,float radius){
vec2 st = oriSt;
//u_time前加负号使之变为顺时针旋转
st = rotate2d( -u_time*1.5) * st;
vec2 center = vec2(0.0);
vec2 vector0 = vec2(cos(0.0),sin(0.0)) - center;
vec2 vector1 = st -center;
vec2 vector2 = vec2(cos(PI/2.0),sin(PI/2.0)) - center;
float dotProd = dot(vector1,vector2);
//计算0度线与圆心到st上向量的夹角
float angle = acos(dot(vector0,vector1)/(length(vector0)*length(vector1)));
if(dotProd>0.0&&dotProd<1.0&&angle>0.0&&angle<PI/2.0&&distance(st,vec2(0.0))<0.6){
//雷达扫描光
float pct = (1.0-smoothstep(0.0,PI/2.0,angle))*0.7;
//小圆点1
float point1 = smallPoint(oriSt,vec2(0.3,0.3),vector1,0.04);
pct = mix(pct,point1,0.1);
//小圆点2
float point2 = smallPoint(oriSt,vec2(0.1,0.2),vector1,0.06);
pct = mix(pct,point2,0.3);
return pct;
}
}
3. demo代码
<body>
<div id="container"></div>
<script type="text/javascript" src="../three/build/three.js"></script>
<script>
var container;
var camera, scene, planeMesh, renderer;
var uniforms = {
u_resolution: {
type: "v2",
value: new THREE.Vector2()
},
radius: {
type: "f",
value: 0.5
},
u_time: {
type: "f",
value: 0.5
}
};
var vertexShader = `
attribute vec3 position;
void main() {
gl_Position = vec4( position, 1.0 );
}
`
var fragmentShader1 = `
#define PI 3.1415926535897932384626433832795
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform float u_time;
float drawCircle(vec2 st,vec2 center,float radius) {
float pct = distance(st,center);
float line_width = 0.01;
return smoothstep(radius-line_width,radius,pct)-smoothstep(radius,radius+line_width,pct);
}
float line_segment_with_two_point (vec2 st,vec2 start,vec2 end,float line_width){
vec2 line_vector_from_end = normalize(vec2(start.x,start.y) - vec2(end.x,end.y));//结束点指向起始点的向量
vec2 line_vector_from_start = -line_vector_from_end;//起始点指向结束点的向量
vec2 st_vector_from_end = st - vec2(end.x,end.y); //结束点指向画布中任意点的向量
vec2 st_vector_from_start = st - vec2(start.x,start.y);//起始点指向画布中任意点的向量
float proj1 = dot(line_vector_from_end,st_vector_from_end);
float proj2 = dot(line_vector_from_start,st_vector_from_start);
if(proj1>0.0&&proj2>0.0){//通过点乘结果>0判断是否同相,过滤掉线段两头超出部分
//屏幕上任意点到直线的垂直距离
float dist = sin(acos(dot(line_vector_from_end,st_vector_from_end)/length(st_vector_from_end)))*length(st_vector_from_end);
//一个向量在单为向量上的投影长度=夹角余弦*向量长度
float st_proj_line_width = (dot(line_vector_from_end,st_vector_from_end)/length(st_vector_from_end))*length(st_vector_from_end);
return pow(1.0-smoothstep(0.0,line_width,dist),6.0);
} else {
return 0.0;
}
}
float flowingLine(vec2 st,float line_width,float radius){
//u_time前加负号使之变为顺时针旋转
float angle = -u_time;
float x = cos(angle)*radius;
float y = sin(angle)*radius;
return line_segment_with_two_point (st,vec2(x,y),vec2(0.0),line_width);
}
float sectorFlowingLight(vec2 st,float radius){
//u_time前加负号使之变为顺时针旋转
float angle = -u_time;
float x = cos(angle)*radius;
float y = sin(angle)*radius;
float flowingAngle = angle-PI/2.0;
float x2 = cos(flowingAngle)*radius;
float y2 = sin(flowingAngle)*radius;
vec2 center = vec2(0.0);
vec2 vector0 = vec2(cos(0.0),sin(0.0)) - center;
vec2 vector1 = vec2(x,y) - center;
vec2 vector2 = st -center;
vec3 crossRes = cross(vec3(vector1,0.0),vec3(vector2,0.0));
float angle1 = atan(y/x);
float angle2 = atan(y2/x2);
float stAngle = atan(st.y/st.x);
if(dot(vec3(0,0,1), crossRes) < 0.0) {
angle = PI/2.0;
flowingAngle = angle-PI/2.0;
x2 = cos(flowingAngle)*radius;
y2 = sin(flowingAngle)*radius;
angle2 = atan(y2/x2);
}
vec2 end = vec2(0.0);
vec2 start = vec2(x,y);
vec2 line_vector_from_end = normalize(vec2(start.x,start.y) - vec2(end.x,end.y));//结束点指向起始点的向量
vec2 line_vector_from_start = -line_vector_from_end;//起始点指向结束点的向量
vec2 st_vector_from_end = st - vec2(end.x,end.y); //结束点指向画布中任意点的向量
vec2 st_vector_from_start = st - vec2(start.x,start.y);//起始点指向画布中任意点的向量
float proj2 = dot(line_vector_from_start,st_vector_from_start);
//angle = acos(dot(line_vector_from_end,st_vector_from_end)/length(st_vector_from_end));
//float stAngle = atan(y/x);
if(dot(vector1,vector2)>0.0&&stAngle-angle1>0.0&&distance(st,vec2(0.0))<0.6){//通过点乘结果>0判断是否同相,过滤掉线段两头超出部分
// return smoothstep(0.0,2.0,angle2-stAngle)-smoothstep(0.0,2.0,stAngle-angle1);
if(angle1>=0.0&&angle1<PI){
//return smoothstep(-PI,PI,stAngle-angle2);
//return 1.0-smoothstep(.0,PI,stAngle-angle2);
return smoothstep(-PI,0.0,angle2-stAngle);
}else{
return smoothstep(0.0,PI,angle2-stAngle);
}
}
}
float line_segment_smooth(vec2 st,float k,float t,float line_width) {
float x = cos(u_time);
float y = sin(u_time);
float fun = st.y-y*st.x/x;
float min = 0.2;
float max = 0.6;
return smoothstep(k*st.x-line_width,k*st.x,fun);
}
void main( void ) {
//窗口坐标调整为[-1,1],坐标原点在屏幕中心
vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;
//窗口坐标调整为[0,1],坐标原点在屏幕左下角
//vec2 st = gl_FragCoord.xy/u_resolution;
vec3 line_color = vec3(0.0,1.0,0.0);
vec3 color = vec3(0.0,0.0,0.0);
float pct = 0.0;
pct = drawCircle(st,vec2(0.0,0.0),0.6);
color +=pct*line_color;
pct = flowingLine(st,0.02,0.6);
color += pct*line_color;
pct = sectorFlowingLight(st,0.6);
color += pct*line_color;
gl_FragColor = vec4(color, 1);
}
`
var fragmentShader = `
#define PI 3.1415926535897932384626433832795
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform float u_time;
//二维旋转矩阵
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),sin(_angle),cos(_angle));
}
float drawCircle(vec2 st,vec2 center,float radius) {
float pct = distance(st,center);
float line_width = 0.01;
//return smoothstep(pct-0.02,pct,st.y)-smoothstep(pct,pct+0.02,st.y);
return smoothstep(radius-line_width,radius,pct)-smoothstep(radius,radius+line_width,pct);
}
//绘制指定两点间的直线
float line_segment_with_two_point (vec2 st,vec2 start,vec2 end,float line_width){
vec2 line_vector_from_end = normalize(vec2(start.x,start.y) - vec2(end.x,end.y));//结束点指向起始点的向量
vec2 line_vector_from_start = -line_vector_from_end;//起始点指向结束点的向量
vec2 st_vector_from_end = st - vec2(end.x,end.y); //结束点指向画布中任意点的向量
vec2 st_vector_from_start = st - vec2(start.x,start.y);//起始点指向画布中任意点的向量
float proj1 = dot(line_vector_from_end,st_vector_from_end);
float proj2 = dot(line_vector_from_start,st_vector_from_start);
if(proj1>0.0&&proj2>0.0){//通过点乘结果>0判断是否同相,过滤掉线段两头超出部分
//屏幕上任意点到直线的垂直距离
float dist = sin(acos(dot(line_vector_from_end,st_vector_from_end)/length(st_vector_from_end)))*length(st_vector_from_end);
return pow(1.0-smoothstep(0.0,line_width,dist),6.0);
}
}
float flowingLine(vec2 st,float line_width,float radius){
//u_time前加负号使之变为顺时针旋转
st = rotate2d( -u_time*1.5) * st;
float x = cos(0.0)*radius;
float y = sin(0.0)*radius;
return line_segment_with_two_point (st,vec2(x,y),vec2(0.0),line_width);
}
//绘制小点模拟被扫描物体
float smallPoint(vec2 st,vec2 position,vec2 vector,float radius){
// 从原点指向物体的向量
vec2 positionVector = position - vec2(0.0);
//雷达扫描光起始线与指向物体的向量间的夹角
float angle = acos(dot(positionVector,vector)/(length(positionVector)*length(vector)));
float dist = distance(st,position);
return (1.0 -step(radius*sin(angle/2.0-PI/16.0),dist));
}
//绘制雷达扫描光及被扫描物体
float sectorFlowingLight(vec2 oriSt,float radius){
vec2 st = oriSt;
//u_time前加负号使之变为顺时针旋转
st = rotate2d( -u_time*1.5) * st;
vec2 center = vec2(0.0);
vec2 vector0 = vec2(cos(0.0),sin(0.0)) - center;
vec2 vector1 = st -center;
vec2 vector2 = vec2(cos(PI/2.0),sin(PI/2.0)) - center;
float dotProd = dot(vector1,vector2);
//计算0度线与圆心到st上向量的夹角
float angle = acos(dot(vector0,vector1)/(length(vector0)*length(vector1)));
if(dotProd>0.0&&dotProd<1.0&&angle>0.0&&angle<PI/2.0&&distance(st,vec2(0.0))<0.6){
//雷达扫描光
float pct = (1.0-smoothstep(0.0,PI/2.0,angle))*0.7;
//小圆点1
float point1 = smallPoint(oriSt,vec2(0.3,0.3),vector1,0.04);
pct = mix(pct,point1,0.1);
//小圆点2
float point2 = smallPoint(oriSt,vec2(0.1,0.2),vector1,0.06);
pct = mix(pct,point2,0.3);
return pct;
}
}
void main( void ) {
//窗口坐标调整为[-1,1],坐标原点在屏幕中心
vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;
//窗口坐标调整为[0,1],坐标原点在屏幕左下角
//vec2 st = gl_FragCoord.xy/u_resolution;
vec3 line_color = vec3(0.0,1.0,0.0);
vec3 color = vec3(0.0,0.0,0.0);
float pct = 0.0;
//绘制圆边界
pct = drawCircle(st,vec2(0.0,0.0),0.6);
color +=pct*line_color;
//绘制雷达扫描光起始线
pct = flowingLine(st,0.02,0.6);
color += pct*line_color;
//绘制雷达扫描光与被扫描物体
pct = sectorFlowingLight(st,0.6);
color += pct*line_color;
gl_FragColor = vec4(color, 1);
}
`
init();
animate();
function init() {
container = document.getElementById('container');
camera = new THREE.Camera();
scene = new THREE.Scene();
var geometry = new THREE.PlaneBufferGeometry(2, 2);
var material = new THREE.RawShaderMaterial({
uniforms: uniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader
});
planeMesh = new THREE.Mesh(geometry, material);
scene.add(planeMesh);
renderer = new THREE.WebGLRenderer();
renderer.setSize(800, 800); //设置窗口大小800px*800px
container.appendChild(renderer.domElement);
uniforms.u_resolution.value.x = renderer.domElement.width;
uniforms.u_resolution.value.y = renderer.domElement.height;
}
function animate() {
requestAnimationFrame(animate);
planeMesh.material.uniforms.u_time.value += 0.01;
renderer.render(scene, camera);
}
</script>
</body>
以上是关于纯shader实现雷达扫描效果(three.js实战13)的主要内容,如果未能解决你的问题,请参考以下文章