Appearance
内容制作常用功能(易现 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 材质提供
用于模拟真实世界的遮挡关系