C#과 LightningChart .NET을 사용한 3D 지형 모델링 애플리케이션 만들기
소개
안녕하세요, 저는 Omar입니다. 이 글에서는 LightningChart .NET과 C#을 사용하여 3D 지형 모델링 애플리케이션을 만들어 보겠습니다. 이 애플리케이션은 JPG 또는 PNG 이미지를 읽어 각 픽셀에 대한 데이터 포인트를 생성하고, 이를 3차원 평면에 배치할 수 있습니다.
각 포인트는 Y축에서 고유한 값을 가지며, 이를 통해 평면 이미지에 수직성(verticality)과 기복(relief)을 부여합니다. 각 포인트의 높이는 픽셀 색상의 강도(intensity)에 따라 결정되며, 더 어두운 색상일수록 더 낮은 Y축 값이 할당됩니다.
또한 이 애플리케이션에는 포인트의 색상을 수정할 수 있는 사용자 컨트롤이 포함되어 있어, 지형 모델의 특정 영역을 강조 표시할 수 있습니다. 변환 및 렌더링해야 할 이미지가 매우 큰 경우(2,000픽셀 초과) 애플리케이션 성능에 영향을 줄 수 있으므로, 중간 크기의 이미지를 사용하는 것이 권장됩니다.
성능은 사용 중인 비디오 카드 또는 프로세서에 따라 달라질 수 있으며, 프로젝트를 실행하는 하드웨어 환경에 따라 결과가 상이할 수 있습니다. 본격적인 개발에 들어가기 전에, 이 3D 지형 모델링 프로젝트의 목표를 보다 잘 이해하기 위해 몇 가지 정의를 먼저 살펴보는 것이 중요합니다.
3D 지형 모델링

3D 지형 모델(3D terrain model) 또는 3D 지형도(3D topographic map)는 특정 지역의 토지를 3차원으로 표현한 것입니다. 이러한 모델은 기복(reliefs), 고도(elevations), 산, 강, 호수, 지형 유형, 건물, 식생 등과 같은 미리 정의된 영역의 지리적 특성을 표현하고자 합니다.
3D 지형 모델링은 어디에 사용되나요?
이러한 점을 고려할 때, 3D 지형 모델링의 중요성과 그 다양한 활용 분야를 짐작할 수 있습니다. 이를 통해 토목 공사, 도시 건설, 자연 재해 시뮬레이션, 비디오 게임 제작, 군사 분야 응용 등을 계획할 수 있습니다. 이러한 모델은 위성 이미지, 레이저 스캐너(LiDAR), 또는 지형도(topographic maps)에서 획득한 데이터를 기반으로 합니다.
이러한 모델을 생성하기 위해서는 3D 폴리곤 생성, 3D 구축 및 설계 도구를 제공하는 전문 소프트웨어가 사용됩니다. 예를 들어 AutoCAD, ArcGIS, Blender와 같은 도구들을 들 수 있습니다.
LightningChart .NET이 3D 모델 생성에 어떻게 도움을 줄 수 있나요?
LightningChart .NET은 설계 도구를 사용해 직접 3D 형상을 생성하는 기능은 제공하지 않지만, 3D 렌더링을 지원합니다. 따라서 X, Y, Z 값을 갖는 데이터 포인트를 3차원 평면에 프로그래밍 방식으로 배치함으로써 3D 요소를 생성할 수 있습니다.
이러한 프로그래밍 과정은 복잡한 개발을 의미할 수 있지만, 충분한 경험이 있다면 기대하는 결과를 얻을 수 있는 알고리즘을 구현할 수 있습니다. 이 프로젝트의 목적은 사용자가 자신의 요구 사항에 맞게 시작하고 점진적으로 개선해 나갈 수 있는 기본 템플릿을 제공하는 데 있습니다.
프로젝트 개요
이 프로젝트는 .NET WPF 프레임워크를 기반으로 합니다. 이 프레임워크를 사용하면 사용자 인터페이스와 C# 코드를 동시에 다룰 수 있습니다. 이 프로젝트는 .NET 8 버전으로 생성되었으므로, 해당 버전 또는 최신 버전을 설치할 것을 권장합니다. 또한 프로젝트를 실행하려면 Visual Studio 2022 이상이 필요합니다.

