gdshader подробнее — vertex, fragment, light
Структура spatial-шейдера, встроенные функции, uniforms, варинги, шаги оптимизации.
В главе про рендеринг видели базовый ShaderMaterial. Здесь — подробнее: устройство 3D-шейдера, что такое vertex/fragment/light-функции и как писать своё освещение.
Полная структура spatial-шейдера
shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_schlick_ggx;
// Uniforms — параметры материала, доступные из Inspector
uniform sampler2D albedo_tex : source_color, filter_linear_mipmap;
uniform vec4 tint : source_color = vec4(1.0);
uniform float roughness : hint_range(0.0, 1.0) = 0.5;
// Varyings — данные, передаваемые из vertex в fragment
varying vec3 world_pos;
varying float vertical_factor;
void vertex() {
// Здесь модифицируем позиции вершин и передаём данные вниз
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
vertical_factor = clamp(VERTEX.y * 0.1, 0.0, 1.0);
// Простая волна: смещение по синусу от мирового времени
VERTEX.y += sin(TIME + world_pos.x) * 0.1;
}
void fragment() {
// Здесь считаем цвет каждого пикселя
vec4 tex = texture(albedo_tex, UV);
ALBEDO = tex.rgb * tint.rgb;
ALPHA = tex.a * tint.a;
ROUGHNESS = roughness;
METALLIC = 0.0;
EMISSION = vec3(0.0, 0.5 * vertical_factor, 0.0); // зелёный градиент по высоте
}
void light() {
// (опционально) кастомное освещение, заменяет встроенное
// LIGHT — итоговый цвет, добавляется к ALBEDO
DIFFUSE_LIGHT += LIGHT_COLOR * dot(NORMAL, LIGHT) * ATTENUATION;
}
Что внутри:
shader_type spatial;— это для 3D mesh’а. Для 2D —canvas_item, для skybox —sky, и т.д.render_mode— флаги:blend_*(transparency),cull_*(отбраковка),depth_*(Z-буфер),diffuse_*(модель диффузного освещения),specular_*(зеркальная),unshaded(без света).uniform— параметр, видимый в Inspector материала и доступный из кода черезset_shader_parameter.varying— переменная, передаваемая из vertex в fragment (интерполируется по треугольнику).vertex()— модифицирует вершины перед растеризацией.fragment()— считает свойства поверхности (ALBEDO, NORMAL, ROUGHNESS, METALLIC, EMISSION, ALPHA).light()— кастомная функция освещения (если хотите свой не-PBR look).
Встроенные переменные
Главные in/out:
В vertex():
VERTEX— позиция вершины в object-space (in/out).NORMAL— нормаль (in/out).TANGENT,BINORMAL— касательные.UV,UV2— координаты текстуры.COLOR— vertex color.MODEL_MATRIX,VIEW_MATRIX,PROJECTION_MATRIX— стандартные матрицы.TIME— игровое время в секундах.INSTANCE_ID,VERTEX_ID— индексы (для MultiMesh).
В fragment():
ALBEDO— диффузный цвет (out, vec3).ALPHA— прозрачность (out, float).METALLIC,ROUGHNESS,SPECULAR— PBR-параметры (out).EMISSION— самосветящееся (out, vec3).NORMAL,NORMAL_MAP— нормали (out).AO,AO_LIGHT_AFFECT— ambient occlusion (out).RIM,RIM_TINT— rim light (out).CLEARCOAT,CLEARCOAT_GLOSS.UV,FRAGCOORD,VIEW,VERTEX— input.DEPTH— out (можно писать в depth buffer).
В light():
LIGHT— направление к источнику (in, vec3).LIGHT_COLOR— цвет с интенсивностью (in, vec3).ATTENUATION— затухание (in, float).DIFFUSE_LIGHT,SPECULAR_LIGHT— итоговые (out, vec3).NORMAL,VIEW— для расчётов.
Hints для uniforms
// Источник: цветовая текстура (применит inverse gamma — sRGB → linear автоматически)
uniform sampler2D albedo : source_color, filter_linear_mipmap, repeat_enable;
// Источник: normal map (распакуется правильно)
uniform sampler2D normal_map : hint_normal;
// Range slider в Inspector
uniform float intensity : hint_range(0.0, 5.0, 0.01) = 1.0;
// Color picker
uniform vec4 emission_color : source_color = vec4(1.0, 0.5, 0.0, 1.0);
// Cubemap для skybox / reflection
uniform samplerCube environment : source_color, filter_linear_mipmap;
Главные hints:
source_color— для color-текстур (sRGB-преобразование).hint_normal— для normal-map.hint_roughness_r/_g/_b/_gray— channel pack для PBR.hint_range(min, max, step)— slider.filter_*— фильтрация:nearest/linear/linear_mipmap/linear_mipmap_anisotropic.repeat_*— wrap:enable/disable.
Пример: dissolve (распад)
Классический эффект “объект растворяется в шумовой узор”:
shader_type spatial;
uniform sampler2D albedo : source_color, filter_linear_mipmap;
uniform sampler2D noise : filter_linear, repeat_enable;
uniform float threshold : hint_range(0.0, 1.0) = 0.0;
uniform vec3 edge_color : source_color = vec3(1.0, 0.5, 0.0);
uniform float edge_width : hint_range(0.0, 0.1) = 0.03;
void fragment() {
vec4 tex = texture(albedo, UV);
float n = texture(noise, UV).r;
if (n < threshold) {
discard; // отбросить этот пиксель
}
ALBEDO = tex.rgb;
if (n < threshold + edge_width) {
EMISSION = edge_color * 3.0;
ALBEDO = edge_color;
}
}
Использование:
var mat = $Mesh.material_override as ShaderMaterial
var tween = create_tween()
tween.tween_property(mat, "shader_parameter/threshold", 1.0, 2.0)
tween.tween_callback(queue_free)
Sub-pass: Cull back и transparency
Прозрачные объекты часто требуют ручной настройки:
shader_type spatial;
render_mode blend_mix, depth_draw_always, cull_disabled;
uniform vec4 color : source_color = vec4(1.0, 0.5, 0.5, 0.4);
void fragment() {
ALBEDO = color.rgb;
ALPHA = color.a;
}
blend_mix— стандартный alpha-blending.depth_draw_always— писать в Z-буфер всегда (для правильных пересечений).cull_disabled— рисовать обе стороны (для стекла, листвы).
Свой light() — toon shading
shader_type spatial;
render_mode unshaded; // полностью отключим built-in lighting
uniform sampler2D albedo : source_color, filter_linear_mipmap;
uniform float bands : hint_range(2, 8, 1) = 3;
varying vec3 world_normal;
void vertex() {
world_normal = (MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz;
}
void light() {
float diffuse = dot(normalize(world_normal), normalize(LIGHT));
float quantized = floor(diffuse * bands) / bands;
DIFFUSE_LIGHT += LIGHT_COLOR * max(quantized, 0.0);
}
void fragment() {
ALBEDO = texture(albedo, UV).rgb;
}
render_mode unshaded отключает встроенное PBR-освещение, мы пишем своё в light().
if (n < threshold) { discard; } — это разветвление в fragment-шейдере. Стоит дёшево, но
массивные ветвления убивают производительность. Чаще лучше smoothstep или маска через
step() — без if’ов.
Particle shader
Для частиц shader_type particles; со своей семантикой:
shader_type particles;
render_mode keep_data;
uniform vec3 attract_center;
uniform float attract_strength : hint_range(0.0, 10.0) = 1.0;
void process() {
vec3 to_center = attract_center - TRANSFORM[3].xyz;
float dist = length(to_center);
VELOCITY += normalize(to_center) * attract_strength * DELTA;
TRANSFORM[3].xyz += VELOCITY * DELTA;
}
Применяется как process_material на GPUParticles3D (заменяет ParticleProcessMaterial).
Что не делать в шейдерах
- Тяжёлые вычисления в fragment(). Каждый пиксель вызывает fragment. Один лишний texture-sampling на 1080p — это 2 миллиона sample’ов в кадр.
- Большие циклы. GPU плохо переносит расходящиеся ветвления. Цикл
for (int i = 0; i < 100; i++)— допустимо, ноfor (int i = 0; i < N; i++) if (cond[i]) ...— нет. - Pixel-perfect логика в fragment. Если можно сделать в vertex (раз в N меньше вызовов) — делайте там.
dynamictexture sampling через переменный индекс — GPU не любит. Лучше один atlas, чем массив текстур.
Что почитать
- godotshaders.com — community-сайт с готовыми шейдерами.
- Документация —
Shading referenceв Godot Docs (docs.godotengine.org/en/stable/tutorials/shaders/). - Исходники встроенных шейдеров — в Godot-репозитории
servers/rendering/renderer_rd/shaders/.
В следующей главе — глоссарий.