reckfellENGINE

SubViewport: отдельная камера и живая текстура

Показать отдельный 3D-мир в интерфейсе и управлять обновлением кадра.

На этой странице

Запустить живую текстуру

Скачать проект. Нужны Reckfell 1.0.0 с C# SDK и Forward+/Vulkan. Распакуйте архив, откройте project.reckfell, соберите C# и запустите Main.rscene. Все модели и текстуры включены: интерьер Pawn Shop от Poly Haven, CC0, со светом и материалами, адаптированными для Reckfell.

У корня установите Preview=Original до запуска. В этом примере 3D-комната действительно рендерится отдельной камерой в SubViewport; TextureRect показывает её текущую текстуру. Это не сохранённая фотография.

Живой 3D-рендер отдельного SubViewport, показанный через TextureRect

Разделить рендер и вывод

ОбъектОтветственность
SourceView: SubViewportсобственный World3D, камера комнаты, размер рендера и частота обновления
SourceView.GetTexture()живая ViewportTexture с результатом SourceView
Display: TextureRectместо и размер показа этой текстуры в интерфейсе
TonePass, Copy, InvertPassобработка уже показанного изображения; разбор в следующем руководстве

SubViewport сам по себе не создаёт прямоугольник на экране. Его результат должен использовать TextureRect, материал или другой потребитель текстуры. В примере камера находится внутри SourceView. Явный World3D отделяет комнату от мира родительского viewport. Если World3D не задан, FindWorld3D ищет доступный мир у самого viewport и его предков.

Размер SourceView.Size задаётся в пикселях. Размер Display.Size управляет отображением и не меняет разрешение рендера. Здесь оба равны 2560×1440, но обязательного соотношения 2:1 у SubViewport нет. При изменении Size уже полученная ViewportTexture сообщает новый размер; повторно назначать Display.Texture не требуется.

Использовать готовый контроллер

ViewportDisplay.cs — корневой Control. PawnShop3D.cs и каталог Assets уже находятся в архиве. GetTexture возвращает ресурс для показа; GetTexture().GetImage() получает отдельное изображение для чтения на CPU. Для живого экрана не нужно вызывать GetImage каждый кадр или сохранять PNG.

C#
using Reckfell;

public partial class ViewportDisplay : Control
{
    public enum Mode { Original, Grayscale, Chained }
    [Export] public Mode Preview { get; set; } = Mode.Grayscale;
    public SubViewport SourceView { get; private set; } = null!;
    public PawnShop3D Room { get; private set; } = null!;
    public TextureRect Display { get; private set; } = null!;
    public ColorRect TonePass { get; private set; } = null!;
    public ColorRect InvertPass { get; private set; } = null!;
    public BackBufferCopy Copy { get; private set; } = null!;
    public ShaderMaterial ToneMaterial { get; private set; } = null!;

    public override void _Ready()
    {
        var size=new Vector2I(2560,1440);
        SourceView=new SubViewport { Size=size,World3D=new World3D(),
            RenderTargetUpdateMode=SubViewport.UpdateMode.Always,
            Msaa3D=Viewport.Msaa.Msaa8X,UseTaa=true,Scaling3DScale=1.5f,
            PositionalShadowAtlasSize=4096 };
        AddChild(SourceView);
        Room=new PawnShop3D();SourceView.AddChild(Room);
        // Bind the viewport's live texture; do not replace it with GetImage().
        Display=new TextureRect { Texture=SourceView.GetTexture(),Size=size,
            ExpandMode=TextureRect.ExpandModeEnum.IgnoreSize,
            StretchMode=TextureRect.StretchModeEnum.Scale,MouseFilter=MouseFilterEnum.Ignore };
        AddChild(Display);
        ToneMaterial=new ShaderMaterial { Shader=ResourceLoader.Load<Shader>("asset://ScreenTone.reckfellshader") };
        TonePass=new ColorRect { Size=size,Material=ToneMaterial,MouseFilter=MouseFilterEnum.Ignore };
        AddChild(TonePass);
        // Draw order is important: display -> tone -> copy -> invert.
        Copy=new BackBufferCopy { CopyMode=BackBufferCopy.CopyModeEnum.Disabled };AddChild(Copy);
        InvertPass=new ColorRect { Size=size,MouseFilter=MouseFilterEnum.Ignore,
            Material=new ShaderMaterial { Shader=ResourceLoader.Load<Shader>("asset://ScreenInvert.reckfellshader") } };
        AddChild(InvertPass);
        SetPreview(Preview);
    }
    public void SetPreview(Mode mode)
    {
        TonePass.Position=Vector2.Zero;TonePass.Size=new Vector2(2560,1440);
        InvertPass.Position=Vector2.Zero;InvertPass.Size=TonePass.Size;
        TonePass.Visible=mode!=Mode.Original;
        ToneMaterial.SetShaderParameter("desaturate",1.0f);
        Copy.CopyMode=mode==Mode.Chained?BackBufferCopy.CopyModeEnum.Viewport:BackBufferCopy.CopyModeEnum.Disabled;
        InvertPass.Visible=mode==Mode.Chained;
    }
    // Disable only drawing. Scripts and physics do not pause with the render target.
    public void Freeze()=>SourceView.RenderTargetUpdateMode=SubViewport.UpdateMode.Disabled;
    public void RefreshOnce()=>SourceView.RenderTargetUpdateMode=SubViewport.UpdateMode.Once;
    public void Resume()=>SourceView.RenderTargetUpdateMode=SubViewport.UpdateMode.Always;
}

Обновлять только когда нужно

Вызывайте методы после _Ready:

ДействиеМетод примераЧто происходит
непрерывный рендерResume()RenderTargetUpdateMode=Always
сохранить последний кадрFreeze()Disabled останавливает перерисовку
получить ещё один кадрRefreshOnce()Once запрашивает одну отрисовку

Freeze останавливает только обновление render target. Это не пауза EntityTree, скриптов или физики. Если остановить viewport до его первой отрисовки, готового кадра ещё не будет.

В текущей сборке getter RenderTargetUpdateMode после выполненного Once по-прежнему возвращает Once. Остановка происходит в RenderingServer. Не стройте ожидание завершения на проверке свойства на Disabled. Повторное присваивание Once запрашивает следующую отрисовку, даже если getter уже возвращал Once.

Для чтения изображения дождитесь RenderingServer.FramePostDraw; в C# используйте ToSignal(RenderingServer.Singleton, RenderingServer.SignalName.FramePostDraw). Этот сигнал означает завершённый кадр, а не полную сходимость GI или готовность любого внешнего асинхронного ресурса.

Диагностика

СимптомПроверка
пустой прямоугольникSourceView уже в дереве, есть активная Camera3D, модель загружена, прошла отрисовка
виден чужой мирпринадлежность камеры к viewport и собственный World3D
картинка растягивается или размытаотдельно сравните Size источника, Size потребителя и режим TextureRect.StretchMode
изменение камеры не видноне оставлен ли Disabled; для одноразового обновления снова присвойте Once
снимок сделан слишком раночтение после FramePostDraw, а не сразу после AddChild
интерфейс не реагирует на мышьTextureRect показывает текстуру, но этот пример не пересылает ввод в SubViewport

Кадры: Forward+/Vulkan, 2560×1440, внутренний масштаб 3D 1.5, MSAA 8× и TAA. Это тяжёлый профиль иллюстраций. Управление вводом, экспорт приложения, все режимы UpdateMode и работа примера в Compatibility здесь не проверены.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.