shader_type spatial; // Поверхности уровня Parkour: освещение запечено в цвета вершин (Blender, Cycles), динамических источников нет. // Итог = цвет материала × запечённый свет × мелкое «зерно» бетона (по мировым координатам) + слабый блик на скользящих углах. render_mode unshaded; uniform vec3 albedo_color = vec3(0.1); uniform float light_scale = 8.0; // цвета вершин сжаты в 8 раз при экспорте (см. bake.py) uniform float exposure = 1.0; uniform sampler2D grain : repeat_enable, filter_linear_mipmap; uniform float grain_scale = 0.3; uniform float grain_amount = 0.45; uniform sampler2D sky_tex : source_color, filter_linear_mipmap; // панорама неба: отражается в «мокрых» горизонтальных поверхностях uniform float wet = 0.6; varying vec3 wpos; varying vec3 wnorm; void vertex() { wpos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; wnorm = normalize((MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz); } vec2 pano_uv(vec3 d) { return vec2(atan(d.x, -d.z) / (2.0 * PI) + 0.5, acos(clamp(d.y, -1.0, 1.0)) / PI); } void fragment() { vec3 n = abs(wnorm); n /= (n.x + n.y + n.z + 0.0001); float g = texture(grain, wpos.zy * grain_scale).r * n.x + texture(grain, wpos.xz * grain_scale).r * n.y + texture(grain, wpos.xy * grain_scale).r * n.z; float gr = mix(1.0 - grain_amount * 0.5, 1.0 + grain_amount * 0.5, g); vec3 lit = COLOR.rgb * light_scale; vec3 nv = normalize(NORMAL); float ndv = clamp(dot(nv, normalize(VIEW)), 0.0, 1.0); float fres = 0.05 + 0.95 * pow(1.0 - ndv, 4.0); vec3 rw = normalize((INV_VIEW_MATRIX * vec4(reflect(-normalize(VIEW), nv), 0.0)).xyz); float up = smoothstep(0.6, 0.95, wnorm.y); // блестят только горизонтальные поверхности vec3 refl = textureLod(sky_tex, pano_uv(rw), 3.0).rgb; ALBEDO = albedo_color * lit * gr * exposure + refl * fres * wet * up * (0.5 + 0.5 * min(lit.r + lit.g + lit.b, 1.0)); }