Android用ネイティブレンダリングPlugin(c++[so])の作成

※この記事は2017年06月09日にqiitaに投稿した内容です。

環境

Unity5.6.1f1 Javaのプラグイン作成ができる状態 Android用Plugin(java[aar])の作成

概要

低レベルネイティブプラグインインターフェースを使用して、c++側でテクスチャの内容を書き換えます https://docs.unity3d.com/ja/540/Manual/NativePluginInterface.html

AndroidStudioとUnityの事前設定

・以下を参照してプロジェクトを作成する(例:プロジェクト名:TestNativeCppRender)  Android用Plugin(java[aar])の作成  今回はaarを出力しないのでプラグインのexportAarタスクは書かなくてもいい  Javaクラスを追加する必要もない

UnityEditorの設定

・プロジェクトを開き、「PlayerSetting」>「OherSetting」>「MultiThreadedRendering」にチェックを入れる

AndroidStudioの設定

c++プロジェクトの追加

・プロジェクトを開き、プロジェクトツリーからプラグイン名(例:unityplugin)を選択する ・右クリックし、「ShowinExplorer」を押し、フォルダを開く ・「プラグイン名(例:unityplugin)」/src/main/に移動し、「cpp」フォルダを作成し、移動する ・「CMakeLists.txt」をテキストエディタ等で作成する  この時点では内容は空のファイルにする ・プロジェクトツリーからプラグイン名(例:unityplugin)を選択する ・右クリックし、「LinkC++ProjectWithGradle」を押す  NDKが「sdk\ndk-bundle」にない場合インストールを促されるのでインストールする   Error:InstallNdkAndsyncprojectをクリックする ・ダイアログの「ProjectPath」で作成した、cppフォルダ内の「CMakeLists.txt」を選択し、OKを押す ・OKを押す ・プロジェクトツリーの根元の部分のタグ(左上)が「Android」になっているので「Project」に変更する

IUnityGraphics インターフェースの追加

・以下のフォルダにある  C:\Program Files\Unity\Editor\Data\PluginAPI ・「PluginAPI」フォルダを[cpp]フォルダの中にフォルダごとコピーし、「Unity」に変更 ・以下のURLにアクセスする  https://bitbucket.org/Unity-Technologies/graphicsdemos/downloads/ ・「リポジトリをダウンロードする」を押し、ファイルを保存する ・内部に入っている「Unity」フォルダを作成した[cpp]フォルダの中にフォルダごとコピーする 「NativeRenderingPlugin/PluginSource/source/Unity」 -> 「cpp/Unity」   IUnityGraphicsXXX.hが6ファイルある

cppファイルの追加

・[cpp]フォルダの中にxxx.cppを追加する(例:native-lib.cpp)  例のnative-lib.cppの内容は下記に記載

CMakeLists.txtの書き換え

・[cpp]フォルダの中の「CMakeLists.txt」を開き、内容を記述する  例のCMakeLists.txtの内容は下記に記載  「add_library」の内部に追加したcppを記載する(例:native-lib.cpp)  ヘッダファイルの入った「Unity」フォルダを追加しているので「include_directories」の中に「Unity」を記述  GLES2.0を使用するので「target_link_libraries」の中に「GLESv2」を記述

exportSoの追加

・プロジェクトツリーから「プラグイン名」>「build.gradle」を開く ・ファイルの最後に下記を追加する  Aarの時と同じ方法で「exportSo」をダブルクリックし、ビルドする  soファイル名はlibから始まらないとならないので自動で付与されるようにしている

def OUTPUT_SONAME='nativecpprender'
def ABI_NAME='armeabi-v7a'
task exportSo(type: Copy){
    from( 'build/intermediates/cmake/release/obj/' + ABI_NAME + '/' )
    into( '../../../Assets/Plugins/Android/' )
    include( 'libnative-lib.so' )
    rename( 'libnative-lib.so', 'lib' + OUTPUT_SONAME + '.so' )
}
exportSo.dependsOn( build )

CMakeLists.txt

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             native-lib.cpp )

# Specifies a path to native header files.
include_directories( Unity/ )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib}
                       GLESv2
                        )

プログラム

テクスチャを作成し、ポインタをc++側に渡し、c++側でテクスチャを黒から赤になるように書き換えています

native-lib.cpp

#include "IUnityInterface.h"
#include "IUnityGraphics.h"
#include <math.h>
#include <stdio.h>
#include <assert.h>
#include <GLES2/gl2.h>

static GLuint   g_textureId = NULL;
static int      g_texWidth;
static int      g_texHeight;
static u_char* g_pBytes = NULL;

#define LOG_PRINTF printf

extern "C" bool SetupNativeTextureRender( void* textureId, int width, int height )
{
    g_textureId = (GLuint)(size_t)textureId;
    g_texWidth = width;
    g_texHeight = height;
    LOG_PRINTF( "SetupNativeTextureRender:%d, %d, %d", g_textureId, g_texWidth, g_texHeight );

    g_pBytes = new u_char[ g_texWidth * g_texHeight * 4 ];
    return true;
}

extern "C" void FinishNativeTextureRender()
{
    if( g_pBytes != NULL )
        delete[] g_pBytes;
    g_pBytes = NULL;
}

static void UNITY_INTERFACE_API
OnRenderEvent( int eventID )
{
    glBindTexture( GL_TEXTURE_2D, g_textureId );

    static u_char s_r = 0;
    u_char* bytes = g_pBytes;
    for( int y = 0; y < g_texHeight; y++ )
    {
        for( int x = 0; x < g_texWidth; x++ )
        {
            int offset = ( ( y * g_texWidth ) + x ) * 4;
            bytes[ offset + 0 ] = s_r;
            bytes[ offset + 1 ] = 0;
            bytes[ offset + 2 ] = 0;
            bytes[ offset + 3 ] = 255;
        }
    }
    glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, g_texWidth, g_texHeight, GL_RGBA, GL_UNSIGNED_BYTE, bytes );
    s_r ++;
    s_r %= 255;
}

extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
GetRenderEventFunc()
{
    return OnRenderEvent;
}

Assets/TestNativeCppRender.cs

using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;

public class TestNativeCppRender : MonoBehaviour
{
    [SerializeField] private RawImage _rawImage = null;
    [SerializeField] private int _width = 512;
    [SerializeField] private int _height = 512;

    //PluginFunction
    [DllImport ("nativecpprender")]
    private static extern bool SetupNativeTextureRender( IntPtr textureId, int width, int height );
    [DllImport ("nativecpprender")]
    private static extern void FinishNativeTextureRender();
    [DllImport ("nativecpprender")]
    private static extern IntPtr GetRenderEventFunc();

    private void Start()
    {
        var texture = new Texture2D( _width, _height, TextureFormat.ARGB32, false );
        _rawImage.texture = texture;
        if( SetupNativeTextureRender( texture.GetNativeTexturePtr(), texture.width, texture.height ) == false )
            return;

        StartCoroutine( NativeTextureRenderLoop() );
    }

    private void OnDestroy()
    {
        FinishNativeTextureRender();
    }

    private IEnumerator NativeTextureRenderLoop()
    {
        while( true )
        {
            yield return new WaitForEndOfFrame();
            GL.IssuePluginEvent( GetRenderEventFunc(), 1 );
        }
    }
}