我的小号巨多 發表於 2019-9-4 18:19:00

TypeScript进阶开发——ThreeJs基础实例,从入坑到入门,Demo实例记录

<h2>  前言</h2>
<p>  我们前面使用的是自己编写的ts,以及自己手动引入的jquery,由于第三方库采用的是直接引入js,没有d.ts声明文件,开发起来很累,所以一般情况下我们使用npm引入第三方的库,本文记录使用npm,typescript开发threejs3D项目,搭建基础实例,为以后开发具体业务做准备</p>
<p>&nbsp;</p>
<h2>  项目结构</h2>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190830172907945-338014390.png" alt="">&nbsp;<img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190830172921638-212121548.png" alt=""></p>
<p>&nbsp;  依旧是熟悉的SpringBoot项目,不同以往的是使用了npm管理工具来下载依赖js库,类似maven,同时为了解决typescript编译后引入npm库的路径有问题,导致浏览器报错的问题,我们采用的webpack打包工具来打包&nbsp; PS:webpack依赖的文件真的是多,全都安装完后,好几百M</p>
<p>  下面简单说一下如何初始化npm、webpack,以及下载jquery、three等js库</p>
<p>&nbsp;</p>
<h2>  npm的使用</h2>
<p>  初始化</p>
<p>  cmd先打开到项目的 threejs\src\main\resources\static 路径,使用npm init命令,回答一系列问题(当然也可以全部按照默认值),初始化成npm项目</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826115729096-2117666897.png" alt=""></p>
<p>  得到node_modules目录以及package.json文件</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826115843302-1851237669.png" alt=""></p>
<p>  当我们使用打包命令,会提示我们还缺少那些依赖,这时候我们按照提示去下载就可以了</p>
<p>  有一点要注意,package.json文件的name值不能用typescript,我们改成</p>
<div class="cnblogs_code">
<pre>"name": "typescript-threejs",</pre>
</div>
<p>  脚本命令</p>
<div class="cnblogs_code">
<pre>"scripts"<span style="color: rgba(0, 0, 0, 1)">: {
    </span>"tsc": "tsc -w",<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">监控文件,有改动时实时编译ts</span>
    "build": "webpack",<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">打包</span>
    "dev": "webpack -w"<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">监控文件,有改动时实时打包</span>
},</pre>
</div>
<p>  webpack.config.js配置</p>
<div class="cnblogs_code">
<pre>const path = require('path'<span style="color: rgba(0, 0, 0, 1)">);
module.exports </span>=<span style="color: rgba(0, 0, 0, 1)"> {
    entry : {
      main : </span>'./src/controller/Main.ts'<span style="color: rgba(0, 0, 0, 1)">
    },
    devtool : </span>'inline-source-map'<span style="color: rgba(0, 0, 0, 1)">,
    mode : </span>'development'<span style="color: rgba(0, 0, 0, 1)">,
    module : {
      rules : [ {
            test : </span>/\.ts$/<span style="color: rgba(0, 0, 0, 1)">,
            use : </span>'ts-loader'<span style="color: rgba(0, 0, 0, 1)">,
            exclude : </span>/node_modules/<span style="color: rgba(0, 0, 0, 1)">
      } ]
    },
    resolve : {
      extensions : [ </span>'.ts', '.js'<span style="color: rgba(0, 0, 0, 1)"> ]
    },
    output : {
      filename : </span>'.js'<span style="color: rgba(0, 0, 0, 1)">,
      path : path.resolve(__dirname, </span>'./dist/'<span style="color: rgba(0, 0, 0, 1)">)
    },
    plugins : [
    ],
    optimization: {
      splitChunks: {
            chunks: </span>"all"<span style="color: rgba(0, 0, 0, 1)">,
            minSize: </span>30000<span style="color: rgba(0, 0, 0, 1)">,
            minChunks: </span>1<span style="color: rgba(0, 0, 0, 1)">,
            maxAsyncRequests: </span>5<span style="color: rgba(0, 0, 0, 1)">,
            maxInitialRequests: </span>3<span style="color: rgba(0, 0, 0, 1)">,
            automaticNameDelimiter: </span>'-'<span style="color: rgba(0, 0, 0, 1)">,
            name: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
            cacheGroups: {
                vendors: {
                  test: </span>/[\\/]node_modules[\\/]/<span style="color: rgba(0, 0, 0, 1)">,
                  priority: </span>-10<span style="color: rgba(0, 0, 0, 1)">
                },
                </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">: {
                  minChunks: </span>2<span style="color: rgba(0, 0, 0, 1)">,
                  priority: </span>-20<span style="color: rgba(0, 0, 0, 1)">,
                  reuseExistingChunk: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
                }
            }
      }
    }
};</span></pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>  使用 npm install 命令就可以下载第三方插件</p>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826120042522-946346368.png" alt=""></p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826120050248-1222213750.png" alt=""></p>
<p>  下载jquery的声明文件</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826120151085-1216950954.png" alt=""></p>
<p>  在node_modules目录下面j就可以看到npm下载下来的库</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826120239189-1560960988.png" alt=""></p>
<p>&nbsp;</p>
<p>  如何引入</p>
<p>  使用typescript编译成js,路径不正确,浏览器报错</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826162204818-326713562.png" alt=""></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>  使用webpack编译、打包成js,路径正确</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826162240909-370047804.png" alt=""></p>
<p>&nbsp;</p>
<h2>  threejs</h2>
<p>  threejs,是一个JavaScript编写的WebGL第三方库。</p>
<p>  官网:https://threejs.org/</p>
<p>  官方文档:https://threejs.org/docs/index.html#manual/zh/introduction/Creating-a-scene</p>
<p>  官方提供的各种例子:https://threejs.org/examples/</p>
<p>  官方GitHub:https://github.com/mrdoob/three.js</p>
<p>&nbsp; &nbsp; &nbsp;网友克隆的官网,gitee国内镜像,网速较快:http://break_egg.gitee.io/three.js/</p>
<p>  这里还有一个大神的博客,有比较丰富的例子可以参考:https://www.wjceo.com/</p>
<p>&nbsp;</p>
<p>  通过查阅官网文档、以及参考大量的例子进行学习,我们开始three.js之旅,采用的是npm引入的方式引入three</p>
<p>&nbsp;</p>
<p>&nbsp;  路径结构介绍</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190830175634553-1338930914.png" alt=""></p>
<p>&nbsp;</p>
<p>  渲染器有三个WebGLRenderer、CSS2DRenderer、CSS3DRenderer,后面这两个是为了能够使用CSS2DObject、CSS3DObject对象作为Label,注意,使用了多个渲染器后,我们的控制器要控制的是CSS3DRenderer.domElement,控制它可以同时控制到WebGLRenderer的对象</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190830180316808-1117440643.png" alt=""></p>
<p>&nbsp;  </p>
<p>  同时,3D的渲染器DOM要加绝对定位,要不然DOM的东西显示不出来(为了统一,2D的我也加了),然后webGL的canvas也加样式,position: absolute;</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190830180438913-906133440.png" alt=""></p>
<p>&nbsp;  </p>
<p>  然后在初始化场景时,不停的渲染,注意,有一个地方不停的渲染就可以了,多了影响性能</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190830180950352-548023249.png" alt=""></p>
<p>&nbsp;  </p>
<p>  封装原生发射射线的方式监听对象的鼠标事件,有个地方要注意,射线只能检测到Mesh对象,Group组对象是检测不到了</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190830181307839-1614788345.png" alt=""></p>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190830181334789-1868055023.png" alt=""></p>
<p>  更新监听事件方法:</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
   * 添加cSS3DRenderer.domElement监听对象事件,利用原生射线原理,被射线检测到自身节点或者子节点则触发监听事件
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    public addEventListener(listenerObj: THREE.Object3D, even: string, callback: (object: THREE.Object3D) </span>=&gt; <span style="color: rgba(0, 0, 255, 1)">void</span>): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> {
      let thid </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">;

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">把具体的事件添加到对象,然后再绑定到dom,这样做的目的是为了方便后期移除</span>
      listenerObj = <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (event) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">阻止冒泡</span>
<span style="color: rgba(0, 0, 0, 1)">            event.preventDefault();

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">光线投射</span>
            let raycaster = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> THREE.Raycaster();
            let mouse </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> THREE.Vector2();

            mouse.x </span>= (event["clientX"] / thid.cSS3DRenderer.domElement.clientWidth) * 2 - 1<span style="color: rgba(0, 0, 0, 1)">;
            mouse.y </span>= -(event["clientY"] / thid.cSS3DRenderer.domElement.clientHeight) * 2 + 1<span style="color: rgba(0, 0, 0, 1)">;

            raycaster.setFromCamera(mouse, thid.camera);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 需要被监听的对象要存储在intersectObjects中,对象本身就是一个group,所以不能直接检测射线(三维世界中点击事件需要检测射线),</span>
            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 其下children里面有许多mesh,才是要被检测的目标。</span>
<span style="color: rgba(0, 0, 0, 1)">            let intersects;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (listenerObj.type == "Mesh"<span style="color: rgba(0, 0, 0, 1)">) {
                intersects </span>= raycaster.intersectObjects(, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                intersects </span>= raycaster.intersectObjects(listenerObj.children, <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
            }
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (intersects.length &gt; 0<span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 回调函数,返回被监听对象本身</span>
                <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (callback) callback(listenerObj);
            }
      };

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">threejs原生事件监听 mousedown</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.cSS3DRenderer.domElement.addEventListener(even, listenerObj, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
    }</span></pre>
</div>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
   * 移除cSS3DRenderer.domElement监听对象事件
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    public removeEventListener(listenerObj: THREE.Object3D, even: string,): </span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.cSS3DRenderer.domElement.removeEventListener(even, listenerObj);
    }</span></pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;  </p>
