2.5D Renderer

Stage 5: Текстурирование

Вертикальное: стены

В прошлых шагах стены были однотонными, теперь будем брать цвет из текстуры, которая хранится как bitmap: двумерный массив индексов и палитра цветов. Сектору достаточно будет знать ID текстуры, и если он не задан, тогда оставляем старое поведение и рисуем стену обычной вертикальной линией.


  interface Sector {
    // ..
    wallTexture: string;
  }

2.5D Renderer

2D Renderer

Управление камерой WASD и ZX

Немного кода

Улучшим drawSolidSegment для рассчета координаты текстуры для текущей экранной координаты. Горизонтальная координата tx показывает, в какой части отрезка стены находится текущий экранный столбец: ближе к началу стены это значение около 0, ближе к концу — около 1. Из него получаем texX, то есть столбец внутри текстуры.

Вертикальная координата считается отдельно. Для каждого пикселя между верхом и низом стены берём долю v: наверху она близка к 0, внизу близка к 1. Так получаем texY. После этого getTextureColor превращает пару texX/texY в настоящий цвет, который и попадает на canvas.


  function drawSolidSegment(
    ctx: CanvasRenderingContext2D,
    camera: Camera, 
    seg: Seg,
    angles: IntersectionAngles, 
    // ..
  ) {
    // ..  

    for (let x = xFrom; x <= xTo; x++) {
      // ..
      const tx = getInterpolationFactor(camera, angles, x);
      // ..

      if (sector.wallTexture) {
        const texture = textures[sector.wallTexture];
        const texX = Math.floor(tx * texture.width);
        
        for (let y = drawTop; y < drawBottom; y++) {
          const v = (y - top) / (bottom - top);
          const texY = Math.floor(v * texture.height) % texture.height;        
          const color = getTextureColor(texture, texX, texY);
          
          drawPixel(ctx, x, y, color);
        }
      } else {
        drawVerticalLine(ctx, x, drawTop, drawBottom, wallColor);
      }
      // ..
    }

    // ..
  }

Где getInterpolationFactor связывает экранный столбец с положением на реальной стене:


  function getInterpolationFactor(
    camera: Camera,
    angles: IntersectionAngles,
    screenX: number,
  ): number {
    const fov = camera.fov.degrees;
    const screenWidth = camera.screen.width;
    const angle = angles.cameraFrom + (screenX / screenWidth) * fov;
    const t = (angle - angles.linedefFrom) / (angles.linedefTo - angles.linedefFrom);
    
    return Math.max(0, Math.min(1, t));
  }

Важно, что текстура не рисуется отдельной картинкой поверх стены. Мы по-прежнему строим стену тем же способом: проецируем seg на экран, находим верх и низ вертикальной полосы, а затем меняем только способ заполнения пикселей внутри этой полосы. Благодаря этому текстурирование естественно работает вместе с уже существующими отсечениями, порталами и BSP-порядком.

Реализация шага на github