Introduction

Landbook은 중소규모 토지 투자자를 위한 신축 개발의 모든 단계를 지원하는 서비스입니다. Landbook의 AI 건축가 서비스는 각 지역의 대지 크기, 용도지역, 건축 규제 등을 고려하여 건물주에게 다양한 건축 설계안을 제공합니다.
Landbook's AI architect service

본 프로젝트는 diffusers와 같은 생성형 이미지 모델을 활용하여 Landbook AI 건축가 서비스의 최종 결과물을 렌더링하는 파이프라인을 개발하는 것입니다. 3D 모델링 데이터를 입력으로 받아 실제 건물과 매우 유사한 사실적인 이미지를 생성함으로써, 건축축주가 자신의 설계안이 실제로 지어졌을 때 어떤 모습일지 시각화하고 검토할 수 있게 합니다. 기존의 3D 렌더링과 달리, AI 기반 생성 모델을 활용하여 실제 건물의 텍스처와 주변 환경과의 조화를 모두 고려한 고품질 시각화를 제공하는 것을 목표로 합니다.


Pipeline Overview

아래의 직관적인 파이프라인은 최종 결과물이 건축 설계를 정확하게 표현할 뿐만 아니라, 건물주가 실제 건물의 모습을 더 잘 이해할 수 있도록 사실적인 시각화를 제공합니다. 파이프라인은 다음과 같은 단계로 구성됩니다:

  1. 2D Plans Generation: 건물 설계의 기초가 되는 2D 평면도를 생성하는 것으로 프로세스가 시작됩니다.
  2. 3D Building Generation: 2D 도면을 적절한 치수와 구조를 가진 3D 건물 모델로 변환합니다.
  3. Three.js Plot: 3D 모델을 Three.js에 플롯하여 시각화 및 조작이 가능하도록 합니다.
  4. Camera Adjustment: 가장 적절한 지점점에서 건물을 포착하기 위해 시야각과 카메라 위치를 신중하게 조정합니다.
  5. Scale Figures: 장면에 규모 참조와 맥락을 제공하기 위해 사람 형상, 나무, 차량을 추가합니다.
  6. Masking: 건물과 환경의 다른 부분들을 구별되는 색상으로 마스킹하여 재료와 표면을 정의합니다.
  7. Canny Edge Detection: 명확한 건물 윤곽과 세부사항을 생성하기 위해 엣지 검출을 적용합니다.
  8. Highlighting: 중요한 건축적 특징과 엣지를 하이라이팅 및 해칭을 통해 강조합니다.
  9. Base Image Generation: 적절한 음영과 텍스처가 있는 기본 이미지를 생성합니다.
  10. Inpainting & Refining: 사실적인 텍스처와 세부사항을 추가하기 위해 여러 번의 인페인팅과 정제 과정을 수행합니다.
Pipeline Diagram


Camera Position Estimation

카메라 위치 추정은 가장 효과적인 시점에서 건물을 포착하는 데 있어 매우 중요한 단계입니다. 알고리즘은 건물의 크기, 대지 배치, 도로 위치를 고려하여 적절한 카메라 위치를 결정합니다.

  1. Road-Based Positioning
    • 건물 대지에 인접한 가장 넓은 도로를 식별합니다
    • 도로의 중심점을 카메라 배치의 기준점으로 사용합니다
    • 거리 레벨의 시점에서 건물이 보이도록 합니다
  2. Vector Calculation
    • 가장 넓은 도로와 정렬된 수평 벡터를 생성합니다 (X 벡터)
    • 수평 벡터를 90도 회전하여 수직 벡터를 생성합니다 (Y 벡터)
    • 이러한 벡터들은 카메라의 시점 방향을 결정하는 기준이 됩니다
  3. Height Determination and Distance Calculation
    • 두 가지 기준을 사용하여 최적의 카메라 높이를 계산합니다
    • 적절한 건물 커버리지를 보장하기 위해 이 기준들 중 최대값을 선택합니다
    • 삼각함수를 사용하여 카메라와 건물 사이의 이상적인 거리를 계산합니다 \[ \tan(\theta) = \frac{h}{d}, \quad d = \frac{h}{\tan(\theta)} \] 여기서 \(d\)는 카메라와 widestRoadCentroid 사이의 거리, \(h\)는 카메라의 높이, \(\theta = \frac{\text{fov}}{2} \times \frac{\pi}{180}\)입니다
