VTK更新多个渲染窗口的位置

8 投票
1 回答
5577 浏览
提问于 2025-04-18 11:55

我在写一个Python VTK应用的时候遇到了一点问题,想要同时运行多个渲染窗口。这个应用的目的是为了在两个不同的视角下渲染一个3D模型,适用于立体显示(也就是左视图和右视图)。但是,我在同时更新每个窗口的摄像头时遇到了麻烦。目前我有两个几乎一模一样的渲染管道,每个都有自己的vtkCameravtkRenderWindowvtkRenderervtkRenderWindowInteractor,唯一的不同是右边的摄像头在X轴上移动了30个单位。

每个渲染窗口的交互器都是通过vtkRenderWindowInteractor.AddObserver()方法来更新的,这个方法会调用一个简单的函数来重置摄像头到它们的原始位置和方向。最大的问题是,这个更新似乎只能在一个窗口上发生,特别是当前获得焦点的窗口。就好像交互器的计时器在失去焦点后就停止了一样。此外,当我按住鼠标(这样就可以移动摄像头)时,渲染的图像开始“漂移”,重置到一个越来越不正确的位置,尽管我已经把坐标硬编码到函数里了。

显然,我对VTK非常陌生,很多事情都让人困惑,因为很多细节都隐藏在后台,所以如果能得到一些帮助就太好了。我的代码在下面。谢谢大家!

from vtk import*
from parse import *
import os
import time, signal, threading

def ParseSIG(signum, stack):
        print signum
        return

class vtkGyroCallback():
        def __init__(self):
                pass
        def execute(self, obj, event):
                #Modified segment to accept input for leftCam position 
                gyro = (raw_input())
                xyz = parse("{} {} {}", gyro)
                #This still prints every 100ms, but camera doesn't update!
                print xyz

                #These arguments are updated and the call is made.
                self.leftCam.SetPosition(float(xyz[0]), float(xyz[1]), float(xyz[2]))
                self.leftCam.SetFocalPoint(0,0,0)
                self.leftCam.SetViewUp(0,1,0)
                self.leftCam.OrthogonalizeViewUp()

                self.rightCam.SetPosition(10, 40, 100)
                self.rightCam.SetFocalPoint(0,0,0)
                self.rightCam.SetViewUp(0,1,0)
                self.rightCam.OrthogonalizeViewUp()

                #Just a guess
                obj.Update()
                return

def main():

        # create two cameras
        cameraR = vtkCamera()
        cameraR.SetPosition(0,0,200)
        cameraR.SetFocalPoint(0,0,0)

        cameraL = vtkCamera()
        cameraL.SetPosition(40,0,200)
        cameraL.SetFocalPoint(0,0,0)



        # create a rendering window and renderer
        renR = vtkRenderer()
        renR.SetActiveCamera(cameraR)

        renL = vtkRenderer()
        renL.SetActiveCamera(cameraL)

        # create source
        reader = vtkPolyDataReader()
        path = "/home/compilezone/Documents/3DSlicer/SlicerScenes/LegoModel-6_25/Model_5_blood.vtk"
        reader.SetFileName(path)
        reader.Update()

        # create render window

        renWinR = vtkRenderWindow()
        renWinR.AddRenderer(renR)
        renWinR.SetWindowName("Right")

        renWinL = vtkRenderWindow()
        renWinL.AddRenderer(renL)
        renWinL.SetWindowName("Left")

        # create a render window interactor
        irenR = vtkRenderWindowInteractor()
        irenR.SetRenderWindow(renWinR)

        irenL = vtkRenderWindowInteractor()
        irenL.SetRenderWindow(renWinL)

        # mapper
        mapper = vtkPolyDataMapper()
        mapper.SetInput(reader.GetOutput())

        # actor
        actor = vtkActor()
        actor.SetMapper(mapper)

        # assign actor to the renderer
        renR.AddActor(actor)
        renL.AddActor(actor)

        # enable user interface interactor
        renWinR.Render()
        renWinL.Render()
        irenR.Initialize()
        irenL.Initialize()

        #Create callback object for camera manipulation
        cb = vtkGyroCallback()
        cb.rightCam = cameraR
        cb.leftCam = cameraL
        renWinR.AddObserver('InteractionEvent', cb.execute)
        renWinL.AddObserver('InteractionEvent', cb.execute)
        irenR.AddObserver('TimerEvent', cb.execute)
        irenL.AddObserver('TimerEvent', cb.execute)
        timerIDR = irenR.CreateRepeatingTimer(100)
        timerIDL = irenL.CreateRepeatingTimer(100)

        irenR.Start()
        irenL.Start()

if __name__ == '__main__':
    main()

编辑:

经过进一步查看,似乎在鼠标点击事件后,计时器事件不会连续触发多次,我不知道为什么。

编辑2:算了,实际上它们肯定是触发的,我在代码中嵌入了一些测试输出。我修改了代码,让用户输入self.leftCam.SetPosition()调用中的参数(把硬编码的“10, 40, 100”替换成三个输入变量),然后将一个简单的脚本的输出(显示三个随机值)传递到我的主程序中。这样做应该能让渲染窗口的位置不断变化。但实际上,什么都没有发生,直到我点击屏幕,这时预期的功能才开始。整个过程中,计时器事件仍然在触发,输入也在被接受,但摄像头就是不更新,直到在它们的窗口内发生鼠标事件。这是怎么回事?

