复刻国外顶尖科技公司的机器人🤖🤖🤖 官网?!HeroSection 经典复刻 ! ! ! (Three.js)
关注更多ai编程资讯请去aicoding专区:https://juejin.cn/aicoding0.前置条件欢迎阅读本篇文章!在深入探讨Three.js和Shader (GLSL)的进阶内容之前,确保您已经具备以下基础知识:
Three.js 基础:您需要熟悉Three.js的基本概念和使用方法,包括场景(Scene)、相机(Camera)、渲染器(Renderer)、几何体(Geometry)、材质(Material)和网格(Mesh)等核心组件。如果您还不熟悉这些内容,建议先学习Three.js的入门教程。我比较推荐外网Threejs知名博主Bruno Simon出的threejs-journey入门课程。当然如果您想让我分享我的学习路径可以在评论区留言,人数够多我会着手开始撰写学习路线。Shader 语法:本文涉及GLSL(OpenGL Shading Language)的编写,因此您需要了解GLSL的基本语法,包括顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)的编写,以及如何在Three.js中使用自定义着色器1. Hero Section 概览Hero Section 是网页设计中的一个术语,通常指页面顶部的一个大型横幅区域。但对于开发人员而言,这个概念可以更直观地理解为用户在访问网站的瞬间所感受到的视觉冲击,或者促使用户停留在该网站的关键因素。
随着AI与机器人概念在国内的又一波井喷式爆发,越来越多的企业开始步入这一科技蓝海。无论是工业机器人、服务机器人,还是智能家居设备,这些领域的技术创新正在重塑我们的生活方式和商业模式。
随着Web3D开发门槛的降低以及数字资产获取难度的减少,全新的交互方式或许很快将成为主流。然而,在展望未来的同时,我们仍需立足当下。希望这篇文章能够帮助您在接手机器人官网开发时,轻松攻克技术难关。(人要吃饭的嘛!嘿嘿)
闲话少说,让我们直接进入正题,看看今日要实现的Hero Section!(注:GIF 压缩较多,实际效果会更加惊艳。)
PC端在线预览地址:sint-copy.vercel.app/
Debug调试界面:sint-copy.vercel.app/#debug
源码地址:github.com/hexianWeb/s…
原网站地址:sint.gg/
2.基础场景搭建首先让那个我们一起来解读一下这个场景里面有什么。首先映入眼帘的是一个静态机器人,它并非完全静止,而是在页面中轻轻晃动。机器人周围的空间中,弥漫着大、中、小三种尺寸的粒子。最后,一条飞线从页面左侧缓缓滑向右侧。
那么让我来分别实现这三个场景吧!
2.1 基础场景搭建对于场景三要素以及如何搭建一个基础场景我曾在过往文章中提及,这里我不再过多的赘述。 您需要能够独立搭建以下场景(或者直接将截图上传到AI tools要求复现以下场景)
2.2机器人资源获取(不感兴趣可跳过)如果对 3D 资源的生成和获取不感兴趣可以直接前往源码文件夹下public/sint下载robot.glb
现在我们要开始着手引入静态机器人,现在的AI 3D Generation技术逐渐趋于可用范围,我有时候不光会在免费的3D 模型网站(sketch 3D, Free3D)也会在如:
MeshyAI:https:www.meshy.ai
Hyper3D:https:hyper3d.ai
混元3D:https:3d.hunyuan.tencent.com
等3D模型网站上获取到一些3D模型资源,比如Hyper3D官网上就能获得前阵子比较火的宇数机器人模型。
随后我们需要用到一个神奇的网站mixamo:https://www.mixamo.com,主要用于3D 角色动画的制作和优化。只要你上传.fbx或者.obj格式的文件,然后选择你想要的动作即可快速给你的模型添加动画,这里我们以刚刚的宇树机器人为例做一个示范。
将压缩包上传到mixamo并等待模型解压,随后模型展示无报错后点NEXT进入以下界面 参考右侧界面对模型关节进行配对。
这里有一些同学可能使用了自己的模型,
上传后进行关节配对出现 以下错误
此时可能需要导入进建模工具检查一下。通常可能是因为模型中某个部位跟其余部位并没有连接在一起。存在“断肢”,可以先用简单的网格基本体连接,后上次mixamo导入完动画再进行删除 !
这样就可以正常绑定关节节点,
然后点击右侧动作就能给模型赋予对应动画
现在选择合适的动作赋予机器人就好了。
这里我们搜索idle并赋予机器人
此时这个模型就被赋予静态动画了。是不是很简单!
随后点击with Skin,然后你就获得了一个带有动画的模型!
随后我们简单的到一些在线3D预览网站传入模型,发现模型的纹理全部丢失了!!
这个时候我们可以借助Blender恢复我们的纹理,还记得之前在Hyper 3D下载FBX时的下载的压缩包吗?解压后进入对应目录就可以看到我们丢失的纹理部分。
我们先导入FBX进入Blender,然后选择我们的机器人,随后新建一个材质。
然后为他新增一个图像纹理
随后将图像纹理传入对应模型。
2.3出现吧!机器人在成功修复纹理问题并导出.glb模型后,我们获得了一个静态资源文件robot.glb。接下来,我们将这个模型引入到 Three.js 场景中,并调整其大小和位置,使其完美融入设计。
步骤 1:加载机器人模型首先,使用GLTFLoader加载robot.glb模型文件,并调整模型的大小和位置:
this.loader =newGLTFLoader();
this.loader.load('path/to/robot.glb', (gltf) = {
this.model = gltf.scene;
// 调整模型大小
this.model.scale.set(0.02,0.02,0.02);
// 设置模型位置
this.model.position.set(0,-2.81,0);
// 将模型添加到场景中
this.scene.add(this.model);
});
步骤 2:播放机器人动画为了让机器人更加生动,我们可以播放其idle动画。以下是实现代码:
// 设置动画
this.animation = {
mixer:newTHREE.AnimationMixer(this.model),// 创建动画混合器
actions: {},// 存储动画动作
animations:this.resources.items.sintRobot.animations// 获取动画片段
};
// 为每个动画创建动作
this.animation.animations.forEach(clip={
this.animation.actions[clip.name] =this.animation.mixer.clipAction(clip);
});
// 默认播放第一个动画
if(this.animation.animations.length 0) {
this.currentAction =this.animation.actions[this.animation.animations[0].name];
this.currentAction.play();
}
步骤 3:更新动画帧在requestAnimationFrame中更新动画混合器,以确保动画流畅播放:
if(this.animation.mixer) {
this.animation.mixer.update(this.time.delta);
}
随后不断调整相机的位置和FOV(如果使用透视相机的话),来达到最佳效果!这一点需要你自己去不断调试!
差不多这样就可以了
2.4 灯光设置上述图片不难看出,只有 环境光存在展示出的模型展示效果实在称不上好的范畴。因为在 Three.js 中,灯光是提升场景高级感和真实感的关键因素之一。通过合理配置灯光,让光线和材质进行作用,可以为场景增添层次感、深度和氛围。以下是我的做法,可供你作为一个参考!
// 第一个方向光源
this.directionalLight1 =newTHREE.DirectionalLight();
this.directionalLight1.position.set(1,2,2);
this.directionalLight1.color.setRGB(0.13,0.09,0.15);
this.directionalLight1.intensity =3;
this.scene.add(this.directionalLight1);
// 第二个方向光源
this.directionalLight2 =newTHREE.DirectionalLight();
this.directionalLight2.position.set(-1,3,1);
this.directionalLight2.color.setRGB(0.45,0.36,0.22);
this.directionalLight2.intensity =4.1;
this.scene.add(this.directionalLight2);
// 环境光
this.ambientLight =newTHREE.AmbientLight();
this.ambientLight.color.setRGB(
0.309_468_922_806_742_8,
0,
0.964_686_247_893_661_2
this.ambientLight.intensity =2;
this.scene.add(this.ambientLight);
3.粒子飞舞此时的背景太过空旷了不是吗?让我们加入一些粒子来填充这个空白的空间。原作中空间就充斥的大量粒子,但仔细看所有的粒子都和机器人擦肩而过。我当前的想法是这样,为了避免粒子在进行飘动过程中出现穿过机器模型这种尴尬行为。我选择在机器人的前后两处增加平面,将他们放置在粒子“飘动”的最大距离,并在平面上进行位置采样。这样粒子不会出现在机器表面飘进飘出的尴尬情况。 当然你也可以在机器人表面采样,或者在Blender 用凸壳做出一个lowpoly风格的外壳,再或者沿着法线进行一定距离的偏移,毕竟条条大路通罗马嘛!
步骤 1:创建采样平面首先,我们创建两个平面几何体,分别放置在场景的 z = 0.5 和 z = -0.5 位置,作为粒子的采样源。记得别忘了updateMatrixWorld更新世界矩阵哦
// 创建一个平面几何体,宽高与视口大小相同
constplaneGeometry =newTHREE.PlaneGeometry(
2*this.sizes.aspect,
2,
1,
1
// 创建基础材质
constplaneMaterial =newTHREE.MeshBasicMaterial({
color:0xFF_FF_FF,
wireframe:true
// 创建平面网格
this.plane =newTHREE.Mesh(planeGeometry, planeMaterial);
// 克隆一个平面让其置于 -0.5 z
constclonedPlane =this.plane.clone();
clonedPlane.position.set(0,0,-0.5);
this.scene.add(clonedPlane);
this.plane.position.set(0,0,0.5);
// 将平面添加到场景中
this.scene.add(this.plane);
// 更新世界矩阵 防止采样到 setPosition前的坐标(重要)
this.plane.updateMatrixWorld();
clonedPlane.updateMatrixWorld();
// 创建粒子系统
this.geometryParticles =newParticleSystem([this.plane, clonedPlane]);
// 隐藏原始平面
this.plane.visible =false;
clonedPlane.visible =false;
步骤 2:采样粒子位置那么如何在平面上挑选初始粒子位置呢?我这里通过MeshSurfaceSampler对平面进行采样,确定粒子的位置、噪声值、速度和大小的代码,当然你可以刻通过创建一个具有一定顶点数量的平面,随后取positionArray中随机index的position的顶点作为初始粒子位置!我没有去研究过两种采样逻辑到底谁的效率会更高,但是方向,本次案例最多粒子数不会超过1000就能获得好的效果,所以不需要对性能优化太过苛刻
constplaneGeometry =newTHREE.PlaneGeometry(
2,
2,
1000,
1000
constrandomArray =[];
constpositions = planeGeometry.attributes.position.array;
while(index 采样粒子数量) {
constrandomIndex =Math.floor(Math.random() * positions.length /3);
constparticlePosition =newTHREE.Vector3(
positions[randomIndex *3],
positions[randomIndex *3+1],
positions[randomIndex *3+2]
index++
randomArray.push(particlePosition)
}
而笔者使用的则是MeshSurfaceSampler,代码如下:
samplePoints(positions, noises, speeds, sizes) {
consttempPosition =newTHREE.Vector3();
// 为每个源网格创建一个采样器
constsamplers =this.sourceMeshes.map(mesh=newMeshSurfaceSampler(mesh).build());
// 计算每个网格应该采样的点数
constpointsPerMesh =Math.floor(this.parameters.count /this.sourceMeshes.length);
constremainingPoints =this.parameters.count %this.sourceMeshes.length;
letcurrentIndex =0;
// 对每个网格进行采样
this.sourceMeshes.forEach((mesh, meshIndex) ={
constsampler = samplers[meshIndex];
// 计算当前网格需要采样的点数
constcurrentMeshPoints = pointsPerMesh + (meshIndex remainingPoints ?1:0);
// 对当前网格进行采样
for(leti =0; i currentMeshPoints; i++) {
sampler.sample(tempPosition);
tempPosition.applyMatrix4(mesh.matrixWorld);
positions[currentIndex *3] = tempPosition.x;
positions[currentIndex *3+1] = tempPosition.y;
positions[currentIndex *3+2] = tempPosition.z;
constnoiseValues = [0.3,0.6,0.9];
noises[currentIndex *3] = noiseValues[Math.floor(Math.random() *3)];
noises[currentIndex *3+1] = noiseValues[Math.floor(Math.random() *3)];
noises[currentIndex *3+2] = noiseValues[Math.floor(Math.random() *3)];
speeds[currentIndex] =0.5+Math.random();
sizes[currentIndex] =0.3+Math.random() *0.7;
currentIndex++;
}
}
步骤 3:应用基础材质为了快速查看粒子效果,我们可以使用PointsMaterial作为临时材质:
this.material =newTHREE.PointsMaterial({
color:'#ffffff',
size:5,
步骤4:扰动粒子现在我们创建ShaderMaterial然后通过 操控vertexShader来达到粒子扰动
// 创建着色器材质
this.material =newTHREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent:true,
depthWrite:false,
blending: THREE.AdditiveBlending,
uniforms: {
pixelRatio: {value:Math.min(window.devicePixelRatio,2) },
time: {value:0},
size: {value:this.parameters.size },
speed: {value:this.parameters.speed },
opacity: {value:this.parameters.opacity },
color: {value:newTHREE.Color(this.parameters.color) },
isOrthographic: {value: isOrthographic },
sizeAttenuation: {value:this.parameters.sizeAttenuation }
}
})
fragmentShader非常的简单,代码如下,基本上就白色 + 片元距离衰减。
void main() {
float distanceToCenter = distance(gl_PointCoord, vec2(0.5));
float strength = 0.05 / distanceToCenter - 0.1;
gl_FragColor = vec4(vec3(1.0), strength );
#include tonemapping_fragment
#include colorspace_fragment
}
vertexShader内容则偏复杂,但也仅仅用到一些三角函数,你可以引入一些经典噪声函数或者让gpt集成fbm配合您的鼠标互动来增加扰动性,但我们这里保持简单。
uniform float pixelRatio;
uniform float time;
uniform float size;
uniform float speed;
uniform float opacity;
uniform vec3 color;
uniform bool sizeAttenuation;
attribute vec3 noise;
attribute float particleSpeed;
attribute float particleSize;
varying vec3 vColor;
varying float vOpacity;
void main() {
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
// 更新粒子位置,基于时间和噪声值
modelPosition.y += sin(time * speed * particleSpeed + modelPosition.x * noise.x * 100.0) * 0.2;
modelPosition.z += cos(time * speed * particleSpeed + modelPosition.x * noise.y * 100.0) * 0.2;
modelPosition.x += cos(time * speed * particleSpeed + modelPosition.x * noise.z * 100.0) * 0.2;
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectionPostion = projectionMatrix * viewPosition;
gl_Position = projectionPostion;
// 正交相机下粒子大小处理
if (isOrthographic) {
gl_PointSize = size * particleSize * 8.0 * pixelRatio; // 正交相机下使用较小的乘数
} else {
gl_PointSize = size * particleSize * 25.0 * pixelRatio;
// 只在透视相机且启用大小衰减时应用距离衰减
if (sizeAttenuation) {
gl_PointSize *= (1.0 / - viewPosition.z);
}
}
vColor = color;
vOpacity = opacity;
}
现在看起来还不错,尤其从正面视角。
4.UI 来袭此时我们场景搭建的差不多
添加上UI看看效果如何:
虽然效果尚可,但整体显得有些平淡。为了增强视觉吸引力,我们在画布后方添加了一个径向渐变背景,为场景增添层次感和氛围感。
现将Threejs画布设置为透明
this.instance =newTHREE.WebGLRenderer({
canvas:this.canvas,
antialias:true,
alpha:true// 启用透明背景
再使用
this.instance.setClearColor(0x00_00_00,0);// 设置透明背景
最后使用 CSS 为画布背后的元素设置径向渐变背景:
mainclass="flex h-full bg-[radial-gradient(circle_at_50%_50%,rgb(27,27,47),rgb(15,15,31)_50%,rgb(0,0,0)_100%)] text-color"
!-- UI部分 --
canvasid="canvas"class="z-[0] h-full w-full"/canvas
/main在复刻这个网站时,我一开始的设想是利用体积光来实现背景的渐变效果。然而,查看源网站后发现,实际上背景是通过普通的页面元素(CSS 渐变)实现的。这让我意识到,虽然我们掌握了 Three.js 这样的 3D 技术,但并不意味着所有视觉效果都需要依赖 3D 技术来实现。Three.js 是一个强大的 3D 渲染框架,但它并不是解决所有问题的唯一工具。对于一些简单的视觉效果(如背景渐变),使用 CSS 或其他前端技术可能会更加高效和灵活。我们需要根据实际需求选择合适的技术,避免过度依赖 3D 技术,从而保持代码的简洁性和性能。
5.视差效果(Parallax Effect)如果场景中只有静态的机器人,互动性会显得不足。为此,我们引入视差效果,利用用户的鼠标移动来增强用户体验。
步骤 1:获取归一化鼠标坐标将鼠标位置标准化到[-1, 1]范围:
window.addEventListener('mousemove', (event) = {
// 将鼠标位置标准化到 [-1, 1] 范围
this.normalizedMouse.x = (event.clientX /window.innerWidth) *2-1;
this.normalizedMouse.y = -(event.clientY /window.innerHeight) *2+1;
步骤2:平滑插值实现视差效果在大多数视差方案中,通常是直接更新相机的位置来实现效果。然而,这里我们采用了一种更优雅的方式:通过更新相机的lookAt目标点,并结合平滑插值,实现自然的视差效果。这种方式不仅更加灵活,还能避免相机位置突变带来的不自然感。
const{ x, y } =this.normalizedMouse;
// 创建视点目标,随鼠标移动但幅度更小
constlookAtTarget =newTHREE.Vector3(
this.scene.position.x + x *this.parallax.factor *0.25,
this.scene.position.y + y *this.parallax.factor *0.15,
this.scene.position.z
);
// 如果是第一次更新,初始化当前目标点
if(!this.currentLookAtTarget) {
this.currentLookAtTarget = lookAtTarget.clone();
}
// 平滑插值过渡到新的目标点
this.currentLookAtTarget.lerp(lookAtTarget,0.07);
// 相机平滑过渡看向这个动态目标点
this.camera.lookAt(this.currentLookAtTarget);
通过以上实现,我们得到了一个动态的视差效果,增强了用户的互动体验:
6.流行飞线在 Three.js 中,飞线效果是一种常见的视觉效果,通常用于展示路径、连接关系或动态轨迹。实现飞线效果的核心思路是:构造曲线,对曲线上的点进行采样,然后利用这些点创建网格基本体。我们可以借助现成的库(如meshline),或者使用 Three.js 自带的几何体(如TubeGeometry或Line)。
关键点:利用透明度属性实现渐变效果1. 创建曲线几何体首先,我们使用BufferGeometry从曲线点集创建几何体:
this.lineGeometry =newTHREE.BufferGeometry().setFromPoints(this.curvePoints);
this.curvePoints是曲线上的点集,通常通过贝塞尔曲线或其他曲线生成算法生成。
2. 为顶点添加透明度属性为了实现飞线的渐变透明效果,我们需要为每个顶点添加一个alpha属性。这个属性将根据顶点在曲线上的位置动态计算透明度值:
constalphas =newFloat32Array(this.curvePoints.length);
for(letindex =0; index this.curvePoints.length; index++) {
// 计算当前点在曲线上的位置 (0-1)
constt = index / (this.curvePoints.length -1);
// 使用正弦函数实现平滑的透明度过渡
alphas[index] =Math.sin(t *Math.PI) *this.parameters.lineOpacity;
}
this.lineGeometry.setAttribute('alpha',newTHREE.BufferAttribute(alphas,1));
3. 创建着色器材质为了实现透明度的动态控制,我们使用ShaderMaterial,并在顶点着色器和片段着色器中处理透明度属性:
this.lineMaterial = new THREE.ShaderMaterial({
vertexShader: `
attribute float alpha;
varying float vAlpha;
void main() {
vAlpha = alpha;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform vec3 color;
varying float vAlpha;
void main() {
gl_FragColor = vec4(color, vAlpha);
#include tonemapping_fragment
#include colorspace_fragment
}
`,
uniforms: {
color: { value: new THREE.Color(this.parameters.lineColor) }
},
transparent: true, //启用透明效果
depthWrite: false,
blending: THREE.AdditiveBlending //使用叠加混合模式,增强飞线的视觉效果。
});
其余的想飞线中Point如何更新位置我就不过多介绍了! 最后的效果如图:
7.最后一些话技术的未来与前端迁移随着 AI 技术的快速发展,各类技术的门槛正在大幅降低,以往被视为高门槛的3D技术也不例外。与此同时,过去困扰开发者的数字资产构建成本问题,也正在被最新的3D generation技术所攻克。这意味着,在不久的将来,前端开发将迎来一次技术迁移,开发者需要掌握更新颖的交互方式和更出色的视觉效果。
为什么选择Three.js?Three.js作为最流行的WebGL库之一,不仅简化了三维图形的开发流程,还提供了丰富的功能和强大的扩展性。无论是创建复杂的 3D 场景,还是实现炫酷的视觉效果,Three.js都能帮助开发者快速实现目标。
本专栏的愿景本专栏的愿景是通过分享Three.js的中高级应用和实战技巧,帮助开发者更好地将3D技术应用到实际项目中,打造令人印象深刻的Hero Section。我们希望通过本专栏的内容,能够激发开发者的创造力,推动Web3D技术的普及和应用。
点击关注公众号,“技术干货” 及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线