Crear nebulosa de píxeles con ruido y corte medio

Quería una nebulosa en mi juego The Last Boundary . Se ven increíbles y el espacio sin ellos no es espacio, sino simplemente píxeles blancos dispersos por el fondo. Pero desde que hice el juego al estilo de "pixel art", necesitaba hacer que mi biblioteca de ruido generara imágenes pixeladas de alguna manera.

Aquí hay algunos ejemplos:



Más ejemplos





En ejemplos de un solo color, se usan 8 colores y en otros, 16 colores. En este artículo, hablaré sobre cómo creé una nebulosa pixelada para The Last Boundary.

Cuando trabajamos con una biblioteca de ruido, como LibNoise , no importa qué motor use (o escriba el suyo), los valores generalmente se distribuyen en el rango de -1 a 1 . Teóricamente es más probable que el ruido 2D esté en el rango de -0.7 a 0.7 , pero algunas implementaciones escalan el resultado, traduciéndolo en el intervalo de -1 a 1 . Para trabajar con texturas 2D, generalmente se convierte en un intervalo de 0 a 1 , y luego RGB(255,255,255) dentro del rango de RGB(0,0,0) a RGB(255,255,255) .


Ruido de Perlin generado a partir de la coordenada x,y de cada píxel escalado a 0.3f

Luego puede usar el movimiento browniano fraccional para darle a la imagen una sensación de esplendor de las nubes.


El ruido de Perlin se sometió a un movimiento browniano fraccional con 8 octavas, frecuencia 0.01 , regularidad 0.5 y lacunaridad 2.0 .

Noté que hay muchas implementaciones incorrectas de ruido Perlin, ruido simplex y movimiento browniano fraccional (fBm) en Internet. Parece haber mucha confusión sobre qué es qué. Asegúrese de utilizar la implementación correcta, porque si desea crear la cadena descrita anteriormente, en caso de una implementación incorrecta, es posible que no obtenga los resultados requeridos.

Imaginemos que queremos crear un efecto de humo, es decir, tal solución nos conviene. Pero nuestro juego de pixel art se vería extraño si aparecieran RGB(255,255,255) colores nuevos, desde RGB(0,0,0) hasta RGB(255,255,255) . De repente, aparecerían 255 nuevos grados de gris en el juego.

Necesitamos convertirlos a un número limitado de colores. Eso es lo que haremos más tarde. Mientras tanto ...

Generar nebulosa aleatoria


Repetí para tutoriales ya preparados sobre la generación de nebulosas aleatorias, pero agregué algunos de mis pasos y apliqué mi propia biblioteca de ruido. Lo escribí hace unos años porque quería comprender bien el ruido de Perlin y cómo puedes usarlo junto con otros conceptos para crear texturas y similares.

Quizás pueda repetirme paso a paso o tendrá que hacer adiciones al código que afectará su ruido. Explicaré todo excepto la generación de ruido inicial y fBm para que pueda escribir el código usted mismo; Creo que se puede suponer que ya tienes la capacidad de generar ruido y fBm.

Para comenzar, mostraré el resultado de generar la nebulosa:


Resultado terminado

Es importante tener en cuenta que aún no está pixelado. Tiene una gama completa de colores con un cielo estrellado pixelado. La nebulosa que pixelaremos más tarde.

Lo primero que debe hacer es generar cinco texturas diferentes: rojo, verde, azul, alfa y máscara. Las texturas roja, verde y azul son necesarias para los canales de color finales correspondientes. De hecho, solo genero uno o dos canales de color, porque resultó que usar los tres produce una nebulosa increíblemente colorida que se ve fea. Cualquier color individual o una combinación de dos colores funcionará bien.

El canal alfa es importante porque depende de si las estrellas inferiores brillarán a través de la nebulosa. Ilustraré esto mostrando el canal alfa del ejemplo que se muestra arriba.


Listo canal alfa de nuestro ejemplo

Cuanto más blanca es el área, más cercano está el valor a 1.0 , lo que nos da un valor alfa de 255 . Cuanto más negra es la zona, más transparente es. Si observa un ejemplo, puede ver que las áreas negras corresponden a áreas en las que el cielo estrellado es visible.


Ejemplo de cielo estrellado

Estas no son las mismas estrellas que en el ejemplo, porque se generan aleatoriamente en cada captura de pantalla. Espero que esto no te impida entender cómo se genera la nebulosa.

