CanvasItem: цвет, координаты и освещение 2D
Полные шейдеры, таблицы встроенных значений, регионы Sprite2D и диагностика.
CanvasItem-шейдер управляет отрисовкой 2D: спрайтов, элементов интерфейса и других объектов CanvasItem. Ресурс Shader назначается в ShaderMaterial, а материал — объекту. Иерархия Entity и граф VisualShader — отдельные механизмы.
Первый работающий материал
- Создайте Sprite2D с видимой текстурой. Оставьте Modulate и SelfModulate белыми.
- Назначьте новый ShaderMaterial в Material.
- Создайте Shader в свойстве Shader материала и вставьте полный код ниже.
- Измените tint у материала: белый сохраняет исходный вид, зелёный оставляет зелёный канал, Alpha=0 скрывает объект.
shader_type canvas_item;
render_mode unshaded;
uniform vec4 tint : source_color = vec4(1.0);
void fragment() {
COLOR *= tint;
}В C# значение меняется через material.SetShaderParameter("tint", new Color(0, 1, 0, 1)). Имя должно совпадать с uniform. Два разных ShaderMaterial могут использовать один Shader с независимыми значениями; общий материал даёт общие параметры. Передача default(Variant) снимает значение материала и возвращает начальное значение uniform.
Пример использует unshaded, чтобы освещение не мешало проверять цвет. Для реакции на Light2D уберите этот режим. Подсказка source_color нужна для цветового параметра; обычный числовой вектор не следует автоматически считать цветом.
COLOR уже содержит текстуру
| Место | Содержимое COLOR |
|---|---|
| Вход vertex() | Цвет примитива × Modulate × SelfModulate. |
| Вход fragment() | Интерполированный цвет вершин × выборка основной TEXTURE. |
| Выход fragment() | Цвет и прозрачность, передаваемые дальнейшему освещению и смешиванию. |
Поэтому COLOR *= texture(TEXTURE, UV) обычно умножает текстуру второй раз. Это частая причина затемнения. Чтобы получить только текстуру и обойти окраску вершин, задайте COLOR напрямую:
shader_type canvas_item;
render_mode unshaded;
void fragment() {
COLOR = texture(TEXTURE, UV);
}Чтобы, наоборот, использовать цвет вершин без основной текстуры, передайте его через varying:
shader_type canvas_item;
render_mode unshaded;
varying vec4 vertex_color;
void vertex() {
vertex_color = COLOR;
}
void fragment() {
COLOR = vertex_color;
}В проверке текстура (0.8, 0.4, 0.2), Modulate (0.5, 1, 1) и SelfModulate (0.5, 1, 1) дают RGB (0.2, 0.4, 0.2). Повторное умножение текстуры даёт (0.16, 0.16, 0.04). Это функциональная проверка арифметики обычного 2D, а не правило для любого HDR/цветового конвейера.
Координаты: не смешивайте пространства
В vertex() VERTEX — локальные координаты геометрии в пикселях. По умолчанию движок применяет MODEL_MATRIX, затем CANVAS_MATRIX и SCREEN_MATRIX. Последние этапы учитывают холст/камеру и проекцию. Фрагментный VERTEX уже содержит положение в координатах холста после его преобразования; это не исходный локальный VERTEX.
UV адресует текстуру, SCREEN_UV — текущий viewport. FRAGCOORD.xy содержит координаты центра пикселя viewport, а не значения 0–1. Для проверенного Vulkan-пути начало сверху слева: FRAGCOORD.xy * SCREEN_PIXEL_SIZE совпадает с SCREEN_UV. Размер окна ОС и размер Sprite2D здесь не заменяют разрешение viewport.
TEXTURE_PIXEL_SIZE — обратный размер исходной текстуры: для 8×4 это (0.125, 0.25), даже если спрайт увеличен. Для отдельного пользовательского sampler используйте его собственный размер, а не размер основной TEXTURE.
У Sprite2D с регионом UV уже адресует подпрямоугольник общей текстуры. В проверенном прямоугольном пути REGION_RECT хранит нормализованные смещение и размер: регион (2,1,4,2) текстуры 8×4 даёт (0.25,0.25,0.5,0.5). Если нужен UV внутри этого региона, вычтите REGION_RECT.xy и разделите на REGION_RECT.zw при ненулевом размере. Не применяйте смещение региона повторно к уже готовому UV. Для flip, атласа, кадров анимации и других примитивов сначала проверьте фактические координаты.
Режим skip_vertex_transform отключает только автоматический переход из локального пространства в мировое. Полный пример сохраняет его вручную; преобразование холста и проекция остаются автоматическими:
shader_type canvas_item;
render_mode unshaded, skip_vertex_transform;
void vertex() {
VERTEX = (MODEL_MATRIX * vec4(VERTEX, 0.0, 1.0)).xy;
}
void fragment() {
COLOR = vec4(UV, 0.0, 1.0);
}В world_vertex_coords, напротив, MODEL_MATRIX уже применяется до пользовательского vertex(). Не умножайте на неё повторно.
Режимы отрисовки
| Режим | Действие |
|---|---|
| blend_mix | Обычное смешивание по alpha; режим по умолчанию. |
| blend_add | Аддитивное смешивание. |
| blend_sub | Вычитающее смешивание. |
| blend_mul | Умножающее смешивание. |
| blend_premul_alpha | RGB должен быть заранее умножен на alpha; не умножайте его дважды. |
| blend_disabled | Смешивание выключено; alpha=0 сама по себе не скрывает RGB. |
| unshaded | Обход освещения и модуляции холста; исходная окраска объекта сохраняется. |
| light_only | Показывает вклад освещения вместо обычного базового цвета. |
| skip_vertex_transform | Локальное → мировое преобразование задаётся в vertex() вручную. |
| world_vertex_coords | Пользовательский vertex() получает мировые координаты. |
Выберите один blend-режим. Для premultiplied alpha сначала вычислите окончательную alpha, затем умножьте RGB на неё ровно один раз. discard отбрасывает выбранный фрагмент; простое обнуление alpha зависит от blend-режима.
Встроенные значения по этапам
in — только чтение; out — результат, которому нельзя приписывать осмысленное начальное значение; inout — начальное значение с возможностью изменения. Sampler предназначен для чтения выборок. Доступность имени определяется этапом: varying передаёт выбранные данные между этапами, но не делает любой встроенный символ доступным везде.
TIME доступен глобально: время рендерера в секундах с учётом Engine.TimeScale. Пауза дерева сама по себе его не останавливает. Период сброса задаёт rendering/limits/time/time_rollover_secs, по умолчанию 3600 секунд. Для управляемой игровой анимации передавайте собственный uniform времени. Глобальные PI, TAU и E — математические константы float.
vertex()
| Тип и имя | Значение |
|---|---|
inout vec2 VERTEX | Локальная позиция; режимы преобразования описаны выше. |
inout vec2 UV | Координаты текстуры, передаваемые fragment(). |
inout vec4 COLOR | Цвет вершин с окраской объекта, до основной текстуры. |
inout float POINT_SIZE | Размер примитива точки. |
in mat4 MODEL_MATRIX / CANVAS_MATRIX / SCREEN_MATRIX | Локальные → мировые → холст → clip координаты. |
in int INSTANCE_ID / VERTEX_ID | Индексы экземпляра отрисовки и вершины; не ID игрового Entity. |
in vec4 INSTANCE_CUSTOM | Пользовательские данные экземпляра; смысл задаёт производитель. У частиц обычно rotation, phase, animation frame в xyz. |
in vec4 CUSTOM0 / CUSTOM1 | Дополнительные атрибуты примитива, если они предоставлены. |
in vec2 TEXTURE_PIXEL_SIZE | Обратный размер основной текстуры. |
in bool AT_LIGHT_PASS | Всегда false; не переключатель повторного прохода. |
fragment()
| Тип и имя | Значение |
|---|---|
in vec4 FRAGCOORD | Центр пикселя viewport; xy в пикселях. |
in vec2 SCREEN_UV / SCREEN_PIXEL_SIZE | UV viewport / обратное разрешение viewport. |
in vec2 UV / POINT_COORD | UV текстуры / координаты внутри точечного примитива. |
in vec4 REGION_RECT | Прямоугольник источника текущего пути отрисовки; для проверенного Sprite2D нормализован. |
sampler2D TEXTURE / NORMAL_TEXTURE / SPECULAR_SHININESS_TEXTURE | Основная текстура, нормали и specular/shininess. |
in vec2 TEXTURE_PIXEL_SIZE | Обратный размер TEXTURE. |
in vec4 SPECULAR_SHININESS | Выборка цвета/показателя блеска. |
inout vec3 NORMAL | Нормаль, которую использует освещение 2D. |
out vec3 NORMAL_MAP / out float NORMAL_MAP_DEPTH | Нормаль в кодировке карты и глубина её влияния; NORMAL_MAP заменяет NORMAL. |
inout vec2 VERTEX / SHADOW_VERTEX | Положение фрагмента / отдельное положение для расчёта теней; изменение здесь не перемещает растеризованную геометрию. |
inout vec3 LIGHT_VERTEX | Положение для освещения, z — высота. |
inout vec4 COLOR | Цвет с основной текстурой; выход этапа. |
in bool AT_LIGHT_PASS | Всегда false. |
light()
| Тип и имя | Значение |
|---|---|
in vec4 FRAGCOORD | Тот же центр пикселя в пикселях, не нормализованный UV. |
in vec3 NORMAL / LIGHT_VERTEX | Нормаль / положение для расчёта света после fragment(). |
in vec4 COLOR / SPECULAR_SHININESS | Цвет после fragment() / параметры блеска. |
in vec2 UV / SCREEN_UV / POINT_COORD / TEXTURE_PIXEL_SIZE | UV текстуры, UV viewport, координата точки и обратный размер текстуры. |
sampler2D TEXTURE | Основная текстура объекта. |
in vec4 LIGHT_COLOR / in float LIGHT_ENERGY | Цвет текущего света и энергия; у PointLight2D учитывается текстура света. |
in vec3 LIGHT_POSITION / LIGHT_DIRECTION | Позиция и направление света в пространстве освещения; у направленного света позиция нулевая. |
in bool LIGHT_IS_DIRECTIONAL | Признак DirectionalLight2D. |
inout vec4 LIGHT | Результат текущего света; пользовательская функция заменяет стандартную. |
out vec4 SHADOW_MODULATE | Множитель цвета теней в этой точке. |
Освещение и карты нормалей
Без light() действует стандартное освещение. Объявление собственной light() заменяет его, даже если тело пустое: в проверенном случае вклад становится нулевым. unshaded обходит пользовательское освещение. Для проверки источника настройте его текстуру, энергию, видимость и совпадающие маски объекта/света; CanvasModulate влияет на базовое освещение.
Полный шейдер с вкладом света по нормали:
shader_type canvas_item;
void light() {
float ndotl = max(0.0, dot(NORMAL, LIGHT_DIRECTION));
LIGHT = vec4(LIGHT_COLOR.rgb * COLOR.rgb * LIGHT_ENERGY * ndotl, LIGHT_COLOR.a);
}Это простой диффузный расчёт, без отдельной specular-модели. CanvasTexture позволяет задать карту нормалей. Для явной выборки карты в кодировке RGB используйте NORMAL_MAP, например присвойте ему texture(NORMAL_TEXTURE, UV).rgb внутри fragment(); NORMAL — уже вектор нормали для освещения. Не подменяйте его необработанным RGB. Проверьте ориентацию карты на реальном источнике света.
SDF: отдельное пространство координат
LightOccluder2D с назначенным OccluderPolygon2D и включённым SDF Collision участвует в поле расстояний viewport. У нового LightOccluder2D этот флаг включён конструктором. Видимость и область/масштаб SDF viewport влияют на результат.
| Функция | Назначение |
|---|---|
vec2 screen_uv_to_sdf(vec2 uv) | UV viewport → координаты поля SDF. |
vec2 sdf_to_screen_uv(vec2 sdf_pos) | Обратное преобразование в UV viewport. |
float texture_sdf(vec2 sdf_pos) | Чтение расстояния в пространстве SDF, а не произвольных пикселях окна. |
vec2 texture_sdf_normal(vec2 sdf_pos) | Направление градиента поля; не нормаль 3D-модели. |
Эти функции доступны из fragment()/light() и вызванных оттуда функций, но не vertex(). Не передавайте обычный UV текстуры в texture_sdf без перевода пространства. Для экранной текстуры используйте собственный sampler с hint_screen_texture; встроенного SCREEN_TEXTURE в текущем реестре нет. См. экранные текстуры.
Диагностика
| Симптом | Что проверить |
|---|---|
| Текстура слишком тёмная | Повторное умножение TEXTURE, Modulate/SelfModulate и влияние освещения. |
| Изменение tint не действует | Точное имя, нужный ShaderMaterial и отсутствие перезаписи COLOR ниже по коду. |
| Alpha=0, но RGB виден | blend_disabled, аддитивный режим или неверный premultiplied RGB. |
| Эффект растягивается при масштабировании | Используется размер текстуры, объекта или viewport не из того пространства. |
| Регион выбирается дважды | UV уже включает источник региона; проверьте REGION_RECT. |
| Объект смещён/исчез после vertex() | Повторный MODEL_MATRIX либо пропущенный переход при skip_vertex_transform. |
| Свет исчез после добавления light() | Пользовательская функция должна записывать LIGHT; проверьте режим, источник и маски. |
| SDF не соответствует объектам | Полигон окклюдера, SDF Collision, область viewport и преобразование screen_uv_to_sdf. |
В Windows Forward+/Vulkan проверены пять полных примеров выше и фактические пиксели цвета, alpha, координат, региона, преобразования и пользовательского света. Полная матрица встроенных значений, SDF, TIME/пауза, прочие blend-режимы и world_vertex_coords сверены по исходникам; для них здесь не заявлена отдельная проверка всех платформ, рендереров и экспортов.