在屏幕点读取像素颜色(有效)

时间:2017-05-02 06:17:54

标签: c# android c++ unity3d

我需要在屏幕上读取C#Unity3D中的像素颜色。

我正在使用Render Texture和ReadPixels方法。 每0.5秒使用它对性能有害(即使我的渲染纹理大小为128x128px),我正试图找到另一种方法来更快地获得这些数据。 我在某处读到可以直接使用PreferenceDataStore,但我不知道如何使用它?

我需要检测在这个地方(屏幕上的点)是否是某种东西。

这是我目前的代码。

glReadPixels

1 个答案:

答案 0 :(得分:3)

您必须使用glReadPixels。通过从OnPostRender函数中的C#调用它可以更容易实现,但是你不能再这样做了。您必须使用GL.IssuePluginEvent来调用将截取屏幕截图的功能。

您还需要位于<UnityInstallationDirecory>\Editor\Data\PluginAPI的Unity C ++ API标头( IUnityInterface.h IUnityGraphics.h )。

我创建了一个名为 UnityPluginHeaders 的文件夹,并将 IUnityInterface.h IUnityGraphics.h 头文件放入其中,以便可以导入它们使用#include "UnityPluginHeaders/IUnityInterface.h"#include "UnityPluginHeaders/IUnityGraphics.h"

C ++ ScreenPointPixel.h):

#ifndef ANDROIDSCREENSHOT_NATIVE_LIB_H
#define ANDROIDSCREENSHOT_NATIVE_LIB_H

#define DLLExport __declspec(dllexport)

extern "C"
{
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY)
DLLExport void initScreenPointPixel(void* buffer, int x, int y, int width, int height);
DLLExport void updateScreenPointPixelBufferPointer(void* buffer);
DLLExport void updateScreenPointPixelCoordinate(int x, int y);
DLLExport void updateScreenPointPixelSize(int width, int height);
int GetScreenPixels(void* buffer, int x, int y, int width, int height);

#else
void initScreenPointPixel(void *buffer, int x, int y, int width, int height);
void updateScreenPointPixelBufferPointer(void *buffer);
void updateScreenPointPixelCoordinate(int x, int y);
void updateScreenPointPixelSize(int width, int height);

int GetScreenPixels(void *buffer, int x, int y, int width, int height);
#endif
}
#endif //ANDROIDSCREENSHOT_NATIVE_LIB_H

C ++ ScreenPointPixel.cpp):

#include "ScreenPointPixel.h"

#include <string>
#include <stdlib.h>

//For Debugging
//#include "DebugCPP.h"
//http://stackoverflow.com/questions/43732825/use-debug-log-from-c/43735531#43735531

//Unity Headers
#include "UnityPluginHeaders/IUnityInterface.h"
#include "UnityPluginHeaders/IUnityGraphics.h"


//Headers for Windows
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY)
#include <windows.h>
#include <gl/GL.h>
#include <gl/GLU.h>
#include <stdlib.h>
#include "glext.h"
#pragma comment(lib, "opengl32.lib")

//--------------------------------------------------

//Headers for Android
#elif defined(ANDROID) || defined(__ANDROID__)
#include <jni.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
//Link lGLESv2 in the CMakeList.txt file
//LOCAL_LDLIBS += −lGLESv2

//--------------------------------------------------

//Headers for MAC and iOS
//http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system
#elif defined(__APPLE__) && defined(__MACH__)
//Apple OSX and iOS (Darwin)
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1
//iOS in Xcode simulator
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#elif TARGET_OS_IPHONE == 1
//iOS on iPhone, iPad, etc.
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#elif TARGET_OS_MAC == 1
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>
#endif

//--------------------------------------------------

//Headers for Linux
#elif defined(__linux__)
#include <GL/gl.h>
#include <GL/glu.h>
#endif

static void* screenPointPixelData = nullptr;
static int _x;
static int _y;
static int _width;
static int _height;

//----------------------------Enable Screenshot-----------------------------
void initScreenPointPixel(void* buffer, int x, int y, int width, int height) {
    screenPointPixelData = buffer;
    _x = x;
    _y = y;
    _width = width;
    _height = height;
}

void updateScreenPointPixelBufferPointer(void* buffer) {
    screenPointPixelData = buffer;
}

void updateScreenPointPixelCoordinate(int x, int y) {
    _x = x;
    _y = y;
}

void updateScreenPointPixelSize(int width, int height) {
    _width = width;
    _height = height;
}

int GetScreenPixels(void* buffer, int x, int y, int width, int height) {
    if (glGetError())
        return -1;

    //glReadPixels(x, y, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
    glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

    if (glGetError())
        return -2;
    return 0;
}

//----------------------------UNITY RENDERING CALLBACK-----------------------------

