cancel
Showing results for 
Search instead for 
Did you mean: 

How to copy Depth API image to a PNG?

oihdfgojihdfgohljfdg
Honored Guest

I want to use the Depth API to copy the depth image to a PNG file on Quest 3.

https://developer.oculus.com/documentation/unity/unity-depthapi/#depth-api

I'm modifying the Depth API sample:

https://github.com/oculus-samples/Unity-DepthAPI

I'm getting the RenderTexture from Util.GetEnvironmentDepthTextureId() and XRDisplaySubsystem.GetRenderTexture.RenderTexture. However, when I save the RenderTexture to a PNG image, the image is entirely gray. Each pixel color is CDCDCDCD. The image is 2000x2000.

My problem is very similar to this reddit thread:

https://www.reddit.com/r/oculusdev/comments/17lwzvz/q3_depth_api_accessing_depth_sensor_data_directl...

I looked at the OpenXR Depth API Overview page and I noticed that it says:

"Depth maps should only be accessed between the xrBeginFrame and xrEndFrame calls"

https://developer.oculus.com/documentation/native/android/mobile-depth/#acquiring-depth-maps

The Unity Depth API page says:

"RenderTexture can then be used in rendering or in compute shaders."

Is this why Texture2D.ReadPixels() doesn't work? Because the RenderTexture is only usable between xrBeginFrame and xrEndFrame? How do I copy the RenderTexture to a texture that I can read and then save to a PNG file? I'm not familiar with Unity's rendering or compute shaders.

2 REPLIES 2

Rads
Honored Guest
// Each kernel tells which function to compile you can have many kernels
#pragma kernel CSMain
 
// Quest3 depth texture reading ( global _EnvironmentDepthTexture) 
// by Radoslaw Szczygiel. 
// Nie wstawiac polskich znakow do shadera nawet w komentarzach !!!
 
// Deklaracja tekstury wejsciowej (mapa glebi)
Texture2DArray<float> _DepthMap ; //rs dziala bo bez RW, tu tekstura podawana jest 
//Texture2DArray<float>  _EnvironmentDepthTexture; //rs dziala od razu na globalnej teksturze o tej nazwie dostarczanej przez Quest3  (Texture2DArray_float tez na Vulcan dziala)
 
// Deklaracja bufora wyjsciowego (do przechowywania punktow)
RWStructuredBuffer<float> _PointsBuffer; //rs tu musi byc RW 
 
float4 _EnvironmentDepthZBufferParams = float4(-0.2f, -1.0f, 0, 0 ); //x=0.2, y=-1.0
//float _MinDistance = 6.0f;
//float _MaxDistance = 0.1f;
 
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    //float depth = _EnvironmentDepthTexture.Load(int4(id.xy , 0,0)).r; // lub
    float depth = _DepthMap.Load(int4(id.xy, 0 /*array layer index*/, 0 /* z i ma byc 0 */)).r; // Odczytanie glebokosci piksela z tekstury wejsciowej
    float inputDepthNdc = depth * 2.0f - 1.0f;
    float linearDepth = (1.0f / (inputDepthNdc + _EnvironmentDepthZBufferParams.y)) * _EnvironmentDepthZBufferParams.x;
    //float linearDepth = (1.0f / (inputDepthNdc - 1.0f )) / -5             +//* (-0.2f);
    //float lerpFactor = (linearDepth - _MinDistance) / (_MaxDistance - _MinDistance); //rs dane do kolorowania
 
    _PointsBuffer[id.x + (id.y * 2000)] = linearDepth ; //rs 2000 to szerokosc tekstury glebi
    //rs todo To nie jest raczej odleglosc od kamery tylko skladowa "z" wzgledem plaszczyzny kamery co powoduje mniejsze odczyty im dalej od srodka kamery w dowolnym kierunku
    //rs np. podnoszac glowe do gury aby dolny punkt objal ten sam punkt sciany do wczesniej srodkowy odczyt jest mniejszy
}
 

Rads
Honored Guest
using Unity.XR.Oculus;
using UnityEngine;
using UnityEngine.XR;
using TMPro;
 
public class ComputeShader1 : MonoBehaviour
{
    public ComputeShader CompShader;
//    public RenderTexture DepthMap;
    public int BufferSize = 2000;
    public TextMeshProUGUI[] myDepthTextArray = new TextMeshProUGUI[9]; //rs add Tu musi być rozmiar jak ilosc el tablicy wektorow "ind" ponizej
 
    private ComputeBuffer m_pointsBuffer;
    private XRDisplaySubsystem m_xrDisplay;
    
