Synty Polygon drop-in replacement for PolygonShader
Synty PolygonShader is used as a material shader for many of the static Synty assets in the Polygon and related packages (e.g. biomes).
Other than providing basic shading for texture, normal and emission, it allows:
- Triplanar shading (texture, normal, emission).
- Overlay texture (for effect like i.e. blinking or selection).
- Ambient Occlusion
- Emission (flat/triplanar)
- Snow effects
Shader code
/**************************************************************
Foliage = Synty models static and tri-planar shader for Godot.
(C) Copyright 2025 - Giancarlo Niccolai
Published under MIT licence.
# Overview
Synty PolygonShader is used as a material shader for many of the
static Synty assets in the Polygon and related packages (e.g.
biomes).
Other than providing basic shading for texture, normal and emission,
it allows:
- Triplanar shading (texture, normal, emission).
- Overlay texture (for effect like i.e. blinking or selection).
- Ambient Occlusion
- Emission (flat/triplanar)
- Snow effects
In particular, instead of a "traditional" XYZ tri-planar shading,
the Synty shader provides top (Y), bottom (-Y) and Side (X and Z)
triplanar projection.
## Note
This shader reproduces the behavior of the original Unity Synty
PolygonShader as closely as possible, but the normal tangent pipeline
is extremely different; I tried to replicate the visuals of the
application of the tri-planar normal map as closely as possible,
considering the different rednering pipelines.
*/
shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_back;
group_uniforms Base;
uniform vec4 color_tint : source_color = vec4(1.0);
uniform float metallic : hint_range(0.0, 1.0) = 0.0;
uniform float smoothness : hint_range(0.0, 1.0) = 0.0;
uniform sampler2D metallic_smoothness_texture: source_color, filter_linear_mipmap, repeat_enable;
uniform vec2 uv_pan = vec2(0.0);
group_uniforms Base.BaseTexture;
uniform bool enable_base_texture = true;
uniform sampler2D base_texture : source_color, filter_linear_mipmap, repeat_enable;
uniform vec2 base_tiling = vec2(1.0);
uniform vec2 base_offset = vec2(0.0);
group_uniforms Base.NormalTexture;
uniform bool enable_normal_texture = true;
uniform sampler2D normal_texture : hint_normal, filter_linear_mipmap, repeat_enable;
uniform float normal_intensity : hint_range(0.0, 2.0) = 1.0;
uniform vec2 normal_tiling = vec2(1.0);
uniform vec2 normal_offset = vec2(0.0);
group_uniforms Base.EmissionTexture;
uniform bool enable_emission_texture = false;
uniform sampler2D emission_texture : source_color, filter_linear_mipmap, repeat_enable;
uniform vec2 emission_tiling = vec2(1.0);
uniform vec2 emission_offset = vec2(0.0);
uniform vec4 emission_color_tint : source_color = vec4(1.0);
group_uniforms Base.AmbientOclusion;
uniform bool enable_ambient_occlusion = false;
uniform float ao_intensity : hint_range(0.0, 5.0) = 1.0;
uniform sampler2D ao_texture : filter_linear_mipmap, repeat_enable;
uniform vec2 ao_tiling = vec2(1.0);
uniform vec2 ao_offset = vec2(0.0);
uniform bool generate_from_base_normals = false;
group_uniforms Overlay;
uniform bool enable_overlay_texture = false;
uniform sampler2D overlay_texture : source_color, filter_linear_mipmap, repeat_enable;
uniform vec2 overlay_tiling = vec2(1.0);
uniform vec2 overlay_offset = vec2(0.0);
uniform int overlay_uv_channel: hint_range(1, 4) = 1;
uniform float overlay_intensity : hint_range(0.0, 1.0) = 1.0;
group_uniforms Triplanar;
group_uniforms Triplanar.Base;
uniform bool enable_triplanar_texture = false;
uniform sampler2D triplanar_texture_top : source_color, filter_linear_mipmap, repeat_enable;
uniform vec2 triplanar_tiling_top = vec2(1.0);
uniform vec2 triplanar_offset_top = vec2(0.0);
uniform sampler2D triplanar_texture_side : source_color, filter_linear_mipmap, repeat_enable;
uniform vec2 triplanar_tiling_side = vec2(1.0);
uniform vec2 triplanar_offset_side = vec2(0.0);
uniform sampler2D triplanar_texture_bottom : source_color, filter_linear_mipmap, repeat_enable;
uniform vec2 triplanar_tiling_bottom = vec2(1.0);
uniform vec2 triplanar_offset_bottom = vec2(0.0);
uniform float triplanar_top_to_side_difference : hint_range(0.0, 1.0) = 0.5;
uniform float triplanar_fade : hint_range(0.0, 1.0) = 0.5;
uniform float triplanar_intensity : hint_range(0.0, 1.0) = 1.0;
uniform float top_metallic : hint_range(0.0, 1.0) = 0.0;
uniform float side_metallic : hint_range(0.0, 1.0) = 0.0;
uniform float bottom_metallic : hint_range(0.0, 1.0) = 0.0;
uniform float top_smoothness : hint_range(0.0, 1.0) = 0.0;
uniform float side_smoothness : hint_range(0.0, 1.0) = 0.0;
uniform float bottom_smoothness : hint_range(0.0, 1.0) = 0.0;
group_uniforms Triplanar.Normal;
uniform bool enable_triplanar_normals = false;
uniform sampler2D triplanar_normal_top : hint_normal, filter_linear_mipmap, repeat_enable;
uniform float triplanar_normal_intensity_top : hint_range(0.0, 1.0) = 1.0;
uniform vec2 triplanar_normal_tiling_top = vec2(1.0);
uniform vec2 triplanar_normal_offset_top = vec2(0.0);
uniform sampler2D triplanar_normal_bottom : hint_normal, filter_linear_mipmap, repeat_enable;
uniform float triplanar_normal_intensity_bottom : hint_range(0.0, 1.0) = 1.0;
uniform vec2 triplanar_normal_tiling_bottom = vec2(1.0);
uniform vec2 triplanar_normal_offset_bottom = vec2(0.0);
uniform sampler2D triplanar_normal_side : hint_normal, filter_linear_mipmap, repeat_enable;
uniform float triplanar_normal_intensity_side : hint_range(0.0, 1.0) = 1.0;
uniform vec2 triplanar_normal_tiling_side = vec2(1.0);
uniform vec2 triplanar_normal_offset_side = vec2(0.0);
uniform float triplanar_normal_top_to_side_difference : hint_range(0.0, 1.0) = 0.5;
uniform float triplanar_normal_fade : hint_range(0.0, 128.0) = 10.0;
group_uniforms Triplanar.Emission;
uniform bool enable_triplanar_emission = false;
uniform sampler2D triplanar_emission_texture : source_color, filter_linear_mipmap, repeat_enable;
uniform float triplanar_emission_tiling = 1.0;
uniform float triplanar_emission_blend : hint_range(0.0, 50.0) = 4.0;
uniform float triplanar_emission_intensity : hint_range(0.0, 50.0) = 1.0;
uniform vec4 triplanar_emission_color_tint : source_color = vec4(1.0);
group_uniforms Snow;
uniform bool enable_snow = false;
uniform bool snow_use_world_up = true;
uniform vec4 snow_color : source_color = vec4(1.0);
uniform float snow_metallic : hint_range(0.0, 1.0) = 0.0;
uniform float snow_smoothness : hint_range(0.0, 1.0) = 0.8;
uniform float snow_level : hint_range(0.0, 1.0) = 0.5;
uniform float snow_transition = 0.95;
// Triplanar mapping function
vec4 triplanar_sample(sampler2D tex_top, sampler2D tex_bottom, sampler2D tex_side,
vec3 world_pos, vec3 world_normal,
vec2 tiling_top, vec2 offset_top,
vec2 tiling_bottom, vec2 offset_bottom,
vec2 tiling_side, vec2 offset_side,
float top_to_side_diff, float fade) {
vec3 abs_normal = abs(world_normal);
// Adjust blend weights based on top_to_side_difference
float top_boost = mix(0.25, 4.0, clamp(top_to_side_diff, 0.0, 1.0));
vec3 blend_weights = vec3(1.0, top_boost, 1.0);
abs_normal *= blend_weights;
// Apply fade/sharpness
float blend_sharpness = mix(0.1, 50.0, fade);
abs_normal = pow(abs_normal, vec3(blend_sharpness));
abs_normal /= dot(abs_normal, vec3(1.0));
// Determine if top or bottom based on Y normal
float is_top = step(0.0, world_normal.y);
// Sample textures
vec2 uv_x = world_pos.zy * tiling_side * 0.01 + offset_side;
vec2 uv_y_top = world_pos.xz * tiling_top * 0.01 + offset_top;
vec2 uv_y_bottom = world_pos.xz * tiling_bottom * 0.01 + offset_bottom;
vec2 uv_z = world_pos.xy * tiling_side * 0.01 + offset_side;
vec4 tex_x = texture(tex_side, uv_x);
vec4 tex_y = mix(texture(tex_bottom, uv_y_bottom), texture(tex_top, uv_y_top), is_top);
vec4 tex_z = texture(tex_side, uv_z);
return tex_x * abs_normal.x + tex_y * abs_normal.y + tex_z * abs_normal.z;
}
vec4 normal_strength(vec4 normal, float intensity) {
normal.xy *= intensity;
normal.z = mix(1.0, normal.z, clamp(intensity, 0.0, 1.0));
return normal;
}
vec3 normal_blend(vec3 A, vec3 B) {
return normalize(vec3(A.xy + B.xy, A.z * B.z));
}
// Triplanar mapping function
vec4 triplanar_normal_sample(sampler2D tex_top, sampler2D tex_bottom, sampler2D tex_side,
vec3 world_pos, vec3 world_normal,
vec2 tiling_top, vec2 offset_top, float intensity_top,
vec2 tiling_bottom, vec2 offset_bottom, float intensity_bottom,
vec2 tiling_side, vec2 offset_side, float intensity_side,
float top_to_side_diff, float fade) {
vec3 abs_normal = abs(world_normal);
// Adjust blend weights based on top_to_side_difference
float top_boost = mix(0.25, 4.0, clamp(top_to_side_diff, 0.0, 1.0));
vec3 blend_weights = vec3(1.0, top_boost, 1.0);
abs_normal *= blend_weights;
// Apply fade/sharpness
float blend_sharpness = mix(0.1, 50.0, fade);
abs_normal = pow(abs_normal, vec3(blend_sharpness));
abs_normal /= dot(abs_normal, vec3(1.0));
// Determine if top or bottom based on Y normal
float is_top = step(0.0, world_normal.y);
// Sample textures
vec2 uv_x = world_pos.zy * tiling_side * 0.01 + offset_side;
vec2 uv_y_top = world_pos.xz * tiling_top * 0.01 + offset_top;
vec2 uv_y_bottom = world_pos.xz * tiling_bottom * 0.01 + offset_bottom;
vec2 uv_z = world_pos.xy * tiling_side * 0.01 + offset_side;
vec4 tex_x = normal_strength(texture(tex_side, uv_x), intensity_top);
vec4 tex_y = normal_strength(mix(texture(tex_bottom, uv_y_bottom), texture(tex_top, uv_y_top), is_top), intensity_bottom);
vec4 tex_z = normal_strength(texture(tex_side, uv_z), intensity_side);
return tex_x * abs_normal.x + tex_y * abs_normal.y + tex_z * abs_normal.z;
}
vec3 triplanar_map(vec3 x, vec3 y, vec3 z, vec3 n) {
n = n*n;
return (x*n.x + y*n.y + z*n.z)/(n.x+n.y+n.z);
}
void fragment() {
// Get world position and normal
vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
vec3 world_normal = normalize((INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
// UV with panning
vec2 uv_panned = UV + uv_pan * TIME;
// === BASE ALBEDO ===
vec4 base_albedo = vec4(1.0);
if (enable_base_texture) {
base_albedo = texture(base_texture, uv_panned * base_tiling + base_offset);
}
// === OVERLAY ===
vec4 overlay = vec4(1.0);
if (enable_overlay_texture) {
vec4 overlay_sample = texture(overlay_texture, uv_panned * overlay_tiling + overlay_offset);
overlay = overlay_sample + vec4(vec3(1.0 - overlay_intensity), 0.0);
overlay = clamp(overlay, vec4(0.0), vec4(1.0));
}
// === TRIPLANAR ALBEDO ===
vec4 triplanar_albedo = vec4(1.0);
if (enable_triplanar_texture) {
triplanar_albedo = triplanar_sample(
triplanar_texture_top,
triplanar_texture_bottom,
triplanar_texture_side,
world_pos, world_normal,
triplanar_tiling_top, triplanar_offset_top,
triplanar_tiling_bottom, triplanar_offset_bottom,
triplanar_tiling_side, triplanar_offset_side,
triplanar_top_to_side_difference, triplanar_fade);
triplanar_albedo = clamp((1.0 - triplanar_intensity) + triplanar_albedo, vec4(0.0), vec4(1.0));
}
// Combine albedo layers
vec4 final_albedo = base_albedo * overlay * triplanar_albedo * color_tint;
// === SNOW ===
float snow_mask = 0.0;
if (enable_snow) {
vec3 normal_for_snow = snow_use_world_up ? world_normal : (inverse(MODEL_MATRIX) * INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz;
float snow_fade = mix(0.35, 50.0, snow_level);
snow_mask = pow(max(0.0, normal_for_snow.y), snow_fade) * snow_transition;
snow_mask = clamp(snow_mask, 0.0, 1.0);
final_albedo = mix(final_albedo, snow_color, snow_mask);
}
// === NORMALS ===
// Only modify normals if at least one normal source is enabled
vec3 normal_sample = vec3(0.0, 0.0, 0.0);
vec3 t_normal_sample = vec3(0.0, 0.0, 0.0);
if (enable_normal_texture) {
vec4 normal_sample_a = texture(normal_texture, uv_panned * normal_tiling + normal_offset);
normal_sample = normal_strength(normal_sample_a, normal_intensity).rgb;
}
if (enable_triplanar_normals) {
t_normal_sample = triplanar_normal_sample(
triplanar_normal_top,
triplanar_normal_bottom,
triplanar_normal_side,
world_pos, world_normal,
triplanar_normal_tiling_top, triplanar_normal_offset_top, triplanar_normal_intensity_top,
triplanar_normal_tiling_bottom, triplanar_normal_offset_bottom, triplanar_normal_intensity_bottom,
triplanar_normal_tiling_side, triplanar_normal_offset_side, triplanar_normal_intensity_side,
triplanar_normal_top_to_side_difference, triplanar_normal_fade).xyz;
}
if (enable_triplanar_normals || enable_normal_texture) {
BINORMAL = triplanar_map(
normalize((VIEW_MATRIX * vec4(0.,-1.,0.,0)).xyz),
normalize((VIEW_MATRIX * vec4(0.,0.,1.,0)).xyz),
normalize((VIEW_MATRIX * vec4(0,-1.,0.,0)).xyz),
world_normal
);
TANGENT = triplanar_map(
normalize((VIEW_MATRIX * vec4(0.,1.,0.,-1.)).xyz),
normalize((VIEW_MATRIX * vec4(1.,0.,0.,1.)).xyz),
normalize((VIEW_MATRIX * vec4(0,0.,1.,-1.)).xyz),
world_normal
);
NORMAL_MAP = normalize(t_normal_sample + normal_sample) ;
}
// === EMISSION ===
vec3 emission = vec3(0.0);
if (enable_emission_texture) {
vec4 emission_sample = texture(emission_texture, uv_panned * emission_tiling + emission_offset);
emission = (emission_sample * emission_color_tint).rgb;
}
if (enable_triplanar_emission) {
vec3 triplanar_uv = world_pos * triplanar_emission_tiling;
vec3 blend = pow(abs(world_normal), vec3(triplanar_emission_blend));
blend /= dot(blend, vec3(1.0));
vec4 em_x = texture(triplanar_emission_texture, triplanar_uv.zy);
vec4 em_y = texture(triplanar_emission_texture, triplanar_uv.xz);
vec4 em_z = texture(triplanar_emission_texture, triplanar_uv.xy);
vec4 triplanar_em = em_x * blend.x + em_y * blend.y + em_z * blend.z;
emission = (triplanar_em * triplanar_emission_color_tint * triplanar_emission_intensity).rgb;
}
// === METALLIC & SMOOTHNESS ===
float final_metallic = metallic;
float final_smoothness = smoothness;
if (enable_triplanar_texture) {
// Approximate triplanar metallic/smoothness based on blend weights
vec3 abs_normal = abs(world_normal);
abs_normal /= (abs_normal.x + abs_normal.y + abs_normal.z);
float is_top = step(0.0, world_normal.y);
float y_metallic = mix(bottom_metallic, top_metallic, is_top);
float y_smoothness = mix(bottom_smoothness, top_smoothness, is_top);
final_metallic = abs_normal.x * side_metallic + abs_normal.y * y_metallic + abs_normal.z * side_metallic;
final_smoothness = abs_normal.x * side_smoothness + abs_normal.y * y_smoothness + abs_normal.z * side_smoothness;
}
if (enable_snow) {
final_metallic = mix(final_metallic, snow_metallic, snow_mask);
final_smoothness = mix(final_smoothness, snow_smoothness, snow_mask);
}
// === AMBIENT OCCLUSION ===
float ao = 1.0;
if (enable_ambient_occlusion) {
if (generate_from_base_normals) {
ao = texture(normal_texture, uv_panned * normal_tiling + normal_offset).r;
} else {
ao = texture(ao_texture, UV * ao_tiling + ao_offset).r;
}
ao = pow(ao, ao_intensity);
}
// === OUTPUT ===
ALBEDO = final_albedo.rgb;
// NORMAL_MAP is only set if has_normal_modification is true
EMISSION = emission;
METALLIC = final_metallic;
ROUGHNESS = 1.0 - final_smoothness;
AO = ao;
ALPHA = final_albedo.a;
ALPHA_SCISSOR_THRESHOLD = 0.1;
}


