Peinture de mur texturée par ordinateur


48

La peinture sur les murs de ma chambre présente une texture tridimensionnelle aléatoire, presque fractale:

Image A

Dans ce défi, vous allez écrire un programme qui génère des images aléatoires qui pourraient faire partie de mes murs.

Ci-dessous, j'ai rassemblé 10 images de différents endroits sur mes murs. Tous ont à peu près le même éclairage et ont tous été pris avec l'appareil photo à un pied du mur. Les bordures ont été rognées de manière uniforme pour atteindre 2048 x 2048 pixels, puis redimensionnées à 512 x 512. L'image ci-dessus est l'image A.

Ce ne sont que des vignettes, cliquez sur les images pour les afficher en taille réelle!

A: B: C: D: E:Image A Image b Image c Image d Image e

F: G: H: I: J:Image f Image g Image h Image je Image j

Votre tâche consiste à écrire un programme qui prend un nombre entier positif compris entre 1 et 2 16 sous forme de graine aléatoire et génère pour chaque valeur une image distincte qui aurait pu être la "onzième image" de mon mur. Si quelqu'un qui regarde mes 10 images et quelques-unes des vôtres ne sait pas quelles images ont été générées par ordinateur, alors vous avez très bien réussi!

Veuillez montrer quelques-unes de vos images générées pour que les téléspectateurs puissent les voir sans avoir à exécuter le code.

Je me rends compte que l'éclairage dans mes images n'est pas parfaitement uniforme en intensité ou en couleur. Je suis désolé pour cela, mais c'est le mieux que je puisse faire sans un meilleur équipement d'éclairage. Vos images n'ont pas besoin d'un éclairage variable (bien qu'elles le puissent). La texture est la chose la plus importante sur laquelle il faut se concentrer.

Détails

  • Vous pouvez utiliser des outils de traitement d'images et des bibliothèques.
  • Prenez l’entrée de la manière que vous souhaitez (ligne de commande, stdin, variable évidente, etc.).
  • L'image de sortie peut être dans n'importe quel format de fichier d'image commun sans perte, ou simplement s'afficher dans une fenêtre / un navigateur.
  • Vous pouvez analyser par programme mes 10 images mais ne supposez pas que toutes les personnes qui exécutent votre code y ont accès.
  • Vous devez générer les images par programme. Vous ne pouvez pas coder en dur une légère variante d'une de mes images ou d'une autre image stock. (Les gens vous voteraient pour cela de toute façon.)
  • Vous pouvez utiliser des générateurs de nombres pseudo-aléatoires intégrés et supposer que la période est égale ou supérieure à 2 16 .

Notation

Il s’agit d’un concours de popularité et la réponse la plus votée gagne.


PerlinNoise + troncature + shading
Octopus

21
Je ne peux pas faire d'images murales, alors ayez plutôt une bande dessinée !
Sp3000

8
@ Sp3000 C'est plus ou moins comment c'est arrivé. Bien que si j'avais levé les yeux, j'aurais probablement choisi mon plafond , qui pourrait également fonctionner ...
Calvin's Hobbies

Réponses:


65

GLSL (+ JavaScript + WebGL)

entrez la description de l'image ici

Démo en direct | Référentiel GitHub

Comment utiliser

Rechargez la page pour une nouvelle image aléatoire. Si vous souhaitez alimenter une graine particulière, ouvrez la console de votre navigateur et appelez drawScreen(seed). La console doit afficher la graine utilisée en charge.

Je n'ai pas vraiment testé cela sur beaucoup de plates-formes, alors laissez-moi savoir si cela ne fonctionne pas pour vous. Bien entendu, votre navigateur doit prendre en charge WebGL. Les erreurs sont affichées soit dans la colonne de gauche, soit dans la console du navigateur (en fonction du type d'erreur).

Nouveau: vous pouvez maintenant donner vie aux murs en cochant la case "source de lumière mobile".

Quelle est cette sorcellerie?

J'ai ce code standard WebGL flottant autour de mon compte GitHub , que j'utilise de temps en temps pour prototyper rapidement des éléments graphiques 2D dans WebGL. Avec un peu de magie de shader, nous pouvons également donner une apparence légèrement 3D, alors j’ai pensé que c’était le moyen le plus rapide d’obtenir de jolis effets. La plupart de l'installation est à partir de ce code standard, et je considère qu'une bibliothèque pour cette soumission ne l'inclura pas dans cet article. Si cela vous intéresse, consultez le fichier main.js sur GitHub (ainsi que les autres fichiers de ce dossier).

Tout ce que JavaScript fait est de définir un contexte WebGL, de stocker la graine dans un uniforme pour le shader, puis de restituer un quad sur tout le contexte. Le vertex shader est un simple shader passthrough, donc toute la magie se passe dans le fragment shader. C'est pourquoi j'ai appelé cela une soumission GLSL.

La plus grande partie du code consiste en fait à générer le bruit Simplex, que j'ai trouvé sur GitHub . Donc, j'omets cela aussi dans la liste de codes ci-dessous. La partie importante est qu’il définit une fonction snoise(vec2 coords)qui renvoie le bruit simplex sans utiliser de texture ou de tableau. Il n’est pas semé du tout, alors l’astuce pour obtenir un bruit différent est d’utiliser la graine pour déterminer où faire la recherche.

