threejs实现贴花效果

2023-11-09 11:30
文章标签 实现 效果 threejs 贴花

本文主要是介绍threejs实现贴花效果,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

引用:官方源码
在官方给出的源码中采用的模型格式是tfgl格式,查看源码可知新建DecalGeometry对象时,传入的参数为mesh, position, orientation, size,因此对obj文件不可以直接操作,需要遍历obj中的children,在这里采用drc格式(drc为使用Draco压缩后的格式)的模型实现贴花效果。
DecalGeometry源码中传入的参数:

function DecalGeometry( mesh, position, orientation, size ){...}

实现贴花效果,需要借助DecalGeometry.js,因此要其进行引入:

<script src="js/geometries/DecalGeometry.js"></script>

接着新建贴花时需要的参数,raycaster用于拾取鼠标点击的对象,intersection中有intersect、point、normal,intersect为boolean用于判断鼠标点击的位置是否属于模型表面,point与normal用于存储鼠标点击模型时的point与normal。

var mouse=new THREE.Vector2();
var moved;//
var line;//跟随鼠标
var raycaster;//拾取射线
var intersection;//相交线
var decalPosition;//贴花位置
var decalOrientation;//贴花方向
var decalMaterial;//贴花材质
var decals;//贴花数组
var decalSize;//贴花数组
var mouseHelper;

对参数进行初始化,首先在场景中添加line,若物体表面出现线条,则表明当前位置允许进行贴花操作,通过textureLoader加载所需要的贴花纹理:

