2021년 7월 4일 일요일

간단한 실시간 센서 데이터 가시화를 위한 아두이노와 언리얼 연결 방법

이 글은 아두이노와 언리얼(Unreal) 게임엔진을 연결하는 간단한 방법을 설명한다. 이 글은 실시간으로 시리얼 통신에서 얻은 센서값을 언리얼의 액터 메쉬의 머터리얼(재질) 색상에 직접 설정하는 방법을 포함한다. 센서와 언리얼 연결 방법을 보여주기 위해, 별도 상용 언리얼 플러그인을 사용하지 않고, C++에서 통신 모듈을 간단히 개발해 사용한다. 이 글에서 설명하는 같은 방식으로 텍스처, 광원, 메쉬 위치 등을 센서 값에 따라 실시간으로 변경할 수 있다.

이 글의 개발 결과는 다음과 같다. 화면에서 각 큐브는 물리적 센서에서 얻은 데이터값을 색상으로 표시한다. 이를 잘 이용하면, 언리얼을 이용해 IoT와 연결된 디지털트윈(Digital Twin)이나 메타버스(Metaverse)를 쉽게 개발할 수 있다.

개발 환경

언리얼과 아두이노 IDE를 각각 설치한다.

이 글은 언리얼에 대한 기본 개념은 설명하지 않는다. 이 내용은 여기를 참고한다. 아두이노에 대한 내용은 여기를 참고한다.

개발 순서

간단한 구현을 위해, 센서는 아두이노에서 얻은 값을 사용하고, RS232 시리얼 통신을 이용한다. 

센서 - 아두이노 보드 - RS232 시리얼 통신 - 언리얼 - 데이터 가시화

주요 개발 순서는 다음과 같다. 

1. 아두이노 IDE에서 센서 데이터 획득하는 회로와 프로그램 작성. 아두이노 A0핀에 광센서 등 사용해 시그널을 A0에 입력. LED는 9번핀에 연결.

주요 코드는 다음과 같음.
const int analogInPin = A0;  // Analog input pin that the potentiometer is attached to
const int analogOutPin = 9; // Analog output pin that the LED is attached to

int sensorValue = 0;        // value read from the pot
int outputValue = 0;        // value output to the PWM (analog out)

void setup() {
  Serial.begin(9600);
}

void loop() {
  // read the analog in value:
  sensorValue = analogRead(analogInPin);
  
  // map it to the range of the analog out:
  outputValue = map(sensorValue, 0, 500, 0, 255);
  analogWrite(analogOutPin, outputValue);

  // print the results to the Serial Monitor:
  Serial.write(outputValue);
  delay(50);
}

2. 언리얼 실행. 빈 프로젝트를 C++ 형식으로 생성
3. 블루프린트 작성. 기본 클래스는 Actor에서 파생받음. 이름은 ColoredCube로 설정. 블루프린트에서 Cube 메쉬 객체 설정

4. 액터의 메쉬 객체에 적용할 재질 작성 및 설정. 재질 이름은 Color로 설정하고 다음과 같이 재질 작성
5. ColoredCube에 Color 재질을 설정
6. Visual Studio에서 Serial Port로 부터 센서 데이터를 얻는 모듈을 작성. 헤더 파일은 다음과 같음. 소스 코드는 여기를 참고.
#pragma once

#include "CoreMinimal.h"
#include "CoreTypes.h"

/**
 * 
 */
class IOT6_API SerialPort
{
public:
SerialPort();
~SerialPort();

bool open(const TCHAR* sPort, int nBaud = 9600);
void close();

int write(TArray<uint8>& Buffer);
int read(TArray<uint8>& Buffer);

private:
void* m_PortHandle;
};

7. Visual Studio에서 액터 소스 파일 편집. Serial Port에서 얻은 센서 데이터로 액터 메쉬의 머터리얼을 얻어 생상을 설정함. 주요 코드는 다음과 같음.
#include "ColoredCube.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Components/StaticMeshComponent.h"
#include "Materials/MaterialInterface.h"

#pragma optimize("", off)

class SerialPortInstance
{
public:
SerialPortInstance();
~SerialPortInstance();

SerialPort _port;
};

SerialPortInstance::SerialPortInstance()
{
_port.open(_T("COM3"));  // 이 부분은 각자 시리얼 통신 포트 이름으로 변경해야 함
}

SerialPortInstance::~SerialPortInstance()
{
_port.close();
}


SerialPortInstance _serial;

// Sets default values
AColoredCube::AColoredCube()
{
  // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}

// Called when the game starts or when spawned
void AColoredCube::BeginPlay() // 최초 액터 생성 시 실행
{
Super::BeginPlay();

auto Cube = FindComponentByClass<UStaticMeshComponent>();
auto Material = Cube->GetMaterial(0);

_DynamicMaterial = UMaterialInstanceDynamic::Create(Material, NULL);
Cube->SetMaterial(0, _DynamicMaterial);

_randomColor = FMath::Rand() % 64;
}

// Called every frame
void AColoredCube::Tick(float DeltaTime)  // 프레임 렌더링 전에 호출됨
{
Super::Tick(DeltaTime);

TArray<uint8> Buffer;
Buffer.Add(0);
int len = _serial._port.read(Buffer);
if (len)
{
FString string = FString::Printf(TEXT("Data = %d"), Buffer[0]);
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, string);

float blend = ((float)Buffer[0] + _randomColor) / (255.0);
// float blend = 0.5f + FMath::Cos(GetWorld()->TimeSeconds) / 2;
_DynamicMaterial->SetScalarParameterValue(TEXT("Blend"), blend);
}
}

#pragma optimize("", on)

구현 결과
이 결과는 다음과 같다. 센서 값에 따라 큐브 색상에 실시간으로 변화하는 것을 확인할 수 있다.

실제 구현 코드는 아래와 같다.
마무리
이 방법을 응용하면 IoT와 같은 객체에서 얻은 센서값을 언리얼과 연결할 수 있다. 이렇게 연결하여, 물리세계를 게임엔진 기반 3차원 가상세계로 맵핑하여 가시화하거나 그 반대로 액추에이터를 제어하는 앱을 개발할 수 있다. 

레퍼런스

댓글 3개:

  1. 와우 팟케스트에서 처음 뵙고 여기서도 보네요! ^^!

    답글삭제
    답글
    1. 이메일로 진로사항에 대해 질문 했었었는데... 지금은 서울예대 디지털아트과에 입학해서 2학년1학기를 다니고 있습니다~ ㅎㅎ 제 홈페이지에요 놀러오세요! https://www.instagram.com/yannenchemin/

      삭제