Я создаю игру 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, я считаю, что этот вопрос не относится к какому-либо языку программирования.