Wygenerowana komputerowo teksturowana farba ścienna

48

Farba na ścianach w moim pokoju ma losową, prawie fraktalną, trójwymiarową teksturę:

Zdjęcie A

W tym wyzwaniu napiszesz program, który generuje losowe obrazy, które wyglądają, jakby mogły być częścią moich ścian.

Poniżej zebrałem 10 zdjęć różnych miejsc na moich ścianach. Wszystkie mają w przybliżeniu to samo oświetlenie i wszystkie zostały zrobione aparatem jedną stopę od ściany. Obramowania zostały równomiernie przycięte, aby uzyskać 2048 na 2048 pikseli, a następnie zostały skalowane do 512 na 512. Powyższy obraz to obraz A.

To tylko miniatury, kliknij obrazy, aby wyświetlić w pełnym rozmiarze!

A: B: C: D: E:Zdjęcie A Zdjęcie B Zdjęcie C Zdjęcie D Zdjęcie E

F: G: H: I: J:Zdjęcie F. Zdjęcie G Zdjęcie H Zdjęcie I Zdjęcie J

Twoim zadaniem jest napisanie programu, który przyjmuje losową liczbę całkowitą od 1 do 2 16 jako losowe ziarno, i dla każdej wartości generuje odrębny obraz, który wygląda, jakby mógł być „jedenastym obrazem” mojej ściany. Jeśli ktoś patrzy na moje 10 zdjęć i kilka z nich nie wie, które zostały wygenerowane komputerowo, to poradziłeś sobie bardzo dobrze!

Pochwal się kilkoma wygenerowanymi obrazami, aby widzowie mogli je zobaczyć bez konieczności uruchamiania kodu.

Zdaję sobie sprawę, że oświetlenie na moich obrazach nie jest idealnie jednolite pod względem intensywności ani kolorów. Przykro mi z tego powodu, ale to najlepsze, co mogłem zrobić bez lepszego sprzętu oświetleniowego. Twoje zdjęcia nie muszą mieć zmiennego oświetlenia (choć mogłyby). Faktura jest najważniejszą rzeczą, na której należy się skupić.

Detale

  • Możesz korzystać z narzędzi do przetwarzania obrazów i bibliotek.
  • Podaj dane wejściowe w dowolny sposób (wiersz poleceń, standardowe, oczywiste zmienne itp.).
  • Obraz wyjściowy może być w dowolnym typowym formacie pliku bezstratnego lub może być po prostu wyświetlany w oknie / dziurkaczu.
  • Możesz programowo analizować moje 10 obrazów, ale nie zakładaj, że każdy, kto uruchamia Twój kod, ma do nich dostęp.
  • Musisz wygenerować obrazy programowo. Nie możesz zakodować na stałe niewielkiego wariantu jednego z moich obrazów lub innego obrazu podstawowego. (Ludzie i tak by za to głosowali).
  • Możesz użyć wbudowanych generatorów liczb pseudolosowych i założyć, że okres wynosi 2 16 lub więcej.

Punktacja

To konkurs popularności, dlatego wygrywa odpowiedź, która uzyskała najwyższy głos.

Hobby Calvina
źródło
PerlinNoise + obcięcie + cieniowanie
Octopus
21
Nie mogę tworzyć obrazów ściennych, więc zamiast tego mam komiks !
Sp3000,
8
@ Sp3000 To mniej więcej tak się stało. Chociaż gdybym patrzył w górę, prawdopodobnie wybrałbym sufit , który również mógłby zadziałać ...
Hobby Calvina

Odpowiedzi:

65

GLSL (+ JavaScript + WebGL)

wprowadź opis zdjęcia tutaj

Demo na żywo | Repozytorium GitHub

Jak używać

Załaduj stronę ponownie, aby uzyskać nowy losowy obraz. Jeśli chcesz nakarmić określone ziarno, otwórz konsolę przeglądarki i zadzwoń drawScreen(seed). Konsola powinna wyświetlać ziarno użyte podczas ładowania.