Camera Position Estimation Diagram


    const estimateCameraPosition = (
      data: BuildingStateInfo,
      buildingHeightEstimated: number,
      fov: number,
    ) => {

      const parcelPolygon = data.plotOutline

      // Obtain the widest road object.
      // The widest road object is computed by widthRaw + edgeLength
      let widestWidth = -Infinity;
      let widestRoad = undefined;
      data.roadWidths.forEach((road) => {
        if (widestWidth < road["widthRaw"] + road["edge"].getLength()) {
          widestWidth = road["widthRaw"]
          widestRoad = road
        }
      })
      
      // Get the centroid of the widest road
      const widestRoadCentroid = Util.centroid(widestRoad["edge"])

      // Get the coordinates of the widest road edge
      const widestRoadEdgeCooridntaes = widestRoad["edge"]._points._coordinates

      // X vector from the widest road edge direction
      const widestRoadEdgeHVector = {
        x: widestRoadEdgeCooridntaes[0].x - widestRoadCentroid.x,
        y: widestRoadEdgeCooridntaes[0].y - widestRoadCentroid.y,
      }

      // Compute the norm of the widest road edge vector
      const widestRoadEdgeHVectorNorm = Math.sqrt(widestRoadEdgeHVector.x ** 2 + widestRoadEdgeHVector.y ** 2)

      // Normalize X vector
      const widestRoadEdgeHVectorUnit = {
        x: widestRoadEdgeHVector.x / widestRoadEdgeHVectorNorm,
        y: widestRoadEdgeHVector.y / widestRoadEdgeHVectorNorm,
      }

      // Create Y vector by rotating the X vector 90 degrees
      const radian = 90 * Math.PI / 180;
      const widestRoadEdgeVVectorUnit = {
        x: widestRoadEdgeHVectorUnit.x * Math.cos(radian) - widestRoadEdgeHVectorUnit.y * Math.sin(radian),
        y: widestRoadEdgeHVectorUnit.x * Math.sin(radian) + widestRoadEdgeHVectorUnit.y * Math.cos(radian)
      }

      // Define height criteria
      const parcelLongestDistance = calculateLongestDistance(parcelPolygon)
      const heightCriterion1 = buildingHeightEstimated / 2
      let heightCriterion2 = parcelLongestDistance / 3


      (...)


      // Determine the camera height based on the height criteria
      const cameraHeight = Math.max(heightCriterion1, heightCriterion2);

      // Compute the distance. C is an arbitarary constant
      const distance = ((cameraHeight / 2) / Math.tan((fov / 2) * (Math.PI / 180))) * C;

      // Estimate the final camera position
      const position = new Vector3(
        widestRoadCentroid.x + (widestRoadEdgeHVectorUnit.x + widestRoadEdgeVVectorUnit.x) * distance,
        Math.max(-cameraHeight / 2, -10),
        widestRoadCentroid.y + (widestRoadEdgeHVectorUnit.y + widestRoadEdgeVVectorUnit.y) * -distance
      );

      return position
    }


Scale Figures

Scale figures는 디퓨전 모델이 더 사실적인 건축 시각화를 이해하고 생성하는 데 필수적인 맥락적 요소입니다. 사람 형상, 나무, 차량을 장면에 포함시킴으로써, 모델이 생성해야 하는 건축물의 공간적 관계와 규모를 이해하는 데 도움이 되는 중요한 참조점을 제공합니다.
Scale Figures

