Skip to content

内容制作常用功能(易现 InsightWeb 渲染引擎)

物体显示和隐藏

javascript
const danact = this.scene.getChildByName("POIShopInfo");
if (danact) {
  danact.visible = true; // 隐藏为false
}

物体 position、rotation、scale 获取和设置

javascript
// 一般物体获取和设置
const danact = this.scene.getChildByName("POIShopInfo");
if (danact) {
    // 获取世界坐标
    let wpos = danact.getWorldPosition(); // 获取世界坐标
    let wqua = danact.getWorldQuaternion(); // 获取世界旋转值
    // 获取本地坐标
    danact.position
    danact.rotation // 单位为弧度(PI/2, 0, PI/2)对应unity(-90, 0, 90)x轴向符号相反
    danact.scale
    // 设置世界坐标
    // 设置本地坐标
   danact.position.set(0, 0, 0); // 设置物体的本地坐标
   danact.rotation.set(0, 0, 0); // 单位弧度
   danact.scale.set(1, 1, 1); // 分别设置三个轴向的值
   danact.position.setScalar(1); // 统一设置三个轴向的值 
   danact.rotation.setScalar(0); // 单位弧度
   danact.scale.setScalar(1); // 统一设置三个轴向的值
   
}
javascript
// 相机获取和设置
this.camera.transform.position // **获取摄像机实时坐标**
this.camera.position // 不可使用此方式,直接获取camera.position是不更新的
this.camera.rotation// 不可使用此方式,直接获取camera.rotation是不更新的
this.camera.getWorldDirection() // 相机的朝向向量

const camPose = this.inner3d.mainCamera.matrixWorld.toArray();            
camPose[12], camPose[13], camPose[14]为摄像机坐标
// 云定位中第一次定位成功回调和摄像机坐标更新存在时间差,需等摄像机坐标更新后获取

物体克隆

javascript
let wishPerfab = this.wishCard.clone();

父物体更换

javascript
// this.wishCard.parent = this.scene;
//使用parent和add更换物体父物体后,物体的坐标未更新;使用attach;
//sub和add会改变原变量的值,要使用new vector3
let wishPerfab = this.wishCard.clone();
this.scene.attach(wishPerfab);

// 以防万一,进行坐标的重置;经验证物体从内层切换父物体为scene节点时,坐标表现正常,可能不需要进行如下操作
let wishWorldsPos = new Insight.Vector3();        
let wishWorldsRot = new Insight.Quaternion();        
this.wishCard.getWorldPosition(wishWorldsPos);  
this.wishCard.getWorldQuaternion(wishWorldsRot);
// 或者
// let wishWorldsPos = this.wishCard.getWorldPosition();         
// let wishWorldsRot = this.wishCard.getWorldQuaternion();       
wishPerfab.position.copy(wishWorldsPos);        
wishPerfab.quaternion.copy(wishWorldsRot);

// 将物体移动到某个物体下作为子物体(需要进行坐标重置或赋值)
this.worscale = this.cardParent.getWorldScale();
this.aimPos.attach(this.cardParent);
this.cardParent.position.set(0, 0, 0);
this.cardParent.rotation.set(0, 0, 0);
this.cardParent.scale.copy(this.worscale);

// 记录初始值时要使用clone克隆出来的常量,直接复制会根据模型位置发生值变化。
this.cardLocalPos = this.cardParent.position.clone();
this.cardLocalQua = this.cardParent.quaternion.clone();
this.cardLocalSca = this.cardParent.scale.clone();

LookAt 朝向

javascript
// 此处以朝向摄像机为示例
// ezxrInnerContent3d.js 脚本中添加方法
fnLookAtCamera(model) {
    if (!model) return console.log(_LOG_TAG_, "model is null");
    this.mainCamera.updateMatrixWorld();
    const camPose = this.mainCamera.matrixWorld.toArray();
    const position = new Insight.Vector3();
    model.getWorldPosition(position);
    model.lookAt(new Insight.Vector3(camPose[12], position.y, camPose[14]));
}
javascript
// 透传到上层
// innerContent3d.js 脚本中添加方法
fnLookAtCamera(model) {
    if (this.inner3d) {
        this.inner3d.fnLookAtCamera(model);
    }
},
// ARContent.js 脚本中添加方法
fnLookAtCamera(model) {
    if (this.contentCpt) {
        this.contentCpt.fnLookAtCamera(model);
    }
},
// arContentCpt 为 ARContent组件
 this.arContentCpt.fnLookAtCamera(this.PlayBall); // PlayBall 始终朝向摄像机

Unity 动画播放