<p>&nbsp;</p>
<p>  同时还封装了一个动画效果,按屏幕刷新率调用,每次调用++一定的值,每次回调函数返回,从0到1递增,等于1的时候结束递归调用</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190904163319666-718485199.png" alt=""></p>
<p>&nbsp;</p>
<p>&nbsp;  还有一个是镜头的巡检</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190904163447560-284973389.png" alt=""></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>  其他地方就不一一介绍了,大家自己看代码</p>
<p>&nbsp;</p>
<h2>  效果演示</h2>
<p>&nbsp;  地球模仿的是:https://wa.qq.com/xplan/earth/index.html?_wv=1</p>
<p>  地球外部有一层漂浮的云层(大气层),效果比较逼真</p>
<p><img src="https://img2020.cnblogs.com/blog/1353055/202007/1353055-20200717114119289-1574091919.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190904172040160-1085135111.gif" alt=""></p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190904182043687-441649701.gif" alt=""></p>
<p>  </p>
<p>  云层模仿的是:&nbsp;http://www.sucai58.com/plus/demo.php?aid=132&nbsp;</p>
<p>  云海又八千多个PlaneGeometry对象组成,推动镜头营造穿越云层的效果,作为地球场景到园区场景的过渡动画</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190904172121825-1725652058.gif" alt=""></p>
<p>  园区是加载了官方例子的obj模型(站立的男人)以及fbx模型(跳舞的女人),跳舞的动画是模型自带的,中间那两个是普通的BoxGeometry对象(录GIF的软件录带绿色的东西效果好难看啊,我就不放上来了...)</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190904172506712-1857903266.png" alt=""></p>
<p>&nbsp;</p>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190904172532548-949549945.png" alt=""></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;  更多效果自己运行查看</p>
<p>&nbsp;</p>
<h2>&nbsp;  补充</h2>
<p>  1、npm install 包的时候报错</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826143507742-483061056.png" alt=""></p>
<p>  解决:给npm降级或者升级</p>
<p>  降级 : npm install -g npm@5.4.0&nbsp; //@后面是具体版本号</p>
<p>  升级 : npm install -g npm //升级到最新版本</p>
<p>  如果还不行就清除缓存再次安装</p>
<p><span class="hljs-attribute">  npm cache clean -f</span></p>
<p>&nbsp;</p>
<p>  </p>
<p><span class="hljs-attribute"> 2、threejs移动的时候老是报错</span></p>
<p><span class="hljs-attribute"><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826165143190-1989870486.png" alt=""></span></p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826165217867-1692469928.png" alt=""></p>
<p>  在three.module.js做一下小调整,非空才调用.call方法</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201908/1353055-20190826170346845-896968353.png" alt=""></p>
<p>&nbsp;</p>
<h2>  后记</h2>
<p>  学习Three.js是因为公司的3D技术储备从原先的Twaver转用其他的库,公司大佬在考察Unity3D跟Three.js后决定用后者,Three.js是开源免费的,而Twaver跟Unity3D是要收钱的,这个例子就是我的学习Demo,主要是参考了官网的例子以及效果演示中提到的那两个列子,Demo例子大体上已经封装实现了加载OBJ、FBX、原生对象、2D、3Dlabel等,封装对象鼠标事件的监听,镜头动画封装,基本上满足业务的开发</p>
<p>  当然还有很多不足的地方,比如事件监听是我们直接封装的检测射线方法,经过测试发现一个bug,我们已经把地球从场景中移除,但点击地球的位置依旧能触发地球的监听事件</p>
<p>&nbsp;</p>
<p>  TypeScript进阶开发——ThreeJs基础实例就暂时记录到这里,以后在补充</p>
<p>&nbsp;</p>
<p>  2019-09-05补充:</p>
<p>  1、解决事件绑定问题,原因:虽然我们已经将对象从场景中移除,但事件监听还在,所以还是能够触发</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190905141638398-177313818.png" alt=""></p>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190905141724357-1314554297.png" alt=""></p>
<p>&nbsp;  2、眼尖的同学可以发现了,我们之前的云层过渡动画卡顿、同时还有类似“马赛克”的情况出现,卡顿是因为我们创建的云对象太多了,而马赛克是因为默认启用了深度测试,我们关掉就可以解决</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190905144235385-1973964068.png" alt=""></p>
<p>&nbsp;</p>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190905144437364-685094219.png" alt=""></p>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190905144448365-1021888818.png" alt=""></p>
<p>&nbsp;</p>
<p>&nbsp;  我们关闭了深度测试、减少了一半的云对象,最后看一下效果(流畅了很多~)</p>
<p><img src="https://img2018.cnblogs.com/blog/1353055/201909/1353055-20190905144605384-545452318.png" alt=""></p>
<p>&nbsp;</p>
<p>&nbsp;  </p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2 id="_label_h25">  代码开源</h2>
<p>  注:node_modules文件夹和里面的各种库文件我就不提交了,几百M太大了,大家按照上面的步骤用npm命令初始化、下载就可以了</p>
<p>&nbsp;</p>
<p>  代码已经开源、托管到我的GitHub、码云:</p>
<p>  GitHub:https://github.com/huanzi-qch/threejs<br></p>
<p>  码云:https://gitee.com/huanzi-qch/threejs</p>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
   