이러한 맥락적 요소들은 또한 모델이 적절한 조명, 그림자, 대기 효과를 생성하는 데 도움을 줍니다. 모델이 장면에서 사람 형상이나 나무를 볼 때, 최종 렌더링에 있어야야 할 조명 효과와 환경적 상호작용의 규모를 더 잘 해석할 수 있습니다. 이는 더욱 설득력 있고 자연스럽게 통합된 건축 시각화를 만드는 데 도움이 됩니다.

우리의 파이프라인에서는 디퓨전 프로세스가 시작되기 전에 이러한 스케일 요소들이 배치됩니다. 모델은 이러한 참조를 사용하여 건물의 의도된 크기와 비율을 더 잘 이해하며, 이는 생성된 이미지의 품질과 정확도를 크게 향상시킵니다. 특히 사람 형상은 디퓨전 모델에 스케일 참조를 제공하여 생성 과정 전반에 걸쳐 일관되고 현실적인 비율을 유지하는 데 중요한 역할을 합니다.
Landbook AI Architect result w/ and w/o scale figures


Material Masking

three.js에서 제공하는 ShaderMaterial은 건물과 환경의 재질을 마스킹하는 데 사용됩니다. ShaderMaterial은 커스텀 셰이더로 렌더링되는 재질입니다. 셰이더는 GPU에서 실행되는 GLSL로 작성된 작은 프로그램입니다.

ShaderMaterial은 사용자가 커스텀 셰이더를 작성할 수 있게 해주므로, 서로 다른 건축 요소에 대해 특정 색상을 정의하여 특수한 마스킹 재질을 만들 수 있습니다. 이러한 마스킹 재질은 3D 모델을 구분된 부분으로 분할하여 디퓨전 모델이 각각을 별도로 처리할 수 있도록 도와줍니다.
Material Masking


    const createMaskMaterial = (color: number) => {
      return new THREE.ShaderMaterial({
        uniforms: {
          color: { value: new THREE.Color(color) }
        },
        vertexShader: `
          void main() {
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
          }
        `,
        fragmentShader: `
          uniform vec3 color;
          void main() {
            gl_FragColor = vec4(color, 1.0);
          }
        `
      });
    };
    
    export const glassMaskMaterial = createMaskMaterial(0xff0000);                   // red
    export const glassPanesMaskMaterial = createMaskMaterial(0x00ffff);              // cyan
    export const columnsMaskMaterial = createMaskMaterial(0xff00ff);                 // magenta
    export const wallMaskMaterial = createMaskMaterial(0x0000ff);                    // blue
    export const surroundingBuildingsMaskMaterial = createMaskMaterial(0xffff00);    // yellow
    export const surroundingParcelsMaskMaterial = createMaskMaterial(0xff8e00);      // orange
    export const roadMaskMaterial = createMaskMaterial(0x000000);                    // black
    export const siteMaskMaterial = createMaskMaterial(0x00ff00);                    // green
    export const railMaskMaterial = createMaskMaterial(0xbc00bc);                    // purple
    export const carMaskMaterial = createMaskMaterial(0xbcbcbc);                     // gray
    export const treeMaskMaterial = createMaskMaterial(0xc38e4d);                    // brown
    export const personMaskMaterial = createMaskMaterial(0x00a800);                  // darkgreen
    export const pathMaskMaterial = createMaskMaterial(0xc0e8f6);                    // skyblue
    export const parkingLineMaskMaterial = createMaskMaterial(0x000080)              // darkblue


EndpointHandler 🤗

우리 파이프라인의 디퓨전 프로세스는 사실적인 건축 이미지를를 생성하기 위해 HuggingFace Diffusers 라이브러리의 여러 모델들을 활용합니다. 이 프로세스는 초기 생성, 타겟팅된 인페인팅, 최종 정제의 세 가지 주요 단계로 구성됩니다. 파이프라인은 canny image 에 대해 학습된 ControlNet 모델을 사용하는 StableDiffusionXLControlNetPipeline으로 시작됩니다.

Canny edge detection, highlighting the main building, hatching some parts

