c# - 读取屏幕点的像素颜色(高效)

标签 c# android c++ unity3d

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

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

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

这是我目前拥有的代码。

using UnityEngine;
using System;
using System.Collections;

public class ColController : MonoBehaviour
{
    public Camera collCamera;
    public Texture2D tex2d;


    void Start()
    {
        collCamera = GetComponent<Camera>();
        tex2d = new Texture2D(collCamera.targetTexture.width, collCamera.targetTexture.height, TextureFormat.ARGB32, false);

        RenderTexture.active = collCamera.targetTexture;
        StartCoroutine (Execute ());

    }

    IEnumerator Execute()
    {
        while (true) {

                yield return new WaitForEndOfFrame ();
                tex2d = GetRTPixels (collCamera.targetTexture);
                yield return new WaitForSeconds (0.5f);

        }
    }


    static public Texture2D GetRTPixels(RenderTexture rt)
    {
        RenderTexture currentActiveRT = RenderTexture.active;

        RenderTexture.active = rt;

        // Create a new Texture2D and read the RenderTexture image into it
        Texture2D tex = new Texture2D(rt.width, rt.height);
        tex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);

        RenderTexture.active = currentActiveRT;
        return tex;
    }

    void Update() {
        screenPos = collCamera.WorldToScreenPoint (player.transform.position);
        positionColor = tex2d.GetPixel ((int)screenPos.x, (int)screenPos.y);

    }


}

最佳答案

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

您还需要位于 <UnityInstallationDirecory>\Editor\Data\PluginAPI 的 Unity C++ API header (IUnityInterface.hIUnityGraphics.h) .

我创建了一个名为 UnityPluginHeaders 的文件夹,并将 IUnityInterface.hIUnityGraphics.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 编译/构建时,它应该为您提供两个文件夹(armeabi-v7ax86,位于 <ProjectDirectory>\app\build\intermediates\cmake\release\obj 目录。它们都应该包含共享的 * .so 库。如果您无法为 Android Studio 编译此文件,请使用我为此制作的 Android Studio 项目拷贝 here。您可以使用它生成共享的 *.so 库。

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

你现在应该:

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

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


C#测试代码:

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

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();
    }
}

关于c# - 读取屏幕点的像素颜色(高效),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43731305/

相关文章:

android - 原始音频数据包到 Android 上的 WAV/GSM_MS 兼容文件

java - 如何完全求助于RecyclerView的SortedList

c++ - STL 映射 - 插入或更新

c++ - 重新 -'new' TCHAR* 数组

c# - 在 C# 中动态更改 css 样式?

java - 如何使用网络摄像头扫描条形码?

c# - WPF:选择 Canvas 中的子控件

c++ - 为什么 printf 函数族关心语言环境?

C# 如何以编程方式获取SQL Server安装路径?

c# - 将自定义 UserControl 添加到 Visual Studio 2005 的工具箱