Mi biblioteca de ruido consta de módulos, siguiendo el ejemplo de Lib Noise . Todo en esta biblioteca son "módulos" que se pueden encadenar juntos. Algunos módulos generan nuevos valores (Módulo Perlin, Valor constante), otros los conectan (Multiplicar, Agregar), y algunos simplemente realizan operaciones sobre el valor (Lerp, Clamp).

Canales de color


No importa si trabajamos con uno, dos o tres colores: los canales rojo, verde y azul se generan de la misma manera; Solo uso un valor semilla diferente para ellos. Mis valores iniciales dependen de la hora actual del sistema.

A continuación se presentan todos en escala de grises, pero en teoría son simplemente valores para uno de los tres canales. La escala de grises está aquí solo para ilustrar los resultados.

1. ruido de Perlin


Como arriba, el ruido de Perlin será el punto de partida. Si lo desea, puede usar ruido simplex, parece que su implementación 2D no pertenece a Ken Perlin, pero podría estar equivocado. Desde un punto de vista matemático, el ruido simple utiliza menos instrucciones, por lo que la generación de una nebulosa similar será más rápida. Dado que usa símplex en lugar de una cuadrícula, crea un ruido un poco más hermoso, pero no trabajaremos mucho con él, por lo que esto no es particularmente importante.

El código real no se muestra a continuación, porque en fuentes reales los valores x,y fueron cambiados por fBm en el paso 3. Esta es solo la coordenada x,y de la imagen, multiplicada por el factor de escala estático.


Ruido de Perlin generado a partir de la coordenada x,y de cada píxel escalado a 0.3f . Es decir PixelValue = PerlinNoise(x * 0.3f, y * 0.3f)

Los valores creados por el ruido de Perlin están aproximadamente en el rango de -1 a 1 , por lo que para crear la imagen de escala de grises habitual que se muestra arriba, los convertimos al intervalo de 0 a 1 . Probé el alcance de los valores para que la conversión produzca el mayor contraste (el valor más bajo corresponde a 0 , el más grande - 1 ).

2. Multiplicación


El siguiente módulo utilizado multiplica el ruido generado por 5 . Esto puede considerarse un ajuste de contraste. Los valores negativos son más oscuros, los valores positivos son más claros.

No tengo nada que mostrar aquí, porque en el proceso de convertir valores del intervalo de -5 a 5 al intervalo de 0 a 1 resultado no cambia.

3. Movimiento browniano fraccional (fBM)


Esta etapa convierte el ruido en lo que muchas personas consideran un verdadero "efecto de ruido". Aquí ejecutamos octavas de muestras cada vez más pequeñas a partir de la función de ruido (en nuestro caso, la función es perlin(x,y) ) para agregar esponjosidad.


El movimiento browniano fraccionado del ruido de Perlin se muestra arriba. 8 octavas, frecuencia .01f , regularidad .5f y 2.5f

Ya puedes ver el origen de algo interesante. La imagen que se muestra arriba no se genera al escalar las coordenadas x,y de los píxeles, fBM hace esto. Nuevamente, estos valores se convierten inversamente a un intervalo de 0 a 1 a un posible intervalo de -5 a 5 .

4. Restricción (abrazadera)


Ahora limitaré los valores a un rango de -1 a 1 . Cualquier cosa fuera de este intervalo se descartará por completo.


El mismo fBm, limitado a -1 a 1

La tarea de esta operación es convertir los valores a un intervalo más corto mientras crea gradientes más nítidos y aumenta el área en blanco o negro. Estas áreas muertas o vacías son importantes para el efecto de nebulosa, que abordaremos más adelante. Si no hubiéramos multiplicado por 5 al principio, entonces la abrazadera no habría cambiado nada.

5. Agregar 1


Ahora tomamos los valores de la abrazadera y les sumamos 1. Por lo tanto, transferimos los valores al intervalo de 0 a 2 . Después de la conversión, los resultados se verán igual que antes.

6. Divide entre 2


Probablemente sepa lo que sucederá cuando divida el resultado por 2 (multiplique por .5 ). En la imagen, nada volverá a cambiar.

Los pasos 5 y 6 convierten los valores en un rango de 0 a 1 .

7. Crea una textura de distorsión


El siguiente paso es crear una textura de distorsión. Haré esto con el ruido Perlin (con el nuevo valor de inicialización)> multiplicar por 4> ejecutar fBm. En este caso, fBm usa 5 octavas, una frecuencia de 0.025 , una regularidad de 0.5 y una lacunaridad de 1.5 .


Textura de distorsión

Esta textura es necesaria para crear más detalles que en la textura existente de la nebulosa. La nebulosa es una nube ondulada bastante grande, y esta textura le hará pequeños cambios. A través de él, la naturaleza de rejilla del ruido de Perlin comenzará a emerger.