javascript
const maskloop = this.scene.getChildByName("DanMu_Mask_Loop");
if (maskloop) {
  this.danmuActAnimator = maskloop.GetComponent("Animator");
  this.danmuActAnimator.Play("Robo_Anim_Hello", -1, 0); // 动画名为animator控件中的动画片段名
  this.danmuActAnimator.speed = 1;
  this.danmuActAnimator.Stop();
  
  //多动画片段控制
  this.logoAni.Play('Start', -1, 0); // 第一段动画
  //参数说明(同unity动画paly参数):(第一个参数为unity中animator组件的动画片段名,第二参数使用默认值-1,第三参数为动画从0开始播放)
  this.logoAni.Play('LOGOChange', -1, 0); // 第二段动画
  // 不建议使用
  this.danmuActAnimator.SetBool('hit', 0);
  this.danmuActAnimator.CrossFade('hit', 0, -1, 0);
  
  // 停止动画播放并重置到开始帧
  this.danmuActAnimator.Play('Card_Float', -1, 0);
  this.danmuActAnimator.speed = 0;
}

DoTween 动画

推荐参考文档: https://blog.csdn.net/yr1102358773/article/details/128083444

参考链接:

https://blog.csdn.net/yr1102358773/article/details/128083444 (简单使用)

https://juejin.cn/post/7117903861339127844 (详细指南)

java
import {
    Insight
} from '../../lib/ezxrRenderer/insight.js';
const TWEEN = Insight.TWEEN;
// 控制物体模型 model 从当前位置插值移动到目标位置
const targetPosition = new Insight.Vector3(10, 10, 10);
this.moveMode(model, targetPosition);

moveMode(mode, Vector3) {
    let position = new Insight.Vector3();
    position.copy(mode.position);
    const coordsPosition = {
        x: position.x,
        y: position.y,
        z: position.z
    }; // Start at (0, 0)
    new TWEEN.Tween(coordsPosition) // Create a new tween that modifies 'coords'.
        .to({
            x: Vector3.x,
            y: Vector3.y,
            z: Vector3.z
        }, 3000) // Move to (300, 200) in 1 second.
        .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
        .onUpdate(a => {
            // console.log('aaaa', a)
            mode.position.copy(new Insight.Vector3(a.x, a.y, a.z));
        })
        .onComplete(() => {
            if(this.timeId) clearInterval(this.timeId);
        })
        .start(); // Start the tween immediately.

    this.timeId = setInterval(() => {
        TWEEN.update();
    }, 3000 / 60);
},

 rotateMode(model, endQua) {
    let startQua = model.quaternion;
    const coordsQua = {
        x: startQua.x,
        y: startQua.y,
        z: startQua.z,
        w: startQua.w
    }; // Start at (0, 0)
    new TWEEN.Tween(coordsQua) // Create a new tween that modifies 'coords'.
        .to({
            x: endQua.x,
            y: endQua.y,
            z: endQua.z,
            w: endQua.w,
        }, 3000) // Move to (300, 200) in 1 second.
        .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
        .onUpdate(a => {
            // console.log('aaaa', a)
            model.quaternion.copy(new Insight.Quaternion(a.x, a.y, a.z, a.w));
        })
        .start(); // Start the tween immediately.

    this.timeId04 = setInterval(() => {
        TWEEN.update();
    }, 3000 / 60);
},

// 单个值插值
lerpValue(start, end, setMode, call) {
    const coordsPosition = {
        v: start,
    }; // Start at (0, 0)
    new TWEEN.Tween(coordsPosition) // Create a new tween that modifies 'coords'.
        .to({
            v: end,
        }, 1000) // Move to (300, 200) in 1 second.
        .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
        .onUpdate(a => {
            // console.log('aaaa', a)
            this.cardParent.rotation.y = a.v;
            // mode = a.v;
        })
        .onComplete(() => {
            if (this.timeId05) clearInterval(this.timeId05);
            if(call) call();
        })
        .start(); // Start the tween immediately.

    this.timeId05 = setInterval(() => {
        TWEEN.update();
    }, 1000 / 60);
},
        
// dotween 动画卸载
clearFlyLantern() {
  if (flyLantern !== null) {
    flyLantern.stop();
    flyLantern = null;
  }
}

射线触发

javascript
import {Insight} from '../../../lib/ezxrRenderer/insight';
// 1.在canvas中监听touch事件
<!-- 渲染引擎依赖的 3d 画布, 一般为innerContent3d 组件 wxml 文件中画布-->
<canvas class="inner-3d" id="inner3d" type="webgl" catchtouchend="touchEnd"></canvas>