Tak naprawdę nie testowałem tego na wielu platformach, więc daj mi znać, jeśli to nie zadziała. Oczywiście Twoja przeglądarka musi obsługiwać WebGL. Błędy są wyświetlane w kolumnie po lewej stronie lub w konsoli przeglądarki (w zależności od rodzaju błędu).

Nowość: Możesz teraz ożywić ściany, zaznaczając pole wyboru „ruchome źródło światła”.

Co to za czary?

Mam ten kod bojlera WebGL pływający wokół mojego konta GitHub , którego od czasu do czasu używam do szybkiego prototypowania niektórych elementów grafiki 2D w WebGL. Dzięki magii shaderów możemy sprawić, że będzie wyglądać nieco 3D, więc pomyślałem, że to najszybszy sposób na uzyskanie fajnych efektów. Większość ustawień pochodzi z tego kodu i zastanawiam się, czy biblioteka tego zgłoszenia nie będzie go zawierać w tym poście. Jeśli jesteś zainteresowany, spójrz na main.js na GitHub (i inne pliki w tym folderze).

Wszystko, co robi JavaScript, to skonfigurowanie kontekstu WebGL, zapisanie ziarna w mundurze dla modułu cieniującego, a następnie renderowanie pojedynczego kwadratu w całym kontekście. Moduł cieniujący wierzchołek jest prostym modułem cieniującym przechodzącym, więc cała magia dzieje się w module cieniującym fragmenty. Dlatego nazwałem to zgłoszeniem GLSL.

Największa część kodu to tak naprawdę generowanie szumu Simplex, który znalazłem na GitHub . Więc pomijam to również w poniższej liście kodów. Ważną częścią jest to, że definiuje funkcję, snoise(vec2 coords)która zwraca szum simpleks bez użycia tekstury lub tablicy. W ogóle nie jest rozstawiony, więc sztuczka w uzyskaniu innego hałasu polega na użyciu nasion w celu ustalenia, gdzie należy przeprowadzić wyszukiwanie.

Więc oto idzie:

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

Otóż ​​to. Jutro mogę dodać więcej wyjaśnień, ale podstawową ideą jest:

  • Wybierz losową pozycję światła.
  • Dodaj kilka oktaw hałasu, aby wygenerować wzór fraktalny.
  • Wyrównuj hałas, aby dno było szorstkie.
  • Poprowadź hałas, tanhaby wyrównać górę.
  • Dodaj dwie kolejne oktawy, aby uzyskać nieco więcej tekstury na górnej warstwie.
  • Oblicz normalne powstałej powierzchni.
  • Przeprowadź proste zacienienie Phong po tej powierzchni, z błyszczącymi i rozproszonymi światłami. Kolory są wybierane na podstawie losowych kolorów, które wybrałem z pierwszego przykładowego obrazu.
Martin Ender
źródło
17
Jest to bardziej realistyczne niż sama ściana: o
Quentin
1
Kolejne „żyły” / „węże” / „robaki” sprawiłyby, że to zdjęcie lepiej pasowałoby do „ściany”. Ale wciąż miło.
Nova,
33

Mathematica Spackling

Poniższa aplikacja stosuje plamki do losowego obrazu. Kliknięcie „nowej poprawki” generuje nowy losowy obraz do pracy, a następnie stosuje efekty zgodnie z bieżącymi ustawieniami. Efekty to malarstwo olejne, filtr Gaussa, posteryzacja i wytłaczanie. Każdy efekt można niezależnie modyfikować. Ziarno generatora liczb losowych może być dowolną liczbą całkowitą od 1 do 2 ^ 16.

Aktualizacja : Filtr Gaussa, który zmiękcza krawędzie, jest teraz ostatnim zastosowanym efektem obrazu. Dzięki tej modyfikacji efekt posteryzacji nie był już potrzebny i został usunięty.

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

