Как применить искажение бочки (коррекция объектива) с SharpDX?

Я сделал небольшое приложение для захвата скриншотов из любой оконной игры и отправки его на iPhone для создания приложения виртуальной реальности, такого как Oculus Rift (см. https://github.com/gagagu/VR-Streamer-Windows-Server для больше информации). Изображения будут сняты с SharpDX, и все работает нормально. Теперь я хочу реализовать такие функции, как коррекция объектива (искажение бочки), и я ищу самый быстрый способ реализовать это. Я просматриваю множество интернет-сайтов с информацией о бочкообразном искажении, и я думаю, что самый быстрый способ - это использовать для него шейдер, но я очень плохо знаком с sharpdx (и не знаю, какие шейдеры), и я не знаю, как это реализовать шейдер к моему коду. В большинстве уроков шейдер применяется к объекту (например, к кубу), а не к захваченному изображению, поэтому я не знаю, как это сделать.

       [STAThread]
    public System.Drawing.Bitmap Capture()
    {
        isInCapture = true;

        try
        {

            // init
            bool captureDone = false;
            bitmap = new System.Drawing.Bitmap(captureRect.Width, captureRect.Height, PixelFormat.Format32bppArgb);

            // the capture needs some time
            for (int i = 0; !captureDone; i++)
            {
                try
                {
                    //capture
                    duplicatedOutput.AcquireNextFrame(-1, out duplicateFrameInformation, out screenResource);
                    // only for wait
                    if (i > 0)
                    {
                        using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
                            device.ImmediateContext.CopyResource(screenTexture2D, screenTexture);

                        mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, MapFlags.None);
                        mapDest = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, captureRect.Width, captureRect.Height),
                                                              ImageLockMode.WriteOnly, bitmap.PixelFormat);

                        sourcePtr = mapSource.DataPointer;
                        destPtr = mapDest.Scan0;

                        // set x position offset to rect.x
                        int rowPitch = mapSource.RowPitch - offsetX;
                        // set pointer to y position
                        sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch * captureRect.Y);

                        for (int y = 0; y < captureRect.Height; y++) // needs to speed up!!
                        {
                            // set pointer to x position
                            sourcePtr = IntPtr.Add(sourcePtr, offsetX);

                            // copy pixel to bmp
                            Utilities.CopyMemory(destPtr, sourcePtr, pWidth);

                            // incement pointert to next line
                            sourcePtr = IntPtr.Add(sourcePtr, rowPitch);
                            destPtr = IntPtr.Add(destPtr, mapDest.Stride);
                        }

                        bitmap.UnlockBits(mapDest);
                        device.ImmediateContext.UnmapSubresource(screenTexture, 0);

                        captureDone = true;
                    }

                    screenResource.Dispose();
                    duplicatedOutput.ReleaseFrame();

                }
                catch//(Exception ex)  //                    catch (SharpDXException e)
                {
                    //if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code)
                    //{
                    //   // throw e;
                    //}

                    return new Bitmap(captureRect.Width, captureRect.Height, PixelFormat.Format32bppArgb);
                }
            }

        }
        catch 
        {
            return new Bitmap(captureRect.Width, captureRect.Height, PixelFormat.Format32bppArgb);
        }

        isInCapture = false;
        return bitmap;
    }

Было бы здорово получить помощь от кого-то, кто хочет помочь. Я нашел несколько шейдеров на inet, но он написан для opengl ( https://github.com/dghost/glslRiftDistort/tree/master/libovr-0.4.x/glsl110). Могу ли я использовать также для DirectX (sharpdx)?

Спасибо за любую помощь!

1 ответ

Теперь я сам никогда не использовал DirectX, но я полагаю, вам нужно будет использовать HLSL вместо GLSL (что должно быть довольно похоже). Идея состоит в том, что вам нужно будет загрузить свой "скриншот" в буфер текстур в качестве входных данных для вашего фрагментного шейдера (пиксельного шейдера). Фрагментные шейдеры обманчиво просты для понимания, это всего лишь фрагмент кода (написанный на GLSL или HLSL), очень похожий на подмножество C, к которому добавлено несколько математических функций (в основном, манипуляций с векторами и матрицами), выполняемых для каждого отдельного пикселя. быть оказанным.

Код должен быть довольно простым: вы берете текущую позицию пикселя, применяете преобразование искажения барреля к его координатам, а затем ищите эту координату в текстуре скриншота. Преобразование должно выглядеть примерно так:

vec2 uv;

/// Barrel Distortion ///

float d=length(uv);
float z = sqrt(1.0 - d * d);
float r = atan(d, z) / 3.14159;
float phi = atan(uv.y, uv.x);

uv = vec2(r*cos(phi)+.5,r*sin(phi)+.5);

Вот ссылка на шейдертой, если вы хотите поиграть с ней и выяснить, как она работает

Я понятия не имею, как HLSL справляется с фильтрацией текстур (какой пиксель вы получите при использовании значений с плавающей запятой для координат), но я бы вложил свои деньги в билинейную фильтрацию, которая вполне может дать неприятную пикселизацию вашему выводу. Вам нужно будет взглянуть на лучшие методы фильтрации, как только вы получите искажение. Не должно быть ничего сложного, ознакомьтесь с синтаксисом HLSL, найдите, как загрузить свой скриншот в текстуру в DirectX и приступайте к работе.

Редактировать: я сказал, что искажение ствола, но код на самом деле для эффекта "рыбий глаз". Конечно, оба в значительной степени идентичны, искажение ствола происходит только по одной оси. Я считаю, что вам нужен эффект "рыбий глаз", это то, что обычно используется для HMD, если я не ошибаюсь.

Другие вопросы по тегам