Эта статья участвовала в мероприятии Haowen Convocation Order, щелкните, чтобы просмотреть:Двойные заявки на внутреннюю и внешнюю стороны, призовой фонд в 20 000 юаней ждет вас, чтобы бросить вызов!
предисловие
Наконец-то выходные, предыдущие статьи знакомили вас с 2d, canvas и svg. В июле я планирую опубликовать 3 статьи по 10 000 символов, чтобы показать вам, как систематически изучать 3 способа визуального выражения: svg, canvas и webgl. Итак, это первая статья о 3d. Что вы узнаете после прочтения этой статьи
- Существует простое понимание фреймворка three.js, и вы можете приступать к работе.
- Изучение Raycaster в 3 в основном использует мышь, чтобы определить, какой объект выбран в данный момент.
- Я использую простой пример, чтобы показать вам простой случай визуализации Земли с тремя.
Выбор 3д фреймворка - three.js
1. Почему стоит выбрать three.js
официальный сайтThreejsВведение очень простое: "Javascript 3D-библиотека".openGLЭто кроссплатформенный стандарт трехмерного/двухмерного рисования,WebGLявляетсяopenGLРеализация в браузере. Разработчики веб-интерфейса могут напрямую использоватьWebGLинтерфейс для программирования, ноWebGLЭто просто очень простой API для рисования, который требует от программистов больших математических знаний и навыков рисования для выполнения задач 3D-программирования, а объем кода огромен.ThreejsправильноWebGLОн был инкапсулирован, поэтому разработчики внешнего интерфейса могут легко разрабатывать веб-3D без необходимости овладевать большим количеством математических знаний и знаний о рисовании, что снижает порог и значительно повышает эффективность. Подводя итог в одном предложении: вы не разбираетесь в компьютерной графике, пока вы понимаете некоторые основные концепции three.js, вы можете.
Основы Threejs — сцена
Определяется следующим образом:
Сцены: Это трехмерное пространство, вместилище для всех предметов, а сцену можно представить как пустую комнату.Далее мы поместим объекты, камеры, источники света и т. д., которые будут представлены в комнате.
В коде это выражается следующим образом:
const scene = new THREE.Scene();
Вы можете представить его как комнату, а затем добавить к нему какие-то объекты, добавить куб, добавить прямоугольник, что угодно. На самом деле, связь между всеми тремя.js представляет собойдревовидная структура.
Базовый элемент Threejs - камера 📷
камера: Threejs должен добавить в сцену камеру.Камера используется для определения положения, направления и угла.То, что видит камера, это то, что мы всегда видим на экране. Во время работы программы вы можете настроить положение, ориентацию и угол наклона камеры.
В three.js есть два типа камер: ортогональная камера📷 и перспективная камера📷. Далее я познакомлю вас с ними один за другим, но чтобы понять камеру, вы должны сначала понять концепцию - усеченный обзор
перспективная камера
Усеченная часть — это пространство, видимое в камеру, и выглядит как пирамида с усеченной вершиной. Усеченный обзор окружен 6 плоскостями отсечения, а 4 стороны, составляющие усеченный обзор, называются левым верхним, правым нижним и соответствуют четырем границам экрана соответственно. Чтобы объект не был слишком близко к камере, установите ближний участок, а чтобы объект не был слишком далеко от камеры, чтобы его можно было увидеть, установите дальний участок.
oc — отмеченное положение камеры в ближней и дальней плоскостях. Как видно из рисунка, предметы внутри шести граней, составленных из призмы, видны. Факторы, влияющие на размер перспективной камеры:
- Угол обзора усеченной камеры по вертикали такой, как на картинке.a
- Ближний торец усеченной камеры - тот, что на картинке.near plane
- Дальний конец усеченного конуса камеры - тот, что на картинке.far plane
- Соотношение сторон усеченной камерыУказывает отношение ширины к высоте выходного изображения.
Соответствующая камера в трех:
const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
Самая большая особенность перспективной камеры: она соответствует характеристикам наших человеческих глаз, чтобы наблюдать за вещами, как крупными, так и очень маленькими.
Принцип, лежащий в основе реализации почти большого и далекого малого, заключается в том, что камера будет иметь проекционную матрицу: то, что делает проекционная матрица, очень просто, а именно преобразование усеченной пирамиды обзора в куб. Поэтому точки в дальнем участке будут уменьшаться, а в ближнем — увеличиваться.
Орфографическая камера
Характеристика орфографической камеры состоит в том, что усеченный конус представляет собой куб.
В этом режиме проецирования размер объекта в окончательном визуализированном изображении остается неизменным независимо от того, насколько далеко или близко объект находится от камеры.
Это полезно для рендеринга 2D-сцен или элементов пользовательского интерфейса. Как показано на рисунке:
Код в тройке выглядит следующим образом:
const camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 );
После разговора о камере мы представим состав графики.
Основы Threejs — Сетка
В компьютерном мире дуга соединена конечным числом отрезков, образованных конечным числом точек. Чем больше линейных сегментов, тем короче длина, и когда вы достигаете точки, в которой невозможно определить, что это линейный сегмент, появляется плавная дуга. Трехмерная модель компьютера аналогична. Просто отрезок становится плоскостью, которая обычно описывается сеткой, состоящей из треугольников. Мы называем эту модель моделью Mesh.
Дуга получается из нескольких отрезков, чем больше отрезков, тем ближе к дуге. Для тех, кто не понял, можете прочитать мою статью:Интервьюер спросил меня, умею ли я рисовать анимацию фейерверка 🎇вКривые Безье можно снабдить небольшими отрезками..
Вся графика, используемая в three.js, триангулируется перед рендерингом, а затем передается в webgl для рендеринга.
Threejs предоставляет некоторые общие геометрические формы, в том числе трехмерные и двумерные, трехмерные, такие как прямоугольный параллелепипед, сфера и т. д., двухмерные, такие как прямоугольник, круг и т. д. Если формы по умолчанию не соответствуют требованиям, вы можете также настраивайте их, определяя вершины. Пользовательские геометрические фигуры можно рисовать, соединяя линии между вершинами и вершинами. Более сложные модели также можно моделировать с помощью программного обеспечения для моделирования, а затем импортировать.
2d
3d
С формой визуализированная графика может не иметь красивого вида, и тогда выходит материал. Сетка фактически состоит из двух частей:
Материал + геометрия — это сетка. Threejs предоставляет концентрированный и репрезентативный материал. Два часто используемых материала — диффузное отражение и зеркальное отражение. Вы также можете импортировать внешние изображения и вставлять их на поверхность объектов, чтобы они становились текстурными картами. Если интересно, можете попробовать сами. Как показано на рисунке:
Основные элементы Threejs — освещение
Если света нет, камера ничего не видит, поэтому нужно добавить в сцену источники света.Чтобы быть ближе к реальному миру, Threejs поддерживает симуляцию разных источников света и показ разных световых эффектов, например точечных источников света , параллельные огни, точечные огни, окружающие огни и т. д.
Окружающий свет
Окружающий свет будет равномерно освещать все объекты в сцене, окружающий свет нельзя использовать для отбрасывания теней, поскольку он не имеет направления.
const light = new THREE.AmbientLight( 0x404040 ); // soft white light
Направленный свет
Параллельный свет — это свет, излучаемый в определенном направлении. Этот свет ведет себя так, как будто он бесконечно далеко, и все исходящие от него лучи параллельны. Параллельный свет часто используется для имитации эффекта солнечного света; солнце находится достаточно далеко, чтобы мы могли думать, что положение солнца бесконечно, поэтому мы думаем, что лучи, исходящие от солнца, также параллельны.
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
Точечный свет
Источник света, излучающий из точки во всех направлениях. Типичным примером является имитация света, излучаемого лампочкой.
const light = new THREE.PointLight( 0xff0000, 1, 100 );
Прожектор
Лучи испускаются из точки в одном направлении, и размер конуса лучей увеличивается по мере удаления луча.
const spotLight = new THREE.SpotLight( 0xffffff );
Есть и другие огни, заинтересованные друзья могут посетить официальный сайт three.js, чтобы проверить.
Основы Threejs — рендерер
Рендерер предназначен для рендеринга источников света, камер и мешей в вашей сцене.
let renderer = new THREE.WebGLRenderer({
antialias: true, // true/false表示是否开启反锯齿
alpha: true, // true/false 表示是否可以设置背景色透明
precision: 'highp', // highp/mediump/lowp 表示着色精度选择
premultipliedAlpha: false, // true/false 表示是否可以设置像素深度(用来度量图像的分率)
preserveDrawingBuffer: true, // true/false 表示是否保存绘图缓冲
maxLights: 3, // 最大灯光数
stencil: false // false/true 表示是否使用模板字体或图案
Я представил некоторые общие элементы three.js, а затем перейду к теме Как в three.js реализуется визуальная карта?
Визуальная карта — реализация three.js
Строительство сцены
Мне все равно, является карта картой или нет, эти формы карты должны быть размещены в сцене. Следуйте за мной шаг за шагом, чтобы построить сцену. Конструкцией сцены является камера и рендерер. Я использую класс карты для представления кода следующим образом:
class chinaMap {
constructor() {
this.init()
}
init() {
// 第一步新建一个场景
this.scene = new THREE.Scene()
this.setCamera()
this.setRenderer()
}
// 新建透视相机
setCamera() {
// 第二参数就是 长度和宽度比 默认采用浏览器 返回以像素为单位的窗口的内部宽度和高度
this.camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
}
// 设置渲染器
setRenderer() {
this.renderer = new THREE.WebGLRenderer()
// 设置画布的大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
//这里 其实就是canvas 画布 renderer.domElement
document.body.appendChild(this.renderer.domElement)
}
// 设置环境光
setLight() {
this.ambientLight = new THREE.AmbientLight(0xffffff) // 环境光
this.scene.add(ambientLight)
}
}
Я объяснил их один за другим выше.Теперь, когда сцена доступна и освещение доступно, давайте посмотрим.
В сцене нет ничего темного.Далее давайте добавим параллелепипед и вызовем метод рендеринга рендерера. код показывает, как показано ниже:
init() {
//第一步新建一个场景
this.scene = new THREE.Scene()
this.setCamera()
this.setRenderer()
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const cube = new THREE.Mesh(geometry, material)
this.scene.add(cube)
this.render()
}
//render 方法
render() {
this.renderer.render(this.scene, this.camera)
}
В соответствии с вышеизложенным, у вас будет страница или ясно, почему?
По умолчанию, когда мы вызываем scene.add(), объект будет добавлен с координатами (0,0,0). Но сделает так, чтобы камера и куб были рядом друг с другом. Чтобы этого не произошло, нам нужно просто немного сдвинуть камеру наружу.
Так что просто отрегулируйте свойство положения камеры по оси Z, чтобы получить изображение.
// 新建透视相机
setCamera() {
// 第二参数就是 长度和宽度比 默认采用浏览器 返回以像素为单位的窗口的内部宽度和高度
this.camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
this.camera.position.z = 5
}
Картина выглядит следующим образом:
В это время некоторые студенты спросят, гм, это уже давно не то же самое, что холст 2d, в чем разница? Не видите ощущения трехмерности? OK Далее я заставлю куб двигаться. На самом деле он продолжает вызывать нашу функцию рендеринга. Мы используем reqestanimationframe. Старайтесь не использовать setInterval, там очень простая оптимизация.
requestAnimationFrameЕсть много преимуществ. Возможно, наиболее важно то, что он приостанавливается, когда пользователь переключается на другую вкладку, поэтому он не тратит впустую драгоценные ресурсы процессора пользователя и не расходует заряд батареи.
То, что я здесь делаю, заставляет x,y куба оставаться +0,1. Сначала посмотрите на код:
render() {
this.renderer.render(this.scene, this.camera)
}
animate() {
requestAnimationFrame(this.animate.bind(this))
this.cube.rotation.x += 0.01
this.cube.rotation.y += 0.01
this.render()
}
Схема эффекта выглядит следующим образом:
У вас есть такое чувство? Я покажу вам, как начать работу с three.js, вращая простейший куб. Если вы видите это и считаете, что это полезно для вас, я надеюсь, что вы можете поставить мне лайк 👍, спасибо, старые утюги! Следующая формальная карта нуждается в анализе.
Получение данных карты
На самом деле, самое главное — получить данные карты, вы можете узнать об openStreetMap
Это бесплатная редактируемая карта мира. OpenStreetMap позволяет вам просматривать, редактировать или использовать географические данные со всего мира, чтобы помочь вам.
Здесь я самостоятельно скопировал данные json карты Китая, код такой:
// 加载地图数据
loadMapData() {
const loader = new THREE.FileLoader()
loader.load('../json/china.json', (data) => {
const jsondata = JSON.parse(JSON.stringify(data))
})
}
Позвольте мне сначала показать вам формат данных json.
![image-20210703154646470](/Users/wangzhengfei/Library/Application Support/typora-user-images/image-20210703154646470.png)
На самом деле, главное, чтобы внизу были координаты широты и долготы. Собственно, это то, что меня волнует. Только с помощью точек можно сгенерировать линию, и, наконец, сгенерировать плоскость. Вот точка познания,Преобразование проекции Меркатора. Преобразование проекции Меркатора может преобразовать наши координаты широты и долготы в двумерные координаты нашей соответствующей плоскости. Вы можете прочитать эту статью о настроении этого процесса вывода:портал
Здесь я использую фреймворк визуализации напрямую —d3Он имеет собственное встроенное преобразование проекции Меркатора.
// 墨卡托投影转换
const projection = d3
.geoMercator()
.center([104.0, 37.5])
.scale(80)
.translate([0, 0])
Поскольку в Китае много провинций, каждая провинция соответствует Object3d.
Object3d — это базовый класс всех three.js, предоставляющий ряд свойств и методов для управления объектами в трехмерном пространстве. Объекты можно комбинировать с помощью метода .add(object), который добавляет объект как дочерний объект.
Весь Китай здесь — это большой Object3d, каждая провинция — это Object3d, а провинция связана с Китаем. Тогда Карта Китая висит под Object3d сцены. Очевидно, three.js — это очень типичная древовидная структура данных, я нарисовал картинку на всеобщее обозрение.
В сцене Сцены висит куча вещей, одна из которых Карта, вся карта, а потом каждая провинция, каждая провинция состоит из Меша и lLine.
Давайте посмотрим на код:
generateGeometry(jsondata) {
// 初始化一个地图对象
this.map = new THREE.Object3D()
// 墨卡托投影转换
const projection = d3
.geoMercator()
.center([104.0, 37.5])
.scale(80)
.translate([0, 0])
jsondata.features.forEach((elem) => {
// 定一个省份3D对象
const province = new THREE.Object3D()
this.map.add(province)
})
this.scene.add(this.map)
}
Увидев это, я думаю, у вас не возникнет проблем, наша общая структура была установлена, и затем мы войдем в основную ссылку.
Создание геометрии карты
Здесь используются Three.shape() и THREE.ExtrudeGeometry() Зачем использовать это? Позвольте мне объяснить вам,Прежде всего, нижний индекс контура каждой провинции представляет собой двумерную координату, но мы хотим сгенерировать куб, shape() может определять двумерную плоскость формы. Его можно использовать с ExtrudeGeometry для получения точек или треугольников.
код показывает, как показано ниже:
// 每个的 坐标 数组
const coordinates = elem.geometry.coordinates
// 循环坐标数组
coordinates.forEach((multiPolygon) => {
multiPolygon.forEach((polygon) => {
const shape = new THREE.Shape()
const lineMaterial = new THREE.LineBasicMaterial({
color: 'white',
})
const lineGeometry = new THREE.Geometry()
for (let i = 0; i < polygon.length; i++) {
const [x, y] = projection(polygon[i])
if (i === 0) {
shape.moveTo(x, -y)
}
shape.lineTo(x, -y)
lineGeometry.vertices.push(new THREE.Vector3(x, -y, 4.01))
}
const extrudeSettings = {
depth: 10,
bevelEnabled: false,
}
const geometry = new THREE.ExtrudeGeometry(
shape,
extrudeSettings
)
const material = new THREE.MeshBasicMaterial({
color: '#2defff',
transparent: true,
opacity: 0.6,
})
const material1 = new THREE.MeshBasicMaterial({
color: '#3480C4',
transparent: true,
opacity: 0.5,
})
const mesh = new THREE.Mesh(geometry, [material, material1])
const line = new THREE.Line(lineGeometry, lineMaterial)
province.add(mesh)
province.add(line)
})
})
Обход первой точки точно такой же, как отрисовка canvas2d, перемещение начальной точки, а затем рисование линии позже, чтобы нарисовать контур. Затем мы можем установить здесь глубину экструзии, а затем следующим шагом будет установка материала. lineGeometry фактически соответствует краю контура. Давайте посмотрим на картинку:
Вспомогательный вид камеры
Чтобы было удобно настраивать положение камеры, я добавил вспомогательный вид cameraHelper. Затем вы снова смотрите на экран и появляется крестик, а затем мы можем постоянно регулировать положение камеры, чтобы наша карта находилась в центре экрана:
addHelper() {
const helper = new THREE.CameraHelper(this.camera)
this.scene.add(helper)
}
Постоянно корректируйте с помощью вспомогательного вида:
Хахахаха, а он так пахнет? На данный момент наша карта Китая реализована в центре холста.
Добавить контроллер взаимодействия
Теперь карта сгенерирована, но взаимодействие с пользователем относительно плохое.Здесь мы вводим три OrbitControls, вы можете использовать мышь для поворота экрана по желанию, и вы можете видеть каждую часть куба. Но этого метода нет в пакете three, и его нужно вводить отдельным файлом, код такой:
setController() {
this.controller = new THREE.OrbitControls(
this.camera,
document.getElementById('canvas')
)
}
Посмотрим на эффект:
трассировка лучей
Но я все равно собой недоволен.Как узнать на какую провинцию я кликнул?Хорошо,введу в нашу тройку очень важный класс,Рейкастер.
Этот класс используется для выполненияraycasting(рейкастинг). Raycasting используется для выбора мыши (выяснения того, что мышь переместила в трехмерном пространстве).
Мы можем отслеживать событие onmouseMove холста, и тогда мы можем знать, какой меш выбран текущей мышью. Но перед этим давайте добавим атрибут к каждому объекту провинции, чтобы указать, к какой провинции он принадлежит.
// 将省份的属性 加进来
province.properties = elem.properties
Хорошо, мы можем ввести трассировку лучей следующим образом:
setRaycaster() {
this.raycaster = new THREE.Raycaster()
this.mouse = new THREE.Vector2()
const onMouseMove = (event) => {
// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
}
window.addEventListener('mousemove', onMouseMove, false)
}
animate() {
requestAnimationFrame(this.animate.bind(this))
// 通过摄像机和鼠标位置更新射线
this.raycaster.setFromCamera(this.mouse, this.camera)
this.render()
}
Поскольку мы постоянно перемещаемся по холсту, нам нужны постоянные положения лучей. Теперь, когда у нас есть лучи, нам нужно все в сцене для сравнения.rayCaster также предоставляет следующие коды методов:
const intersects = this.raycaster.intersectObjects(
this.scene.children, // 场景的
true // 若为true,则同时也会检测所有物体的后代。否则将只会检测对象本身的相交部分
)
Есть много пересечений, полученных этим пересечением, но мы выбираем только одно из них, то есть количество материалов объекта равно двум, потому что мы использовали два материала для меша выше.
const mesh = new THREE.Mesh(geometry, [material, material1])
Итак, код фильтра выглядит следующим образом
animate() {
requestAnimationFrame(this.animate.bind(this))
// 通过摄像机和鼠标位置更新射线
this.raycaster.setFromCamera(this.mouse, this.camera)
// 算出射线 与当场景相交的对象有那些
const intersects = this.raycaster.intersectObjects(
this.scene.children,
true
)
const find = intersects.find(
(item) => item.object.material && item.object.material.length === 2
)
this.render()
}
Как узнать, нашел ли я его?Мы делаем поверхность найденного меша серой, но это вызовет проблему.Когда мы снова двигаем мышь, мы должны восстановить ему последнюю текстуру.
код показывает, как показано ниже:
animate() {
requestAnimationFrame(this.animate.bind(this))
// 通过摄像机和鼠标位置更新射线
this.raycaster.setFromCamera(this.mouse, this.camera)
// 算出射线 与当场景相交的对象有那些
const intersects = this.raycaster.intersectObjects(
this.scene.children,
true
)
// 恢复上一次清空的
if (this.lastPick) {
this.lastPick.object.material[0].color.set('#2defff')
this.lastPick.object.material[1].color.set('#3480C4')
}
this.lastPick = null
this.lastPick = intersects.find(
(item) => item.object.material && item.object.material.length === 2
)
if (this.lastPick) {
this.lastPick.object.material[0].color.set(0xff0000)
this.lastPick.object.material[1].color.set(0xff0000)
}
this.render()
}
Взгляните на рендеры:
Добавить подсказку
Чтобы сделать взаимодействие более совершенным, я обнаружил, что всплывающая подсказка отображается в правом нижнем углу мыши одновременно, тогда это должен быть div, который по умолчанию скрыт, а затем перемещает соответствующую позицию в соответствии с движением мыши. мышь.
Первый шаг — создать новый div.
<div id="tooltip"></div>
Второй шаг — установить стиль, который по умолчанию скрыт.
#tooltip {
position: absolute;
z-index: 2;
background: white;
padding: 10px;
border-radius: 2px;
visibility: hidden;
}
Третий шаг изменяет положение div:
setRaycaster() {
this.raycaster = new THREE.Raycaster()
this.mouse = new THREE.Vector2()
this.tooltip = document.getElementById('tooltip')
const onMouseMove = (event) => {
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
// 更改div位置
this.tooltip.style.left = event.clientX + 2 + 'px'
this.tooltip.style.top = event.clientY + 2 + 'px'
}
window.addEventListener('mousemove', onMouseMove, false)
}
Последний шаг — установить имя всплывающей подсказки:
showTip() {
// 显示省份的信息
if (this.lastPick) {
const properties = this.lastPick.object.parent.properties
this.tooltip.textContent = properties.name
this.tooltip.style.visibility = 'visible'
} else {
this.tooltip.style.visibility = 'hidden'
}
}
На этом вся 3D-визуализация проекта земли завершена, давайте взглянем на эффект.
Суммировать
Уважаемые читатели, если вы считаете, что чтение полезно для вас, надеюсь, вы не поскупитесь с 👍 в руке. Нажимать 👍 и следовать за ним — самая большая поддержка для меня. Вывод знаний непрост, и я не забуду свой первоначальный намерение и продолжать делиться достоинствами визуализации.Статьи, если вы заинтересованы в визуализации, вы можете следить за моей колонкой визуализации ниже, или вы можете следить за моей общедоступной учетной записью:Графика интерфейса, продолжайте делиться знаниями о компьютерной графике. Весь код для этой статьи находится в моемgithubДобро пожаловать в звездочку, если в последней статье что-то не так, добро пожаловать на исправление и обмен.