wynik końcowy


Wyjaśnienie

Wyjaśnienie opiera się na nieco innej wersji, w której zastosowano posteryzację i GaussianFilterzastosowano ją wcześnie. Ale nadal służy wyjaśnieniu, w jaki sposób każdy efekt obrazu zmienia obraz. Efektem końcowym jest tekstura farby z ostrzejszymi krawędziami. Gdy filtr Gaussa zostanie zastosowany tylko na końcu, wynik będzie gładszy, jak pokazuje powyższy obrazek.

Przyjrzyjmy się niektórym efektom obrazu, pojedynczo.

Wygeneruj obraz początkowy.

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

losowy obraz


Lena pokaże nam, jak każdy efekt obrazu przekształca obraz podobny do życia.

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

Lena


Efekt malarstwa olejnego zastosowany do Leny.

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

olej lena

Efekt malowania olejem zastosowany do naszego losowego obrazu. Efekt został zintensyfikowany (16 zamiast 8).

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

olej


Efekt filtru gaussowskiego zastosowany do Leny (nie do wersji Leny z efektem malowania olejów). Promień wynosi 10 pikseli. (W ostatecznej wersji u góry tego wpisu GaussianFilter jest stosowany jako efekt końcowy.)

 GaussianFilter[Lena, 10]

Lena Gaussian.


Nieco łagodniejszy efekt filtra Gaussa zastosowany do r1. Promień wynosi 5 pikseli.

 r2 = GaussianFilter[r1, 5]

gaus


Intensywny efekt posteryzacji zastosowany wobec Leny. (W ostatecznej wersji aplikacji usunąłem posteryzację. Ale zostawimy to w analizie, ponieważ przykłady w analizie były oparte na wcześniejszej wersji z posteryzacją).

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

Lena posteryzuje


Efekt posteryzacji zastosowany do r2.

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

posteryzować


Tłoczenie Lena

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

tłoczenie Lena


Tłoczenie r3 kończy przetwarzanie obrazu. Ma to wyglądać podobnie do sufitu PO.

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

wyryć


Dla ciekawskich oto Lena z zastosowanymi tymi samymi efektami obrazu.

lena4

DavidC
źródło
... jest wbudowany, aby uzyskać obraz Lena? LOL.
user253751
7
Tak. W Mathematica jest około 30 wbudowanych zdjęć używanych do testów przetwarzania obrazu.
DavidC
Przy niewielkiej modyfikacji można było nakarmić RandomIntegerziarno, gwarantując w ten sposób szczególną wydajność. Czy masz na myśli coś innego, na przykład początkowy, nieprzypadkowy obraz, do którego zastosowano efekty?
DavidC,
1
Teraz przyjmuje ziarno od 1 do 2 ^ 16.
DavidC
1
+1, ponieważ Lena will show us how each image effect transforms a life-like picturesprawiło, że LOL. Dziwne jest to, że na ostatecznym zdjęciu Leny widać Azteków lub Inków skierowanych w lewo, ubranych w nakrycie głowy i zbierających gałązkę, jakby to była broń.
Level River St
13

POV-Ray

Biegaj z dużym potencjałem golfowym povray /RENDER wall.pov -h512 -w512 -K234543 wprowadź opis zdjęcia tutaj

Najpierw tworzy losową teksturę, ale zamiast się tam zatrzymać, przekształca teksturę w pole o wysokości 3D, aby promieniowanie cienia z lampy błyskowej aparatu było bardziej realistyczne. I na dokładkę dodaje kolejną teksturę małych guzków na górze.
Jedynym sposobem poza zakodowaniem losowego materiału siewnego jest użycie clockzmiennej przeznaczonej do animacji, która jest przekazywana z -K{number}flagą

#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
źródło
W ogóle nie ma potrzeby grać w golfa. Niezłe wyniki! :)
Martin Ender