//2.射线交互  用于 innerContent3d.js 脚本中
touchEnd(e) {
    // console.log(_LOG_TAG_, "touchEnd", e);
    // 屏幕坐标转设备坐标
    const pixelRatio = wx.getSystemInfoSync().pixelRatio;
    var mouse = new Insight.Vector2();
    mouse.x = (e.changedTouches[0].clientX / (this.inner3d.cnv3d.width / pixelRatio)) * 2 - 1;
    mouse.y = -(e.changedTouches[0].clientY / (this.inner3d.cnv3d.height / pixelRatio)) * 2 + 1;
    // 射线检测
    var raycaster = new Insight.Raycaster();
    raycaster.near = 0.2;
    raycaster.far = 500;
    raycaster.setFromCamera(mouse, this.inner3d.mainCamera);
    // model为需要触发点击的物体
    this.modelValue = raycaster.intersectObject(model, true);
    console.log('射线和场景选中物体', this.modelValue);
    if (this.modelValue.length > 0) {
        // 点中物体,触发射线交互
    }
}

三维坐标转化屏幕坐标

css
const sysInfo = wx.getSystemInfoSync();
var vector = worldVector.project(camera);//通过世界坐标获取转标准设备坐标
let w = sysInfo.windowWidth / 2;
let h = sysInfo.windowHeight / 2;
var x = Math.round(vector.x *w + w);//标准设备坐标转屏幕坐标
var y = Math.round(-vector.y * h + h); // Math.round为取整处理,可根据具体情况判断是否需要

材质属性修改

javascript
const danact = this.scene.getChildByName("POIShopInfo");
if (danact) {
  danact.material.uniforms._Alpha.value = 1; // 自定义shader
  danact.material.uniforms._TextTex.value = texture; // 自定义shader 贴图都在uniforms下
  danact.material.side = Insight.DoubleSide; // 双面渲染
  // unity 内置shader都使用 map
  danact.material.map = texture;// 使用 Unity 内置 Unlit/Transparent Shader
  danact.material.opacity = 0.7; // 使用 Unity 内置(如 Unlit/Transparent) 设置透明度
  this.Offset = {x: 0.33, y: 0}; // offset
  this.Tiling = {x: 0.33, y: 1}; // tiling 0.33 = 1/3; 3为图片张数
  danact.material.map.offset = this.Offset; // Unity内置 可通过dotween做轮播
  danact.material.map.repeat = this.Tiling; // 
}

使用网络图片更新模型贴图

参考文档:使用网络图片更新模型贴图

取消剔除

java
// skinnedMesh在特殊视角时会被剔除,加强制取消剔除
const shugan = this.scene.getChildByName('shugan');
shugan.frustumCulled = false;

锁帧

java
// 用于 ezxrInnerContent3d.js 脚本 重写 m_Render 方法
clock = new Insight.Clock();
FPS = 30;
renderT = 1 / this.FPS;
timeS = 0;

m_Render() {
    this.requestAnimationId = this.cnv3d.requestAnimationFrame(this.m_Render.bind(this));

    var T = this.clock.getDelta();
    this.timeS += T;
    // console.log('inter time', T, this.timeS);

    if(this.timeS > this.renderT) {
        this.onInner3dUpdate();
        this.wcEngine.Render();
        this.wcEngine.renderer.state.setCullFace(Insight.CullFaceNone);
        /*  */
        this.timeS = this.timeS % this.renderT;
    } 
}

渲染引擎内置环境光

java
for(let i=0;i<this.wcEngine.scene.children.length;i++) {
    if(this.wcEngine.scene.children[i].type == 'AmbientLight' || this.wcEngine.scene.children[i].type == 'Light') {
        // this.wcEngine.scene.children[i].visible = false; // 不能直接关闭,否则会出现模型时明时暗问题
         this.wcEngine.scene.children[i].intensity = 1;
        // console.log('AmbientLight', this.wcEngine.scene.children[i]); 
    }
  }

导入环境贴图, 需要物体材质支持 envMap

目前支持的材质类型: MeshBasicMaterial, MeshStandardMaterial, MeshLambertMaterial, MeshPhongMaterial

