Pintura de pared texturizada generada por computadora

48

La pintura en las paredes de mi habitación tiene una textura tridimensional aleatoria, casi de tipo fractal:

Imagen A

En este desafío, escribirás un programa que genera imágenes aleatorias que parecen ser parte de mis paredes.

A continuación, he recopilado 10 imágenes de diferentes puntos en mis paredes. Todos tienen aproximadamente la misma iluminación y todos fueron tomados con la cámara a un pie de distancia de la pared. Los bordes se recortaron uniformemente para hacerlos de 2048 por 2048 píxeles, luego se escalaron a 512 por 512. La imagen de arriba es la imagen A.

¡Estas son solo miniaturas, haga clic en las imágenes para verlas a tamaño completo!

A: B: C: D: E:Imagen A Imagen B Imagen C Imagen D Imagen E

F: G: H: I: J:Imagen F Imagen G Imagen H Imagen I Imagen J

Su tarea es escribir un programa que tome un entero positivo de 1 a 2 16 como una semilla aleatoria, y para cada valor genera una imagen distinta que parece que podría haber sido la "undécima imagen" de mi muro. Si alguien que mira mis 10 imágenes y algunas de las suyas no sabe cuáles fueron generadas por computadora, ¡lo ha hecho muy bien!

Muestre algunas de sus imágenes generadas para que los espectadores puedan verlas sin tener que ejecutar el código.

Me doy cuenta de que la iluminación de mis imágenes no es perfectamente uniforme en intensidad o color. Lo siento, pero es lo mejor que podría hacer sin un mejor equipo de iluminación. Sus imágenes no necesitan tener iluminación variable (aunque podrían). La textura es lo más importante para enfocarse.

Detalles

  • Puede usar herramientas y bibliotecas de procesamiento de imágenes.
  • Tome la entrada de la manera común que desee (línea de comando, stdin, variable obvia, etc.).
  • La imagen de salida puede estar en cualquier formato de archivo de imagen sin pérdida común, o simplemente se puede mostrar en una ventana / bowser.
  • Puede analizar mis 10 imágenes mediante programación, pero no asuma que todos los que ejecutan su código tienen acceso a ellas.
  • Debe generar las imágenes mediante programación. No puede codificar una variante leve de una de mis imágenes o alguna otra imagen de stock. (La gente lo rechazaría por esto de todos modos).
  • Puede usar generadores de números pseudoaleatorios integrados y asumir que el período es 2 16 o más.

Puntuación

Este es un concurso de popularidad, por lo que gana la respuesta más votada.

Pasatiempos de Calvin
fuente
PerlinNoise + truncamiento + sombreado
Octopus
21
No puedo hacer imágenes de pared, ¡así que en lugar de eso tengo un cómic !
Sp3000
8
@ Sp3000 Así sucedió más o menos. Aunque si hubiera estado mirando hacia arriba, probablemente habría elegido mi techo , que podría funcionar también ...
Calvin's Hobbies

Respuestas:

65

GLSL (+ JavaScript + WebGL)

ingrese la descripción de la imagen aquí

Demostración en vivo | Repositorio de GitHub

Cómo utilizar

Vuelva a cargar la página para obtener una nueva imagen aleatoria. Si desea alimentar una semilla en particular, abra la consola de su navegador y llame drawScreen(seed). La consola debe mostrar la semilla utilizada en la carga.

Realmente no he probado esto en muchas plataformas, así que avíseme si no funciona para usted. Por supuesto, su navegador debe ser compatible con WebGL. Los errores se muestran en la columna de la izquierda o en la consola del navegador (según el tipo de error).

Nuevo: ahora puede dar vida a las paredes un poco, marcando la casilla de verificación "fuente de luz móvil".

¿Qué es esta hechicería?

Tengo este código repetitivo de WebGL flotando alrededor de mi cuenta de GitHub , que uso de vez en cuando para crear rápidamente prototipos de elementos gráficos 2D en WebGL. Con algo de magia de sombreador, también podemos hacer que se vea un poco en 3D, así que pensé que era la forma más rápida de obtener algunos efectos agradables. La mayor parte de la configuración es de ese código repetitivo, y estoy considerando que es una biblioteca para este envío y no la incluiré en esta publicación. Si está interesado, eche un vistazo a main.js en GitHub (y los otros archivos en esa carpeta).

Todo lo que JavaScript hace es configurar un contexto WebGL, almacenar la semilla en un uniforme para el sombreador y luego renderizar un único quad sobre todo el contexto. El sombreador de vértices es un sombreador de paso simple, por lo que toda la magia ocurre en el sombreador de fragmentos. Es por eso que llamé a esto una presentación GLSL.

La mayor parte del código es en realidad generar el ruido Simplex, que encontré en GitHub . Así que también estoy omitiendo eso en la lista de códigos a continuación. La parte importante es que define una función snoise(vec2 coords)que devuelve ruido simplex sin usar una búsqueda de textura o matriz. No está sembrado en absoluto, por lo que el truco para obtener un ruido diferente es usar la semilla para determinar dónde hacer la búsqueda.