编辑3:我又深入挖掘了一下,发现每次交互事件中调用的vtkObject::InvokeEvent()方法里有一个焦点循环,它会覆盖所有与当前焦点对象无关的观察者。我打算调查一下是否有办法移除焦点,这样就可以绕过这个焦点循环,转而进入处理非焦点对象的循环。

1 个回答

9

这个解决方案其实很简单,但由于VTK提供的文档质量不高,我不得不自己翻阅源代码来找到答案。基本上,你只需要通过你用来处理TimerEvent的回调方法,让每个交互器的Render()调用变成伪线程。我是通过给每个交互器添加ID属性来实现的(下面的代码中可以看到)。你会发现,每当irenR交互器的内部计时器触发TimerEvent时(irenR负责右眼),irenLRender()函数就会被调用,反之亦然。

为了解决这个问题,我首先意识到标准的交互器功能(比如鼠标事件等)是正常工作的。于是我在vtkRenderWindowInteractor.cxx的源代码中查找,发现这些方法实际上是抽象到各个vtkInteractorStyle实现中的。在查看vtkInteractorStyleTrackball.cxx的源代码时,我发现vtkRenderWindowInteractor类中实际上有一个Render()函数。真是让人意外!文档里可没提到这一点!

不过,不幸的是,同时进行两个渲染其实是非常慢的。如果我只用一个窗口来做这个方法(这时就变得没必要了),运行得很好。但是如果有第二个窗口,帧率就会下降。唉,这也没办法。

这是我修正后的代码(终于可以开始做我原本应该开发的东西了):

from vtk import*
from parse import *
import os
import time, signal, threading

def ParseSIG(signum, stack):
        print signum
        return

class vtkGyroCallback():
        def __init__(self):
                pass
        def execute(self, obj, event):
                #Modified segment to accept input for leftCam position 
                gyro = (raw_input())
                xyz = parse("{} {} {}", gyro)
                #print xyz 



                # "Thread" the renders. Left is called on a right TimerEvent and right is called on a left TimerEvent.
                if obj.ID == 1 and event == 'TimerEvent':
                        self.leftCam.SetPosition(float(xyz[0]), float(xyz[1]), float(xyz[2]))
                        self.irenL.Render()
                        #print "Left"
                elif obj.ID == 2 and event == 'TimerEvent':
                        self.rightCam.SetPosition(float(xyz[0]), float(xyz[1]), float(xyz[2]))
                        self.irenR.Render()
                        #print "Right"
                return

def main():

        # create two cameras
        cameraR = vtkCamera()
        cameraR.SetPosition(0,0,200)
        cameraR.SetFocalPoint(0,0,0)

        cameraL = vtkCamera()
        cameraL.SetPosition(40,0,200)
        cameraL.SetFocalPoint(0,0,0)



        # create a rendering window and renderer
        renR = vtkRenderer()
        renR.SetActiveCamera(cameraR)

        renL = vtkRenderer()
        renL.SetActiveCamera(cameraL)

        # create source
        reader = vtkPolyDataReader()
        path = "/home/compilezone/Documents/3DSlicer/SlicerScenes/LegoModel-6_25/Model_5_blood.vtk"
        reader.SetFileName(path)
        reader.Update()

        # create render window

        renWinR = vtkRenderWindow()
        renWinR.AddRenderer(renR)
        renWinR.SetWindowName("Right")

        renWinL = vtkRenderWindow()
        renWinL.AddRenderer(renL)
        renWinL.SetWindowName("Left")

        # create a render window interactor
        irenR = vtkRenderWindowInteractor()
        irenR.SetRenderWindow(renWinR)

        irenL = vtkRenderWindowInteractor()
        irenL.SetRenderWindow(renWinL)

        # mapper
        mapper = vtkPolyDataMapper()
        mapper.SetInput(reader.GetOutput())

        # actor
        actor = vtkActor()
        actor.SetMapper(mapper)

        # assign actor to the renderer
        renR.AddActor(actor)
        renL.AddActor(actor)


        # enable user interface interactor
        renWinR.Render()
        renWinL.Render()
        irenR.Initialize()
        irenL.Initialize()

        #Create callback object for camera manipulation
        cb = vtkGyroCallback()
        cb.rightCam = renR.GetActiveCamera()#cameraR
        cb.leftCam = renL.GetActiveCamera()#cameraL
        cb.irenR = irenR
        cb.irenL = irenL

        irenR.ID = 1
        irenL.ID = 2
        irenR.AddObserver('TimerEvent', cb.execute)
        irenL.AddObserver('TimerEvent', cb.execute)
        timerIDR = irenR.CreateRepeatingTimer(100)
        timerIDL = irenL.CreateRepeatingTimer(100)

        irenL.Start()
        irenR.Start()

if __name__ == '__main__':

        main()

撰写回答