Jump to content

[SOLVED] Возможно ли применить шейдер к GUI?


photo

Recommended Posts

Posted (edited)

Добрый день.

Подскажите, можно ли применить шейдер к элементу GUI? Я делаю визуализация эхолота, глубины получаю в коде через intersection и сгружаю их в список.

Потом в коде рисую в GUI на Canvas расстояния глубин из списка в виде точек, т.е. тут пока все без шейдеров. Получается так

image.png.cb76e3d85dfc8efc3336f3b369ebd8b6.png

и видео

 

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

4205-shipwreck.thumb.jpg.cdcf1135c75249f81f9a7d9114da11f7.jpg

В шейдерах пока что я полный 0, поэтому хочу спросить совета, принципиально можно ли изменять шейдером то, что рисуется на GUI в канве?

Посмотрел в мануале, и +- понял, что шейдер работает как часть материала, который назначается на 3д объект, а у меня 2д картинка.

И если эта задача решаема, то можно вкратце примерный порядок действий как это сделать?

Я правильно понимаю порядок так?

  1. Создать gui
  2. назначить на него мой кастомный материал на который действует шейдер
  3. написать шейдер, который получает текстуру с моими жёлтыми линиями
  4. изменять текстуру возможностями шейдера

Спасибо!

Edited by sevas55
  • sevas55 changed the title to Возможно ли применить шейдер к GUI?
Posted

День добрый!

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, а усложняем первый пункт. Тут я с ходу не смогу сказать, что нужно делать. Надо пробежаться по семплам и демкам. :)

 

С уважением,
Александр

  • Like 1
Posted

Конкретно по изображению. Сложно сказать, что надо сделать, чтобы стало выглядеть реалистично.

Имхо:
1) Надо рендить карту глубины (движок так может - смотрите семпл с LiDAR'ом) в виде полоски (1024х1).
2) Имитировать "освещение" (затемнение). Освещение - как в Blinn–Phong алгоритме. "Теневые" зоны - как в каком-нибудь Parallax Occlusion Mapping'е (конкретно: self-shadowing).
3) Окрасить результат в желтый
4) Добавить контрастного шума поверх (заготовить карту шума заранее и добавлять ее в шейдере на исходное изображение)
5) Добавлять результат в общую текстуру (линия за линией. Один шейдер сдвигает всё изображение ниже на 1 пиксель, второй шейдер добавляет новую полосу (1-4 пункты) наверх).

Для такого, конечно, семпл уже не сделаешь. Работы прилично. Но общий порядок действий вижу примерно так.
 

Posted

Спасибо за развернутую инструкцию! вот про про шейдер для виджета не знал, буду изучать.
 

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

При старте ошибка

image.png.0cd111152de3a079eec4484d7a2f46eb.png

Что имеется ввиду в мануале для этого параметра? 

string name - Post-process material name. This is an optional parameter.

И правильно ли я догадываюсь что basemat предназначен для 3д объектов и ошибка как раз из-за того, что для спрайта нужен другой тип материала? Тогда какой и как его включить на с#?

спасибо!

Edited by sevas55
Posted

Опа. А в документации-то ошибка. Спасибо, что нашли. Исправим!

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").

  • Like 1
Posted

Чтобы запихнуть дополнительные текстуры в шейдре, надо делать так:

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

  • Like 1
Posted (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 by sevas55
Posted

У вас WidgetSpriteShader создаётся без дефолтный текстурки (Александр про это писал пару постов назад):

wss = new WidgetSpriteShader();

Скорее всего, поможет просто добавить её в конструктор:

wss = new WidgetSpriteShader("white.texture");

 

  • Like 1

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Posted

Спасибо! Все так просто оказалось.

Заработало, вижу розовый квадратик текстурки (без зеленого канала по факту получается всё работает)

  • Like 1
Posted (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 by sevas55
Posted
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 метод. Так исторически сложилось, видимо)

  • Like 1
Posted

Спасибо! все получилось

Spoiler


Вижу путь самурая, сложен он

6 hours ago, alexander said:

Так исторически сложилось, видимо)


 

  • silent changed the title to [SOLVED] Возможно ли применить шейдер к GUI?
  • 3 months later...
Posted (edited)

Появился вопрос в продолжении темы. Я полностью разобрался с шейдерами в спрайтами, и вот неожиданно вылезла проблема там, где вообще не ожидал.

Сейчас мой код накладывает на Canvas разные элементы, линии, текст, спрайты и кнопки.

Проблема возникает с текстом, который находится на Canvas вместе с WidgetSpriteShader

Если к спрайту я применяю шейдер (у меня фильтр сглаживания и эффект спекл), то весь текст на канве превращается в сплошные квадратики. То есть как будто материал шейдера применяется и к тексту.

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

image.png.5d1cc191e33a67ad1e4b285fe4b3321f.png

А вот что происходит с тем же кодом, только я на спрайт назначаю свой материал с шейдером

Внизу текст превратился в квадратики

image.png.6e55087f31af01332c3edcd5a8fa59ff.png

 

В чем может быть ошибка?

Edited by sevas55
Posted

Странный совет, но попробуй выключить WidgetCanvas на кадр, а затем включить его снова (setHidden). Мне почему-то помогало.

  • Like 1
Posted

Спасибо, hidden не сработало
Но совет натолкнул на мысль попробовать изменить очередность добавления элементов на окно, сначала добавляю канву, а потом спрайты

Теперь текст нормально рисуется.

Спасибо!

image.png.d931f7c4245c4d1b8ef10775a92fe851.png

 
window.AddChild(Canvas, Gui.ALIGN_OVERLAP);
        for (int i = 0; i < SpriteList.Count; i++)
        {
            window.AddChild(SpriteList[i], Gui.ALIGN_OVERLAP);
 
        }
  • Like 2
×
×
  • Create New...