javascript
// 将HDR切割为6张图, 导出工具中会将天空盒的材质球按顺序切分好
const imageUrls = [
    "https://ar-scene-source.nosdn.127.net/d1c694ca97a24d1c0f41f70b66bf5566.png",
    "https://ar-scene-source.nosdn.127.net/2560b8742a47ec5d65bb34cae027d822.png",
    "https://ar-scene-source.nosdn.127.net/c51161dae7d69bfb98d04f4cdfe84469.png",
    "https://ar-scene-source.nosdn.127.net/e9e353bf0bdf166a28bcfb72fcf2cc6d.png",
    "https://ar-scene-source.nosdn.127.net/5887369554da5b3a1e5f92bc8b57aaa3.png",
    "https://ar-scene-source.nosdn.127.net/a3537de0ce109a0abfe0577288c40416.png"
];
// 环境贴图载入,生成纹理
const loader = new Insight.CubeTextureLoader();
let textureCube = loader.load(imageUrls);
// 材质中设置envMap
let material = new Insight.MeshStandardMaterial( {
    color: 0xffffff,
    metalness: 1.0, 
    roughness: 0.0,
    envMap: textureCube
} );
// 示例
const geometry = new Insight.SphereGeometry( 0.2, 32, 32 );
let mesh = new Insight.Mesh( geometry, material );

this.scene.add( mesh );

模型动态添加卸载

java
**// 动态加载**
import {
    Insight
} from "../lib/ezxrRenderer/insight.js";

// 资源加载
export async function loadPackage(engine, url) {
    const additiveRoot = new Insight.Object3D();
    return new Promise((resolve, reject) => {
        engine.LoadModel(
            url,
            async function (scene) {
                console.log("load package complete.", scene.children[0]);
                resolve(scene.children[0]);
            },
            function (event) {
                // console.log("load package progress", event);
            },
            function (error) {
                console.error("load package error", error);
            },
            additiveRoot
        );
    });
}

// url 为动态加载的资源场景打包,不需要包含摄像机,只包含当前点位展示的美术资源即可
let engine = this.arContentCpt.fnGetWCEngine()
let model = await loadPackage(engine, url);
this.scene.add(model); // 添加到场景根节点下
// 亦可添加到某个指定组下
let doorPlateRoot = this.scene.getChildByName("doorPlateRoot");
doorPlateRoot.add(model);
java
**// 动态卸载**
// 相关方法
export async function unloadModel(model) {
    if (!model) return;
    let objs = [];
    model.traverse(obj => {
      if (obj.isMesh || obj.isSkinnedMesh || obj.isParticle) {
          objs.push(obj);
      }
    });
    // console.log(_LOG_TAG_, 'clear objs', objs);
    objs.forEach(obj => {
        // console.log(_LOG_TAG_, '##start dispose:', obj.name);
        if (obj.skeleton) {
            if (obj.skeleton.boneTexture && obj.skeleton.boneTexture.dispose) {
                obj.skeleton.boneTexture.dispose();
                obj.skeleton.boneTexture = null;
            }
            // console.log(_LOG_TAG_, '##dispose skeleton:', obj.name);
        }
        if (obj.material) {
            if (obj.material.uniforms) {
                for (let key in obj.material.uniforms) {
                    let note = obj.material.uniforms[key];
                    if (note.value && note.value.dispose) {
                        note.value.dispose();
                        note.value = null;
                        // console.log(_LOG_TAG_, '##dispose uniforms map:', obj.name);
                    }
                }
            }
            if (obj.material.map && obj.material.map.dispose) {
                obj.material.map.dispose();
                obj.material.map = null;
                // console.log(_LOG_TAG_, '##dispose map:', obj.name);
            }
            if (obj.material.envMap && obj.material.envMap.dispose) {
                obj.material.envMap.dispose();
                obj.material.envMap = null;
                // console.log(_LOG_TAG_, '##dispose envMap:', obj.name);
            }
            if (obj.material.emissiveMap && obj.material.emissiveMap.dispose) {
                obj.material.emissiveMap.dispose();
                obj.material.emissiveMap = null;
                // console.log(_LOG_TAG_, '##dispose emissiveMap:', obj.name);
            }
            if (obj.material.aoMap && obj.material.aoMap.dispose) {
                obj.material.aoMap.dispose();
                obj.material.aoMap = null;
                // console.log(_LOG_TAG_, '##dispose aoMap:', obj.name);
            }
            if (obj.material.metalnessMap && obj.material.metalnessMap.dispose) {
                obj.material.metalnessMap.dispose();
                obj.material.metalnessMap = null;
                // console.log(_LOG_TAG_, '##dispose metalnessMap:', obj.name);
            }
            if (obj.material.normalMap && obj.material.normalMap.dispose) {
                obj.material.normalMap.dispose();
                obj.material.normalMap = null;
                // console.log(_LOG_TAG_, '##dispose normalMap:', obj.name);
            }
            if (obj.material.roughnessMap && obj.material.roughnessMap.dispose) {
                obj.material.roughnessMap.dispose();
                obj.material.roughnessMap = null;
                // console.log(_LOG_TAG_, '##dispose roughnessMap:', obj.name);
            }
            if (obj.material.dispose) obj.material.dispose();
        }
        if (obj.geometry && obj.geometry.dispose) obj.geometry.dispose();
        if (obj.BufferGeometry && obj.BufferGeometry.dispose) obj.BufferGeometry.dispose();
        if (obj.parent) obj.parent.remove(obj);
    });
    objs = [];
}