8. Compense la textura de color usando la textura de compensación


Luego tomaré estas dos texturas y usaré una para compensar las coordenadas de la otra por un factor. En nuestro caso, la combinación se ve así:


Resultado de sesgo

La textura de distorsión se usa para cambiar las coordenadas x,y que estamos buscando en los datos de ruido de origen.

Recuerde que las imágenes que se muestran arriba son solo para fines ilustrativos. En cada etapa, en realidad solo tenemos una función de ruido. Le pasamos el valor x,y , y devuelve un número. En ciertas etapas, el intervalo de este número puede ser diferente, pero arriba lo convertimos nuevamente en escala de grises para crear una imagen. La imagen se crea utilizando cada coordenada x,y de la imagen como x,y , transmitida por la función de ruido.

Es decir, cuando decimos:

Dame el valor para el píxel de la esquina superior izquierda con X = 0 e Y = 0

La función nos devuelve un número. Si le preguntamos a Perlin por esto, sabemos que estará entre -1 y 1 , si, como arriba, aplicamos la pinza, la suma y la multiplicación, obtenemos un valor entre 0 y 1 .

Una vez entendido esto, aprendemos que la función de ruido de distorsión crea valores en el rango de -1 a 1 . Por lo tanto, para realizar el sesgo cuando decimos:

Dame el valor para el píxel en la esquina superior izquierda con el píxel X = 0 e Y = 0

el módulo de compensación primero le pide a la función de compensación las coordenadas x,y . El resultado de esto es entre -1 y 1 (como estaba arriba). Luego se multiplica por 40 (este es el coeficiente que seleccioné). El resultado será un valor entre -40 y 40 .

Luego tomamos este valor y lo agregamos a las coordenadas de la x,y que estábamos buscando, y usamos este resultado para buscar la textura de color. Cortamos los valores negativos con la abrazadera a 0, porque es imposible buscar coordenadas negativas x,y en las funciones de ruido (al menos en mi biblioteca de ruido).

Es decir, en general, se ve así:

 ColourFunction(x,y) =     0  1 DisplaceFunction(x,y) =     -1  1 DoDisplace(x,y) = { v = DisplaceFunction(x,y) * factor clamp(v,0,40) x = x + v; y = y + v; if x < 0 then x = 0 if y < 0 then y = 0 return ColourFunction(x,y) } 

Espero que entiendas esto. De hecho, no estamos mirando la x,y que estábamos, sino el desplazamiento. Y dado que la magnitud también es un gradiente suave, cambia suavemente.

Hay otras formas de realizar el desplazamiento. Mi biblioteca de ruido tiene un módulo que crea un desplazamiento en espiral. Se puede usar para dibujar textura, disminuyendo gradualmente a varios puntos. Aquí hay un ejemplo .

Eso es todo Repetimos las operaciones anteriores tres veces, utilizando nuevos valores de inicialización para cada canal de color. Puedes crear uno o dos canales. No creo que valga la pena crear un tercero.

Canal alfa


