全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-10-24_WebGL实现soul星球效果

您的位置:首页 >> 新闻 >> 行业资讯

WebGL实现soul星球效果 点击关注公众号,“技术干货”及时达! WebGL实现soul星球效果最近在研究webGL,觉得soul app的星球挺有意思的,于是就实现了一下,中间涉及的细节和知识点挺多的,写篇博客分享一下 soul原版WebGL实现的 主要技术要点1.自由转动因为要解决万向锁的问题,所以不能使用rotateX、rotateY、rotateZ来旋转,应当使用四元数THREE.Quaternion 2.背面小球变暗这里通过内部放置了一个半透明的黑色小球来实现 //创建半透明球体 constsphereGeometry=newTHREE.SphereGeometry(4.85,16,16) 为了使小球从正面转动的背面的过程中可以平滑的变暗,这里还需要把半透明小球的边沿处理成高斯模糊,具体实现就是使用GLSL的插值函数smoothstep fragmentShader:` uniformvec3color; uniformfloatopacity; varyingvec3vNormal; voidmain(){ floatalpha=opacity*smoothstep(0.5,1.0,vNormal.z); gl_FragColor=vec4(color,alpha); } 但是需要注意的是需要关闭小球的深度测试,否则会遮挡小球 side:THREE.FrontSide, depthWrite:false, 3.使用THREE.Sprite创建小球标签4.标签位置计算for(leti=0;inumPoints;i++){ consty=1-(i/(numPoints-1))*2 constradiusAtY=Math.sqrt(1-y*y) consttheta=(2*Math.PI*i)/goldenRatio constx=Math.cos(theta)*radiusAtY constz=Math.sin(theta)*radiusAtY constsmallBallMaterial=newTHREE.MeshBasicMaterial({ color:getRandomBrightColor(), depthWrite:true, depthTest:true, side:THREE.FrontSide, }) constsmallBall=newTHREE.Mesh(smallBallGeometry,smallBallMaterial) smallBall.position.set(x*radius,y*radius,z*radius) 5.超出长度的标签采用贴图采样位移来实现跑马灯效果6.滚动阻尼,鼠标转动球体之后速度能衰减到转动旋转的速率7.自动旋转需要保持上一次滚动的方向8.使用射线拾取来实现点击交互完整代码!DOCTYPEhtml htmllang="zh" head metacharset="UTF-8"/ title3D半透明球体与可交互小球/title style body{ margin:0; background-color:black; touch-action:none; } canvas{ display:block; } /style /head body scripttype="module" import*asTHREEfrom'https://cdn.jsdelivr.net/npm/three@0.169.0/build/three.module.js' //创建场景 constscene=newTHREE.Scene() //创建相机 constcamera=newTHREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 ) camera.position.set(0,0,14) camera.lookAt(0,0,0) //创建渲染器 constrenderer=newTHREE.WebGLRenderer({antialias:true,alpha:true}) renderer.setSize(window.innerWidth,window.innerHeight) renderer.setPixelRatio(window.devicePixelRatio) renderer.setClearColor(0x000000,0) document.body.appendChild(renderer.domElement) //创建半透明球体 constsphereGeometry=newTHREE.SphereGeometry(4.85,16,16) constsphereMaterial=newTHREE.ShaderMaterial({ uniforms:{ color:{value:newTHREE.Color(0x000000)}, opacity:{value:0.8}, }, vertexShader:` varyingvec3vNormal; voidmain(){ vNormal=normalize(normalMatrix*normal); gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0); } `, fragmentShader:` uniformvec3color; uniformfloatopacity; varyingvec3vNormal; voidmain(){ floatalpha=opacity*smoothstep(0.5,1.0,vNormal.z); gl_FragColor=vec4(color,alpha); } `, transparent:true, side:THREE.FrontSide, depthWrite:false, }) constsphere=newTHREE.Mesh(sphereGeometry,sphereMaterial) scene.add(sphere) //创建小球体和标签数组 constsmallBallGeometry=newTHREE.SphereGeometry(0.15,16,16) constsmallBalls=[] constlabelSprites=[] constradius=5 constnumPoints=88 constgoldenRatio=(1+Math.sqrt(5))/2 constmaxWidth=160 consttextSpeed=0.002 //创建射线投射器 constraycaster=newTHREE.Raycaster() constmouse=newTHREE.Vector2() functioncreateTextTexture(text,parameters={}){ const{ fontSize=24, fontFace='PingFangSC,MicrosoftYaHei,NotoSans,Arial,sans-serif', textColor='white', backgroundColor='rgba(0,0,0,0)', maxWidth=160, }=parameters constcanvas=document.createElement('canvas') constcontext=canvas.getContext('2d') context.font=`${fontSize}px${fontFace}` consttextMetrics=context.measureText(text) consttextWidth=Math.ceil(textMetrics.width) consttextHeight=fontSize*1.2 constneedMarquee=textWidthmaxWidth letcanvasWidth=maxWidth if(needMarquee){ canvasWidth=textWidth+60 } canvas.width=canvasWidth canvas.height=textHeight context.font=`${fontSize}px${fontFace}` context.clearRect(0,0,canvas.width,canvas.height) context.fillStyle=backgroundColor context.fillRect(0,0,canvas.width,canvas.height) context.fillStyle=textColor context.textAlign=needMarquee?'left':'center' context.textBaseline='middle' if(needMarquee){ context.fillText(text,0,canvas.height/2) }else{ context.fillText(text,maxWidth/2,canvas.height/2) } consttexture=newTHREE.CanvasTexture(canvas) texture.needsUpdate=true if(needMarquee){ texture.wrapS=THREE.RepeatWrapping texture.wrapT=THREE.ClampToEdgeWrapping texture.repeat.x=maxWidth/canvas.width }else{ texture.wrapS=THREE.ClampToEdgeWrapping texture.wrapT=THREE.ClampToEdgeWrapping } texture.minFilter=THREE.LinearFilter texture.magFilter=THREE.LinearFilter texture.generateMipmaps=false return{texture,needMarquee,HWRate:textHeight/maxWidth} } for(leti=0;inumPoints;i++){ consty=1-(i/(numPoints-1))*2 constradiusAtY=Math.sqrt(1-y*y) consttheta=(2*Math.PI*i)/goldenRatio constx=Math.cos(theta)*radiusAtY constz=Math.sin(theta)*radiusAtY constsmallBallMaterial=newTHREE.MeshBasicMaterial({ color:getRandomBrightColor(), depthWrite:true, depthTest:true, side:THREE.FrontSide, }) constsmallBall=newTHREE.Mesh(smallBallGeometry,smallBallMaterial) smallBall.position.set(x*radius,y*radius,z*radius) sphere.add(smallBall) smallBalls.push(smallBall) constlabelText=getRandomNickname() const{texture,needMarquee,HWRate}=createTextTexture(labelText,{ fontSize:28, fontFace:'PingFangSC,MicrosoftYaHei,NotoSans,Arial,sans-serif', textColor:'#bbbbbb', maxWidth:maxWidth, }) constspriteMaterial=newTHREE.SpriteMaterial({ map:texture, transparent:true, depthWrite:true, depthTest:true, blending:THREE.NormalBlending, }) constsprite=newTHREE.Sprite(spriteMaterial) sprite.scale.set(1,HWRate,1) labelSprites.push({sprite,smallBall,texture,needMarquee,labelText}) scene.add(sprite) } //添加灯光 constlight=newTHREE.AmbientLight(0xffffff,0.5) scene.add(light) constdirectionalLight=newTHREE.DirectionalLight(0xffffff,1) directionalLight.position.set(5,5,5) scene.add(directionalLight) //定义自动旋转速度和轴 constautoRotationSpeed=0.0005 letautoRotationAxis=newTHREE.Vector3(0,1,0).normalize() letcurrentAngularVelocity=autoRotationAxis.clone().multiplyScalar(autoRotationSpeed) letisDragging=false letpreviousMousePosition={x:0,y:0} letlastDragDelta={x:0,y:0} constdecayRate=0.92 constincreaseRate=1.02 //鼠标事件处理 constonMouseDown=(event)={ isDragging=true previousMousePosition={ x:event.clientX, y:event.clientY, } } constonMouseMove=(event)={ if(isDragging){ constdeltaX=event.clientX-previousMousePosition.x constdeltaY=event.clientY-previousMousePosition.y lastDragDelta={x:deltaX,y:deltaY} constrotationFactor=0.005 constangleY=deltaX*rotationFactor constangleX=deltaY*rotationFactor constquaternionY=newTHREE.Quaternion().setFromAxisAngle( newTHREE.Vector3(0,1,0), angleY ) constquaternionX=newTHREE.Quaternion().setFromAxisAngle( newTHREE.Vector3(1,0,0), angleX ) constdeltaQuat=newTHREE.Quaternion().multiplyQuaternions(quaternionY,quaternionX) sphere.quaternion.multiplyQuaternions(deltaQuat,sphere.quaternion) constdragRotationAxis=newTHREE.Vector3(deltaY,deltaX,0).normalize() constdragRotationSpeed=Math.sqrt(deltaX*deltaX+deltaY*deltaY)*rotationFactor if(dragRotationAxis.length()0){ currentAngularVelocity.copy(dragRotationAxis).multiplyScalar(dragRotationSpeed) } previousMousePosition={ x:event.clientX, y:event.clientY, } } } constonMouseUp=()={ if(isDragging){ isDragging=false constdeltaX=lastDragDelta.x constdeltaY=lastDragDelta.y if(deltaX!==0||deltaY!==0){ constnewAxis=newTHREE.Vector3(deltaY,deltaX,0).normalize() if(newAxis.length()0){ autoRotationAxis.copy(newAxis) } constdragSpeed=currentAngularVelocity.length() if(dragSpeedautoRotationSpeed){ //维持当前旋转速度 }else{ currentAngularVelocity.copy(autoRotationAxis).multiplyScalar(autoRotationSpeed) } } } } //触摸事件处理 constonTouchStart=(event)={ isDragging=true consttouch=event.touches[0] previousMousePosition={ x:touch.clientX, y:touch.clientY, } } constonTouchMove=(event)={ event.preventDefault() if(isDragging){ consttouch=event.touches[0] constdeltaX=touch.clientX-previousMousePosition.x constdeltaY=touch.clientY-previousMousePosition.y lastDragDelta={x:deltaX,y:deltaY} constrotationFactor=0.002 constangleY=deltaX*rotationFactor constangleX=deltaY*rotationFactor constquaternionY=newTHREE.Quaternion().setFromAxisAngle( newTHREE.Vector3(0,1,0), angleY ) constquaternionX=newTHREE.Quaternion().setFromAxisAngle( newTHREE.Vector3(1,0,0), angleX ) constdeltaQuat=newTHREE.Quaternion().multiplyQuaternions(quaternionY,quaternionX) sphere.quaternion.multiplyQuaternions(deltaQuat,sphere.quaternion) constdragRotationAxis=newTHREE.Vector3(deltaY,deltaX,0).normalize() constdragRotationSpeed=Math.sqrt(deltaX*deltaX+deltaY*deltaY)*rotationFactor if(dragRotationAxis.length()0){ currentAngularVelocity.copy(dragRotationAxis).multiplyScalar(dragRotationSpeed) } previousMousePosition={ x:touch.clientX, y:touch.clientY, } } } constonTouchEnd=(event)={ if(isDragging){ isDragging=false constdeltaX=lastDragDelta.x constdeltaY=lastDragDelta.y if(deltaX!==0||deltaY!==0){ constnewAxis=newTHREE.Vector3(deltaY,deltaX,0).normalize() if(newAxis.length()0){ autoRotationAxis.copy(newAxis) } constdragSpeed=currentAngularVelocity.length() if(dragSpeedautoRotationSpeed){ //维持当前旋转速度 }else{ currentAngularVelocity.copy(autoRotationAxis).multiplyScalar(autoRotationSpeed) } } } //检查点击事件 if(event.changedTouches.length0){ consttouch=event.changedTouches[0] mouse.x=(touch.clientX/window.innerWidth)*2-1 mouse.y=-(touch.clientY/window.innerHeight)*2+1 checkIntersection() } } //事件监听 window.addEventListener('mousedown',onMouseDown) window.addEventListener('mousemove',onMouseMove) window.addEventListener('mouseup',onMouseUp) window.addEventListener('touchstart',onTouchStart) window.addEventListener('touchmove',onTouchMove) window.addEventListener('touchend',onTouchEnd) document.addEventListener('gesturestart',function(e){ e.preventDefault() }) //添加点击事件监听 window.addEventListener('click',onMouseClick) //处理窗口大小调整 window.addEventListener('resize',()={ camera.aspect=window.innerWidth/window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth,window.innerHeight) }) functiononMouseClick(event){ event.preventDefault() mouse.x=(event.clientX/window.innerWidth)*2-1 mouse.y=-(event.clientY/window.innerHeight)*2+1 console.log(event.clientX,mouse.x,mouse.y) checkIntersection() } functioncheckIntersection(){ raycaster.setFromCamera(mouse,camera) constintersects=raycaster.intersectObjects(smallBalls) if(intersects.length0){ constintersectedBall=intersects[0].object constindex=smallBalls.indexOf(intersectedBall) if(index!==-1){ constlabelInfo=labelSprites[index] showLabelInfo(labelInfo) } } } functionshowLabelInfo(labelInfo){ alert(`点击的小球标签:${labelInfo.labelText}`) } //动画循环 functionanimate(){ requestAnimationFrame(animate) if(!isDragging){ constdeltaQuat=newTHREE.Quaternion().setFromEuler( newTHREE.Euler( currentAngularVelocity.x, currentAngularVelocity.y, currentAngularVelocity.z, 'XYZ' ) ) sphere.quaternion.multiplyQuaternions(deltaQuat,sphere.quaternion) constcurrentSpeed=currentAngularVelocity.length() if(currentSpeedautoRotationSpeed){ currentAngularVelocity.multiplyScalar(decayRate) if(currentAngularVelocity.length()autoRotationSpeed){ currentAngularVelocity.copy(autoRotationAxis).multiplyScalar(autoRotationSpeed) } }elseif(currentSpeedautoRotationSpeed){ currentAngularVelocity.multiplyScalar(increaseRate) if(currentAngularVelocity.length()autoRotationSpeed){ currentAngularVelocity.copy(autoRotationAxis).multiplyScalar(autoRotationSpeed) } }else{ currentAngularVelocity.copy(autoRotationAxis).multiplyScalar(autoRotationSpeed) } } //更新标签的位置和跑马灯效果 labelSprites.forEach(({sprite,smallBall,texture,needMarquee})={ smallBall.updateMatrixWorld() constsmallBallWorldPos=newTHREE.Vector3() smallBall.getWorldPosition(smallBallWorldPos) constupOffset=newTHREE.Vector3(0,0.3,0) sprite.position.copy(smallBallWorldPos).add(upOffset) if(needMarquee){ texture.offset.x+=textSpeed if(texture.offset.x1){ texture.offset.x=0 } } }) renderer.render(scene,camera) } animate() functiongetRandomBrightColor(){ consthue=Math.floor(Math.random()*360) constsaturation=Math.floor(Math.random()*40+10) constlightness=Math.floor(Math.random()*40+40) constrgb=hslToRgb(hue,saturation,lightness) return(rgb.r16)|(rgb.g8)|rgb.b } functionhslToRgb(h,s,l){ s/=100 l/=100 constc=(1-Math.abs(2*l-1))*s constx=c*(1-Math.abs(((h/60)%2)-1)) constm=l-c/2 letr,g,b if(h=0h60){ r=c g=x b=0 }elseif(h=60h120){ r=x g=c b=0 }elseif(h=120h180){ r=0 g=c b=x }elseif(h=180h240){ r=0 g=x b=c }elseif(h=240h300){ r=x g=0 b=c }else{ r=c g=0 b=x } return{ r:Math.round((r+m)*255), g:Math.round((g+m)*255), b:Math.round((b+m)*255), } } functiongetRandomNickname(){ constadjectives=[ 'Cool', 'Crazy', 'Mysterious', 'Happy', 'Silly', 'Brave', 'Smart', 'Swift', 'Fierce', 'Gentle', ] constnouns=[ 'Tiger', 'Lion', 'Dragon', 'Wizard', 'Ninja', 'Pirate', 'Hero', 'Ghost', 'Phantom', 'Knight', ] constrandomAdjective=adjectives[Math.floor(Math.random()*adjectives.length)] constrandomNoun=nouns[Math.floor(Math.random()*nouns.length)] constnickname=`${randomAdjective}${randomNoun}` if(nickname.length2){ returngetRandomNickname() }elseif(nickname.length22){ returnnickname.slice(0,22) } returnnickname } /script /body /html 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2019-05-04_签证原因无法参加ICLR 2019,研究者请愿不在美国举办CS大会 下一篇:2019-04-03_渐进式图像重构网络:像画画一样重构图像

TAG标签:

18
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价