Skip to content

Instantly share code, notes, and snippets.

@hecomi
Created January 24, 2014 01:40
Show Gist options
  • Save hecomi/8590584 to your computer and use it in GitHub Desktop.
Save hecomi/8590584 to your computer and use it in GitHub Desktop.
** はじめに
Oculus Rift などのアプリのデモを行っていると、HMD をつけていない観客がデモ動画を脇から見る際、画面には Oculus Rift 用の映像が左右に出てしまっていて、ゲーム画面が小さくなってしまったり歪んだ映像になってしまっているため、デモ効果が薄れてしまう懸念があります。ということを、Twitter で [https://twitter.com/koukiwf:title=@koukiwf] さんが指摘されていまして、その解決法をブロマガの方で提案されています。
-[http://ch.nicovideo.jp/whitetail/blomaga/ar437125:title]
そこで、次のように述べられていました。
>>
unity側で複数窓出せるようになるまでの、力ずく対症法です。
<<
先日のエントリ([http://tips.hecomi.com/entry/2014/01/16/011727:title])を利用すれば、もしかして逆に Unity の世界のテクスチャを外の世界で使えるんじゃないかと思い、試してみましたので共有します。
** 環境
- Mac OS X 10.9.1
- Unity 4.3.2f1 (Pro)
** デモ
** 原理の説明
原理的には単純で、Render Texture (プロ版限定)を利用してカメラ画をテクスチャ化し、その生データを DLL (bundle) 経由で貰ってきて、それを DLL 側で生成したウィンドウに描画するという形になります。まずは C++ 側で必要な最小限のコードを以下に示します。
<b>externalWindow.h</b>
>|cpp|
extern "C" {
void openWindow(const char* window_name);
void destroyWindow(const char* window_name);
void setTexture(const char* window_name, unsigned char* const data, int width, int height);
}
||<
<b>externalWindow.cpp</b>
>|cpp|
#include <opencv2/opencv.hpp>
#include <cstdio>
#include "opencv.h"
void openWindow(const char* window_name)
{
cv::namedWindow(window_name, CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO);
}
void destroyWindow(const char* window_name)
{
cv::destroyWindow(window_name);
}
void setTexture(const char* window_name, unsigned char* const data, int width, int height)
{
cv::Mat img(height, width, CV_8UC4, data);
cv::imshow(window_name, img);
}
||<
別ウィンドウを開くのとテクスチャの表示には OpenCV を利用しています(OpenCV の利用は前回のエントリを参照)。これを Xcode で 32bit ビルドして bundle を作成します。次に、これを利用する Unity 側のコードを示します。
>|cs|
using UnityEngine;
using System;
using System.Runtime.InteropServices;
public class ExternalWindow : MonoBehaviour {
[DllImport ("OpenCVTest")]
private static extern IntPtr openWindow(string windowName);
[DllImport ("OpenCVTest")]
private static extern void destroyWindow(string windowName);
[DllImport ("OpenCVTest")]
private static extern void setTexture(string windowName, IntPtr data, int width, int height);
public string windowName = "My Camera";
public RenderTexture renderTexture;
public Camera renderedCamera;
private Texture2D cameraTexture_;
private Color32[] cameraTexturePixels_;
private GCHandle cameraTexturePixelsHandle_;
private IntPtr cameraTexturePixelsPtr_;
void Awake()
{
openWindow(windowName);
cameraTexture_ = new Texture2D(renderTexture.width, renderTexture.width, TextureFormat.ARGB32, false);
renderedCamera.targetTexture = renderTexture;
}
void Update()
{
RenderTexture activeRenderTexture = RenderTexture.active;
RenderTexture.active = renderTexture;
renderedCamera.Render();
cameraTexture_.ReadPixels(new Rect(0.0f, 0.0f, renderTexture.width, renderTexture.height), 0, 0);
cameraTexture_.Apply();
RenderTexture.active = activeRenderTexture;
cameraTexturePixels_ = cameraTexture_.GetPixels32();
cameraTexturePixelsHandle_ = GCHandle.Alloc(cameraTexturePixels_, GCHandleType.Pinned);
cameraTexturePixelsPtr_ = cameraTexturePixelsHandle_.AddrOfPinnedObject();
setTexture(windowName, cameraTexturePixelsPtr_, cameraTexture_.width, cameraTexture_.height);
cameraTexturePixelsHandle_.Free();
}
void OnApplicationQuit()
{
destroyWindow(windowName);
}
}
||<
Render Texture 周りのコードは以下の Unity のフォーラムを参考にしています。
- [http://forum.unity3d.com/threads/98827-Render-Texture-explanation:title]
これを任意の GameObject にアタッチして、大きさやレンダリングの方法を指定した Render Texture と、その Render Texture をアタッチしたターゲットとなるカメラをエディタで指定して実行すると、その Render Texture を別ウィンドウに描画する形になります。
ただ、小さなサイズの場合は問題ないのですが、テクスチャサイズが大きくなるとフレームレートが顕著に低くなってしまいます。RenderTexture への描画、カメラ画の ReadPixels での取得、またそれにより GetPixels32 の再呼び出し、ネイティブ側の描画などなど色々重い要素が含まれています。そこで色々と対策を講じてみました。
** ネイティブの別スレッド化
ネイティブ側の描画でブロッキングが起きているのでココを別スレッド化してみます。
ExternalWindow.h
>|cpp|
extern "C" {
void* openWindow(const char* window_name, int width, int height);
void destroyWindow(void* window);
void drawTextureOnWindow(void* window, unsigned char* const data);
}
||<
ExternalWindow.cpp
>|cpp|
#include <opencv2/opencv.hpp>
#include <string>
#include <cstdio>
#include <thread>
#include "opencv.h"
class ExternalWindow
{
public:
ExternalWindow(const std::string& window_name, int width, int height)
: window_name_(window_name), width_(width), height_(height),
texture_(height, width, CV_8UC4)
{
cv::namedWindow(window_name_, CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO);
}
~ExternalWindow()
{
if ( drawThread_.joinable() ) {
drawThread_.join();
}
cv::destroyWindow(window_name_.c_str());
}
void draw(unsigned char* data)
{
if ( drawThread_.joinable() ) {
drawThread_.join();
}
std::memcpy(texture_.data, data, texture_.total() * texture_.elemSize());
drawThread_ = std::thread([this] {
cv::imshow(window_name_, texture_);
});
}
private:
const std::string window_name_;
const int width_, height_;
cv::Mat texture_;
std::thread drawThread_;
};
void* openWindow(const char* window_name, int width, int height)
{
return new ExternalWindow(window_name, width, height);
}
void destroyWindow(void* window)
{
delete static_cast<ExternalWindow*>(window);
}
void drawTextureOnWindow(void* window, unsigned char* const data)
{
static_cast<ExternalWindow*>(window)->draw(data);
}
||<
これでちょっと軽くなりました。
** OnRenderImage の利用
先程は RenderTexture.active を差し替えていましたが、プロ版であれば使えるポストエフェクトの <b>OnRenderImage</b> を利用すると以下の様な短いコードでカメラ画を利用できます。
- [http://docs.unity3d.com/Documentation/ScriptReference/MonoBehaviour.OnRenderImage.html:title]
>|cs|
using UnityEngine;
using System;
using System.Runtime.InteropServices;
public class ExternalWindow : MonoBehaviour {
[DllImport ("ExternalWindow")]
private static extern IntPtr openWindow(string windowName, int width, int height);
[DllImport ("ExternalWindow")]
private static extern void destroyWindow(IntPtr window);
[DllImport ("ExternalWindow")]
private static extern void drawTextureOnWindow(IntPtr window, IntPtr data);
private IntPtr window_;
public string windowName = "My Camera";
public RenderTexture renderTexture;
private Texture2D cameraTexture_;
private Color32[] cameraTexturePixels_;
private GCHandle cameraTexturePixelsHandle_;
private IntPtr cameraTexturePixelsPtr_;
void Start()
{
window_ = openWindow(windowName, Screen.width, Screen.height);
cameraTexture_ = new Texture2D(Screen.width, Screen.height, TextureFormat.ARGB32, false);
}
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
cameraTexture_.ReadPixels(new Rect(0.0f, 0.0f, src.width, src.height), 0, 0);
cameraTexturePixels_ = cameraTexture_.GetPixels32();
cameraTexturePixelsHandle_ = GCHandle.Alloc(cameraTexturePixels_, GCHandleType.Pinned);
cameraTexturePixelsPtr_ = cameraTexturePixelsHandle_.AddrOfPinnedObject();
drawTextureOnWindow(window_, cameraTexturePixelsPtr_);
Graphics.Blit(src, dest);
}
void OnApplicationQuit()
{
cameraTexturePixelsHandle_.Free();
destroyWindow(window_);
}
}
||<
ただこれでも未だ重いです。。
** おわりに
次回は、Low-level Native Plugin Interface を利用して見たいと思います。
**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment