Воспринимаемая ширина наклейки в зависимости от угла поворота стены

Я создаю игру raycasting с нуля, используя холст JavaScript.

Часть задачи (для меня) состоит в том, чтобы украсить стены случайными изображениями (картинками). Я уже реализовал отрисовку стен, пола потолка и спрайтов. При рисовании стен я сохраняю для каждого x (координата экрана) расстояние до стены (Z-BUFFER), высоту стены (H-BUFFER) и фактические координаты пикселя в базовой 2D-сетке (GRID_BUFFER).

Мой подход к рисованию наклеек (изображений) на стене следующий (после определения списка наклеек, которые теоретически могут быть видны):

  • рассчитывается расстояние до положения деколи (положение определяется как находящееся в середине вершины сетки, обращенной к наблюдателю)
  • координата экрана decalScreenX вычисляется на основе матрицы преобразования координат сетки в координаты экрана. Это работает правильно:

let decalScreenX = Math.floor((RAYCAST.SCREEN_WIDTH / 2) * (1 + CAMERA.transformX /CAMERA.transformDepth));

  • Затем я получаю данные изображения для рассматриваемой наклейки и получаю ее ширину и высоту.
  • И исходя из расстояния и наблюдаемого угла, я рассчитываю воспринимаемую ширину деколи. Вот где настоящая проблема, так как я вижу, что я не совсем точно вычисляю эту ширину.
  • со всей этой информацией затем легко рассчитать левые и правые координаты экрана - где начать и где закончить отрисовку деколи, используйте H-BUFFER для расчета коэффициента высоты и используйте GRID_BUFFER для рисования только на сетке, принадлежащей этой декали.

Я видел расчет ширины с точки зрения того, что декаль поворачивается от вектора направления игрока на угол, если направление игрока не противоположно направлению, с которым декаль смотрит в пространство (пример):

угол

или если направление игрока прямо противоположно направлению надписи, этот угол равен 0° (пример): напротив

Мой первый подход состоял в том, чтобы использовать скалярное произведение обратного направления игрока и направления, обращенного к декалю, таким образом получая косинус угла между векторами и используя его как фактор для уменьшения воспринимаемой ширины:

let CosA = PLAYER.dir.mirror().dot(decal.facingDir);
let widthScale = CosA * (CAMERA.transformDepth / decal.distance);

Проблема с этим решением заключается в том, что когда перпендикулярно , коэффициент равен 0, и наклейка не рисуется, но поскольку стены рисуются в перспективе, этого не должно быть. Поэтому я начал импровизировать. Я определил коэффициент CAMERA.minPerspective, как показано ниже. Поле зрения (FOV) составляет 70°.

CAMERA.minPerspective = Math.cos(Math.radians((90 + this.FOV) / 2));

Моя интуиция подсказывала (увы, без знаний перспективы и геометрии), что для малых углов множитель должен оставаться равным 1. А для углов близких к 90° должен быть какой-то минимальный множитель, чтобы декаль оставалась видимой. Итак, я пришел с этим улучшенным кодом:

let CosA = PLAYER.dir.mirror().dot(decal.facingDir);
let FACTOR = Math.min(1, CosA + CAMERA.minPerspective);
FACTOR = Math.max(FACTOR, CAMERA.minPerspective);
let widthScale = FACTOR * (CAMERA.transformDepth / decal.distance);

Это работает значительно лучше, но имеет некоторые недостатки. Визуально для углов 0-50° коэффициент уменьшения слишком велик. Это можно наблюдать, если я использую декали такой ширины, что они должны закрывать всю поверхность сетки. (см. изображение ниже; слева от лестницы видна стена под ней, наклейка должна закрывать всю сетку, но этого не происходит, потому что FACTOR слишком мала).

аномалия

Я искал в Stack Overflow и остальной части Интернета лучшее решение, но, похоже, мои знания геометрии также мешают мне распознавать правильные решения, если они находятся вне этого контекста.

Поэтому, пожалуйста. Вероятно, существуют детерминированные решения для расчета воспринимаемой ширины без повторного использования фазы raycasting или с использованием информации, которую я могу сохранить на фазе raycasting. Хотя в примере кода используется JavaScript, я считаю, что этот вопрос не относится к какому-либо языку программирования.


person Lovro    schedule 04.04.2021    source источник
comment
хм, это звучит как raycast в сочетании с движком 3D-рендеринга ... это странно, но быстро. Однако это приводит именно к таким проблемам. Я бы сделал это по-другому и визуализировал изображение деколи так же, как и ваша стена... однако для этого вам нужно сохранить его в том же разрешении/размере, что и текстура стены, и использовать его как спрайт (без рендеринга граничного пространства) так что либо смешивание, либо другой тест...   -  person Spektre    schedule 05.04.2021


Ответы (1)


Я нашел решение, которое сохраняет (или даже улучшает) простоту и временную сложность подхода в вопросе.

  • Я добавил две точки в определение деколи — leftDrawStart и rightStartDraw. Их легко вычислить в момент создания деколи, основываясь на реальной ширине спрайта (деколи) и определении размера сетки (блока). Выполняя этот расчет, я рассматриваю leftDrawStart с точки зрения камеры (а не координат сетки).
  • при рендеринге деколи я вычисляю, используя матрицу преобразования (как в вопросе, пример кода ниже), координаты экрана для leftDrawStart и rightStartDraw из их координат сетки:
transform(spritePos) {
    let invDet = 1.0 / (CAMERA.dir.x * PLAYER.dir.y - PLAYER.dir.x * CAMERA.dir.y);
    CAMERA.transformX = invDet * (PLAYER.dir.y * spritePos.x - PLAYER.dir.x * spritePos.y);
    CAMERA.transformDepth = invDet * (-CAMERA.dir.y * spritePos.x + CAMERA.dir.x * spritePos.y);
  }
  • Я различаю рассчитанные абсолютные drawStartX и drawEndX и их настройку, чтобы они соответствовали границам экрана или возвращались из функции, если они полностью за кадром
  • наконец, воспринимаемая ширина деколи даже не требуется, поскольку положение текстуры можно рассчитать, используя соотношение разностей между текущим рисунком stripe — абсолютное начало рисования и разность абсолютного конца рисунка — абсолютное начало рисования:
let texX = (((stripe - drawStartX_abs) / (drawEndX_abs - drawStartX_abs)) * imageData.width) | 0;

Этот подход является полностью точным и значительно более быстрым по сравнению с подходом, при котором литье декалей было бы включено в этап raycasting.

person Lovro    schedule 06.04.2021