我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛
Hi,大家好,我是扫地盲僧,时隔数年,我们又见面了。这次给大家带来有一个大礼品,标题党为:《扫地盲僧又一开源效率神器》
炎热的夏天,疫情让技术内卷越来越严重。大家拼命的加班,爆肝,熬夜,为富裕的生活努力。作为一个多年被内卷的老程序员,给大家炎热的夏天解解暑,减少一半的加班时间,换来2倍的产出效率,让你空出时间欣赏欣赏日落,体验体验走在大街上的单身生活。
为了给你们解暑,我又卷了2周才把代码抽离出来。
2022年,炎夏最强3D解暑神器:3DD(3D Design),号称提高300%的产出效率,代码下高达80%的视觉还原度的调优神器。
背景
随着3D、AR、VR等技术的兴起,很多技术团队面临新视觉和新创新的压力。对于中小型企业来说,高级的技术美术、图形工程师、Web前端等人才缺失尤为严重。对于Web前端来说,更是隔行如隔山,如果没有图形技术的基础很难在短时间涉足3D图形领域。
为了保证交付和还原,即使在移动端,很多数团队也无奈选择加持庞大的动画渲染引擎。
当前3D交互技术面临的问题:
- 3D交互技术对前端不友好,兼容性差
- 技术资源稀缺,专业领域越来越细分
- 建模软件渲染时间长,交付周期长
- 软件代码还原度低,沟通成本高
- 资产复用困难,尤其是材质属性“因软件而异,因引擎而异”
- …
为了解决以上问题,我们在项目结束后,进行了无数次的脑暴和Search,最终也没有找到合适的方案。我们决定自己动手解决这个问题,无意间看到了五福背后的 Web 3D 引擎 Oasis Engine 正式开源 ,体验了一下,正是我们需要的效果,于是便开始了我们3D领域探索的征程。
架构-设计
我们希望交互效果的验收不仅仅是技术和美术,还可以是UE,甚至PM。让团队所有人一起参与到3D交互设计的过程中来。
针对以上问题,我们加持Oasis引擎,通过整合3D资产、在线材质调整、实时渲染等技术来提供问题的解决方案。
3DD介绍
优势
- 移动端优先,轻量级框架
- 模型设计规范统一,3D资产复用
- 建模+调优协同进行,降低沟通成本
- JS自定义脚本,无缝调用3D视觉模型
- 提升还原度,代码还原下可以选择实时渲染,提高产出效率
- …
技术框架
3DD,基于Vite+TypeScript+Vue3+Ant-design等先进框架,加持蚂蚁技术团队强大的Oasis引擎,打造的一款3D资产调优工作台。
- Vite2.* ,下一代前端开发与构建工具
- Vue3.0, 渐进式JavaScrip框架
- TypeScript,离不开的JS超集,持续霸占开发语言榜首
- Pinia,被任命为下一代的vuex状态管理方案
- Oasis,出自蚂蚁技术团队,移动优先的高性能 Web 图形引擎
- …扫地盲僧没啥大本事,就喜欢折腾新技术
底层实现
更换材质
/**基础颜色纹理。搭配基础颜色使用,是个相乘的关系。 */
baseTexture: Texture2D | undefined;
/**法线纹理。可以设置法线纹理 ,在视觉上造成一种凹凸感,还可以通过法线强度来控制凹凸程度。*/
normalTexture: Texture2D | undefined;
normalTextureIntensity: number | undefined;
/**自发射光纹理。我们可以设置自发光纹理和自发光颜色(emissiveFactor)达到自发光的效果,即使没有光照也能渲染出颜色。 */
emissiveTexture: Texture2D | undefined;
/**阴影遮蔽纹理。我们可以设置阴影遮蔽纹理来提升物体的阴影细节。 */
occlusionTexture: Texture2D | undefined;
/**金属粗糙度纹理。搭配金属粗糙度使用,是相乘的关系。 */
roughnessMetallicTexture: Texture2D | undefined;
changeMaterial(material: PBRMaterial | Partial<OneMaterial>) {
const createTinyColorFun =
material instanceof PBRMaterial ? tinycolor.fromRatio : tinycolor;
this.name = material?.name || "";
this.baseColor = createTinyColorFun(
material?.baseColor || { r: 1, g: 1, b: 1, a: 1 }
).toRgb();
this.opacity = this.baseColor.a;
this.emissiveColor = createTinyColorFun(
material?.emissiveColor || { r: 1, g: 1, b: 1, a: 1 }
).toRgb();
this.metallic = material?.metallic || 0;
this.roughness = material?.roughness || 0;
this.isTransparent = !!material?.isTransparent;
}
复制代码
更换纹理
/**
* 修改部件的材质纹理
* @param textureName 纹理名称
* @param textureType 纹理的类型 'baseTexture'| 'normalTexture'| 'emissiveTexture'| 'occlusionTexture'| 'roughnessMetallicTexture'
*/
async changeTexture(textureName: string, textureType: string) {
let texture = TextureManager.defaultTextures[textureName];
if (!texture.texture) {
texture.texture = await GameManager.ins.engine.resourceManager.load(
texture.path
);
}
// 有可能加载纹理失败,所以还是要判断纹理是否存在
if (texture.texture) {
let material_in_engine = this.renderer?.getInstanceMaterial() as PBRMaterial;
material_in_engine[textureType] = texture.texture;
this.material[textureType] = texture.texture;
}
}
复制代码
饱和度亮度调整
// 饱和度和亮度
function handleChangeSV(e) {
let w = refs.saturation_value.value.clientWidth;
let h = refs.saturation_value.value.clientHeight;
let x = e.pageX - refs.saturation_value.value.getBoundingClientRect().left;
let y = e.pageY - refs.saturation_value.value.getBoundingClientRect().top;
x = (x < w && x > 0) ? x : (x > w ? w : 0);
y = (y < h && y > 0) ? y : (y > h ? h : 0);
// 计算饱和度和亮度
saturation.value = Math.floor((x / w) * 100 + 0.5) / 100;
value.value = Math.floor((1 - y / h) * 100 + 0.5) / 100;
// hsv转化为rgb
let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
red.value = r;
green.value = g;
blue.value = b;
// 移动背景板圆圈
pointStyle.value = `top: ${y}px;left: ${x}px;`;
}
复制代码
播放模型动画
/**播放模型动画 */
playAnimation() {
let { animations } = this.modelGltf;
if (animations && animations.length > 0) {
const animator = this.modelGltf?.defaultSceneRoot.getComponent(Animator);
const animatorController = new AnimatorController();
animations?.forEach((clip: AnimationClip, index) => {
const layer = new AnimatorControllerLayer("layer" + index);
const animatorStateMachine = new AnimatorStateMachine();
animatorController.addLayer(layer);
layer.stateMachine = animatorStateMachine;
if (index > 0) layer.blendingMode = AnimatorLayerBlendingMode.Additive;
const animatorState = animatorStateMachine.addState(clip.name);
animatorState.clip = clip;
if (index > 0) animatorState.clipStartTime = 1;
});
animator.animatorController = animatorController;
animator.speed = 1;
animations?.forEach((clip: AnimationClip, index) => {
index == 0 && animator.play(clip.name, index);
});
}
}
复制代码
…更多黑科技请移步仓库
Oasis使用体验
优点
- glTF模型资源的加载和管理
- PBR材质的渲染
- 对节点部件的位移、旋转、缩放、矩阵等操作
- 鼠标交互实现组件高精度拾取
- JS\TS脚本实现交互通信
- 相比Three,拥有完善的中文文档和专业的维护团队
- npm 工作流,纯 TypeScript 编写,对前端工程化优化
- …more
不足
- 功能相对Three,不太齐全,当然定位也有所不同
- 生态不够丰富,优秀参考案例有待补充
- 社区插件比较少,编辑器…这个官方的RoadMap好像已经规划
神器-功效
产出效率的提升
以前的模式:美术大量时间花费在于找平建模软件和Web 3D引擎之间的渲染差异,以及沟通技术对材质反复调整。
现在的模式:美术集中精力在建模和材质微调上,只需要简单的沟通即可实现秒级渲染和导出图片或代码。
为此我们特意做了一个实验,实验项目为:设计一个相对复杂的IP模型,计算从建模、调试、渲染、导出、骨骼动画等整个设计过程的工时,和结合3DD方案以后做了对比。
效能计算公式:正常人效1/5,实际人效1/2,单个效能提升=(1/2-1/5)* 5 *100%
还原质量的提高
我们经过也通过专业软件 Beyond Compare 4
,做了无数次渲染图和引擎实时渲染图的对比,发现其折损率在一个相对可以接受的范围内,当然依旧有很大的提升空间。
能够肉眼可见的是,建模软件的渲染图拥有更好的光影效果,但3DD秒级实时渲染的产出图,也是完全可以接受的。
针对阴影渲染的补偿方案也有很多,Oasis官网也给出了一些方案,感兴趣的小伙伴可以去扒拉扒拉。
实战-案例
请欣赏我们针对以前项目,进行3DD爆改以后的效果
IP模型和交互动画结合以后的效果
我们在图标交互领域的尝试,以下为多个JS动画方案
还有更多姿势,等着你来挖掘…
不值一提小成绩
- 21年底,基于Blender烘焙的还原度方案,被收录到Oasis官网
- 22年2月,受邀参加Oasis一周年沙龙做分享
- 22年Q1季度,获得公司首个创新S级奖和最具潜力团队奖
- Now,经过1个多月的打磨和层层评选,最终很荣幸3DD被Oasis官方团队评选为《 Oasis 第一个社区开源项目》
Github仓库
github.com/tobe-fe-dal… || 在线体验 || 手心出汗就帮我点点赞和Star
3DD开源直播夜
为了更好的展示3DD的魅力,扫地盲僧决定于5月23晚8点,三端(抖音、B站、微信)同步直播,聊聊它的能力&连线蚂蚁技术专家&公开招募开源维护者。请点击选择自己喜欢的渠道,提前锁定我的直播间。