function initDecalData(){let geometry=new THREE.BufferGeometry();geometry.setFromPoints([new THREE.Vector3(),new THREE.Vector3()]);line=new THREE.Line(geometry,new THREE.LineBasicMaterial({color:0xff0000}));scene.add(line);decalSize=new THREE.Vector3( 10, 10, 10 );decalPosition=new THREE.Vector3();decalOrientation=new THREE.Euler();decals=[];mouseHelper = new THREE.Mesh( new THREE.BoxBufferGeometry( 1, 1, 10 ), new THREE.MeshNormalMaterial() );mouseHelper.visible = false;scene.add( mouseHelper );//let textureLoader=new THREE.TextureLoader();let decalDiffuse=textureLoader.load( 'textures/decal/decal-diffuse.png' );//贴花 let decalNormal=textureLoader.load( 'textures/decal/decal-normal.jpg' );decalMaterial=new THREE.MeshLambertMaterial( {pecular: 0x444444,map: decalDiffuse,//贴花图normalMap: decalNormal,//法向量纹理normalScale: new THREE.Vector2( 1, 1 ),shininess: 30,transparent: true,//透明度depthTest: true,depthWrite: false,polygonOffset: true,polygonOffsetFactor: - 4,wireframe: false} );intersection={//相交线intersects:false,point:new THREE.Vector3(),normal:new THREE.Vector3()};}

通过拾取射线对选取的贴花的位置进行判断,是否位于模型表面。当位于模型表面时,获取点击模型时的point:

function checkIntersection(){if(!model) return;raycaster=new THREE.Raycaster();//拾取射线raycaster.setFromCamera(mouse,camera);let intersects=raycaster.intersectObjects([model]);//console.log(intersects);if(intersects.length>0){//在模型表面let modelPoint=intersects[0].point;//获取模型的pointmouseHelper.position.copy(modelPoint);intersection.point.copy(modelPoint);let modelNormal=intersects[0].face.normal;//.clone();modelNormal.transformDirection(model.matrixWorld);modelNormal.multiplyScalar(10);modelNormal.add(modelPoint);intersection.normal.copy(modelNormal);mouseHelper.lookAt(modelNormal);let decalPosition=line.geometry.attributes.position;decalPosition.setXYZ(0,modelPoint.x,modelPoint.y,modelPoint.z);decalPosition.setXYZ(1,modelNormal.x,modelNormal.y,modelNormal.z);decalPosition.needsUpdate=true;intersection.intersects=true;//在模型表面 则贴花}else{intersection.intersects=false;}
}

将需要进行贴花操作的模型、获取的point即decalPosition等传入DecalGeometry中,进行贴花操作:

function shoot() {//实现贴花decalPosition.copy( intersection.point );decalOrientation.copy( mouseHelper.rotation );// if ( params.rotate ) orientation.z = Math.random() * 2 * Math.PI;//params为GUI组件// var scale = params.minScale + Math.random() * ( params.maxScale - params.minScale );// size.set( scale, scale, scale );var material = decalMaterial.clone();//克隆贴花材质material.color.setHex( Math.random() * 0xffffff );var m = new THREE.Mesh( new THREE.DecalGeometry( model, decalPosition, decalOrientation, decalSize ), material );decals.push( m );//放入数组中,为了后续的清除操作scene.add( m );//场景中添加贴花console.log(decals);
}

事件监听部分,需要将鼠标点击的二维坐标转换为三维坐标:

			window.addEventListener( 'mousedown', function () {moved = false;}, false );window.addEventListener( 'mouseup', function () {//鼠标抬起checkIntersection();//判断是否为合理的位置if ( ! moved && intersection.intersects ) shoot();//进行贴花} );window.addEventListener( 'mousemove', onTouchMove );window.addEventListener( 'touchmove', onTouchMove );function onTouchMove( event ) {//获取三维场景的坐标var x, y;if ( event.changedTouches ) {x = event.changedTouches[ 0 ].pageX;y = event.changedTouches[ 0 ].pageY;} else {x = event.clientX;y = event.clientY;}mouse.x = ( x / window.innerWidth ) * 2 - 1;mouse.y = - ( y / window.innerHeight ) * 2 + 1;//console.log(mouse.x+","+mouse.y);checkIntersection();//合理的位置内线条始终跟着鼠标}

运行代码后,点击模型中的任意位置,都可以实现贴花效果,实现如下:

贴花前
点击后:
贴花后
完整代码:
HTML部分:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>贴花</title><script type="text/javascript" src="build/three.js"></script><script type="text/javascript" src="js/loaders/DRACOLoader.js"></script><script type="text/javascript" src="js/loaders/OBJLoader.js"></script><script type="text/javascript" src="js/loaders/MTLLoader.js"></script><script type="text/javascript" src="js/geometries/DecalGeometry.js"></script><script type="text/javascript" src="js/controls/OrbitControls.js"></script>
</head>
<body><div id="webgl-out"></div><script type="text/javascript"  src="drcDecals.js"></script>
</body>
</html>

JS部分:

var scene;
var camera;
var renderer;
var ambientLight,spotLight;
var orbitControls;
var model;
var box;var mouse=new THREE.Vector2();
var moved;//
var line;//跟随鼠标
var raycaster;//拾取射线
var intersection;//相交线
var decalPosition;//贴花位置
var decalOrientation;//贴花方向
var decalMaterial;//贴花材质
var decals;//贴花数组
var decalSize;//贴花数组
var mouseHelper;init();
animate();function init(){moved = false;initScene();initRenderer();initCamera();addOrbitControls();addLight();initDecalData();addModeldrc({//添加模型 drcPath:"obj/OM132326单150dpi_OM132326/",drcName:"OM132326单150dpi_OM132326_outside.drc",texturePath:"obj/OM132326单150dpi_OM132326/OM132326单150dpi_OM132326-textures/",textureName:"outside_tex_3a1887db.png"});document.getElementById("webgl-out").appendChild(renderer.domElement);window.addEventListener("resize",onWindowResize,false);orbitControls.addEventListener( 'change', function () {		moved = true;} );window.addEventListener( 'mousedown', function () {moved = false;}, false );window.addEventListener( 'mouseup', function () {//鼠标抬起checkIntersection();//判断是否为合理的位置if ( ! moved && intersection.intersects ) shoot();//进行贴花} );window.addEventListener( 'mousemove', onTouchMove );window.addEventListener( 'touchmove', onTouchMove );function onTouchMove( event ) {//获取三维场景的坐标var x, y;if ( event.changedTouches ) {x = event.changedTouches[ 0 ].pageX;y = event.changedTouches[ 0 ].pageY;} else {x = event.clientX;y = event.clientY;}mouse.x = ( x / window.innerWidth ) * 2 - 1;mouse.y = - ( y / window.innerHeight ) * 2 + 1;//console.log(mouse.x+","+mouse.y);checkIntersection();//合理位置内线条始终跟着鼠标}
}
function initScene(){scene=new THREE.Scene();}
function initRenderer(){renderer=new THREE.WebGLRenderer({antialias:true});//抗锯齿renderer.setSize(window.innerWidth,window.innerHeight);renderer.setPixelRatio( window.devicePixelRatio );renderer.setClearColor(new THREE.Color(0x4f6cca));}
function initCamera(){camera=new THREE.PerspectiveCamera( 45, window.innerWidth/window.innerHeight, 1, 1000 );camera.position.set(0,0,500);scene.add(camera);
}
function addOrbitControls(){orbitControls=new THREE.OrbitControls(camera,renderer.domElement);orbitControls.autoRotate=false;orbitControls.zoomSpeed=1.2;orbitControls.rotateSpeed=0.5;orbitControls.stopAutoRotateWhenTuch=true;orbitControls.enableDamping=false;orbitControls.minDistance=60;orbitControls.maxDistance=900;orbitControls.intensity=0.1;//灵敏度orbitControls.enablePan=true;//启用右键}
function addLight(){ambientLight=new THREE.AmbientLight( {color:0x0fffff} );scene.add(ambientLight);spotLight=new THREE.SpotLight({color:0xffffff});spotLight.position.set(100,300,1300);scene.add(spotLight);
}
function addModeldrc(drcDef){//加载drc文件var material;var textureLoader=new THREE.TextureLoader();var dracoLoader=new THREE.DRACOLoader();THREE.DRACOLoader.setDecoderConfig( { type: 'js' } );//js类型//dracoLoader.setPath("obj/OM132326单150dpi_OM132326/");dracoLoader.setPath(drcDef.drcPath);//dracoLoader.load("OM132326单150dpi_OM132326.drc",function(mesh){dracoLoader.load(drcDef.drcName,function(mesh){//var texture=new THREE.ImageUtils.loadTexture(texturePath+"outside_tex_3a1887db.png");var texture=new THREE.ImageUtils.loadTexture(drcDef.texturePath+drcDef.textureName);material=new THREE.MeshLambertMaterial();//material.side=THREE.FrontSide;material.side=THREE.BreakSide;material.map=texture;material.needsUpdate=true;model=new THREE.Mesh(mesh,material);setModelPosition(model);scene.add(model);},onProgress, onError);var onProgress = function ( xhr ) {if ( xhr.lengthComputable ) {var percentComplete = xhr.loaded / xhr.total * 100;console.log( Math.round(percentComplete, 2) + '% downloaded' );}};var onError = function ( xhr ) { alert("错误了") ;};}
function setModelPosition(model){box=new THREE.Box3().setFromObject(model);var scaleNum=1;var modelMax=box.max;var modelMin=box.min;var modelWidth=modelMax.x-modelMin.x;var modelHeight=modelMax.y-modelMin.y;if(modelWidth>window.innerWidth/10){scaleNum=window.innerWidth/(10*modelWidth);model.scale.set(scaleNum,scaleNum,scaleNum);//设置模型的缩放大小}if(modelHeight>window.innerHeight/3){if(scaleNum>window.innerHeight/(3*modelHeight)){scaleNum=window.innerHeight/(3*modelHeight);model.scale.set(scaleNum,scaleNum,scaleNum);//设置模型的缩放大小}}console.log(scaleNum);box.max.x*=scaleNum;//重置包围盒的大小box.max.y*=scaleNum;box.max.z*=scaleNum;box.min.x*=scaleNum;box.min.y*=scaleNum;box.min.z*=scaleNum;console.log(box);console.log(model.position);box.getCenter(model.position);model.position.multiplyScalar(-1);
}
function initDecalData(){let geometry=new THREE.BufferGeometry();geometry.setFromPoints([new THREE.Vector3(),new THREE.Vector3()]);line=new THREE.Line(geometry,new THREE.LineBasicMaterial({color:0xff0000}));scene.add(line);decalSize=new THREE.Vector3( 10, 10, 10 );decalPosition=new THREE.Vector3();decalOrientation=new THREE.Euler();decals=[];mouseHelper = new THREE.Mesh( new THREE.BoxBufferGeometry( 1, 1, 10 ), new THREE.MeshNormalMaterial() );mouseHelper.visible = false;scene.add( mouseHelper );//let textureLoader=new THREE.TextureLoader();let decalDiffuse=textureLoader.load( 'textures/decal/decal-diffuse.png' );//贴花 let decalNormal=textureLoader.load( 'textures/decal/decal-normal.jpg' );decalMaterial=new THREE.MeshLambertMaterial( {pecular: 0x444444,map: decalDiffuse,//贴花图normalMap: decalNormal,//法向量纹理normalScale: new THREE.Vector2( 1, 1 ),shininess: 30,transparent: true,//透明度depthTest: true,depthWrite: false,polygonOffset: true,polygonOffsetFactor: - 4,wireframe: false} );intersection={//相交线intersects:false,point:new THREE.Vector3(),normal:new THREE.Vector3()};}
function checkIntersection(){if(!model) return;raycaster=new THREE.Raycaster();//拾取射线raycaster.setFromCamera(mouse,camera);let intersects=raycaster.intersectObjects([model]);//console.log(intersects);if(intersects.length>0){//在模型表面let modelPoint=intersects[0].point;//获取模型的pointmouseHelper.position.copy(modelPoint);intersection.point.copy(modelPoint);let modelNormal=intersects[0].face.normal;//.clone();modelNormal.transformDirection(model.matrixWorld);modelNormal.multiplyScalar(10);modelNormal.add(modelPoint);intersection.normal.copy(modelNormal);mouseHelper.lookAt(modelNormal);let decalPosition=line.geometry.attributes.position;decalPosition.setXYZ(0,modelPoint.x,modelPoint.y,modelPoint.z);decalPosition.setXYZ(1,modelNormal.x,modelNormal.y,modelNormal.z);decalPosition.needsUpdate=true;intersection.intersects=true;//在模型表面 则贴花}else{intersection.intersects=false;}
}
function shoot() {//实现贴花decalPosition.copy( intersection.point );decalOrientation.copy( mouseHelper.rotation );// if ( params.rotate ) orientation.z = Math.random() * 2 * Math.PI;//params为GUI组件// var scale = params.minScale + Math.random() * ( params.maxScale - params.minScale );// size.set( scale, scale, scale );var material = decalMaterial.clone();//克隆贴花材质material.color.setHex( Math.random() * 0xffffff );var m = new THREE.Mesh( new THREE.DecalGeometry( model, decalPosition, decalOrientation, decalSize ), material );decals.push( m );//放入数组中,为了后续的清除操作scene.add( m );//场景中添加贴花console.log(decals);
}
function onWindowResize(){camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight);
}
function animate(){requestAnimationFrame(animate);render();}
function render(){renderer.render(scene,camera);
}

这篇关于threejs实现贴花效果的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/375829

相关文章

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

SpringBoot中使用Flux实现流式返回的方法小结

《SpringBoot中使用Flux实现流式返回的方法小结》文章介绍流式返回(StreamingResponse)在SpringBoot中通过Flux实现,优势包括提升用户体验、降低内存消耗、支持长连... 目录背景流式返回的核心概念与优势1. 提升用户体验2. 降低内存消耗3. 支持长连接与实时通信在Sp

Conda虚拟环境的复制和迁移的四种方法实现

《Conda虚拟环境的复制和迁移的四种方法实现》本文主要介绍了Conda虚拟环境的复制和迁移的四种方法实现,包括requirements.txt,environment.yml,conda-pack,... 目录在本机复制Conda虚拟环境相同操作系统之间复制环境方法一:requirements.txt方法

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja