shader_type spatial; // Настройки, которые можно крутить в Инспекторе uniform sampler2D albedo_texture : source_color, filter_nearest; uniform float vertex_snap_resolution = 60.0; // Чем меньше число, тем сильнее "дрожь" uniform vec2 texture_pixelation = vec2(64.0, 64.0); // Пикселизация текстуры uniform float dither_strength = 0.15; // Сила дизеринга (шума) // Матрица Байера 4x4 для упорядоченного дизеринга const float bayer_matrix[16] = float[16]( 0.0, 8.0, 2.0, 10.0, 12.0, 4.0, 14.0, 6.0, 3.0, 11.0, 1.0, 9.0, 15.0, 7.0, 13.0, 5.0 ); void vertex() { // 1. Эффект "дрожания" вершин (Vertex Snapping) // Переводим вершину в пространство камеры vec4 view_pos = MODELVIEW_MATRIX * vec4(VERTEX, 1.0); // Умножаем на коэффициент сетки, округляем и делим обратно view_pos.xyz *= vertex_snap_resolution; view_pos.xyz = floor(view_pos.xyz); view_pos.xyz /= vertex_snap_resolution; // Возвращаем в локальное пространство VERTEX = (inverse(MODELVIEW_MATRIX) * view_pos).xyz; } void fragment() { // 2. Пикселизация UV координат текстуры vec2 uv = UV; vec2 pixelated_uv = floor(uv * texture_pixelation) / texture_pixelation; // Читаем цвет из текстуры (filter_nearest не дает ей мылиться) vec4 tex_color = texture(albedo_texture, pixelated_uv); // 3. Упорядоченный дизеринг (имитация 16-битного цвета) // Берем экранные координаты в разрешении 320x240 (как на PS1) vec2 screen_pos = SCREEN_UV * vec2(320.0, 240.0); int x = int(mod(screen_pos.x, 4.0)); int y = int(mod(screen_pos.y, 4.0)); // Достаем порог из матрицы Байера float threshold = (bayer_matrix[y * 4 + x] / 16.0) - 0.5; // Применяем шум к цвету vec3 final_color = tex_color.rgb + (threshold * dither_strength); final_color = clamp(final_color, 0.0, 1.0); ALBEDO = final_color; }