Реализация AR-размещения 3D-объектов на плоскости
Поставить 3D-модель на обнаруженную плоскость — это не задача одного вечера, если речь идёт о стабильном production-решении. Объект должен стоять ровно при движении камеры, не «плыть» при смене освещения, адекватно масштабироваться и не утопать в поверхности на 5 сантиметров. Каждый из этих пунктов — отдельный технический вопрос.
Форматы 3D-моделей и оптимизация для AR
Стандарт для мобильного AR — USDZ (iOS/RealityKit) и GLB/GLTF (ARCore/cross-platform). Типичная ошибка: брать модель из Blender или 3ds Max без оптимизации и пытаться грузить её в AR. Полигональность 500k треугольников, текстуры 4096×4096 без mip-mapping — гарантированный FPS провал на iPhone 12 и ниже.
Целевые параметры для мобильного AR:
| Тип объекта | Полигоны | Текстуры | Размер GLB |
|---|---|---|---|
| Небольшой предмет (стул, светильник) | до 30k | 1024×1024 | до 5 МБ |
| Средний объект (диван, стол) | до 80k | 2048×2048 | до 15 МБ |
| Крупный (комплект мебели, кухня) | до 200k | 2048×2048 | до 40 МБ |
Компрессия: KTX2 + Basis Universal для GLB, HEIC-текстуры в USDZ. В RealityKit — Reality Composer Pro (Xcode 15+) для запекания физических материалов PBR прямо в .reality формат.
Anchor, Raycast и почему hitTest устарел
В ARKit 4+ и ARCore 1.18+ предпочтительный способ определения точки размещения — Raycast, а не устаревший hitTest. Разница существенная:
ARSession.raycast(from:allowing:alignment:) возвращает список ARRaycastResult с target: .estimatedPlane или .existingPlaneGeometry. existingPlaneGeometry точнее — используется геометрия уже детектированной плоскости. estimatedPlane работает и там, где плоскость ещё не зафиксирована.
guard let query = arView.makeRaycastQuery(
from: arView.center,
allowing: .existingPlaneGeometry,
alignment: .horizontal
) else { return }
let results = arView.session.raycast(query)
if let first = results.first {
placeEntity(at: first.worldTransform)
}
Для ARCore — Frame.hitTest() всё ещё документируется, но Session.createRaycastQuery() + Frame.raycast() даёт более стабильный результат на краях плоскостей.
Стабилизация позиции объекта
После первого размещения объект не должен «гулять» при движении камеры. Стандартный подход — привязка к AnchorEntity:
let anchor = ARAnchor(transform: worldTransform)
arView.session.add(anchor: anchor)
let anchorEntity = AnchorEntity(anchor: anchor)
anchorEntity.addChild(modelEntity)
arView.scene.addAnchor(anchorEntity)
Без явного ARAnchor объект продолжает обновлять позицию вместе с обновлением плоскости. Это особенно заметно в первые 10-15 секунд сессии, когда ARKit активно уточняет геометрию плоскости.
Тени и физическое освещение
AR-объект без тени выглядит «летящим». RealityKit рендерит contact shadow автоматически для .castsShadow = true. В SceneKit — SCNLight с типом .ambient + directional light с castsShadow = true.
ARKit Environment Texturing (доступно с A12+): ARWorldTrackingConfiguration.environmentTexturing = .automatic — ARKit строит environment map из камеры и применяет его к PBR-материалам. Металлические и глянцевые поверхности начинают отражать реальное окружение. Без этого хромированный предмет выглядит пластиковым.
Перемещение и вращение после размещения
Drag gesture для перемещения объекта — через ARView.installGestures(.translation, for: entity) в RealityKit. Для кастомного поведения — UILongPressGestureRecognizer + continuous raycast во время движения пальца.
Вращение вокруг вертикальной оси — ARView.installGestures(.rotation, for: entity) или ручное через UIPanGestureRecognizer с simd_quatf(angle:axis:). Обязательно ограничить ось вращения только Y — иначе объект начнёт «заваливаться» при неточном жесте.
Сроки
Базовое размещение одного объекта с raycast, anchor и тенью — 4-6 дней. Мультиобъектное размещение, перемещение/вращение, поддержка нескольких форматов моделей — 10-15 дней. Стоимость рассчитывается индивидуально.