Entonces aquí va:

#ifdef GL_ES
precision mediump float;
#endif
#extension GL_OES_standard_derivatives : enable

uniform float uSeed;
uniform vec2 uLightPos;

varying vec4 vColor;
varying vec4 vPos;

/* ... functions to define snoise(vec2 v) ... */

float tanh(float x)
{
    return (exp(x)-exp(-x))/(exp(x)+exp(-x));
}

void main() {
    float seed = uSeed * 1.61803398875;
    // Light position based on seed passed in from JavaScript.
    vec3 light = vec3(uLightPos, 2.5);
    float x = vPos.x;
    float y = vPos.y;

    // Add a handful of octaves of simplex noise
    float noise = 0.0;
    for ( int i=4; i>0; i-- )
    {
        float oct = pow(2.0,float(i));
        noise += snoise(vec2(mod(seed,13.0)+x*oct,mod(seed*seed,11.0)+y*oct))/oct*4.0;
    }
    // Level off the noise with tanh
    noise = tanh(noise*noise)*2.0;
    // Add two smaller octaves to the top for extra graininess
    noise += sqrt(abs(noise))*snoise(vec2(mod(seed,13.0)+x*32.0,mod(seed*seed,11.0)+y*32.0))/32.0*3.0;
    noise += sqrt(abs(noise))*snoise(vec2(mod(seed,13.0)+x*64.0,mod(seed*seed,11.0)+y*64.0))/64.0*3.0;

    // And now, the lighting
    float dhdx = dFdx(noise);
    float dhdy = dFdy(noise);
    vec3 N = normalize(vec3(-dhdx, -dhdy, 1.0)); // surface normal
    vec3 L = normalize(light - vec3(vPos.x, vPos.y, 0.0)); // direction towards light source
    vec3 V = vec3(0.0, 0.0, 1.0); // direction towards viewpoint (straight up)
    float Rs = dot(2.0*N*dot(N,L) - L, V); // reflection coefficient of specular light, this is actually the dot product of V and and the direction of reflected light
    float k = 1.0; // specular exponent

    vec4 specularColor = vec4(0.4*pow(Rs,k));
    vec4 diffuseColor = vec4(0.508/4.0, 0.457/4.0, 0.417/4.0, 1.0)*dot(N,L);
    vec4 ambientColor = vec4(0.414/3.0, 0.379/3.0, 0.344/3.0, 1.0);

    gl_FragColor = specularColor + diffuseColor + ambientColor;
    gl_FragColor.a = 1.0;
}

Eso es. Podría agregar alguna explicación más mañana, pero la idea básica es:

  • Elige una posición de luz aleatoria.
  • Suma unas pocas octavas de ruido para generar el patrón fractal.
  • Ajusta el ruido para mantener el fondo áspero.
  • Alimenta el ruido tanhpara nivelar la parte superior.
  • Agregue dos octavas más para obtener un poco más de textura en la capa superior.
  • Calcule las normales de la superficie resultante.
  • Ejecute un sombreado Phong simple sobre esa superficie, con luces especulares y difusas. Los colores se eligen en función de algunos colores aleatorios que elegí de la primera imagen de ejemplo.
Martin Ender
fuente
17
Esto es más realista que el muro mismo: o
Quentin
1
Algunas más "venas" / "serpientes" / "gusanos" harían esta imagen más adecuada para la "pared". Pero sigue siendo agradable.
Nova
33

Mathematica Spackling

La aplicación a continuación aplica moteado a una imagen aleatoria. Al hacer clic en "nuevo parche", se genera una nueva imagen aleatoria para trabajar y luego se aplican los efectos según la configuración actual. Los efectos son pintura al óleo, filtro gaussiano, posterización y estampado. Cada efecto se puede modificar de forma independiente. La semilla para el generador de números aleatorios puede ser cualquier número entero de 1 a 2 ^ 16.

Actualización : El filtro gaussiano, que suaviza los bordes, es ahora el último efecto de imagen aplicado. Con esta modificación, el efecto de posterización ya no era necesario y, por lo tanto, se eliminó.

Manipulate[
 GaussianFilter[ImageEffect[ImageEffect[r, {"OilPainting", o}], {"Embossing", e, 1.8}], g],
 Button["new patch", (SeedRandom[seed] r = RandomImage[1, {400, 400}])], 
 {{o, 15, "oil painting"}, 1, 20, 1, ContinuousAction -> False, Appearance -> "Labeled"}, 
 {{e, 1.64, "embossing"}, 0, 5, ContinuousAction -> False, Appearance -> "Labeled"},
 {{g, 5, "Gaussian filter"}, 1, 12, 1, ContinuousAction -> False, Appearance -> "Labeled"},
 {{seed, 1}, 1, 2^16, 1, ContinuousAction -> False, Appearance -> "Labeled"}, 
 Initialization :> (SeedRandom[seed]; r = RandomImage[1, {400, 400}])]

