sevas55 Posted October 12, 2025 Posted October 12, 2025 (edited) Добрый день. Подскажите, можно ли применить шейдер к элементу GUI? Я делаю визуализация эхолота, глубины получаю в коде через intersection и сгружаю их в список. Потом в коде рисую в GUI на Canvas расстояния глубин из списка в виде точек, т.е. тут пока все без шейдеров. Получается так и видео Я хочу попробовать оживить картинку тенями, оттенками и прочими возможностями шейдеров, чтобы стало похоже на такое, это картинка с реального эхолота. В шейдерах пока что я полный 0, поэтому хочу спросить совета, принципиально можно ли изменять шейдером то, что рисуется на GUI в канве? Посмотрел в мануале, и +- понял, что шейдер работает как часть материала, который назначается на 3д объект, а у меня 2д картинка. И если эта задача решаема, то можно вкратце примерный порядок действий как это сделать? Я правильно понимаю порядок так? Создать gui назначить на него мой кастомный материал на который действует шейдер написать шейдер, который получает текстуру с моими жёлтыми линиями изменять текстуру возможностями шейдера Спасибо! Edited October 12, 2025 by sevas55
alexander Posted October 13, 2025 Posted October 13, 2025 День добрый! Quote Посмотрел в мануале, и +- понял, что шейдер работает как часть материала, который назначается на 3д объект, а у меня 2д картинка. Я бы сказал так: шейдер работает как подпрограмма, которая принимает на вход несколько текстур и некие параметры (float, int...), обрабатывает ее полностью параллельно на каждом пикселе, а на выход передает одну результирующую текстуру (или несколько, но не об этом сейчас). У нас шейдера - это части материалов. Сами материалы, при этом, можно прикреплять как к 3D моделям, так и к виджетам (Unigine::WidgetSpriteShader или посмотрите в аддон сторе Toolkit аддон. Там почти все элементы UI дают возможность прикреплять к ним шейдера/материалы). Есть еще материалы для пост процессинга (те, которые добавляются на экран - 2D текстуру. Например: bloom, depth of field, всякие тонмапперы и т.д.). В крайнем случае, шейдера можно применять прямо в коде, используя RenderState/RenderRenderer/Viewport'ы.. Самый неудобный, но самый низкоуровневый способ, для которого нужны только 2 текстуры (входная и исходящая) и шейдер. В демо Superposition данный способ применялся для отрисовки мела на доске. Quote И если эта задача решаема, то можно вкратце примерный порядок действий как это сделать? Если оставлять Unigine::WidgetCanvas и работать через Unigine::WidgetSpriteShader, то надо: 1) Отрисовать канвас не в ObjectGUI, а в текстуру. Для этого Canvas привязываем к отдельно созданному Gui классу. Далее отрисовываем Gui (в котором лежит только один Canvas) в текстуру. Это будет исходное изображение. В исходниках Toolkit аддона можно посмотреть, как это делается: const TexturePtr &Canvas::render(int width, int height, const TexturePtr &bg_texture) { if (!render_texture) { render_target = RenderTarget::create(); render_texture = Texture::create(); render_texture->create2D( width, height, Texture::FORMAT_RGBA8, Texture::FORMAT_USAGE_RENDER); gui->setSize(ivec2(width, height)); } else if (width != render_texture->getWidth() || height != render_texture->getHeight()) { render_texture->create2D( width, height, Texture::FORMAT_RGBA8, Texture::FORMAT_USAGE_RENDER); gui->setSize(ivec2(width, height)); } before_render_gui_event.run(); RenderState::saveState(); render_target->bindColorTexture(0, render_texture); render_target->enable(); { Ffp::enable(); Ffp::setTransform(ortho(-1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f)); RenderState::setTexture(RenderState::BIND_FRAGMENT, 0, bg_texture); RenderState::flushStates(); bg_texture->render2D(); Ffp::disable(); gui->enable(); gui->update(); gui->preRender(); gui->render(); gui->disable(); } render_target->disable(); render_target->unbindAll(); RenderState::restoreState(); return render_texture; } 2) Создаем WidgetSpriteShader с материалом, который будет принимать на вход исходную текстуру и что-то с ней делать. Вот самый простой вариант шейдера для WidgetSpriteShader'а, который просто перерисовывает исходную текстуру, убирая оттуда зеленый канал: BaseMaterial <preview_hidden=true var_prefix=var texture_prefix=tex> { Texture color = "white.texture" <anisotropy=true, hidden=true> Shader shader = #{ #define VERTEX_ATTRIBUTE_POST #include <core/materials/shaders/render/common.h> STRUCT(FRAGMENT_IN) INIT_POSITION INIT_DATA(float2, 0, DATA_UV0) INIT_DATA(float4, 1, DATA_VERTEX_COLOR) END #ifdef VERTEX MAIN_BEGIN(FRAGMENT_IN, VERTEX_IN) float4 row_0 = s_transform[0]; float4 row_1 = s_transform[1]; float4 row_2 = s_transform[2]; float4 vertex = float4( dot(ATTRIBUTE_POSITION, row_0), dot(ATTRIBUTE_POSITION, row_1), dot(ATTRIBUTE_POSITION, row_2), 1.0f ); OUT_POSITION = mul4(s_projection, vertex); DATA_UV0 = ATTRIBUTE_UV.xy; DATA_VERTEX_COLOR = ATTRIBUTE_COLOR; MAIN_END #elif FRAGMENT INIT_TEXTURE(0, TEX_COLOR) STRUCT_FRAG_BEGIN INIT_COLOR(float4) STRUCT_FRAG_END MAIN_FRAG_BEGIN(FRAGMENT_IN) float4 color = TEXTURE_BIAS_ZERO(tex_color, DATA_UV0); // do something here: color.g = 0; // ------------------ OUT_COLOR = color * DATA_VERTEX_COLOR; MAIN_FRAG_END #endif #} Pass post { Fragment = shader Vertex = shader } } 3) Дальше по ситуации. Если результат WidgetSpriteShader'а хочется видеть в виде текстуры в 3D мире, то... Либо повторяем первый пункт, но уже не Canvas кладем в Gui, а WidgetSpriteShader. Либо вообще не создаем WidgetSpriteShader, а усложняем первый пункт. Тут я с ходу не смогу сказать, что нужно делать. Надо пробежаться по семплам и демкам. :) С уважением, Александр 1
alexander Posted October 13, 2025 Posted October 13, 2025 Конкретно по изображению. Сложно сказать, что надо сделать, чтобы стало выглядеть реалистично. Имхо: 1) Надо рендить карту глубины (движок так может - смотрите семпл с LiDAR'ом) в виде полоски (1024х1). 2) Имитировать "освещение" (затемнение). Освещение - как в Blinn–Phong алгоритме. "Теневые" зоны - как в каком-нибудь Parallax Occlusion Mapping'е (конкретно: self-shadowing). 3) Окрасить результат в желтый 4) Добавить контрастного шума поверх (заготовить карту шума заранее и добавлять ее в шейдере на исходное изображение) 5) Добавлять результат в общую текстуру (линия за линией. Один шейдер сдвигает всё изображение ниже на 1 пиксель, второй шейдер добавляет новую полосу (1-4 пункты) наверх). Для такого, конечно, семпл уже не сделаешь. Работы прилично. Но общий порядок действий вижу примерно так.
sevas55 Posted October 13, 2025 Author Posted October 13, 2025 Спасибо за развернутую инструкцию! вот про про шейдер для виджета не знал, буду изучать.
sevas55 Posted October 19, 2025 Author Posted October 19, 2025 (edited) Решил выбрать вариант работы с канвой и спрайтом, так как вариант с рендером картинки через карты глубины/теней это для эхолота совсем не то, принцип отображения там другой. Начал ковырять WidgetSpriteShader и застрял в самом начале. Проект с# Хочу пока просто разобраться как статичную текстуру менять шейдером в спрайте, потом буду с канвой. Подскажите как подключить шейдер к материалу и материал к спрайту. Пробую по мануалу , вроде как нужно указать имя материала для спрайта при инициализации Cделал в корне MyMat.basemat и скопировал туда код вашего шейдера с удалением зеленого канала В коде указываю имя этого материала using System.Collections; using System.Collections.Generic; using Unigine; [Component(PropertyGuid = "fc5e20c83ccf08e3612db93239c38a32a8db3b45")] public class MyWidgetSpriteShader : Component { Gui gui; WidgetSpriteShader wss; Material mat; Image image = new Image(); Shader shader = new Shader(); void Init() { // image.Load("GBO.png"); gui = Gui.GetCurrent(); mat = new Material(); wss = new WidgetSpriteShader(gui, "MyMat.basemat"); //wss = new WidgetSpriteShader(gui, "MyShader.vert"); wss.SetImage(image); gui.AddChild(wss,Gui.ALIGN_TOP); } } При старте ошибка Что имеется ввиду в мануале для этого параметра? string name - Post-process material name. This is an optional parameter. И правильно ли я догадываюсь что basemat предназначен для 3д объектов и ошибка как раз из-за того, что для спрайта нужен другой тип материала? Тогда какой и как его включить на с#? спасибо! Edited October 19, 2025 by sevas55
alexander Posted October 20, 2025 Posted October 20, 2025 Опа. А в документации-то ошибка. Спасибо, что нашли. Исправим! name - это ровно то же самое, что и у WidgetSprite'а. То есть, путь до текстуры. Аналогично, что и WidgetSprite::setTexture(name) или WidgetSprite::setLayerTexture(0,name). В материал (и шейдер) оно попадает в этот слот: Texture color = "white.texture" <anisotropy=true, hidden=true> Так что пишем так: wss = new WidgetSpriteShader(gui, "white.texture"); wss.SetMaterial(Materials.FindMaterialByPath("path_to_my/MyMat.basemat")); wss.SetImage(image); // -> изображение image скопируется в текстуру и попадет в MyMat.basemat в слот color (из-за "texture_prefix=tex" в материале, обращаться к этой текстуре в шейдере надо как "TEX_COLOR" и "tex_color"). 1
alexander Posted October 20, 2025 Posted October 20, 2025 Чтобы запихнуть дополнительные текстуры в шейдре, надо делать так: 1) В материале добавляем слоты: Texture color = "white.texture" <anisotropy=true, hidden=true> Texture additional_texture = "white.texture" // ... INIT_TEXTURE(0, TEX_COLOR) INIT_TEXTURE(1, TEX_ADDITIONAL_TEXTURE) // ... float4 add_color = TEXTURE(tex_additional_texture, DATA_UV0); // ... 2) В коде создаем наследника базового материала MyMat и заполняем дополнительные поля: Material my_mat = Materials.FindMaterialByPath("path_to_my/MyMat.basemat").inherit(); my_mat.SetTexture("additional_texture", texture); // или SetTexturePath("addition_texture", path_to_texture); или SetTextureImage("addition_texture", image); wss.SetMaterial(my_mat); 1
sevas55 Posted October 20, 2025 Author Posted October 20, 2025 (edited) Спасибо, не сразу, но от ошибок избавился. wss.SetMaterial в с# не получилось, методом тыка удалось завести как wss.Material = mat; gui = Gui.GetCurrent(); image.Load("GBO.png");//картинка лежит в корне wss = new WidgetSpriteShader(); gui = Gui.GetCurrent(); mat = new Material(); mat = Materials.FindMaterialByPath(FileSystem.GetAbsolutePath("") + "/MyMat.basemat").Inherit(); mat.SetTextureImage(1, image);//возвращает 1 - успех, проверено //mat.SetTextureImage(0, image);//возвращает 1 - успех, проверено wss.Material = mat; gui.AddChild(wss,Gui.ALIGN_OVERLAP); код MyMat.basemat Spoiler BaseMaterial <preview_hidden=true var_prefix=var texture_prefix=tex> { Texture color = "white.texture" <anisotropy=true, hidden=false> Texture additional_texture = "white.texture" Shader shader = #{ #define VERTEX_ATTRIBUTE_POST #include <core/materials/shaders/render/common.h> STRUCT(FRAGMENT_IN) INIT_POSITION INIT_DATA(float2, 0, DATA_UV0) INIT_DATA(float4, 1, DATA_VERTEX_COLOR) END #ifdef VERTEX MAIN_BEGIN(FRAGMENT_IN, VERTEX_IN) float4 row_0 = s_transform[0]; float4 row_1 = s_transform[1]; float4 row_2 = s_transform[2]; float4 vertex = float4( dot(ATTRIBUTE_POSITION, row_0), dot(ATTRIBUTE_POSITION, row_1), dot(ATTRIBUTE_POSITION, row_2), 1.0f ); OUT_POSITION = mul4(s_projection, vertex); DATA_UV0 = ATTRIBUTE_UV.xy; DATA_VERTEX_COLOR = ATTRIBUTE_COLOR; MAIN_END #elif FRAGMENT INIT_TEXTURE(0, TEX_COLOR) STRUCT_FRAG_BEGIN INIT_COLOR(float4) STRUCT_FRAG_END MAIN_FRAG_BEGIN(FRAGMENT_IN) float4 color = TEXTURE_BIAS_ZERO(tex_color, DATA_UV0); // do something here: color.g = 0; // ------------------ OUT_COLOR = color * DATA_VERTEX_COLOR; MAIN_FRAG_END #endif #} Pass post { Fragment = shader Vertex = shader } } Правда пока что все равно не вижу рендера картинки, но повторю - ошибок в логах нет. То есть получается теперь проблема в шейдере? Edited October 20, 2025 by sevas55
silent Posted October 21, 2025 Posted October 21, 2025 У вас WidgetSpriteShader создаётся без дефолтный текстурки (Александр про это писал пару постов назад): wss = new WidgetSpriteShader(); Скорее всего, поможет просто добавить её в конструктор: wss = new WidgetSpriteShader("white.texture"); 1 How to submit a good bug report --- FTP server for test scenes and user uploads: ftp://files.unigine.com user: upload password: 6xYkd6vLYWjpW6SN
sevas55 Posted October 21, 2025 Author Posted October 21, 2025 Спасибо! Все так просто оказалось. Заработало, вижу розовый квадратик текстурки (без зеленого канала по факту получается всё работает) 1
sevas55 Posted October 24, 2025 Author Posted October 24, 2025 (edited) Подскажите пожалуйста еще один вопрос, как назначить отрендеренную в рантайме текстуру на spriteshader? я нашел в примерах gui to texture как его отрендерить вместе с canvas, проверил, все работает, правда текстуру я выгружаю в файл, она появляется как скриншот с gui, всё ок А вот как закинуть в спрайт полученную текстуру в другой gui на котором добавлен спрайт с шейдером не пойму, по коду он требует строку то есть так не работает widgetSpriteShader.Texture = guiTexture; Или мне надо полученную текстуру подкидывать через свойства в сам материал *.basemat? Spoiler using System.Collections; using System.Collections.Generic; using Unigine; [Component(PropertyGuid = "fc5e20c83ccf08e3612db93239c38a32a8db3b45")] public class MyWidgetSpriteShader : Component { Gui gui; Gui gui2; WidgetSpriteShader widgetSpriteShader; Material mat; Image image = new Image(); Texture texture; WidgetCanvas Canvas; WidgetSprite widgetSprite; RenderTarget renderTarget; Viewport viewport; ivec2 TextureResolution = new(256, 256); private Texture guiTexture; void Init() { renderTarget = new RenderTarget(); guiTexture = new Texture(); guiTexture.Create2D(512, 512, Texture.FORMAT_RGBA8, Texture.FORMAT_USAGE_RENDER); widgetSpriteShader = new WidgetSpriteShader("black.texture"); widgetSpriteShader.Texture = "GBO2.texture"; widgetSprite = new WidgetSprite(); gui2 = Gui.Create(); gui2.Size = TextureResolution; Canvas = new WidgetCanvas(); int n = Canvas.AddPolygon(); Canvas.SetPolygonColor(n, vec4.RED); Canvas.AddPolygonPoint(n, new vec3(0, 0, 0)); Canvas.AddPolygonPoint(n, new vec3(0, 100, 0)); Canvas.AddPolygonPoint(n, new vec3(100, 100, 0)); gui2.AddChild(Canvas, Gui.ALIGN_LEFT); gui = Gui.GetCurrent(); mat = new Material(); mat = Materials.FindMaterialByPath(FileSystem.GetAbsolutePath("") + "/MyMat.basemat").Inherit(); widgetSpriteShader.Material = mat; gui.AddChild(widgetSpriteShader); } float timePrev = 0; void Update() { if (Game.Time - timePrev>1) { RenderToTexture(); timePrev = Game.Time; } } void RenderToTexture() { // Render gui onto texture // Save render state and put it at the top of the stack // To pop current settings, we will need to call RenderState.RestoreState() at the and of this method RenderState.SaveState(); // Now we clear state, so that our rendered texture won't be affected by other render activities RenderState.ClearStates(); // Set viewport size matching texture resolution RenderState.SetViewport(0, 0, TextureResolution.x, TextureResolution.y); // Now we bind gui texture to slot 0, because gui renders in slot 0 renderTarget.BindColorTexture(0, guiTexture); // Enable render target renderTarget.Enable(); // Clear texture and fill it with black color RenderState.ClearBuffer(RenderState.BUFFER_COLOR, vec4.ZERO); // Now we need to perform the whole gui render loop // Enable gui so that it will be updated and rendered gui2.Enable(); // Update all widgets gui2.Update(); // Render gui gui2.PreRender(); gui2.Render(); // Disable gui gui2.Disable(); // Now we need to free render target and unbind texture renderTarget.Disable(); renderTarget.UnbindColorTexture(0); // Create texture mipmaps (set of textures of different resolutions to ensure correct rendering at longer distances) guiTexture.CreateMipmaps(); // Pop render state from top of the stack to let render pipeline continue as usual RenderState.RestoreState(); Render.AsyncTransferTextureToImage( null, (Image image) => { if (!Render.IsFlipped) image.FlipY(); string timenow = System.DateTime.Now.ToString("yyyyMMdd-HHmmss"); string fullName = "WIP/" + $"{timenow}.png"; image.Save(fullName); Unigine.Console.OnscreenMessageLine(vec4.GREEN, $"{fullName} saved."); }, guiTexture); } } Edited October 24, 2025 by sevas55
alexander Posted October 24, 2025 Posted October 24, 2025 2 hours ago, sevas55 said: то есть так не работает widgetSpriteShader.Texture = guiTexture; Используйте для этого метод SetRender: widgetSpriteShader.SetRender(guiTexture); - при таком подходе текстура попадет в материал в поле "color", а в шейдере - в "tex_color". https://developer.unigine.com/en/docs/future/api/library/gui/class.widgetsprite?implementationLanguage=cs&subnavView=brief&subnavSort=none#setRender_Texture_int_void (не спрашивайте, почему он называется SetRender, но при этом Image кладется через SetImage метод. Так исторически сложилось, видимо) 1
sevas55 Posted October 24, 2025 Author Posted October 24, 2025 Спасибо! все получилось Spoiler Вижу путь самурая, сложен он 6 hours ago, alexander said: Так исторически сложилось, видимо)
sevas55 Posted April 9 Author Posted April 9 (edited) Появился вопрос в продолжении темы. Я полностью разобрался с шейдерами в спрайтами, и вот неожиданно вылезла проблема там, где вообще не ожидал. Сейчас мой код накладывает на Canvas разные элементы, линии, текст, спрайты и кнопки. Проблема возникает с текстом, который находится на Canvas вместе с WidgetSpriteShader Если к спрайту я применяю шейдер (у меня фильтр сглаживания и эффект спекл), то весь текст на канве превращается в сплошные квадратики. То есть как будто материал шейдера применяется и к тексту. Вот скрин когда текст работает правильно, тут к спрайтам материал не применяется А вот что происходит с тем же кодом, только я на спрайт назначаю свой материал с шейдером Внизу текст превратился в квадратики В чем может быть ошибка? Edited April 9 by sevas55
alexander Posted April 10 Posted April 10 Странный совет, но попробуй выключить WidgetCanvas на кадр, а затем включить его снова (setHidden). Мне почему-то помогало. 1
sevas55 Posted April 10 Author Posted April 10 Спасибо, hidden не сработало Но совет натолкнул на мысль попробовать изменить очередность добавления элементов на окно, сначала добавляю канву, а потом спрайты Теперь текст нормально рисуется. Спасибо! window.AddChild(Canvas, Gui.ALIGN_OVERLAP); for (int i = 0; i < SpriteList.Count; i++) { window.AddChild(SpriteList[i], Gui.ALIGN_OVERLAP); } 2
Recommended Posts