마크업
마크업(Markup)은 다양한 컨트롤을 사용하여 사용자 인터페이스를 구성할 수 있게 해주며, 이를 통해 애플리케이션에 표시되는 결과를 매우 정밀하게 관리할 수 있습니다. 이러한 인터페이스 개발은 XAML(Extensible Application Markup Language)을 사용하여 이루어집니다. 처음에는 XML 템플릿처럼 보일 수 있지만, XAML은 애플리케이션 간 데이터 교환이 아니라 애플리케이션 인터페이스를 구축하기 위해 특별히 설계된 언어입니다. 인터페이스의 데이터, 그래픽, 애니메이션은 외부 소스 파일에서 불러올 수도 있고, 코드 비하인드(code-behind)를 통해 동적으로 생성할 수도 있습니다.
코드 비하인드
코드 비하인드(Code Behind)는 사용자가 필요로 하는 결과를 읽고, 생성하며, 처리하는 실행 코드가 포함된 파일을 의미합니다. 그 주요 목적 중 하나는 XAML, HTML, CSS와 같은 그래픽 인터페이스 코드와 실행 코드를 분리하는 데 있습니다. 이러한 분리는 사용자 인터페이스 설계와 내부 로직 개발을 나누어 진행할 수 있게 해주어, 보다 안전하고 체계적이며 빠른 개발을 가능하게 합니다.
WPF(윈도우 프레젠테이션 재단)의 경우, C# 프로그래밍 언어를 사용합니다. C#은 객체/컴포넌트 지향 언어로서 이러한 접근 방식과 잘 어울립니다. LightningChart .NET은 실행 가능한 C# 코드가 포함된 WPF 프로젝트를 생성해 주며, 해당 코드 내에서 LightningChart .NET 고유의 도구들을 사용할 수 있습니다. LC .NET 프레임워크가 설치되어 있다면 이러한 도구들은 손쉽게 가져와 사용할 수 있습니다.
로컬 설정
이 프로젝트를 컴파일하기 위해 다음 요구 사항을 고려해야 합니다.
OS: 32비트 또는 64비트 Windows Vista 이상, Windows Server 2008 R2 이상.
DirectX: 9.0c(Shader Model 3 이상) 또는 11.0과 호환되는 그래픽 어댑터.
Visual Studio: 개발용으로는 2022 버전 필요, 배포 시에는 필수 아님.
Platform .NET Framework: 8.0 이상 버전 설치 필요.
이제 다음 URL로 이동하여 LightningChart .NET을 다운로드하세요. 이후 로그인 페이지로 리디렉션되며, LightningChart 계정에 접근하기 위해 간단한 회원가입 절차를 완료해야 합니다.

계정에 로그인한 후, 이 3D 지형 모델링 튜토리얼에 필요한 주요 기능을 사용할 수 있는 SDK “free trial” 버전을 다운로드할 수 있습니다. SDK를 다운로드하면 다음과 같은 .exe 파일을 받게 됩니다:

설치는 일반적인 Windows 설치 과정이므로, 완료될 때까지 그대로 진행해 주세요. 설치가 끝나면 다음과 같은 프로그램들을 확인할 수 있습니다:

라이선스 매니저
이애플리케이션에서는구매옵션을확인할수있습니다. 이트라이얼 SDK로생성한모든프로젝트는향후개발시에도모든기능이활성화된상태로그대로사용할수있습니다.

XAML 코드 리뷰
애플리케이션의 디자인은 MainWindow.xaml 파일 내에 포함됩니다. 비교적 단순한 구성으로 진행되지만, 현대적인 디자인에서 일반적으로 사용되는 어두운 색상을 적용하여 보다 “부드럽고” 덜 강한 인상을 주는 시각적 속성들을 적용해 볼 예정입니다.

글꼴은 약간 회색 톤을 사용해 눈이 편안하고 편안한 스타일을 줄 것입니다
<!-- Modern style for labels -->
<Style TargetType="Label">
<Setter Property="Foreground" Value="#E1E1E1"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5"/>
</Style>
<!-- Modern style for checkboxes -->
<Style TargetType="CheckBox">
<Setter Property="Foreground" Value="#E1E1E1"/>
<Setter Property="Margin" Value="5"/>
</Style>
<!-- Modern style for textboxes -->
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="#E1E1E1"/>
<Setter Property="Background" Value="#333333"/>
<Setter Property="BorderBrush" Value="#444444"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="FontSize" Value="14"/>
</Style>
컬러 팔레트는 강한 대비를 피하기 위해 어두운 톤으로 유지합니다. 버튼에는 지나치게 밝지 않은 파란색을 적용하고, 가장자리는 거의 보이지 않게 처리하여 “Windows 95” 스타일에서 흔히 보이는 공격적인 디자인을 피할 것입니다.
<!-- Modern style for buttons -->
<Style TargetType="Button">
<Setter Property="Background" Value="#007ACC"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#005A9E"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="8,4"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
LC .NET 차트는 애플리케이션에서 가장 큰 영역을 차지하게 되며, 여기에 원본 이미지를 표시하기 위한 두 번째 섹션을 추가합니다:

