Vue+Three.js 入门八(使用canvas自定义纹理)

LZQ plus

发布于 2020.03.10 19:03 阅读 5175 评论 0

前言

  通常一个3D场景必不可缺少元素的是一个数据的显示,像3D游戏里的人物血条、3D工厂里面的大屏生产数据(类似上图),因为这些数据都是实时的,我们不可能用像图片这种静态资源来渲染,况且有时显示样式还比较复杂,所以我们需要自定义这些实时数据的显示,这里的核心便是canvas。

核心知识点

  1、canvas:一系列canvas的操作,渲染文字、绘画图片、透明背景等。

  2、CanvasTexture (Canvas纹理):继承了Texture,它的作用就是将我们生成的canvas数据存到纹理数据里面。之后就是将纹理数据赋给材质,材质赋给模型,进而实现渲染。

示例代码

  根据上述核心知识点可以看出实现渲染效果的最基本操作流程:创建canvas -> 创建纹理赋予canvas数据 -> 创建材质并赋予纹理数据 -> 将材质赋给模型。下面以建立上图左一的看板为例介绍主要代码。

1、创建canvas,并定义内容。

        let context = canvas.getContext('2d');

        //添加背景颜色为rgba(44, 62, 80, 0.5)

        context.clearRect(0, 0, canvasWidth, canvasHeight);

        context.fillStyle = "rgba(44, 62, 80, 0.5)";

        context.fillRect(0, 0, canvasWidth, canvasHeight);

        context.textAlign = 'start';

        context.font = 'Bold 50px Microsoft YaHei';

        context.fillStyle = 'rgba(255, 255, 255, 1.0)';

        context.fillText('测试--1', 10, 50);

        context.fillText('测试--2', 10, 100);

        context.fillText('测试--3', 10, 150);

        context.fillText('测试--4', 10, 200);

2、创建纹理赋予canvas数据

        let texture = new THREE.CanvasTexture(canvas);

3 & 4、创建材质并赋予纹理数据 -> 将材质赋给模型

        let panel1 = new THREE.PlaneBufferGeometry(300, 150);

        let panelMate1 = new THREE.MeshBasicMaterial({map: texture, transparent: true});

        let panelMesh1 = new THREE.Mesh(panel1, panelMate1);

到了这里,大家应该能够明白three是怎么将canvas作为渲染纹理赋给模型的,最基础和最核心的就是canvas的一些基本操作,但是有几个点需要注意:

  ①、我们创建的canvas的高和宽最好都是2的次方数;

  ②、我们的模型可能是个长方形,但canvas是一个正方形,纹理渲染时文字会被拉长,我们可以通过纹理和材质的配置来达到我们想要的效果;

  ③、渲染时想达到背景半透明效果,canvas设置透明背景的情况下,材质的透明属性必须设为true;

  ④、数据发生变化,我们修改canvas、texture或material都可以完成数据的更新。

拓展部分

  看完上文中的介绍,应该能够完成最基本的canvas自定义纹理,但是有的同学想要在画布里添加背景图片达到上图中中间看板的效果,按照上文流程去做,有的时候发现图片并不会渲染,这里最重要的一个原因可能是因为图片没有加载完,纹理就渲染完了,所以我们需要针对这个原因进行优化,具体实施就是将代码进行异步处理,如下:

      makeTextPlaneMate2(canvasConfig) {

        let canvasWidth = canvasConfig.width || 512;

        let canvasHeight = canvasConfig.height || 256;

        let canvas = document.createElement('canvas');

        canvas.width = canvasWidth;

        canvas.height = canvasHeight;

        let context = canvas.getContext('2d');

        //添加背景图片,进行异步操作

        return new Promise((resolve, reject) => {

          let img = new Image();

          img.src = canvasConfig.backImg;

          //图片加载之后的方法

          img.onload = () => {

            //将画布处理为透明

            context.clearRect(0, 0, canvasWidth, canvasHeight);

            //绘画图片

            context.drawImage(img, 0, 0, canvasWidth, canvasHeight);

            resolve(makeText(context, canvas))

          };

          //图片加载失败的方法

          img.onerror = (e)=>{

            reject(e)

          }

        });

        //内部方法进行文字输入

        function makeText(context, canvas) {

          context.textAlign = 'start';

          context.font = 'Bold 40px Microsoft YaHei';

          context.fillStyle = '#0eb0d8';

          context.fillText('生产总数:2659', 30, 60);

          context.fillText('合格数量:2600', 160, 110);

          context.fillText('车间人数:188', 30, 170);

          context.fillText('人均时效:3.56', 160, 220);

          //将画布写入纹理,并返回材质

          let texture = new THREE.CanvasTexture(canvas);

          return new THREE.MeshBasicMaterial({map: texture, transparent: true});

        }

      },

创建模型写入场景,用异步处理的方式加载

        // 创建Plane2

        let panel2 = new THREE.PlaneBufferGeometry(300, 150);

        this.makeTextPlaneMate2({backImg: 'objs/factory2/panel.png'}).then((panelMate2) => {

          let panelMesh2 = new THREE.Mesh(panel2, panelMate2);

          panelMesh2.position.set(0, 200, 0);

          this.scene.add(panelMesh2);

          this.render();

        });

  当然,我是因为封装了一个专门方法来渲染画布才用的Promise进行异步处理,如果你们在主程序里运用img.onload,直接在onload里面进行scene.add()操作就可以了。如果有同学问Promise是什么,它就是为处理异步事件(监听事件、Ajax请求等)而生的,用起来非常简单,由于这里的专题是canvas,想了解可以自行搜索也不用感谢我 [滑稽狗头]。

再次拓展——渲染Echart图表

  众所周知,echart图表的功能非常强大,同时它的核心技术也是操作canvas,而且接口简单,只需要init()一下,然后执行setOption()即可完成图表的渲染,所有的配置交给option完成。既然都是操作的canvas,是不是我们可以把echart当作“工具人”来使用?答案是是的。根据上文思路,当setOption()执行完之后,echart就将图表生成了,此时我们就可以将canvas拿来当作纹理来使用了,继而得到类似于上图右一的看板效果。代码如下:

        let canvas = document.createElement('canvas');

        canvas.width = canvasWidth;

        canvas.height = canvasHeight;

        return new Promise((resolve, reject) => {

          let myChart = ECharts.init(canvas, 'dark');

          try {

            myChart.setOption(option);

            myChart.on('finished', () => {

              let texture = new THREE.CanvasTexture(canvas);

              resolve(new THREE.MeshBasicMaterial({map: texture, transparent: true}));

            });

          } catch (e) {

            reject(e)

          }

        });

需要注意的是echart是on('finished',function(){})剩余的部分和图片的异步操作一样。其实到了这里,可以看出我们可以用“第三者”处理的这种方式达到我们想要的视觉效果,达到效果之后我们再将其拿来当作纹理渲染使用。