
目录

最近,在github上面找到了一个不错的技术介绍网站,主要使用html+css+js原生三件套写的。我在此基础之上利用three.js加了一点3D元素在里面,让这个网站看起来更炫酷。
改的时候,感觉原生还是比不上框架来的方便,后续有时间我会抽离一个vue组件的版本。
在线访问:个人简历
国内镜像:InsCode
github源码:个人简历

对于模型来说,我们可以自己慢慢的建一个模型,但是很费时间。不想自己建模的话,自己可以直接在网上找到一些资源,导入到blender进行相关修改。
以下是一些好用的3D资源网站:
这些网站提供了各种类型的3D模型和纹理,包括游戏资源、建筑物、人物、动物、车辆等。有些网站提供免费的资源,而有些网站则需要付费才能下载高质量的资源。希望这些网站可以帮助您找到所需的3D资源。

修改好想要的模型之后,由于网页端要追求性能,所以我们要对模型进行压缩(一般可以压缩到原来的1/10)。压缩后使用three.js特定的DRACOLoader解压文件。
虽然我们可能会觉得使用Draco压缩是个双赢局面,但实际上并非如此。
确实它会让几何体更轻量,但首先要使用的时候必须加载DracoLoader类和解码器。其次,我们计算机解码一个压缩文件需要时间和资源,这可能会导致页面打开时有短暂冻结,即便我们使用了worker和WebAssembly。
因此我们必须根据实际来决定使用什么解决方案。如果一个模型具有100kb的几何体,那么则不需要Draco压缩,但是如果我们有MB大小的模型要加载,并且不关心在开始运行时有些许页面冻结,那么便可能需要用到Draco压缩。
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'
const canvas = document.querySelector('.webgl')
const rect = canvas.getBoundingClientRect();
const sizes = {
width: rect.width,
height: rect.height
}
let scene, camera, renderer
let controls, gui
let init = () => {
//场景
scene = new THREE.Scene()
// 相机
camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 1000)
camera.position.set(-2.85, 4.37, 2.49)
camera.lookAt(scene.position)
// 渲染器
renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, alpha: true })
renderer.setSize(sizes.width, sizes.height)
// renderer.setClearColor('lightsalmon', 0.5)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.useLegacyLights = true
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
// 真实性物理渲染
renderer.physicallyCorrectLights = true
renderer.outputEncoding = THREE.sRGBEncoding
renderer.toneMapping = THREE.ACESFilmicToneMapping
//控制器
controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = false
controls.enableZoom = false
controls.enablePan = false
controls.minPolarAngle = Math.PI / 6
controls.maxPolarAngle = Math.PI / 3
controls.minAzimuthAngle = -Math.PI / 6
controls.maxAzimuthAngle = Math.PI / 2
}
//加载模型
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('./assets/js/three/draco/')
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
let loadModel = () => {
gltfLoader.load('./assets/model/office.glb', (gltf) => {
const office = gltf.scene
office.rotation.y = Math.PI / 2
office.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.castShadow = true
child.receiveShadow = true
if (child.name === 'mac-screen') {
screen = child
} else if (child.name === 'Chair') {
chair = child
} else if (child.name === 'lamp-top') {
// console.log(child.position);
}
}
})
setScreenVideo()
setChairRotate()
scene.add(office)
})
}
//渲染
let animate = () => {
controls.update()
renderer.render(scene, camera)
requestAnimationFrame(animate)
}
init()
loadModel()
animate()
three.js的场景基础包括:
在 three.js 中,灯光用来模拟现实中的光照条件,可以让场景中的物体更加真实地呈现。灯光可以为物体提供不同的光照效果,如明亮的阳光、柔和的夜灯、闪烁的蜡烛等。
three.js 中的灯光有以下几种类型:
通过设置不同的灯光类型、颜色、强度、位置等属性,可以在 three.js 中模拟出各种不同的光照效果,使得场景中的物体看起来更加真实。