이를 위해 WPF의 Grid 요소를 사용하며, 두 개의 열을 할당합니다. 이 중 하나의 열은 다시 두 개의 행으로 구성되며, 하나는 버튼과 포인트 카운터를 위한 영역이고, 다른 하나는 차트를 표시하기 위한 영역입니다.
<Grid Grid.Row="2" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.3*"/>
</Grid.ColumnDefinitions>
<!-- Updated to include two rows in Column 1 -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Added Label for "Points" -->
<Label Grid.Row="0" Content="Point Count:" Margin="5"/>
<Grid x:Name="ChartGrid" Grid.Row="1" Background="Transparent" Margin="0,10,0,0"/>
<TextBox x:Name="PointsTextBox" Margin="95,5,405,5" Width="100" HorizontalAlignment="Left" IsEnabled="False"/>
<Button x:Name="UpdateColors" Click="UpdateColors_Click" Content="Apply Changes" Width="160" Margin="0,5,0,5" HorizontalAlignment="Center"/>
<Button x:Name="LoadImageButton" Content="Load Image" Click="LoadImageButton_Click" Width="160" Margin="385,5,0,5" HorizontalAlignment="Left"/>
</Grid>
<Image x:Name="DisplayImage" Grid.Column="1" Margin="10" VerticalAlignment="Center"/>
</Grid>
CreateChart()
이 메서드는 시각적 속성을 추가하고 이를 하나의 생성자(constructor)로 캡슐화하여 3D 차트를 생성합니다.
_chart = new LightningChart();
_chart.ActiveView = ActiveView.View3D;
_chart.View3D.WallOnBack.Visible = false;
_chart.View3D.WallOnBottom.Visible = false;
_chart.View3D.WallOnFront.Visible = false;
_chart.View3D.WallOnLeft.Visible = false;
_chart.View3D.WallOnRight.Visible = false;
_chart.View3D.WallOnTop.Visible = false;
_chart.View3D.XAxisPrimary3D.Visible = false;
_chart.View3D.ZAxisPrimary3D.Visible = false;
_chart.View3D.YAxisPrimary3D.Visible = false;
_chart.View3D.LegendBox.Visible = false;
_chart.Background = System.Windows.Media.Brushes.Black;
_chart.ChartBackground.Color = Colors.Black;
_chart.ChartBackground.GradientFill = GradientFill.Solid;
ChartGrid.Children.Add(_chart);
3D 평면에서 벽이나 각도를 표시할 필요가 없기 때문에, 여러 속성들은 false로 설정되었습니다.