// Plugin function to handle a specific rendering event
static void UNITY_INTERFACE_API OnRenderEventScreenPointPixel(int eventID)
{
    //Put rendering code below
    if (screenPointPixelData == nullptr) {
        //Debug::Log("Pointer is null", Color::Red);
        return;
    }
    int result = GetScreenPixels(screenPointPixelData, _x, _y, _width, _height);

    //std::string log_msg = "Cobol " + std::to_string(result);
    //Debug::Log(log_msg, Color::Green);
}

// Freely defined function to pass a callback to plugin-specific scripts
extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
GetRenderEventScreenPointPixelFunc()
{
    return OnRenderEventScreenPointPixel;
}

从Android Studio编译/构建时,它应该为您提供两个文件夹({em> armeabi-v7a 和 x86 <ProjectDirectory>\app\build\intermediates\cmake\release\obj目录。它们都应包含共享* .so库。如果您无法为 Android Studio 编译此项,请使用我为此here制作的Android Studio项目副本。您可以使用它来生成共享* .so图书馆。

将两个文件夹放在Assets\Plugins\Android\libs的Unity项目文件夹中。

你现在应该:

Assets\Plugins\Android\libs\armeabi-v7a\libScreenPointPixel-lib.so

Assets\Plugins\Android\libs\x86\libScreenPointPixel-lib.so

C#测试代码:

创建一个简单的小RawImage组件,并将其放置在屏幕的右上角中。将RawImage拖动到下面脚本中的rawImageColor槽。当您单击屏幕上的任意位置时,该屏幕点的像素颜色应显示在rawImageColor RawImage上。

<强> C#

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

public class ScreenPointPixel : MonoBehaviour
{
    [DllImport("ScreenPointPixel-lib", CallingConvention = CallingConvention.Cdecl)]
    public static extern void initScreenPointPixel(IntPtr buffer, int x, int y, int width, int height);
    //-------------------------------------------------------------------------------------
    [DllImport("ScreenPointPixel-lib", CallingConvention = CallingConvention.Cdecl)]
    public static extern void updateScreenPointPixelBufferPointer(IntPtr buffer);
    //-------------------------------------------------------------------------------------
    [DllImport("ScreenPointPixel-lib", CallingConvention = CallingConvention.Cdecl)]
    public static extern void updateScreenPointPixelCoordinate(int x, int y);
    //-------------------------------------------------------------------------------------
    [DllImport("ScreenPointPixel-lib", CallingConvention = CallingConvention.Cdecl)]
    public static extern void updateScreenPointPixelSize(int width, int height);
    //-------------------------------------------------------------------------------------

    //-------------------------------------------------------------------------------------
    [DllImport("ScreenPointPixel-lib", CallingConvention = CallingConvention.StdCall)]
    private static extern IntPtr GetRenderEventScreenPointPixelFunc();
    //-------------------------------------------------------------------------------------
    int width = 500;
    int height = 500;

    //Where Pixel data will be saved
    byte[] screenData;
    //Where handle that pins the Pixel data will stay
    GCHandle pinHandler;

    //Used to test the color
    public RawImage rawImageColor;

    // Use this for initialization
    void Awake()
    {
        Resolution res = Screen.currentResolution;
        width = res.width;
        height = res.height;

        //Allocate array to be used
        screenData = new byte[width * height * 4];

        //Pin the Array so that it doesn't move around
        pinHandler = GCHandle.Alloc(screenData, GCHandleType.Pinned);

        //Register the screenshot and pass the array that will receive the pixels
        IntPtr arrayPtr = pinHandler.AddrOfPinnedObject();

        initScreenPointPixel(arrayPtr, 0, 0, width, height);

        StartCoroutine(caller());
    }

    IEnumerator caller()
    {
        while (true)
        {
            //Use mouse position as the pixel position
            //Input.tou


#if UNITY_ANDROID || UNITY_IOS || UNITY_WSA_10_0
            if (!(Input.touchCount > 0))
            {
                yield return null;
                continue;
            }

            //Use touch position as the pixel position
            int x = Mathf.FloorToInt(Input.GetTouch(0).position.x);
            int y = Mathf.FloorToInt(Input.GetTouch(0).position.y);
#else
            //Use mouse position as the pixel position
            int x = Mathf.FloorToInt(Input.mousePosition.x);
            int y = Mathf.FloorToInt(Input.mousePosition.y);
#endif
            //Change this to any location from the screen you want
            updateScreenPointPixelCoordinate(x, y);

            //Must be 1 and 1
            updateScreenPointPixelSize(1, 1);

            //Take screenshot of the screen
            GL.IssuePluginEvent(GetRenderEventScreenPointPixelFunc(), 1);

            //Get the Color
            Color32 tempColor = new Color32();
            tempColor.r = screenData[0];
            tempColor.g = screenData[1];
            tempColor.b = screenData[2];
            tempColor.a = screenData[3];

            //Test it by assigning it to a raw image
            rawImageColor.color = tempColor;

            //Wait for a frame
            yield return null;
        }
    }

    void OnDisable()
    {
        //Unpin the array when disabled
        pinHandler.Free();
    }
}