Alors, voici:

#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;
}

C'est ça. J'ajouterai peut-être plus d'explications demain, mais l'idée de base est la suivante:

  • Choisissez une position de lumière aléatoire.
  • Ajoutez quelques octaves de bruit pour générer le motif fractal.
  • Place le bruit pour garder le fond rugueux.
  • Faites passer le bruit tanhpour niveler le haut.
  • Ajoutez deux octaves supplémentaires pour un peu plus de texture sur la couche supérieure.
  • Calcule les normales de la surface résultante.
  • Exécuter une simple ombrage Phong sur cette surface, avec des lumières spéculaires et diffuses. Les couleurs sont choisies en fonction de certaines couleurs aléatoires que j'ai choisies dans le premier exemple d'image.

17
C'est plus réaliste que le mur lui-même: o
Quentin

1
Quelques autres "veines" / "serpents" / "vers" rendraient cette image plus adaptée au "mur". Mais toujours sympa.
Nova

33

Mathematica Spackling

L'application ci-dessous applique les taches sur une image aléatoire. Un clic sur "nouveau patch" génère une nouvelle image aléatoire à utiliser, puis applique les effets en fonction des paramètres actuels. Les effets sont la peinture à l'huile, le filtre gaussien, la postérisation et le gaufrage. Chaque effet peut être modifié indépendamment. La valeur de départ du générateur de nombres aléatoires peut être un entier compris entre 1 et 2 ^ 16.

Mise à jour : le filtre gaussien, qui adoucit les contours, est désormais le dernier effet d'image appliqué. Avec cette modification, l’effet de postérisation n’était plus nécessaire et a donc été supprimé.

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}])]

résultat final


Explication

L'explication est basée sur une version légèrement différente, dans laquelle la postérisation a été utilisée et a GaussianFilterété appliquée tôt. Mais cela sert toujours à clarifier comment chaque effet d'image modifie une image. Le résultat final est une texture de peinture avec des bords plus nets. Lorsque le filtre gaussien est uniquement appliqué à la fin, le résultat sera plus lisse, comme le montre l'image ci-dessus.

Regardons quelques effets d'image, un à la fois.

Générez une image de départ.

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

image aléatoire


Lena va nous montrer comment chaque effet d'image transforme une image réaliste.

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

Lena


Un effet de peinture à l'huile appliqué à Lena.

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

huile de léna

Un effet de peinture à l'huile appliqué à notre image aléatoire. L'effet s'est intensifié (16 au lieu de 8).

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

pétrole


Un effet de filtre gaussien appliqué à Lena (et non à la version à effet de peinture à l'huile de Lena). Le rayon est de 10 pixels. (Dans la version finale, en haut de cette entrée, GaussianFilter est appliqué comme effet final.)

 GaussianFilter[Lena, 10]

Lena Gaussian.


Un effet de filtre gaussien un peu plus doux appliqué à r1. Le rayon est de 5 pixels.

 r2 = GaussianFilter[r1, 5]

gauss


Un effet de postérisation intense appliqué à Lena. (Dans la version finale de l'application, j'ai supprimé la postérisation. Mais nous la laisserons dans l'analyse, car les exemples de l'analyse étaient basés sur une version antérieure avec postérisation.)

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

Lena Postérisée


Un effet de postérisation appliqué à r2.

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

postériser


Gaufrage Lena

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

lena en relief


La gravure en relief r3 termine le traitement de l'image. Ceci est destiné à ressembler à quelque chose comme le plafond de l'OP.

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

gaufrer


Pour les curieux, voici Lena avec les mêmes effets d’image appliqués.

lena4


... il y a un élément intégré pour obtenir l'image Lena? LOL.
user253751

7
Oui. Mathematica contient environ 30 images intégrées utilisées pour les tests de traitement d'image.
DavidC

Avec une légère modification, on pourrait nourrir RandomIntegerune graine, garantissant ainsi un rendement particulier. Ou voulez-vous dire autre chose, comme une image de départ non aléatoire à laquelle des effets sont appliqués?
DavidC

1
Il accepte maintenant une graine de 1 à 2 ^ 16.
DavidC

1
+1 parce que Lena will show us how each image effect transforms a life-like picturem'a fait LOL. Ce qui est étrange, c'est que l'image finale de Lena semble avoir un Aztèque ou un Inca tourné vers la gauche, portant une coiffe et coiffant une brindille comme s'il s'agissait d'une arme à feu.
Level River St

13

POV-Ray

Beaucoup de potentiel de golf, courir avec povray /RENDER wall.pov -h512 -w512 -K234543 entrez la description de l'image ici

Premièrement, il crée une texture aléatoire, mais au lieu de s’arrêter à cet endroit, il transforme la texture en un champ de hauteur 3D pour rendre les ombres radiales de la caméra plus réalistes. Et pour faire bonne mesure, il ajoute une autre texture de petites bosses sur le dessus.
En dehors du codage en dur de la graine aléatoire, la seule façon d'utiliser la clockvariable destinée aux animations est transmise avec l' -K{number}indicateur

#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>
}

Il n’ya nul besoin de jouer au golf. De bons résultats! :)
Martin Ender
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.