用VR觀看LoL,這到底是如何實現(xiàn)的

前段時間,網(wǎng)上出現(xiàn)了一段使用HTC vive觀看LoL的視頻,相關鏈接在這里。這是由一位名叫Fire-Proof的大神開發(fā)完成的,并且大神公布了源代碼。作為一名程序員,對牛掰的代碼自然充滿了好奇,用VR觀看LoL,這到底是如何實現(xiàn)的呢?

查看代碼發(fā)現(xiàn),程序完全使用Python開發(fā)(再次感受到Python的強大),包含的代碼并不多,一共也就10幾個文件。


這里寫圖片描述

首先查看main.py。在最下方定義了start_app方法

def start_app():
    app = QApplication(sys.argv)
    tray_icon = MainDialog()
    tray_icon.show()
    app.exec_()

程序創(chuàng)建了一個名為MainDialog的對話框,讓我們看一下MainDialog中有些什么(代碼有刪減)

class MainDialog(QDialog, Ui_MainDialog):
    def __init__(self, parent=None):
        super(MainDialog, self).__init__(parent)
        self.spectate = None
        self.spectate_started = False
        self.setupUi(self)
        self.lol_watcher = ProcessWatcher(b"League of Legends")
        self.lol_watcher.running.connect(self.lol_watcher_update, Qt.QueuedConnection)
        self.lol_watcher.start()
        self.vorpx_watcher = ProcessWatcher(b"vorpControl")
        self.vorpx_watcher.running.connect(self.vorpx_watcher_update,Qt.QueuedConnection)
        self.vorpx_watcher.start()
        self.pushButtonStart.clicked.connect(self.toggle_spectate)
    def toggle_spectate(self):
        if self.spectate_started:
            self.pushButtonStart.setText("Start")
            self.stop_spectate()
            self.spectate_started = False
        else:
            self.pushButtonStart.setText("Stop")
            self.start_spectate()
            self.spectate_started = True
    def start_spectate(self):
        if self.spectate is None:
            self.spectate = SpectateThread()
            self.spectate.error.connect(self.spectate_error, Qt.QueuedConnection)
        self.spectate.start()

對話框創(chuàng)建了一個start按鈕,并將該按鈕的點擊事件綁定到了toggle_spectate方法上,該方法會調(diào)用start_spectate方法,并最終通過SpectateThread創(chuàng)建了一個線程。繼續(xù)查看SpectateThread(代碼有刪減)

class SpectateThread(QThread):
    def __init__(self):
        super(SpectateThread, self).__init__()
        self.spectate = None
    def run(self, *args, **kwargs):
        self.spectate = VRSpectate()
        self.spectate.run()    

看來玄機在VRSpectate方法中。在查看VRSpectate之前,注意到MainDailog中開啟了兩個線程來檢測"League of Legends"進程和"vorpControl"進程。前一個很好理解,觀看LoL自然需要"League of Legends"進程,那這個"vorpControl"進程又是什么鬼呢?這是vorpX軟件的運行進程,用來將非VR游戲的畫面轉(zhuǎn)換成VR模式并同步到頭顯設備中??吹竭@里是不是有種被欺騙的感覺,通過VR觀看LoL是通過軟件來實現(xiàn)的?答案確實如此,Python程序并不負責轉(zhuǎn)換LoL的畫面到VR設備上,它只是不停的檢測VR頭顯和控制器的狀態(tài),并以此調(diào)整LoL的畫面,這樣就可以通過轉(zhuǎn)動頭部或觸發(fā)控制器來以不同的視角觀看LoL畫面(比如站立時鳥瞰整個戰(zhàn)場,蹲下來觀看局部戰(zhàn)場)。

VRSepecaterun方法中,會不停的調(diào)用_next_frame 方法來更新畫面

def _next_frame(self):
  self.scale = self.z_offset
  controller_frame = self.vr.controller_frame()
  # Camera movement
  if len(controller_frame) == 2:
    if all(controller_frame.button_pressed("trigger")):
      self.yaw_offset += self.prev_controller_frame.relative_rotation - controller_frame.relative_rotation
      self.z_offset += (self.prev_controller_frame.relative_distance - controller_frame.relative_distance) * self.z_offset * 2
      self._move_offset(self.prev_controller_frame.position(), controller_frame.position())
    elif any(controller_frame.button_pressed("trigger")):
        active_controller = [i for i, x in enumerate(controller_frame.button_pressed("trigger")) if x][0]
        self._move_offset(self.prev_controller_frame.position(active_controller), controller_frame.position(active_controller))
 if len(controller_frame) == 1:
   if any(controller_frame.button_pressed("trigger")):
     self._move_offset(self.prev_controller_frame.position(), controller_frame.position(), update_z=True)

  self.prev_controller_frame = controller_frame
  pos = self.vr.hmd.position()
  self.lol.yaw = pos.yaw + self.yaw_offset
  self.lol.pitch = pos.pitch*-1
  self.lol.x = (pos.x*self.scale) + self.x_offset
  self.lol.y = (pos.y*self.scale) + self.y_offset
  self.lol.z = (pos.z*self.scale) + self.z_offset

該方法會首先來檢測VR控制器是否觸發(fā),并獲取VR頭顯的位置信息,根據(jù)這些數(shù)據(jù)來更新LoL的畫面信息。其中訪問VR設備是通過調(diào)用openvr庫來實現(xiàn)的,并在OpenVR.py中做了進一步的封裝。那么更新LoL畫面如何實現(xiàn)呢,答案是修改LoL進程的內(nèi)存。

在LeagueOfLegends.py中,封裝了修改LoL進程內(nèi)存的方法,例如執(zhí)行self.lol.x = 520,實際上執(zhí)行的是下面的代碼

 @x.setter
 def x(self, val):
   self._x.write(val, type="float")

而更底層的實現(xiàn)是通過memorpy目錄中的相關代碼來實現(xiàn)的。那么如何知道應該修改哪些內(nèi)存位置呢(畢竟直接修改內(nèi)存一個不慎可能進程就掛了)。涉及到畫面信息的內(nèi)存位置都是通過一個cam_base_address 進行偏移得到的,作者給出了cam_base_address 的搜索方法:

HOW TO: Find base addr

  1. Move screen to lowest y coord
  2. Scan for float = 520
  3. Move screen to highest y coord
  4. Scan for float = 14765
  5. Select the address that will updated the camera pos when changed
  6. Right click this address and click "find out what accesses this address"
  7. Select an element and press more information
  8. Copy the value which is listed at the end of the string "The value of the pointer needed to find this ..."
  9. Search for this this value as an 8-bit hex value
  10. Now search for the first address found
  11. You should now have found a "green" address, select this and double click the address
  12. You should now be able to see a string similar to "League of Legends.exe"+13D4280 <-- base addr

看起來作者是通過某些內(nèi)存查看軟件(例如IDA)來進行分析得到的內(nèi)存地址(在此膜拜大神)。

了解了這些,基本就明白程序的技術(shù)原理了,剩下的部分感興趣的同學可以自行查看源碼,當然有條件可以上設備體驗一番。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容