<div>
        <h3>版权声明</h3>
        <div>作者:huanzi-qch</div>
        <div>出处:https://www.cnblogs.com/huanzi-qch</div>
        <div>若标题中有“转载”字样,则本文版权归原作者所有。若无转载字样,本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利.</div>
</div><br/>

<div>
        <h3><span style="color: #4f84b3;cursor: pointer;" onclick="window.open('https://huanzi-qch.github.io/file-server/ad/adservice.html', '_blank')" title="AD广告位服务说明">AD广告位</span>(长期招租,如有需要请私信)</h3>
       
        【阿里云】阿里云最全的优惠活动聚集地!<br/>
        【腾讯云】腾讯云当前最新优惠活动专区!<br/>
    <br/>
</div>

<div>
        <h3>捐献、打赏</h3>
        <div><span style="color: red">请注意:</span>相应的资金支持能更好的持续开源和创作,如果喜欢这篇文章,请随意打赏!</div>
        <div style="text-align: center;">
                <div style="
                        float: left;
                        margin-left: 200px;
                ">
                        <img style="width: 100px;" src="https://huanzi-qch.github.io/file-server/images/zhifubao.png"/>
                        <p>支付宝</p>
                </div>
                <div>
                        <img style="width: 100px;" src="https://huanzi-qch.github.io/file-server/images/weixin.png"/>
                        <p>微信</p>
                </div>
        </div>               
</div><br/>


<div>
        <h3>交流群</h3>
        <div style="text-align: center;">
                有事请加群,有问题进群大家一起交流!<br/>
                <img style="width: 300px;" src="https://huanzi-qch.github.io/file-server/images/qq.png"/>
        </div>
</div><br/><br><br>
来源:https://www.cnblogs.com/huanzi-qch/p/11413739.html
頁: [1]
查看完整版本: TypeScript进阶开发——ThreeJs基础实例,从入坑到入门,Demo实例记录