当前位置:网站首页>基于threejs的商品VR展示平台的设计与实现思路
基于threejs的商品VR展示平台的设计与实现思路
2022-08-02 11:13:00 【InfoQ】
基于threejs的商品VR展示平台的设计与实现思路
前言
VR
本文仅仅展示部分核心内容
总体开发方案设计
总体开发设计思维导图
- 对于企业用户,该平台实现如下的功能:可以加载产品模型,可以将制作的三维模型添加到特定的虚拟环境中,前提是符合系统平台支持的文件格式的三维模型文件;可以构建3D模型组件信息库,即企业或商家将商品的3D模型导入到该平台中,平台将该模型中各个组件模型的关联信息与构成信息,封装存储在云存储中,以便日后组合使用;设定模型组件之间的关系,即企业或者商家可以通过一些简单的便捷的操作的方式建立组件间的关系,形成树状结构;可以设定组件运动行为参数,即企业或者商家可以设定模型的分解状态;设定组件的文字说明信息,即企业或者商家可以对组件添加商品文字信息说明,或者重新命名,或者添加一些参数,设定和编辑顾客用户自定义的信息,即企业或商家可以设定顾客用户自定义的权限;方便快捷的在线编辑功能,即提供一个在线编辑器功能,可以供商家实时修改,实时预览,简单操作即可完成商品模型的制作,商品模型的运动分解参数,商品信息的添加等。
- 对于顾客用户来说,该平台如下的功能:任意调整观察点,即顾客用户在可以随意角度浏览该VR商品展示平台上的商品;获取模型组件的信息,即顾客用户以鼠标点击或者手指触摸的方式,触发查看商品模型的某个组件模型,同时可以获得该组件模型的名称、尺寸、组成成分等参数;分解展示,即顾客用户以鼠标点击或者手指触摸的方式,决定让商品模型进行分解运动,使该商品模型的内部结构关系展现出来;搭配商品,即顾客用户可以选择商品的一些规格参数,比如口味,尺寸大小等;个性化定制,即顾客用户可以对商品模型进行自定义设置。
平台整体架构核心
模型制作模块
前端展示模块
存储模块
后端管理模块
后台管理实现
Vue
Element UI
商品模型制作
商品模型前期准备
为商品实物,下图为模型
UV贴图
商品模型展示环境搭建
Perspective Camera
PointLight
OrbitControls
requestAnimationFrame
setTimeout
商品模型组件制作
//制作空心圆柱源码
var HollowBufferCylinder = function (radiusTop, radiusBottom, radiusStep, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength, phiSegments) {
BufferGeometry.call(this);
this.type = 'HollowBufferCylinder';
this.parameters = {
radiusTop: radiusTop,
radiusBottom: radiusBottom,
radiusStep: radiusStep,
height: height,
radialSegments: radialSegments,
heightSegments: heightSegments,
openEnded: openEnded,
thetaStart: thetaStart,
phiSegments: phiSegments,
thetaLength: thetaLength
};
var scope = this;
radiusTop = radiusTop !== undefined ? radiusTop : 10;
radiusBottom = radiusBottom !== undefined ? radiusBottom : 10;
height = height || 10;
radiusStep = radiusStep || 2
radialSegments = Math.floor(radialSegments) || 8;
heightSegments = Math.floor(heightSegments) || 1;
phiSegments = Math.floor(phiSegments) || 8;
openEnded = openEnded !== undefined ? openEnded : false;
thetaStart = thetaStart !== undefined ? thetaStart : 0.0;
thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;
var indices = [];
var vertices = [];
var normals = [];
var uvs = [];
var arr = [];
var index = 0;
var halfHeight = height / 2;
var groupStart = 0;
generateTorso(true);//Inside
generateTorso(false);//Outside
if (radiusTop > 0) generateCap(true);
if (radiusBottom > 0) generateCap(false);
generateTorsoSide(true);
generateTorsoSide(false);
this.setIndex(indices);
this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
function generateTorso(inside) {
var x, y;
var normal = new Vector3();
var vertex = new Vector3();
var indexArray = [];
var groupCount = 0;
var slope = (radiusBottom - radiusTop) / height;
for (y = 0; y <= heightSegments; y++) {
var indexRow = [];
var v = y / heightSegments;
var radius = inside ? v * (radiusBottom - radiusTop) + radiusTop - radiusStep : v * (radiusBottom - radiusTop) + radiusTop;
for (x = 0; x <= radialSegments; x++) {
var u = x / radialSegments;
var theta = u * thetaLength + thetaStart;
var sinTheta = Math.sin(theta);
var cosTheta = Math.cos(theta);
vertex.x = radius * sinTheta;
vertex.y = - v * height + halfHeight;
vertex.z = radius * cosTheta;
vertices.push(vertex.x, vertex.y, vertex.z);
normal.set(sinTheta, slope, cosTheta).normalize();
normals.push(normal.x, normal.y, (inside ? 1 : -1) * normal.z);
uvs.push(u, 1 - v);
indexRow.push(index++);
}
indexArray.push(indexRow);
}
for (x = 0; x < radialSegments; x++) {
for (y = 0; y < heightSegments; y++) {
var a = indexArray[y][x];
var b = indexArray[y + 1][x];
var c = indexArray[y + 1][x + 1];
var d = indexArray[y][x + 1];
indices.push(a, b, d);
indices.push(b, c, d);
groupCount += 6;
}
}
scope.addGroup(groupStart, groupCount, inside === true ? 0 : 1);
groupStart += groupCount;
}
function generateTorsoSide(sideLeft) {
var groupCount = 0;
var theta = sideLeft == true ? thetaLength + thetaStart : thetaStart;
vertices.push(radiusTop * Math.sin(theta), halfHeight, radiusTop * Math.cos(theta));
normals.push(Math.sin(theta), (sideLeft ? 1 : -1) * -1, Math.cos(theta));
uvs.push(0, 0);
vertices.push((radiusTop - radiusStep) * Math.sin(theta), halfHeight, (radiusTop - radiusStep) * Math.cos(theta));
normals.push(Math.sin(theta), (sideLeft ? 1 : -1) * -1, Math.cos(theta));
uvs.push(0, 1);
vertices.push(radiusBottom * Math.sin(theta), -halfHeight, radiusBottom * Math.cos(theta));
normals.push(Math.sin(theta), (sideLeft ? 1 : -1) * 1, Math.cos(theta));
uvs.push(1, 0);
vertices.push((radiusBottom - radiusStep) * Math.sin(theta), -halfHeight, (radiusBottom - radiusStep) * Math.cos(theta));
normals.push(Math.sin(theta), (sideLeft ? 1 : -1) * 1, Math.cos(theta));
uvs.push(1, 1);
var a = index++;
var b = index++;
var c = index++;
var d = index++;
if (sideLeft) {
indices.push(d, c, b);
indices.push(b, a, c);
} else {
indices.push(a, b, c);
indices.push(c, d, b);
}
groupCount += 6;
scope.addGroup(groupStart, groupCount, sideLeft === true ? 4 : 5);
groupStart += groupCount;
}
function generateCap(top) {
var x, centerIndexStart, centerIndexEnd;
var uv = new Vector2();
var vertex = new Vector3();
var groupCount = 0;
var outerRadius = (top === true) ? radiusTop : radiusBottom;
var sign = (top === true) ? 1 : - 1;
var segment;
var radius = outerRadius - radiusStep;
var j, i;
centerIndexStart = index;
for (j = 0; j <= phiSegments; j++) {
for (i = 0; i <= radialSegments; i++) {
egment = thetaStart + i / radialSegments * thetaLength;
vertex.x = radius * Math.sin(segment);
vertex.y = halfHeight * sign;
vertex.z = radius * Math.cos(segment);
vertices.push(vertex.x, vertex.y, vertex.z);
normals.push(0, sign, 0);
uv.x = (vertex.x / outerRadius + 1) / 2;
uv.z = (vertex.z / outerRadius + 1) / 2;
uvs.push(uv.x, uv.z);
index++;
}
radius += radiusStep / phiSegments;
}
for (j = 0; j < phiSegments; j++) {
var radialSegmentLevel = j * (radialSegments + 1);
for (i = 0; i < radialSegments; i++) {
segment = i + radialSegmentLevel;
var a = centerIndexStart + segment;
var b = centerIndexStart + segment + radialSegments + 1;
var c = centerIndexStart + segment + radialSegments + 2;
var d = centerIndexStart + segment + 1;
indices.push(a, b, d);
indices.push(b, c, d);
groupCount += 6;
}
}
scope.addGroup(groupStart, groupCount, top === true ? 2 : 3);
groupStart += groupCount;
}
}
模型在线编辑功能实现
模型轮廓高亮实现
模型分解运动的实现
sO(speed Orientation)
sV(speed Velocity)
sI(speed Increment)
//解析函数源码
function Component(_Group, modelsConfig) { // 解析器函数
for (let i = 0; i < modelsConfig.length; i++) {
if (modelsConfig[i].Nid) continue;
var _M = modelsConfig[i].ChildrenModel && modelsConfig[i].Type == "Group" ? buildGroup(modelsConfig[i]) : buildModel(modelsConfig[i]);
_M instanceof Array ? addArray(_Group, _M) : _Group.add(_M);
}
}
function buildGroup(GConfig) {
/*创模型组*/ var _G = new THREE.Group();
/*模组名称*/ _G.name = GConfig.Name;
/*模组数据*/
GConfig.UserData ? _G.userData = GConfig.UserData : "";
/*模组位置*/
GConfig.Position ? _G.position.set(GConfig.Position[0], GConfig.Position[1], GConfig.Position[2]) : "";
/*模组方向*/
GConfig.Rotate ? _G.rotation.set(GConfig.Rotate[0] / 180 * Math.PI, GConfig.Rotate[1] / 180 * Math.PI, GConfig.Rotate[2] / 180 * Math.PI) : '';
/*模组缩放*/
GConfig.Scale ? _G.scale.set(GConfig.Scale[0], GConfig.Scale[1], GConfig.Scale[2]) : "";
/*模组组件*/
for (let i = 0; i < GConfig.ChildrenModel.length; i++) {
if (GConfig.ChildrenModel[i].Nid) continue;
var _M = GConfig.ChildrenModel[i].ChildrenModel && GConfig.ChildrenModel[i].ChildrenModel.Type == "Group" ? buildGroup(GConfig.ChildrenModel[i]) : buildModel(GConfig.ChildrenModel[i]);
_M instanceof Array ? addArray(_G, _M) : _G.add(_M);
}
/*组件克隆*/ if (GConfig.CloneList) {
var cloneList = [_G];
for (let i = 0; i < GConfig.CloneList.length; i++) {
cloneList.push(cloneModel(_G, GConfig.CloneList[i]));
}
return cloneList;
}
return _G
}
function buildModel(MConfig) {
/*材料*/
var _Material = MConfig.Material.Color.indexOf(".") != -1 ? new THREE['MeshBasicMaterial']({ map: new THREE.ImageUtils.loadTexture(MConfig.Material.Color) }) : new THREE['MeshBasicMaterial']({ color: MConfig.Material.Color }); MConfig.Side ? _Material.side = THREE.DoubleSide : '';
/*两面都显示*/
MConfig.Material.Others ? setOthers(_Material, MConfig.Material.Others) : "";
/*几何*/
var _Geometry = new THREE[MConfig.Type](MConfig.Paramete[0], MConfig.Paramete[1], MConfig.Paramete[2], MConfig.Paramete[3], MConfig.Paramete[4], MConfig.Paramete[5], MConfig.Paramete[6], MConfig.Paramete[7], MConfig.Paramete[8], MConfig.Paramete[9]);
/*模型*/ var _M = new THREE.Mesh(_Geometry, _Material);
/*名称*/ _M.name = MConfig.Name;
/*位置*/
MConfig.Position ? _M.position.set(MConfig.Position[0], MConfig.Position[1], MConfig.Position[2]) : "";
/*方向*/
MConfig.Rotate ? _M.rotation.set(MConfig.Rotate[0] / 180 * Math.PI, MConfig.Rotate[1] / 180 * Math.PI, MConfig.Rotate[2] / 180 * Math.PI) : '';
/*缩放*/
MConfig.Scale ? _M.scale.set(MConfig.Scale[0], MConfig.Scale[1], MConfig.Scale[2]) : "";
/*数据*/ MConfig.UserData ? _M.userData = MConfig.UserData : "";
/*其他*/ MConfig.Others ? setOthers(_M, MConfig.Others) : "";
if (MConfig.ChildrenModel) {
for (let i = 0; i < MConfig.ChildrenModel.length; i++) {
if (MConfig.ChildrenModel[i].Cid) continue;
var _CM = MConfig.ChildrenModel[i].ChildrenModel && MConfig.ChildrenModel[i].ChildrenModel.Type == "Group" ? buildGroup(MConfig.ChildrenModel[i]) : buildModel(MConfig.ChildrenModel[i]); _CM instanceof Array ? addArray(_M, _CM) : _M.add(_CM);
}
}
if (MConfig.CloneList) {
var cloneList = [_M];
for (let i = 0; i < MConfig.CloneList.length; i++) {
cloneList.push(cloneModel(_M, MConfig.CloneList[i]));
}
return cloneList;
}
return _M;
}
function cloneModel(M, CConfig) {
/*克隆母体*/ var _C = M.clone();
/*重置位置*/
CConfig.Position ? _C.position.set(CConfig.Position[0], CConfig.Position[1], CConfig.Position[2]) : "";
/*重置方向*/
CConfig.Rotate ? _C.rotation.set(CConfig.Rotate[0] / 180 * Math.PI, CConfig.Rotate[1] / 180 * Math.PI, CConfig.Rotate[2] / 180 * Math.PI) : '';
/*重置缩放*/
CConfig.Scale ? _C.scale.set(CConfig.Scale[0], CConfig.Scale[1], CConfig.Scale[2]) : "";
/*重置数据*/
CConfig.UserData ? _C.userData = CConfig.UserData : '';
return _C;
}
function setOthers(G, M) {
for (var key in M) {
G[key] = M[key];
}
}
function addArray(G, M) {
M.forEach(ele => {
G.add(ele);
});
}
边栏推荐
- 大疆P4M云遮挡矫正
- 如何在技术上来保证LED显示屏质量?
- ansible模块--yum模块
- 微信小程序---组件开发与使用
- mysql清除binlog日志文件
- STM32+MPU6050 Design Portable Mini Desktop Clock (Automatically Adjust Time Display Direction)
- 流动性质押挖矿系统开发如何制作?单双币系统开发成熟技术
- org.apache.ibatis.binding.BindingException Invalidbound statement (not found)的解决方案和造成原因分析(超详细)
- leetcode: 200. 岛屿数量
- 放苹果(暑假每日一题 13)
猜你喜欢
随机推荐
C#为listview选中的项添加右键菜单
Coroutines and Lifecycle in Kotlin
LeetCode笔记:Weekly Contest 304
go语言的接口
ssm网页访问数据库数据报错
npm WARN deprecated [email protected] This version of tar is no longer supported, and will not receive
ASP.NET Core 6框架揭秘实例演示[31]:路由&quot;高阶&quot;用法
Oracle 单实例19.11升级到19.12
C#/VB.NET to add more lines more columns image watermark into the Word document
循环语句综合练习
字节跳动软件测试岗,收到offer后我却拒绝了~给面试的人一些忠告....
Deep Learning 100 Examples - Convolutional Neural Network (CNN) for mnist handwritten digit recognition
SQL 数据更新
爆款视频怎么做?这里或许有答案!
MySql模糊查询大全
初探zend引擎
如何在技术上来保证LED显示屏质量?
Alibaba CTO Cheng Li: Alibaba Open Source History, Concept and Practice
Hello, my new name is "Bronze Lock/Tongsuo"
图形处理单元(GPU)的演进