vite+vue3+threejs实现一个3D模型的展示案例

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vite+vue3+threejs实现一个3D模型的展示案例相关的知识,希望对你有一定的参考价值。

参考技术A

1.检查npm -v版本和使用对应的vite安装vue3项目

需要安装依赖:npm install
运行:npm run dev
目录结构:

2.threejs官网:

3.安装threejs

4.准备3D模型素材(我这里使用glb格式)和HDR图片,素材网上可以找有免费的。

5.在App.vue中绑定id,挂载,实例化使用

6.在项目中的src目录下创建utils目录,在utils目录下创建Base3d.js脚本

效果展示:手机模型已经加载至场景中,背景是hdr图。
功能展示:用户可以滑动滚轮将模型进行放大缩小,场景360度无死角旋转。

Vue2+ThreeJS工程无痛搭建指南

工程初始化流程

全局安装vue-cli

npm install -g vue-cli

进入目录-初始化项目

vue init webpack my-project

进入项目

cd my-project

安装工程核心包(Three JS)

npm install three

核心功能实现与避坑

fbx载入功能代码实现

     //模型初始化
        initModelFbx() 
            console.log('模型加载');

            let loader = new FBXLoader();

            loader.load(modelPath, function (object) 
     
                var mat = modelMat;
                console.log(object);
                showModel = object;
                showModel.position.set(0, -30, 0);
                // geometry.center(); //居中显示
                showModel.children[1].material =  mat;
                //获取原生shader代码
                let fragStr=THREE.ShaderLib["basic"].fragmentShader;
                let VertStr=THREE.ShaderLib["basic"].vertexShader;
                console.log(fragStr);
                console.log(VertStr);
                // showModel.children[1].material=mat;
                
                //添加骨骼辅助
                // let meshHelper = new THREE.SkeletonHelper(showModel);
                // scene.add(meshHelper);
                scene.add(showModel);
                console.log(showModel);
                console.log(showModel.children[1]);
            );

        ,