이 단계에서는 3D 모델의 엣지 정보를 가져와 base image를 생성합니다. ControlNet은 프롬프트의 도움을 받아 생성된 base image가 건물 디자인의 정확한 기하학적 윤곽을 따르도록 보장합니다:

    self.prompt_positive_base = ", ".join(
        [
            "<>", 
            "[Bold Boundary of given canny Image is A Main Building outline]",
            "[Rich Street Trees]", "[Pedestrians]", "[Pedestrian path with hatch pattern paving stone]", 
            "[Driving Cars on the asphalt roads]", "At noon", "[No Clouds at the sky]", "First floor parking lot", 
            "glass with simple mullions", "BLENDED DIVERSE ARCHITECTURAL MATERIALS", "Korean city context",  "REALISTIC MATERIAL TEXTURE",
            "PROPER PERSPECTIVE VIEW",  "PROPER ARCHITECTURAL SCALE", "8k uhd", "masterpiece", "[Columns placed at the corner of the main building]"
            "best quality", "ultra detailed", "professional lighting", "Raw photo", "Fujifilm XT3", "high quality",
        ]
    )


초기 생성 이후, 파이프라인은 StableDiffusionXLInpaintPipeline을 사용하여 일련의 타겟팅된 인페인팅 작업을 수행합니다. 인페인팅 프로세스는 다양한 건축 요소들을 처리하기 위해 특정한 순서를 따릅니다. 각 인페인팅 단계는 적절한 재질 텍스처와 건축 디테일이 각 요소에 대해 생성되도록 하기 위해 정교하게 작성된 프롬프트와 마스크를 사용합니다. 각 인페인팅 단계 이후에는 base image와 병합되어 새로운 base image를 생성합니다.

  1. 아스팔트 텍스처가 있는 도로 표면
  2. 주변 필지와 보행자 도로
  3. 하늘을 포함한 배경 요소
  4. 적절한 건축 디테일이 포함된 주변 건물
( ... )
Masked images


마지막 단계에서는 StableDiffusionXLImg2ImgPipeline을 사용하여 전체 이미지를 정제하고, 렌더링 이미지의 일관성과 사실성을 향상시킵니다. 이 정제 과정은 더 나은 해상도와 디테일 향상을 통해 전반적인 이미지 품질을 개선하는 데 중점을 둡니다.

더 자연스럽고 사실적인 효과를 만들기 위해 조명과 그림자를 조정하고, 건물의 다양한 표면에 걸쳐 일관된 재질 표현을 보장하며, 설계의 정확성을 유지하기 위해 건축 디테일을 미세 조정합니다. 이러한 정제 과정들이 함께 작용하여 건축적으로 정확하면서도 시각적으로 매력적인 최종 이미지지를 만들어냅니다.


Results

위에서 설명한 다단계 디퓨전 파이프라인을 적용한 결과, 일관된 재질, 조명, 건축 디테일을 갖춘 고품질 건축 렌더링을 생성하는 데 있어 우리의 접근 방식이 효과적임을 보여주는 다음과 같은 결과를 얻을 수 있습니다.


Future Works

현재 파이프라인이 사실적인 건축 렌더링 이미지를 성공적으로 생성하고 있지만, 다음과 같은 개선 및 향후 개발이 필요한 영역들이 있습니다:
  • Material Diversity Enhancement: 주변 건물 외관의 다양한 텍스처와 재질을 처리하고, 더 사실적인 환경 맥락을 만들기 위한 재질 상호작용과 풍화 효과를 적용해볼 수 있습니다.
  • Sky Condition Variation: 향후 개발에서는 다양한 시간대, 날씨 효과, 구름 패턴, 그리고 동적인 대기 조건을 지원하여 더 다양한 시각화 시나리오를 제공할 수 있습니다.
  • Road Detail Improvements: 파이프라인을 개선하여 다양한 포장 유형, 도로 표시, 표면 마모 패턴, 주변 요소와의 더 나은 통합을 포함한 더 상세한 도로 표면을 생성할 수 있습니다.