本例,我们要尽可能的模拟灯光效果,所以对于阴影也要考虑在内。对于阴影来说,会先用一个特殊的相机(称为阴影相机)从光源的位置来渲染场景,并将渲染结果保存到一个深度纹理中。这个深度纹理记录了场景中每个像素距离光源的距离,也即是场景中哪些物体遮挡了该像素。
当渲染场景时,系统会根据阴影相机生成的深度纹理来计算每个像素是否在阴影中。具体来说,对于每个像素,系统会根据它在阴影相机中的位置、深度信息和光源的位置和方向来计算它是否被遮挡。如果该像素被遮挡,则它的颜色值将被调整以模拟出阴影效果,否则它的颜色值不变。
在 three.js 中,有三种灯光类型可以被渲染,分别是平行光(DirectionalLight)、点光源(PointLight)和聚光灯(SpotLight)。
这些灯光可以被添加到场景中,并通过设置它们的属性来控制它们的位置、颜色、强度、范围等参数,从而实现不同的光照效果。
这些灯光可以被渲染到不同类型的相机中,具体如下:

使用 Three.js 的 VideoTexture 可以将视频作为纹理应用到 3D 对象上,实现很酷的效果。首先,在一开始加载模型的时候要把需要贴图的对象找到传入函数,第二步添加视频纹理。
// 屏幕播放音频
let screen = null
let setScreenVideo = () => {
const video = document.createElement('video')
video.src = './assets/video/kda.mp4'
video.muted = true
video.playsInline = true
video.autoplay = true
video.loop = true
video.play()
const videoTexture = new THREE.VideoTexture(video)
// 添加真实性渲染,后面改
screen.material = new THREE.MeshBasicMaterial({
map: videoTexture,
})
}
这里对于过渡的效果统一使用gsap完成,GSAP是一个JavaScript动画库,用于创建高性能、流畅的动画效果。
文章参考:GSAP的香,我来带你get~时入1k算少的!!
// 添加椅子旋转
let chair = null
let setChairRotate = () => {
gsap.to(chair.rotation, {
y: Math.PI / 4,
duration: 10,
ease: 'power1.inOut',
repeat: -1,
yoyo: true,
})
}
当我们切换界面的明暗时,模型理应跟着变换。所以模型要响应变化,一开始我们为灯光添加**debug-gui,**可以时刻调试灯光。