fbx载入可能出现THREE.FBXLoader: Cannot find the version number for the file given错误,解决办法为,移动文件到static目录下,具体操作见文章(【Vue2+ThreeJS踩坑记录(一)】

本地文件载入功能

//本地文件读取
        load(name) 
            let xhr = new XMLHttpRequest(),
                okStatus = document.location.protocol === "file:" ? 0 : 200;
            xhr.open('GET', name, false);
            xhr.overrideMimeType("text/html;charset=utf-8");//默认为utf-8
            xhr.send(null);
            return xhr.status === okStatus ? xhr.responseText : null;
        ,

shader代码

顶点着色器代码
#include <common>
#include <skinning_pars_vertex>
varying vec3 vNormal;
varying vec2 vUv;
varying vec3 objectPos;
void main() 
	#include <morphcolor_vertex>
	#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )
		#include <beginnormal_vertex>
		#include <morphnormal_vertex>
		#include <skinbase_vertex>
		#include <skinnormal_vertex>
		#include <defaultnormal_vertex>
	#endif
	#include <begin_vertex>
	#include <skinning_vertex>
	#include <project_vertex>
	#include <worldpos_vertex>
    vNormal=normal;
    vUv = uv;
    objectPos= position;

片段着色器代码

#include <common>

uniform vec3 diffuse;
uniform float opacity;
uniform mat4 modelMatrix;
uniform vec3 color;
uniform vec3 lightPosition;
uniform vec3 _mainColor;
uniform sampler2D _mainTex;
uniform sampler2D _normalTex;
uniform vec2 tilling;
uniform  float _roughness;
uniform  float _roughnessContrast;
uniform  float _roughnessInit;
uniform  float _roughnessMin;
uniform  float _roughnessMax;
varying vec3 objectPos;
varying vec3 vNormal;
varying vec2 vUv;

vec3 ACETompping(vec3 x)

    float a=2.51;
    float b=.03;
    float c=2.43;
    float d=.59;
    float e=.14;
    return saturate((x*(a*x+b))/(x*(c*x+d)+e));

vec4 lerp(vec4 a,vec4 b,vec4 w)

    return a+w*(b-a);
    

float lerpFloat(float a,float b,float w)

    return a+w*(b-a);
    

mat3 cotangent_frame(vec3 N,vec3 p,vec2 uv)

   
    vec3 dp1=dFdx(p);
    vec3 dp2=dFdy(p);
    vec2 duv1=dFdx(uv);
    vec2 duv2=dFdy(uv);
    
    
    vec3 dp2perp=cross(dp2,N);
    vec3 dp1perp=cross(N,dp1);
    vec3 T=dp2perp*duv1.x+dp1perp*duv2.x;
    vec3 B=dp2perp*duv1.y+dp1perp*duv2.y;
    
   
    float invmax=inversesqrt(max(dot(T,T),dot(B,B)));
    return mat3(T*invmax,B*invmax,N);

vec3 ComputeNormal(vec3 nornal,vec3 viewDir,vec2 uv,sampler2D normalMap)

 
    vec3 map=texture2D(normalMap,uv).xyz;
    
    map=map*255./127.-128./127.;
    
    mat3 TBN=cotangent_frame(nornal,-viewDir,uv);
    return normalize(TBN*map);
    return(texture2D(normalMap,vUv).rgb-.5)*2.;

void main() 
	
    vec3 worldNormal = normalize( vec3( modelMatrix * vec4( vNormal, 0.0 ) ) );
     vec3 worldPosition=(modelMatrix*vec4(objectPos,1.0)).xyz;//获取世界坐标
	
     vec3 vDir=normalize(cameraPosition-worldPosition);
    vec3 nDir=ComputeNormal(worldNormal,vDir,vUv*tilling,_normalTex);
    vec3 lDir = normalize( lightPosition - worldPosition );
    //向量操作
    
    vec3 rvDir=reflect(-vDir,nDir);
    float NdotV=dot(nDir,vDir);
    float NdotL=dot(nDir,lDir);
    //贴图操作
 
    
    vec3 mainTex=texture2D(_mainTex,vUv).xyz;
    vec3 roughnessTex=vec3(1.0,1.0,1.0);
    vec3 _roughnessContrasts=vec3(_roughnessContrast,_roughnessContrast,_roughnessContrast);
    
    //粗糙度
    float finalRoughness=saturate(pow(roughnessTex,_roughnessContrasts)*_roughnessInit).x;
    finalRoughness=lerpFloat(_roughnessMin,_roughnessMax,finalRoughness);
    finalRoughness=finalRoughness*(1.7-.7*finalRoughness);
    finalRoughness=finalRoughness*6.;
    
    //漫反射模型(兰伯特)
    vec3 lambert=vec3(max(0.0,NdotL),max(0.0,NdotL),max(0.0,NdotL));

    
    //最终颜色
    vec3 finalColor=(lambert.xyz*_mainColor);
    vec3 finalColor_liner=pow(finalColor,vec3(2.2,2.2,2.2));
    finalColor=ACETompping(finalColor_liner);
    vec3 finalColor_gamma=pow(finalColor,vec3(1./2.2,1./2.2,1./2.2));
    
    gl_FragColor=vec4(finalColor_gamma,1);
	

这里注意一下,如果不使用#inlcude引用库,可能会导致fbx模型动画无效。具体情况见此文章链接(【Vue2+ThreeJS踩坑记录(二)】fbx蒙皮网格模型挂载ShaderMaterial材质之后,挂载动画无效的解决办法】

材质初始化

 initMat() 
            fragShaderStr = this.load(shaderPath + `.frag`);
            vertexShaderStr = this.load(shaderPath + `.vert`);

            modelMat = new THREE.ShaderMaterial(
                uniforms: 
                    _mainColor:  value: new THREE.Vector3(1.0, 1.0, 1.0) ,
                    lightPosition:  value: new THREE.Vector3(0, -10, 0) ,
                    tilling:  value: new THREE.Vector2(1, 1) ,
                    _normalTex:  value: new THREE.TextureLoader().load("static/texture/Naria/Naria_N.tga") ,
                    _roughness:  value: 1.0 ,
                    _roughnessContrast:  value: 1.06 ,
                    _roughnessInit:  value: 1.92 ,
                    _roughnessMin:  value: 0.0 ,
                    _roughnessMax:  value: 0.7 
                ,
                //236,65,65
                vertexShader: vertexShaderStr,
                fragmentShader: fragShaderStr,


            );
        ,

载入并播放fbx动画

    //载入模型动画
        initModelAnim() 
            console.log('动画加载');

            let loader = new FBXLoader();

            loader.load(animationPath, function (object) 

                //创建动画混合器,并指定模型,混合器会自动根据指定模型寻找骨骼,并绑定
                let mixer = new THREE.AnimationMixer(showModel);
                //添加至动画混合器数组
                animationMixers.push(mixer);
                //挂载动画
                showModel.animations.push(object.animations[0]);
                //获取动画片
                let action = mixer.clipAction(showModel.animations[0]);
                //播放动画片
                action.play();
            );
        ,

更新动画混合器

 if (animationMixers.length > 0) 
     //遍历并更新所有动画混合器
     animationMixers[0].update(clock.getDelta());

工程打包

工程打包之前建议按这篇文章配置(【Vue2+ThreeJS踩坑记录(三)】Vue2打包项目之后,运行本地项目为空白页解决办法

配置完之后使用如下命名打包

npm run build

案例代码链接

工程链接
DEMO链接(大小为8mb,用的是GitHubPage,可能加载要几分钟)

最终效果图

以上是关于vite+vue3+threejs实现一个3D模型的展示案例的主要内容,如果未能解决你的问题,请参考以下文章

【 攻城略地 】vue3 + vite + ts加载3dTiles

mapbox结合threeJS载入3d模型,并实现点击事件

threejs加载3d模型 怎样控制鼠标

threejs控制3d模型透明度

如何在 Aframejs 中加载 3d 模型?它目前在threejs中运行良好

Threejs物联网,养殖场3D可视化