Walls(WallOnFront, WallOnBack, WallOnTop, WallOnBottom, WallOnLeft, WallOnRight)는 축 그리드와 그리드 스트립을 표시하고, 축을 위한 기준 면을 제공하는 데 사용됩니다.
기본적으로 하단, 좌측, 우측, 후면, 전면 벽이 표시되어 있으며, 이들의 AutoHide 속성은 true로 설정되어 있습니다. 뷰를 회전할 때 차트 콘텐츠의 시야를 가리지 않도록, 시야를 방해하는 벽은 일시적으로 숨겨집니다.
특정 벽을 항상 표시하려면 Visible = true와 AutoHide = false로 설정합니다. XGridAxis, YGridAxis, ZGridAxis, GridStripColorX, GridStripColorY, GridStripColorZ, GridStrips 속성을 사용하여 어떤 축에서 그리드를 적용할지 선택하고, 그리드 스트립의 색상을 수정할 수 있습니다. 사용 가능한 속성은 벽의 방향에 따라 달라집니다.
imageToXYZ()
이 메서드는 이미지의 각 픽셀에 대해 X, Y, Z 값을 생성하여, 이후 데이터 세트에 추가될 데이터 집합을 만듭니다.
최소 및 최대 z 값 찾기:
이미지의 모든 픽셀을 스캔합니다. 각 픽셀에 대해 색상 채널(Red, Green, Blue)의 값을 가중치로 계산하는 수식을 기반으로 z 값을 산출합니다. 이 수식은 각 채널에 서로 다른 가중치를 적용하여 색상을 그레이스케일 값으로 변환하는 방식입니다. minZ와 maxZ는 이미지에서 발견된 z 값의 범위를 반영하도록 업데이트됩니다.
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
System.Drawing.Color pixelColor = bitmap.GetPixel(x, y);
double z = 0.299 pixelColor.R + 0.587 pixelColor.G + 0.114 * pixelColor.B;
if (z < minZ) minZ = z;
if (z > maxZ) maxZ = z;
}
}
픽셀 처리 및 정규화
이 단계에서는 이미지의 모든 픽셀을 다시 한 번 처리합니다. 각 픽셀에 대해 z 값을 계산한 뒤, 첫 번째 단계에서 구한 z 값 범위를 기준으로 정규화(즉, 0과 1 사이로 스케일링)하고, 이후 이를 0에서 255 범위로 다시 스케일링합니다.
마지막으로, 좌표계의 변경을 반영하기 위해 픽셀 좌표를 반전하여(y와 x를 서로 반전), z 값과 r, g, b 값과 함께 결과 리스트에 추가합니다.
List<string[]> results = new List<string[]>();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
System.Drawing.Color pixelColor = bitmap.GetPixel(x, y);
int r = pixelColor.R;
int g = pixelColor.G;
int b = pixelColor.B;
double z = 0.299 pixelColor.R + 0.587 pixelColor.G + 0.114 * pixelColor.B;
double normalizedZ = (z - minZ) / (maxZ - minZ);
double scaledZ = normalizedZ;
// Optionally, clamp the scaled Z values
if (scaledZ > 255) scaledZ = 255;
if (scaledZ < 0) scaledZ = 0;
// Invert the coordinates
results.Add([$"{height - y - 1}", $"{width - x - 1}", $"{scaledZ}", $"{r}", $"{g}", $"{b}"]);
}
}
return results;
BuildDataSeries()
이 3D 차트에서는 3D 라인과 포인트로 구성된 시리즈를 사용합니다. 먼저 imageToXYZ 메서드를 사용하여 데이터 세트를 가져와야 합니다:
var data = imageToXYZ(imageURi);
이제 3D 점들의 연속을 만들고, 몇 가지 시각적 속성을 설정합니다:
var pls3d = new PointLineSeries3D
{
AllowUserInteraction = false,
LineVisible = false,
ShowInLegendBox = false,
PointsType = PointsType3D.CompactPointsColor,
PointStyle = { ShapeType = ShapeType.Shape2D, Shape2D = { Shape = Shape.Circle } },
PointsOptimization = PointsRenderOptimization3D.Pixels,
IndividualPointColors = true
};
AllowUserInteraction = false: 시리즈에 대한 사용자 상호작용을 비활성화합니다(예: 포인트를 클릭할 수 없음).
LineVisible = false: 포인트 간을 연결하는 라인을 표시하지 않습니다.
ShowInLegendBox = false: 시리즈가 차트 범례에 표시되지 않습니다.
PointsType = PointsType3D.CompactPointsColor: 포인트 유형을 컴팩트하고 색상이 적용된 포인트로 정의합니다.
PointStyle: 포인트의 스타일을 정의합니다.
ShapeType = ShapeType.Shape2D: 포인트를 2D 형태로 그립니다(3D 컨텍스트에서 표현되지만 형태 자체는 2D).
Shape2D = { Shape = Shape.Circle }: 포인트의 모양을 원형으로 설정합니다.
PointsOptimization = PointsRenderOptimization3D.Pixels: 포인트 렌더링을 픽셀 단위로 최적화합니다.
IndividualPointColors = true: 각 포인트에 개별 색상을 적용할 수 있도록 합니다.
데이터를 위한 몇 가지 설정 변수를 생성합니다:
const int columnslenght = 500;
const int rowslenght = 500;
const int xStart = 10000;
const int zStart = 8000;
const float cells = 20;
columnslenght 및 rowslenght: 열(columns)과 행(rows) 기준으로 정의된 영역 또는 그리드의 크기입니다.
xStart 및 zStart: X축과 Z축에서의 시작 지점을 의미합니다.
cells: 그래프에서 포인트의 크기를 정의합니다.
시리즈에 포함될 포인트들을 위한 배열이 생성되며, 표시할 영역의 모서리(corners)를 저장하기 위한 리스트도 함께 생성됩니다.
var seriesPoints = new SeriesPointCompactColored3D[data.Count];
var corners = new List<(int, int)> { (xStart, zStart) };
시리즈 내 포인트 생성
이 코드 스니펫은 변환된 각 이미지 데이터를 순회하면서 좌표(X, Y, Z)를 조정하고, RGB 값에 따라 각 포인트에 색상을 할당합니다. 이렇게 생성된 포인트들은 seriesPoints 시리즈에 추가됩니다:
for (int i = 0; i < data.Count; i++)
{
var current = data[i];
var x = float.Parse(current[0]) * cells;
var z = float.Parse(current[1]) * cells;
var y = float.Parse(current[2]) cells 2.8f;
int red = int.Parse(current[3]);
int green = int.Parse(current[4]);
int blue = int.Parse(current[5]);
var hue = getHueColor(red, green, blue);
var color = ChartTools.ColorToInt(ChartTools.ColorHSVA(hue, 0.5, Value, Alpha));
seriesPoints[i] = new SeriesPointCompactColored3D
{
X = x,
Y = y,
Z = z,
Color = color
};
}
pls3d.AddPoints(seriesPoints, false);
경계 상자 계산
표시 영역을 결정하기 위해 X축과 Z축에서의 좌표 범위(최소값과 최대값)를 계산합니다:
var (xmin, xmax, zmin, zmax) = (int.MaxValue, int.MinValue, int.MaxValue, int.MinValue);
foreach (var (x, z) in corners)
{
xmin = Math.Min(xmin, x);
xmax = Math.Max(xmax, x);
zmin = Math.Min(zmin, z);
zmax = Math.Max(zmax, z);
}
차트 업데이트
계산된 바운딩 박스(bounding box)를 기준으로 축 범위를 설정하고 차트의 크기를 조정하여 차트를 업데이트합니다. 또한 포인트 개수를 표시하는 텍스트 박스도 함께 갱신되며, 차트 제목은 초기화됩니다. 마지막으로 차트 업데이트를 안전하게 종료합니다.
_chart.BeginUpdate();
_chart.View3D.PointLineSeries3D.DisposeAllAndClear();
_chart.View3D.PointLineSeries3D.Add(pls3d);
_chart.View3D.XAxisPrimary3D.SetRange(xmin, xmax + columnslenght);
_chart.View3D.YAxisPrimary3D.SetRange(0, 100);
_chart.View3D.ZAxisPrimary3D.SetRange(zmin, zmax + rowslenght);
PointsTextBox.Text = seriesPoints.Length.ToString();
_chart.Title.Text = "";
_chart.View3D.Dimensions.SetValues(
200 * (zmax + columnslenght - zmin) / 1000,
200,
200 * (xmax + rowslenght - xmin) / 1000
);
Dispatcher.Invoke(() => _chart.EndUpdate());
getHueColor()
이 메서드는 RGB(Red, Green, Blue) 색상 공간의 색상을 HSV(Hue, Saturation, Value) 색상 공간의 hue 값으로 변환합니다. 이 메서드는 각 데이터 포인트를 매핑하는 과정에서 사용되며, 각 포인트에 색상을 할당하는 데 도움을 줍니다.
RGB 값 정규화:RGB 값 정규화:
// Convert RGB to normalized values
float r = red / 255f;
float g = green / 255f;
float b = blue / 255f;
빨강(red), 초록(green), 파랑(blue)은 0에서 255 범위의 색상 구성 요소 값입니다. HSV 색상 공간에서의 계산에 더 적합하도록, 이 값들은 255로 나누어 0에서 1 범위로 정규화됩니다.
RGB 부품 중에서 최대값과 최소값을 구합니다:
float max = Math.Max(r, Math.Max(g, b));
float min = Math.Min(r, Math.Min(g, b));
float delta = max - min;
float hue = 0f;
max는 정규화된 빨강(red), 초록(green), 파랑(blue) 구성 요소 중 최대값입니다.
min은 최소값입니다.
delta는 max와 min의 차이로, 색상의 “폭(width)”을 나타내며 hue 값을 계산하는 데 중요한 요소입니다.
hue는 계산되어 반환될 값입니다.
델타가 0이 아니라면(즉, 색상이 회색이 아닐 경우) 색조를 계산합니다:
if (delta != 0)
{
if (max == r)
{
hue = 60 * (((g - b) / delta) % 6);
}
else if (max == g)
{
hue = 60 * (((b - r) / delta) + 2);
}
else if (max == b)
{
hue = 60 * (((r - g) / delta) + 4);
}
if (hue < 0)
{
hue += 360;
}
}
else
{
// If delta is zero, the color is grayscale
hue = 0; // or some undefined value
}
delta가 0이 아닌 경우, 최대값을 가지는 RGB 구성 요소(r, g, b)가 무엇인지에 따라 hue 값이 계산됩니다.
계산 결과 hue 값이 음수가 되는 경우, 0에서 360도 범위로 맞추기 위해 360을 더해 보정합니다.
UpdateC()
이 메서드는 “apply Changes” 버튼을 통해 실행되며, 선택된 색상 계수(color factor)를 기준으로 포인트의 색상을 업데이트합니다.