    public float interval = 0.05f; // Interwał czasowy w sekundach
 
 
 
    // x=1250 i y=1200 to pi * drzwi = srodek. 2000 to szer tekstury głębi 2000x2000 (0..1999) pix
#if true  //true lub false
    //prawdziwe miejsca pokrywające się z ekranem
    const int GORNY_Y = 1550; 
    const int SRODEK_Y = 1100;
    const int DOLNY_Y = 500;
    const int LEWY_X = 470; const int SRODEK_X = 1270; const int PRAWY_X = 1999;
#else 
    // rozszerzenie aby wykorzystać maksymalnie pole odczytu
    const int GORNY_Y = 1900;
    const int SRODEK_Y = 1000;
    const int DOLNY_Y = 300; //500;
    const int LEWY_X = 100; const int SRODEK_X = 1300; const int PRAWY_X = 1900;
#endif
    private Vector2Int[] ind = {
        new Vector2Int(LEWY_X, GORNY_Y),       //0 lewa góra
        new Vector2Int(SRODEK_X, GORNY_Y),       //1 środkowa góra
        new Vector2Int(PRAWY_X, GORNY_Y),       //2 prawa góra
        new Vector2Int(LEWY_X, SRODEK_Y),       //3 lewy środek
        new Vector2Int(SRODEK_X, SRODEK_Y),       //4 środkowy środek
        new Vector2Int(PRAWY_X, SRODEK_Y),     //5 prawy środek
        new Vector2Int(LEWY_X, DOLNY_Y),       //6 lewy dół
        new Vector2Int(SRODEK_X, DOLNY_Y),       //7 środkowy dół
        new Vector2Int(PRAWY_X, DOLNY_Y),       //8 prawy dół
        };
 
    private void Start()
    {
        m_xrDisplay = OVRManager.GetCurrentDisplaySubsystem();
 
        // Inicjalizacja bufora dla punktów
        m_pointsBuffer = new ComputeBuffer(BufferSize * BufferSize, sizeof(float) );
 
        // Przypisanie bufora do Compute Shadera
        CompShader.SetBuffer(0, "_PointsBuffer", m_pointsBuffer);
 
    }
 
    private void Update()
    {
        uint id = 0;
        // Używamy modulo, aby sprawdzić, czy przyszedł czas na wykonanie akcji. Próba bedzie podejmowana tylko w jednej klatce (max 2) a następna za kolejny interval
        // Tak więc opóźnione wywołanie nie wykona się i zostanie ominięte do następnego okienka
//        if (Time.time % interval < Time.deltaTime) // to nie jest doskonałe
        {
            if (Utils.GetEnvironmentDepthTextureId(ref id) && m_xrDisplay != null && m_xrDisplay.running)
            {
                var rt = m_xrDisplay.GetRenderTexture(id);
                if (rt != null)
                {
                    // Uruchomienie Compute Shadera
                    var kernelID = CompShader.FindKernel("CSMain");
                    CompShader.SetTexture(kernelID, "_DepthMap", rt /*DepthMap*/); //rs mozna tez od razu w shaderze czytac z tej textury bo globalna
                    CompShader.Dispatch(kernelID, BufferSize / 8, BufferSize / 8, 1);
 
                    // Odczytanie danych z bufora
                    float[] points = new float[BufferSize * BufferSize];
                    m_pointsBuffer.GetData(points);
                    //2000 * 1200 + 1250 // pi * drzwi = srodek. 2000 to szer tekstury głębi. Druga liczba to pion a trzecia to poziom
                    for (int i = 0; i < myDepthTextArray.Length; i++)
                    {
                        float depth = points[ind[i].y * 2000 + ind[i].x]; // 2000 to szer tekstury głębi. y to pion od dolu a x to poziom od lewej
                        depth = Mathf.Clamp(depth, 0.01f, 8.0f);
                        //                    myDepthTextArray[i].text = $"{depth,4:0.00}\nx={ind[i].x} y={ind[i].y}";
                        myDepthTextArray[i].text = $"{depth,4:0.00}\n";
                    }
                    // Przetworzenie otrzymanych punktów (przykładowe działanie)
                    //foreach (var point in points)
                    {
                        //Debug.Log("Point: " + point);
                    }
                }
            }
        }
    }
 
    private void OnDestroy()
    {
        // Zwolnienie bufora
        if (m_pointsBuffer != null)
        {
            m_pointsBuffer.Release();
            m_pointsBuffer = null;
        }
    }
}