// 卸载调用
this.scene.remove(model);
unloadModel(model);
java
// 克隆物体的添加卸载
this.wishCard= this.scene.getChildByName("wishCard");
let wishPrefab= this.wishCard.clone(); // 使用已有资源lastModel克隆
// model.parent = this.scene;
//使用parent和add更换物体父物体后,物体的坐标未更新;使用attach;
this.scene.attach(wishPrefab); // 
// 克隆物体和被克隆物体坐标统一
let wishWorldsPos = new Insight.Vector3();
let wishWorldsRot = new Insight.Quaternion();
this.wishCard.getWorldPosition(wishWorldsPos);
this.wishCard.getWorldQuaternion(wishWorldsRot);
wishPrefab.position.copy(wishWorldsPos);
wishPrefab.quaternion.copy(wishWorldsRot);
// 卸载
this.scene.remove(wishPrefab);

粒子相关控制

javascript
粒子的播放和重新播放
一、通过控制接口;
// 挂载粒子系统的物体
this.object = this.scene.getChildByName("HZYY_balloon_03");
// 播放前重制粒子初始状态
this.object.particleSystem.Reset();
this.object.particleSystem.Play();
this.object.particleSystem.Stop();
// 停止后再次播放也需要Reset(), 否则从上次停止位置开始
this.object.particleSystem.Reset();
this.object.particleSystem.Play();
java
粒子的播放和重新播放 
二、通过控制粒子的显隐来实现;
控制粒子显隐可通过代码或动画K帧的形式;

// 粒子系统先隐藏立马激活,不能将粒子效果重置,例如:
this.YSGL_FX_Park_Bobble.visible = false;
this.YSGL_FX_Park_Bobble.visible = true;
// 需做一个短暂的时间差,例如:
this.YSGL_FX_Park_Bobble.visible = false;
setTimeout(() => {
  this.YSGL_FX_Park_Bobble.visible = true;
}, 100)
javascript
// 粒子初始化,粒子开始时保持激活,并设置其位置在相对远的地方(视觉范围看不到)
// 可规避两个问题:1.进入后激活粒子时相机卡死问题;2.使用上述代码激活粒子时,第一次粒子无法正常播放问题
this.YSGL_FX_Park_Bobble.visible = true; // 前提没有父物体,或者父物体也处于激活状态
this.bollbePos = this.YSGL_FX_Park_Bobble.position;
this.YSGL_FX_Park_Bobble.position.setScalar(1000);

// 在需要播放粒子效果时,将保存的坐标信息再复制回来
this.YSGL_FX_Park_Bobble.position.copy(this.bollbePos);

小程序阴影设置

参考文档:小程序阴影设置

小程序光照设置

参考文档:小程序光照设置

小程序音频播放

javascript
// 音频url
bgmUrl: 'https://ar-scene-source.nosdn.127.net/579a3bc813fb9d7fe29f886d9fc9c04a.mp3'
// 创建音频播放器, 并初始化
// IOS手机需要设置是否遵循静音键设置, 默认遵循
wx.setInnerAudioOption({obeyMuteSwitch: false});
this.data.innerAudioContext = wx.createInnerAudioContext();
this.data.innerAudioContext.autoplay = false;
this.data.innerAudioContext.loop = true;
this.data.innerAudioContext.src = page.data.config.bgmUrl;
// 播放接口
// 播放
this.data.innerAudioContext.play();
// 暂停。暂停后的音频再播放会从暂停处开始播放
this.data.innerAudioContext.pause();
// 停止。停止后的音频再播放会从头开始播放。
this.data.innerAudioContext.stop();
// 跳转到指定位置
// 跳转的时间,单位 s。精确到小数点后 3 位,即支持 ms 级别精确度
this.data.innerAudioContext.seek(1.5);

小程序视频播放

视频资源要求请参考:视频要求 的 3D 视频纹理(透明视频)模块

参考文档:小程序视频播放

小程序拍照

参考文档:小程序拍照内容实现

遮罩 shader 材质提供

用于模拟真实世界的遮挡关系