‘*’: 포인트의 Y 값과 곱셈 계수를 기준으로 시작 색상에 값을 더합니다.
‘+’: 고정된 값에 포인트의 Y 값을 더하여 시작 색상을 변경합니다.
‘-’: 포인트의 Y 값에서 고정된 값을 빼서 시작 색상을 수정합니다.
다음 코드 블록은 성능 향상을 위해 차트의 각 시리즈에 포함된 모든 데이터 포인트의 색상을 병렬로 업데이트한 후, 이러한 변경 사항이 반영되도록 차트를 새로 고칩니다:
Parallel.For(0, pointLines.Count, (i) =>
{
SeriesPointCompactColored3D[] colored3Ds = pointLines[i].PointsCompactColored;
for (int x = 0; x < colored3Ds.Length; x++)
{
colored3Ds[x].Color = ChartTools.ColorToInt(ChartTools.ColorHSVA(_Startcolor + colored3Ds[x].Y * Multiplier, Saturation, Value, Alpha));
}
_chart.View3D.PointLineSeries3D[i].InvalidateData();
});
break;
Parallel.For는 여러 스레드에 걸쳐 루프를 병렬로 실행합니다. 이 루프는 0부터 pointLines.Count까지 반복되며, i는 현재 포인트 라인 시리즈의 인덱스를 나타냅니다. 현재 시리즈의 모든 포인트 업데이트가 완료되면, 데이터가 변경되었고 새로 고쳐야 함을 알리기 위해 해당 시리즈에 대해 InvalidateData를 호출합니다.
결론
이 프로젝트는 결과가 매우 만족스러워 개인적으로도 큰 흥미를 느꼈습니다. 최종 이미지를 처음 보았을 때는 이런 프로젝트를 만드는 과정이 복잡할 것이라고 생각했을지도 모릅니다. 하지만 LightningChart .NET이 포인트 할당과 차트 생성을 단순화해 주는 도구들을 제공하기 때문에, 실제로는 생각보다 어렵지 않습니다.
다른 .NET 프로젝트들을 살펴본 적이 있다면, 익숙한 패턴을 발견했을 것입니다. LightningChart 생성자의 인스턴스를 만들고, 시각적 속성을 설정한 다음, 시리즈 유형을 정의하고 데이터 배열을 할당하는 방식입니다.
이러한 일관성은 큰 장점입니다. LC .NET의 인터랙티브 예제나 아티클에서 기본 프로젝트를 쉽게 가져와 필요한 코드를 수정해 사용할 수 있기 때문입니다. 이를 통해 개발 시간을 절약하고, 가장 중요한 요소인 데이터 소스에 집중할 수 있습니다.
3D 지형 모델링의 경우, 이미지에서 각 픽셀을 추출하고 해당 색상을 식별한 뒤, X, Y, Z 좌표를 할당하는 과정으로 이루어집니다.
이미지는 Y가 세로축, X가 가로축인 2차원 평면을 나타냅니다. 여기에 깊이를 추가하기 위해 3차원 평면에서는 Z축을 도입합니다. 각 픽셀 색상의 강도를 기준으로 Z 값을 할당하면, 지형의 높이 맵(heightmap)이 효과적으로 생성됩니다.
모델을 더욱 향상시키기 위해 각 포인트에 “cells”와 같은 추가 속성을 적용할 수 있습니다. 이 곱셈 계수는 포인트를 확장하여, 더 크고 세밀한 지형을 만들어 줍니다.
이 곱셈 계수를 애플리케이션 UI에서 조절 가능한 옵션으로 구현하는 것도 충분히 가능합니다. 이는 실습용 과제로도 매우 적합합니다. 논리가 비교적 논리가 비교적 단순해 보이지 않나요? 처음에는 코드가 다소 복잡해 보일 수 있지만, 점차 다듬고 연습하다 보면 충분히 다룰 수 있게 됩니다.