resultado final


Explicación

La explicación se basa en una versión ligeramente diferente, en la que se empleó la posterización y GaussianFilterse aplicó desde el principio. Pero aún sirve para aclarar cómo cada efecto de imagen altera una imagen. El resultado final es una textura de pintura con bordes más afilados. Cuando el filtro gaussiano solo se aplica al final, el resultado será más suave, como muestra la imagen de arriba.

Veamos algunos efectos de imagen, uno a la vez.

Genera una imagen inicial.

 r = RandomImage[1, {200, 200}]

Imagen aleatoria


Lena nos mostrará cómo cada efecto de imagen transforma una imagen realista.

Lena = ExampleData[{"TestImage", "Lena"}]

Lena


Un efecto de pintura al óleo aplicado a Lena.

ImageEffect[Lena, {"OilPainting", 8}]

aceite de lena

Un efecto de pintura al óleo aplicado a nuestra imagen aleatoria. El efecto se intensificó (16 en lugar de 8).

 r1 = ImageEffect[r, {"OilPainting", 16}]

petróleo


Un efecto de filtro gaussiano aplicado a Lena (no a la versión del efecto de pintura al óleo de Lena). El radio es de 10 píxeles. (En la versión final, en la parte superior de esta entrada, GaussianFilter se aplica como efecto final).

 GaussianFilter[Lena, 10]

lena gaussian.


Un efecto de filtro gaussiano algo más suave aplicado a r1. El radio es de 5 píxeles.

 r2 = GaussianFilter[r1, 5]

gauss


Un intenso efecto de posterización aplicado a Lena. (En la versión final de la aplicación, eliminé la posterización. Pero la dejaremos en el análisis, ya que los ejemplos en el análisis se basaron en una versión anterior con posterización).

 ImageEffect[Lena, {"Posterization", 2}]

lena posterizar


Un efecto de posterización aplicado a r2.

r3 = ImageEffect[r2, {"Posterization", 4}]

posterizar


Estampado Lena

 ImageEffect[Lena, {"Embossing", 1.2, 1.8}]

lena gofrado


La grabación en relieve de r3 completa el procesamiento de la imagen. Esto tiene la intención de parecerse al techo del OP.

 ceilingSample = ImageEffect[r3, {"Embossing", 1.2, 1.8}]

realzar


Para los curiosos, aquí está Lena con los mismos efectos de imagen aplicados.

lena4

DavidC
fuente
... hay un incorporado para obtener la imagen de Lena? Jajaja
user253751
77
Si. Hay aproximadamente 30 imágenes incorporadas en Mathematica que se utilizan para las pruebas de procesamiento de imágenes.
DavidC
Con una ligera modificación, se podría alimentar RandomIntegeruna semilla, garantizando así un rendimiento particular. ¿O quieres decir algo más, como una imagen inicial no aleatoria a la que se aplican los efectos?
DavidC
1
Ahora acepta una semilla de 1 a 2 ^ 16.
DavidC
1
+1 porque Lena will show us how each image effect transforms a life-like pictureme hizo LOL. Lo extraño es que la imagen final de Lena parece tener un azteca o inca mirando hacia la izquierda, con un tocado y armando una rama como si fuera un arma.
Level River St el
13

POV-Ray

Mucho potencial de golf, corre con povray /RENDER wall.pov -h512 -w512 -K234543 ingrese la descripción de la imagen aquí

Primero crea una textura aleatoria, pero en lugar de detenerse allí, transforma la textura en un campo de altura 3D para hacer que las sombras radiales de la cámara destellen más realistas. Y, por si acaso, agrega otra textura de pequeñas protuberancias en la parte superior.
La única forma, aparte de codificar la semilla aleatoria, es usar la clockvariable destinada a animaciones, esto se pasa con la -K{number}bandera

#default{ finish{ ambient 0.1 diffuse 0.9 }} 

camera {location y look_at 0 right x}
light_source {5*y color 1}

#declare R1 = seed (clock); // <= change this

#declare HF_Function  =
 function{
   pigment{
     crackle turbulence 0.6
     color_map{
       [0.00, color 0.01]
       [0.10, color 0.05]
       [0.30, color 0.20]
       [0.50, color 0.31]
       [0.70, color 0.28]
       [1.00, color 0.26]
     }// end color_map
    scale <0.25,0.005,0.25>*0.7 
    translate <500*rand(R1),0,500*rand(R1)>
   } // end pigment
 } // end function

height_field{
  function  512, 512
  { HF_Function(x,0,y).gray * .04 }
  smooth 
  texture { pigment{ color rgb<0.6,0.55,0.5>}
            normal { bumps 0.1 scale 0.005}
            finish { phong .1 phong_size 400}
          } // end of texture  
  translate< -0.5,0.0,-0.5>
}
DenDenDo
fuente
No hay necesidad de jugar golf en absoluto. Buenos resultados! :)
Martin Ender