let debugUI = () => {
//gui控制器
gui = new GUI()
let folder1 = gui.addFolder('环境光')
folder1.addColor(ambientLight, 'color')
folder1.add(ambientLight, 'intensity', 0, 10, 0.01)
folder1.close()
let folder2 = gui.addFolder('太阳光')
folder2.add(sunLight.position, 'x', -5, 5, 0.01)
folder2.add(sunLight.position, 'y', -5, 5, 0.01)
folder2.add(sunLight.position, 'z', -5, 5, 0.01)
folder2.addColor(sunLight, 'color')
folder2.add(sunLight, 'intensity', 0, 10, 0.01)
folder2.close()
let folder3 = gui.addFolder('台灯')
folder3.add(spotLight.position, 'x', -5, 5, 0.01)
folder3.add(spotLight.position, 'y', -5, 5, 0.01)
folder3.add(spotLight.position, 'z', -5, 5, 0.01)
folder3.addColor(spotLight, 'color')
folder3.add(spotLight, 'intensity', 0, 10, 0.01)
folder3.close()
let folder4 = gui.addFolder('相机')
folder4.add(camera.position, 'x', -10, 10, 0.01)
folder4.add(camera.position, 'y', -10, 10, 0.01)
folder4.add(camera.position, 'z', -10, 10, 0.01)
// folder4
folder4.add(params, 'showCmeraInfo')
// gui.close()
}
let gsapTheme = () => {
if (getCurrentTheme() === 'light') {
gsap.to(ambientLight, { intensity: 2.5 })
gsap.to(ambientLight.color, {
...ambientLightColor,
duration: durationTime
})
gsap.to(sunLight, { intensity: 2.5, duration: durationTime })
gsap.to(spotLight, { intensity: 0, duration: durationTime })
} else {
gsap.to(ambientLight, { intensity: 3.8, duration: durationTime })
gsap.to(ambientLight.color, {
...ambientDarkColor,
duration: durationTime
})
gsap.to(sunLight, { intensity: 0, duration: durationTime })
gsap.to(spotLight, { intensity: 3.5, duration: durationTime })
}
}
这里的技术并没有手动贴上uv贴图,但是作为一项基本的理论,还是要掌握一下。
UV贴图是将纹理图像映射到三维物体表面上的一种技术,它依赖于UV坐标系来确定纹理图像在物体表面上的位置。其中,U和V分别表示纹理图像在水平和垂直方向上的坐标,取值通常是0到1之间(水平方向的第U个像素/图片宽度,垂直方向的第V个像素/图片高度)。
对于某些特殊的3D贴图技术,可能会使用到W坐标,但在一般情况下,UV坐标系就足够描述纹理映射了。展UV是将物体表面展开成二维平面,以便进行纹理贴图,这一过程也需要使用到UV坐标系来确定各个点在展开平面上的位置。
在three.js中,physicallyCorrectLights是一个属性,用于指定渲染器是否使用物理正确的光照模型。当该属性设置为true时,渲染器会使用基于物理的光照模型,以便更准确地模拟真实世界中的光照效果。
renderer.physicallyCorrectLights = true
尽管目前看起来效果还行,但在颜色方面还是有点欠缺需要下点工夫。这是因为WebGLRenderer 属性的问题。


outputEncoding属性控制输出渲染编码。默认情况下,outputEncoding的值为THREE.LinearEncoding,看起来还行但是不真实,建议将值改为THREE.sRGBEncoding
色调映射Tone mapping旨在将超高的动态范围HDR转换到我们日常显示的屏幕上的低动态范围LDR的过程。
说明一下HDR和LDR(摘自知乎LDR和HDR):
那为了改变色调映射tone mapping,则要更新WebGLRenderer上的toneMapping属性,有以下这些值
THREE.NoToneMapping (默认)
THREE.LinearToneMapping
THREE.ReinhardToneMapping
THREE.CineonToneMapping
THREE.ACESFilmicToneMapping
尽管我们的贴图不是HDR,但使用tone mapping可以塑造更真实的效果。
在计算曲面是否处于阴影中时,由于精度原因,阴影失真可能会发生在平滑和平坦表面上。
而现在在汉堡包上发生的是汉堡包在它自己的表面上投射了阴影。因此我们必须调整灯光阴影shadow的“偏移bias”和“法线偏移normalBias”属性来修复此阴影失真。
让我们计算MRI范围内的类别:defcount_classesObjectSpace.count_objects[:T_CLASS]endk=count_classes用类方法定义类:classAdefself.foonilendend然后运行:putscount_classes-k#=>3请解释一下,为什么是三个? 最佳答案 查看MRI代码,每次你创建一个Class时,在Ruby中它是Class类型的对象,ruby会自动为这个新类创建“元类”类,这是另一个单例类型的Class对象。C函数调用(class.c)是:rb_define
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
在Ruby中可以使用哪些替代方法来ping一个ip地址?标准库“ping”库的功能似乎非常有限。我对在这里滚动我自己的代码不感兴趣。有没有好的gem?我应该接受它并忍受它吗?(我在Linux上使用Ruby1.8.6编写代码) 最佳答案 net-ping值得一看。它允许TCPping(如标准rubyping),但也允许UDP、HTTP和ICMPping。ICMPping需要root权限,但其他则不需要。 关于ruby-Pingruby网站?,我们在StackOverflow上找到一个类
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定
我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项