Сглаживание шумов с разными амплитудами (часть 2)

Ну, я продолжаю этот вопрос без ответа ( сглаживание случайных шумов с разными амплитудами), и у меня есть еще один вопрос.

Я решил использовать контур / тень фигуры ( Translating / transformation - список точек от его центра со смещением / расстоянием).

Этот контур / тень больше, чем текущий путь. Я использовал этот репозиторий ( https://github.com/n-yoda/unity-vertex-effects), чтобы воссоздать тень. И это работает довольно хорошо, за исключением одного факта.

Чтобы узнать высоту всех точек (полученную с помощью этого алгоритма тени ( строка 13 из ModifiedShadow.cs и строка 69 из CircleOutline.cs)), я получаю расстояние от текущей точки до центра и делю между максимальным расстоянием до центра:

float dist = orig.Max(v => (v - Center).magnitude);
foreach Point in poly --> float d = 1f - (Center - p).magnitude / dist;

Где orig - полный список точек, полученных алгоритмом тени. D - высота тени.

Но проблема очевидна: я получаю идеальный круг:

В красном и черном, чтобы увидеть контраст:

И это не то, что я хочу

Как видите, это не идеальный градиент. Давайте объясним, что происходит.

Я использую эту библиотеку для генерации шумов: https://github.com/Auburns/FastNoise_CSharp

Примечание. Если вы хотите узнать, что я использую для получения шумов с различной амплитудой: сглаживание случайных шумов с различными амплитудами (см. Первый блок кода), чтобы увидеть это в действии, см. Этот репозиторий.

  • Зеленый цвет фона представляет шумы со средней высотой -0,25 и амплитудой 0,3
  • Белый цвет фона представляет шумы со средней высотой 0 и амплитудой 0,1
  • Красный означает 1 (полная интерполяция для шумов, соответствующих белым пикселям)
  • Черный означает 0 (общая интерполяция для шумов, соответствующих зеленым пикселям)

Вот почему у нас есть такой вывод:

На самом деле я попытался сравнить расстояния каждой отдельной точки до центра, но это привело к странному и неожиданному результату.

На самом деле, я не знаю, что попробовать...

1 ответ

Решение

Проблема заключается в том, что процент отклонения (например, от высокого / низкого или "красного" до "черного" в вашей визуализации) является только функцией расстояния точки от центра, которое делится на константу (которая оказывается максимальное расстояние любой точки от центра). Вот почему он выглядит круглым.

Например, центральная точка на левой стороне многоугольника может находиться на расстоянии 300 пикселей от центра, а центральная точка на правой стороне может составлять 5 пикселей. Оба должны быть красными, но основываться на них 0 distance from center = red не будет или красным, и основывая его на min distance from center = red будет иметь только красный на правой стороне.

Соответствующие минимальные и максимальные расстояния будут меняться в зависимости от того, где находится точка

Один альтернативный метод для каждой точки: найти ближайший белый пиксель и найти ближайший зеленый пиксель (или ближайший теневой пиксель, смежный с зеленым / белым, например, здесь). Затем выберите свое покраснение в зависимости от того, как расстояния сравниваются между этими двумя точками и текущей точкой.

Следовательно, вы можете сделать это (псевдо-C#):

foreach pixel p in shadow_region {

    // technically, closest shadow pixel which is adjacent to x Pixel: 
    float closestGreen_distance = +inf;
    float closestWhite_distance = +inf;

    // Possibly: find all shadow-adjacent pixels prior to the outer loop 
    // and cache them. Then, you only have to loop through those pixels.
    foreach pixel p2 in shadow {
        float p2Dist = (p-p2).magnitude;

        if (p2 is adjacent to green) {
           if (p2Dist < closestGreen_distance) {
               closestGreen_distance = p2Dist;
           }
        }

        if (p2 is adjacent to white) {
           if (p2Dist < closestWhite_distance) {
               closestWhite_distance = p2Dist;
           }
        }
    }

    float d = 1f - closestWhite_distance / (closestWhite_distance + closestGreen_distance)
}

Используя код, который вы разместили в комментариях, это может выглядеть так:

foreach (Point p in value)
{
    float minOuterDistance = outerPoints.Min(p2 => (p - p2).magnitude);
    float minInnerDistance = innerPoints.Min(p2 => (p - p2).magnitude);

    float d = 1f - minInnerDistance / (minInnerDistance + minOuterDistance);

    Color32? colorValue = func?.Invoke(p.x, p.y, d);

    if (colorValue.HasValue)
        target[F.P(p.x, p.y, width, height)] = colorValue.Value;
}

Вышеуказанная часть была выбрана для решения. Приведенная ниже часть, упомянутая в качестве другого варианта, оказалась ненужной.


Если вы не можете определить, находится ли теневой пиксель рядом с белым / зеленым, вот альтернатива, которая требует только вычисления нормалей каждой вершины в вашем розовом (оригинальном) контуре.

Создайте внешние "желтые" вершины, перейдя к каждой розовой вершине и следуя ее нормальному внешнему виду. Создайте внутренние "синие" вершины, перейдя к каждой розовой вершине и следуя за ней внутрь.

Затем, проходя через каждый пиксель в тени, проходите по желтым вершинам, чтобы получить "самый близкий к зеленому", и по синему, чтобы получить "самый близкий к белому".

Проблема в том, что поскольку ваши фигуры не полностью выпуклые, эти проецируемые синие и желтые контуры могут быть в некоторых местах наизнанку, поэтому вам придется как-то с этим справиться. У меня проблемы с определением точного метода борьбы с этим, но вот что у меня есть:

Один шаг состоит в том, чтобы игнорировать любые синие / желтые цвета, которые имеют внешние нормали, которые указывают на текущий теневой пиксель.

Однако, если текущий пиксель находится внутри точки, где желто-синяя фигура наизнанку, я не уверен, как поступить. Там может быть что-то, чтобы игнорировать синие / желтые вершины, которые ближе к ближайшей розовой вершине, чем они должны быть.

крайне грубый псевдокод:

list yellow_vertex_list = new list 
list blue_vertex_list = new list 
foreach pink vertex p:
    given float dist;
    vertex yellowvertex = new vertex(p+normal*dist)
    vertex bluevertex = new vertex(p-normal*dist)

    yellow_vertex_list.add(yellowvertex)
    blue_vertex_list.add(bluevertex)

create shadow

for each pixel p in shadow:
    foreach vertex v in blue_vertex_list
        if v.normal points towards v: break;
        if v is the wrong side of inside-out region: break;
        if v is closest so far:
            closest_blue = v
            closest_blue_dist = (v-p).magnitude

    foreach vertex v in yellow_vertex_list
        if v.normal points towards v break;
        if v is the wrong side of inside-out region: break;
        if v is closest so far:
            closest_yellow = v
            closest_yellow_dist = (v-p).magnitude


    float d = 1f - closest_blue_dist / (closest_blue_dist + closest_yellow_dist)
Другие вопросы по тегам