Un canal alfa se crea de manera muy similar a los canales de color:

  1. Comenzamos con el ruido de Perlin
  2. Multiplicar por 5
  3. fBM con 8 octavas, frecuencia 0.005 , regularidad 0.5 y lacunaridad 2.5
  4. Limitamos los resultados usando Clamp al intervalo de -1 a 1 , sumamos 1 , dividimos por 2 (es decir, cambiamos el intervalo de -1 a 1 al intervalo de 0 a 1 .
  5. Cambiamos el resultado por una pequeña cantidad en la dirección negativa. Lo compensé por 0.4 . Gracias a esto, todo se vuelve un poco más oscuro.
  6. Limitamos los resultados a un intervalo de 0 a 1 . Como movimos todo, haciéndolo un poco más oscuro, de hecho, creamos más áreas con 0 , y algunas áreas entraron en valores negativos.

El resultado es una textura de canal alfa.


Textura alfa

Como dije, las áreas negras serán transparentes y las áreas blancas serán opacas.

Máscaras de canal


Esta es la última textura que se usa para crear sombras superpuestas sobre todo lo demás. Comienza como todas las otras texturas:

  1. Ruido perlin
  2. Multiplicar por 5
  3. Realizamos fBm, 5 octavas, frecuencia 0.01 , regularidad 0.1 , lacunaridad 0.1 . La regularidad es pequeña, por lo que la nube es menos densa.
  4. Realizar un cambio de intervalo de -1 a 1 a un intervalo de 0 a 1

Pero creamos dos de esas texturas:


Enmascarar un


Máscara B

Exponemos estas dos texturas a lo que yo llamo el módulo Seleccionar . De hecho, utilizamos el valor del módulo A o del módulo B. La elección depende del valor del módulo C. Requiere dos valores más: Seleccionar punto y Disminución .

Si el valor en el punto x,y módulo C es mayor o igual que SelectPoint , entonces usamos el valor en el punto x,y módulo B. Si el valor es menor o igual que SelectPoint - Falloff , entonces usamos el valor en x,y módulo A.

Si está entre SelectPoint - Falloff y SelectPoint , entonces realizamos una interpolación lineal entre los valores x,y del módulo A y el módulo B.

 float select(x, y, moduleA, moduleB, moduleC, selectPoint, falloff) { float s = moduleC(x,y); if(s >= selectPoint) return moduleB(x,y); else if(s <= selectPoint - falloff) return moduleA(x,y); else { float a = moduleA(x,y); float b = moduleB(x,y); return lerp(a, b, (1.0 / ((selectPoint - (selectPoint-falloff)) / (selectPoint - s))); } } 

En nuestro caso, el módulo A es un módulo constante con un valor de 0 . El módulo B es la primera textura de la máscara A, y Selector (módulo C) es la segunda máscara de B. SelectPoint será 0.4 , y Falloff será 0.1 . Como resultado, obtenemos:


Máscara definitiva

Al aumentar o disminuir SelectPoint , disminuimos o aumentamos la cantidad de negro en la máscara. Al aumentar o disminuir la falloff , aumentamos o disminuimos los bordes suaves de las máscaras. En lugar de una de las máscaras, podría usar el módulo Constante con un valor de 1 , pero quería agregar un poco de aleatoriedad a las áreas "sin máscara".

Mezcle el canal de color y la máscara


Ahora necesitamos aplicar una máscara a cada uno de los canales de color. Esto se hace usando el módulo de fusión . Combina los porcentajes de valores de dos módulos para que la suma de los valores sea del 100%.

Es decir, podemos tomar el 50% del valor en x,y módulo A y el 50% del valor en x,y módulo B. O el 75% y el 25%, etc. El porcentaje que tomamos de cada módulo depende de otro módulo: el módulo C. Si el valor en x,y módulo C es 0 , tomaremos el 100% del módulo A y el 0% del módulo B. Si es 1 , entonces tomamos valores inversos

Combina para cada textura de color.

  • Módulo A - Valor constante 0
  • El módulo B es el canal de color que ya hemos visto.
  • Módulo C - resultado de la máscara

Esto significa que el ruido del canal de color se mostrará solo donde la máscara tenga valores superiores a 0 (áreas más cercanas al blanco), y la magnitud de su visibilidad depende del valor de la máscara.

Aquí está el resultado de nuestro ejemplo:


Resultado final

Compare esto con el original antes de aplicar la mezcla con una máscara.


Antes de mezclar con una mascarilla

Quizás este ejemplo no sea muy obvio, pero debido a la posibilidad es difícil seleccionar específicamente un buen ejemplo. El efecto de la máscara es crear áreas más oscuras. Por supuesto, puede personalizar la máscara para que sea más pronunciada.

Aquí es importante que se aplique la misma máscara a todo el canal de color, es decir, las mismas áreas aparecen en la sombra.

Combinamos todo juntos


Nuestro ejemplo final terminado:


Ejemplo listo

Utiliza los canales rojo, verde y alfa:


Canal rojo


Canal verde


Canal alfa

Y luego los colocamos sobre nuestro cielo estrellado.

Ahora todo se ve bastante bien, pero no es muy adecuado para un juego de pixel art. Necesitamos reducir el número de colores ...

Corte medio


Esta parte del artículo se puede aplicar a cualquier cosa. Supongamos que genera una textura de mármol y desea reducir la cantidad de colores. Aquí es donde el algoritmo de corte medio resulta útil. Lo usaremos para reducir la cantidad de colores en la nebulosa que se muestra arriba.

Esto sucede antes de que se superponga en el cielo estrellado. El número de colores es completamente arbitrario.

El algoritmo Median Cut como se describe en Wikipedia:

Supongamos que tenemos una imagen con un número arbitrario de píxeles y queremos generar una paleta de 16 colores. Ponga todos los píxeles en la imagen (es decir, sus valores RGB ) en la papelera . Descubra qué canal de color (rojo, verde o azul) entre todos los píxeles de la cesta tiene el mayor rango de valores, y luego ordene los píxeles de acuerdo con los valores de este canal. Por ejemplo, si el canal azul tiene el mayor rango de valores, entonces el píxel con el valor RGB (32, 8, 16) es más pequeño que el píxel con el valor RGB (1, 2, 24), porque 16 <24. Después de ordenar la canasta, coloque la mitad superior de los píxeles en una nueva canasta. (Este paso le dio el nombre al algoritmo de corte medio; las cestas se dividen a la mitad por la mediana de la lista de píxeles). Repita el proceso para ambas cestas, lo que nos dará 4 cestas, luego repita para las 4 cestas, obtenga 8 cestas, luego repita para 8 cestas, obtenemos 16 cestas Hacemos un promedio de los píxeles en cada una de las canastas y obtenemos una paleta de 16 colores. Dado que el número de cestas se duplica en cada iteración, el algoritmo solo puede generar tales paletas, el número de colores en el cual es una potencia de dos . Por ejemplo, para generar una paleta de 12 colores, primero debe generar una paleta de 16 colores y luego combinar de alguna manera algunos colores.

Fuente: https://en.wikipedia.org/wiki/Median_cut

Esta explicación me pareció bastante mala y no particularmente útil. Al implementar el algoritmo, se obtienen imágenes bastante feas de esta manera. Lo implementé con algunos cambios:

  1. Almacenamos el contenedor de boxes junto con el valor que indica el intervalo (más sobre esto a continuación). El box simplemente almacena un número dinámico de píxeles de la imagen original.
  2. Agregue todos los píxeles de la imagen original como primer y use el intervalo 0
  3. Si bien el número total de menor que el número requerido de colores, continuamos los siguientes pasos.
  4. Si el valor del intervalo es 0 , entonces para cada cuadro actual determinamos el canal de color principal de este box , y luego ordenamos los píxeles en este box por este color. — Red, Green, Blue Alpha, . , redRange = Max(Red) - Min(Red) . , .
  5. box boxes . , box .
  6. , 4 5 box , boxes . , , , . , , .
  7. box ( == ) boxes . 0 ( ). , , , — . .

Cuando alcanzamos el número de cuadros igual al número de colores deseado, simplemente promediamos todos los píxeles en cada cuadro para determinar el elemento de paleta que mejor se adapte a estos colores. Acabo de usar la distancia euclidiana, pero hay soluciones perceptivas que pueden hacerlo mejor.

Aquí hay una imagen que explicará todo más claramente. Para la demostración, uso solo RGB, porque alfa es difícil de mostrar.


Apliquemos este método a nuestra imagen de ejemplo.


El original


Corte medio hasta 16 colores

Descubrí que cuando se usan dos canales de color, se obtiene un buen efecto con 16 colores. Pero tenga en cuenta que aquí usamos el canal alfa, que también está involucrado en el cálculo de la distancia entre los colores. Entonces, si no le importa la transparencia, puede usar menos colores. Dado que mi corte medio, a diferencia del ejemplo de Wikipedia, puede usar una cantidad arbitraria de colores (y no solo potencias de dos), puede personalizarlo para satisfacer sus necesidades.


De 16 a 2 colores.

Seleccionamos un color de cada uno box, simplemente promediando todos los valores. Sin embargo, esta no es la única forma. Es posible que haya notado que nuestro resultado en comparación con el original no es tan brillante. Si lo necesita, puede dar preferencia en los intervalos superiores, agregando peso a la definición de intervalos. O puede seleccionar fácilmente 1, 2 o 3 de los colores más brillantes de la imagen y agregarlos a la paleta. Por lo tanto, si necesita 16 colores, genere una paleta de 13 colores y agregue manualmente sus colores brillantes.


Una paleta con los tres colores más brillantes

Ahora todo se ve bastante bien, pero la imagen es demasiado irregular. Tiene grandes áreas del mismo color. Ahora necesitamos suavizarlos.

Tramado


No necesito decirte qué es el tramado, porque ya trabajas con pixel art. Entonces, para obtener una imagen más uniforme, utilizaremos uno de los algoritmos de interpolación, de los cuales hay muchos.

Implementé un algoritmo de difuminado simple de Floyd-Steinberg . No hubo sorpresas desagradables. Sin embargo, el efecto fue bastante fuerte. Aquí está nuestro ejemplo nuevamente:


Original

Luego cortamos la paleta a 16 colores:


Los valores se asignan a una paleta de 16 colores,

y ahora el tramado seguido de la conversión a una paleta:


Resultado terminado con tramado

Source: https://habr.com/ru/post/464203/


All Articles