VTK 의 Renderer를 여러개 사용하는 방법을 공부한 것을 간단하게 정리하려고 한다. 개념적인 부분에 대한 설명에서는 개인적인 이해에 따라 작성한 것이므로 잘못된 내용도 있을 수 있다.
1.개요
VTK 의 RenderWindow는 Rendering 수행을 관리하는 Renderer 를 등록하여 관리한다.
그리고 Renderer는 독립적인 버퍼를 갖고 활성화된 Camera 정보를 이용하여 Actor와 같은 Prop을 그린다.
RenderWindow는 컨트롤마다 한개만을 갖고 있으나 Renderer 는 여러개를 갖고 독립적으로 관리할 수 있다.
여러개의 Renderer 를 만들고 원하는 목적에 따라서 관리한다면 어떻게 활용해야 할 것인가..
2. Multi Renderer Viewport
여러개의 Renderer 를 사용하는 방법은 간단한다. 필요한 Renderer 를 만들어서 RenderWindow 에 추가만 하면된다. 이때 RenderWindow의 영역 즉 viewport를 지정하고 그 영역에 그린다.
만약에 viewport 가 중첩이 될 경우에는 마지막에 추가한 Renderer가 우선순위를 갖고 덮어서 그리게 된다.
public void TestRenderMultiViewport()
{
var rendererBg = vtkRenderer.New();
var rendererActor = vtkRenderer.New();
renderWindow.AddRenderer(rendererActor);
renderWindow.AddRenderer(rendererBg);
rendererBg.SetViewport(0, 0, 0.5, 1);
rendererActor.SetViewport(0.5, 0, 1, 1);
rendererBg.SetBackground(VtkColorUtils.GetNamedColor3d("White").ToIntPtr());
rendererActor.SetBackground(VtkColorUtils.GetNamedColor3d("White").ToIntPtr());
rendererBg.AddActor(MakeActor("Red"));
rendererActor.AddActor(MakeActor("Blue"));
rendererActor.SetActiveCamera(rendererBg.GetActiveCamera());
rendererBg.ResetCamera();
VtkViewportUtils.ViewportBorder(rendererBg, "Yellow", true);
VtkViewportUtils.ViewportBorder(rendererActor, "Yellow", true);
}
2-1. Camera 제어 일치
Renderer 별 독립적인 camera를 동일하게 설정하면 Interaction 을 일치화 할 수 있다.
rendererActor.SetActiveCamera(rendererBg.GetActiveCamera());
rendererBg.ResetCamera();
3.Multi Layer Renderer
Multi layer 는 포토샵의 layer처럼 같은 viewport 영역에 renderer를 겹쳐서 그리는 방법이다. 이때 Renderer는 layer 숫자를 지정하게 되고 0번 layer는 배경 역할을 하며 나머지 renderer 들의 배경은 투명한 상태에서 그리게 된다.
첫번째 그림은 같은 layer에 있기 때문에 Z값에 따라서 actor 들이 겹쳐서 그려진다. 반면 다음 그림에서는 상관없이 volume의 영상 위에 axes actor를 그리고 있다.
3-1. Multi Layer Interaction
같은 영역에서 여러개의 layer를 그리기 때문에 interaction 을 수행할 renderer를 지정하는 작업도 필요하다. 만약에 별도의 지정이 없으면 Layer 숫자가 높은 쪽의 Interaction이 우선순위를 갖는다.
아래의 영상은 layer0의 빨간 원통이 layer1의 파란 원통에 가려져 그려지고 파란원통이 먼저 interaction이 된다.
이를 변경하기 위해 원하는 renderer.InteractiveOn() 명령으로 변경하고 나머지 renderer는 Off를 해야한다.
if (keyInput == "0")
{
// interactive on 변경
renBg.InteractiveOn();
}
else if (keyInput == "1")
{
// interactive on 변경
renActor.InteractiveOn();
}
3-2. Layer 변경
Renderer의 layer는 필요에 따라 언제든지 변경이 가능하다.
아래의 영상에서는 4를 누르면 빨간 원통 Renderer가 layer0이 되고, 5를 누르면 파란 원통의 Renderer가 layer0 이 되면서 배경으로 넘어간다.
else if (keyInput == "4")
{
// Layer 변경
renBg.SetLayer(0);
renActor.SetLayer(1);
}
else if (keyInput == "5")
{
// Layer 변경
renActor.SetLayer(0);
renBg.SetLayer(1);
}
3-3. PreserveColorBuffer
PreserveColorBuffer는 기본적으로 이전 render를 통해 그렸던 이미지를 유지할 것인지를 묻는다. 이는 Renderer가 어떤 layer에 있는지에 따라 동작하는 것이 다르다. 왜냐면 layer의 순서에 따라 renderer의 영상을 덮어 그리기 때문이다.
Layer # | BufferOn | BufferOff |
0 | 중첩그리기 | 기본동작 |
1 | 기본동작 | layer0 가려지고 layer1이 배경 layer가 됨 |
아래의 영상에서는 2번을 누르면 빨간 원통의 PreserColorBuffer 상태를 toggle 하고 3번을 누르면 파란원통을 제어한다.
3-4 동작 영상
3-4 구현코드
public void TestRenderMultyLayer()
{
var rendererBg = vtkRenderer.New();
var rendererActor = vtkRenderer.New();
renderWindow.SetNumberOfLayers(2);
renderWindow.AddRenderer(rendererBg);
renderWindow.AddRenderer(rendererActor);
rendererBg.SetBackground(VtkColorUtils.GetNamedColor3d("White").ToIntPtr());
rendererActor.SetBackground(VtkColorUtils.GetNamedColor3d("Aquamarine").ToIntPtr());
rendererBg.AddActor(MakeActor("Red"));
rendererActor.AddActor(MakeActor("Blue"));
rendererBg.AddViewProp(cornerAnnotation);
rendererBg.SetLayer(0);
rendererActor.SetLayer(1);
var switchStyle = (vtkInteractorStyleSwitch)interactor.GetInteractorStyle();
var interactStyle = switchStyle.GetCurrentStyle();
interactStyle.CharEvt += (sender, args) =>
{
// Ignore StereoRender on/off event
if (interactor.GetKeySym() == "3")
return;
interactStyle.OnChar();
};
interactStyle.KeyPressEvt += InteractorPriority_KeyPressEvt;
}
private void InteractorPriority_KeyPressEvt(vtkObject sender, vtkObjectEventArgs e)
{
var _interactorStyle = (vtkInteractorStyle) sender;
var _interactor = _interactorStyle.GetInteractor();
var renWin = _interactor.GetRenderWindow();
var renders = renWin.GetRenderers();
var keyInput = _interactor.GetKeySym();
// Iterator 방식의 데이터 접근
var itr = renders.NewIterator();
if ((keyInput == "0") || (keyInput == "1"))
{
itr.InitTraversal();
for (itr.GoToFirstItem(); itr.IsDoneWithTraversal() != 1; itr.GoToNextItem())
{
var _ren = (vtkRenderer) itr.GetCurrentObject();
// 모든 renderer의 interactive off
_ren.InteractiveOff();
}
}
// Collection 방식의 데이터 접근
var coll = itr.GetCollection();
var renBg = (vtkRenderer) coll.GetItemAsObject(0);
var renActor = (vtkRenderer) coll.GetItemAsObject(1);
if (keyInput == "0")
{
// interactive on 변경
renBg.InteractiveOn();
}
else if (keyInput == "1")
{
// interactive on 변경
renActor.InteractiveOn();
}
else if (keyInput == "2")
{
if (renBg.GetPreserveColorBuffer() == 0)
renBg.PreserveColorBufferOn();
else
renBg.PreserveColorBufferOff();
}
else if (keyInput == "3")
{
if (renActor.GetPreserveColorBuffer() == 0)
renActor.PreserveColorBufferOn();
else
renActor.PreserveColorBufferOff();
}
else if (keyInput == "4")
{
// Layer 변경 및 0번 layer interactive on
renBg.SetLayer(0);
renActor.SetLayer(1);
}
else if (keyInput == "5")
{
// Layer 변경 및 0번 layer interactive on
renActor.SetLayer(0);
renBg.SetLayer(1);
}
cornerAnnotation.SetText(0, $"KeyPressed : {keyInput}");
cornerAnnotation.SetText(1,
$"PreserveColorBuffer: {renBg.GetPreserveColorBuffer()}, {renActor.GetPreserveColorBuffer()}");
Debug.WriteLine(
$"PreserveColorBuffer: {renBg.GetPreserveColorBuffer()}, {renActor.GetPreserveColorBuffer()}");
renWin.Render();
}