Pintura de parede texturizada gerada por computador

48

A tinta nas paredes do meu quarto tem uma textura tridimensional aleatória, quase fractal:

Imagem A

Neste desafio, você escreverá um programa que gera imagens aleatórias que parecem fazer parte das minhas paredes.

Abaixo, coletei 10 imagens de diferentes pontos nas paredes. Todos têm aproximadamente a mesma iluminação e foram tirados com a câmera a um pé da parede. As bordas foram cortadas uniformemente para torná-las 2048 por 2048 pixels e, em seguida, foram redimensionadas para 512 por 512. A imagem acima é a imagem A.

Estas são apenas miniaturas, clique nas imagens para ver em tamanho real!

A: B: C: D: E:Imagem A Imagem B Imagem C Imagem D Imagem E

F: G: H: I: J:Imagem F Imagem G Imagem H Imagem I Imagem J

Sua tarefa é escrever um programa que receba um número inteiro positivo de 1 a 2 16 como uma semente aleatória e, para cada valor, gera uma imagem distinta que parece ter sido a "décima primeira imagem" da minha parede. Se alguém olhando minhas 10 imagens e algumas suas não souberem dizer quais foram geradas por computador, você se saiu muito bem!

Exiba algumas das imagens geradas para que os espectadores possam vê-las sem precisar executar o código.

Percebo que a iluminação nas minhas imagens não é perfeitamente uniforme em intensidade ou cor. Sinto muito, mas é o melhor que posso fazer sem um equipamento de iluminação melhor. Suas imagens não precisam ter iluminação variável (embora possam). A textura é a coisa mais importante a ser focada.

Detalhes

  • Você pode usar ferramentas e bibliotecas de processamento de imagens.
  • Pegue a entrada da maneira que desejar (linha de comando, stdin, variável óbvia, etc).
  • A imagem de saída pode estar em qualquer formato de arquivo de imagem sem perdas comum ou apenas pode ser exibida em uma janela / bowser.
  • Você pode analisar programaticamente minhas 10 imagens, mas não presuma que todos que executam seu código tenham acesso a elas.
  • Você deve gerar as imagens programaticamente. Você não pode codificar uma pequena variante de uma das minhas imagens ou de outra imagem. (As pessoas votariam em você de qualquer maneira.)
  • Você pode usar geradores de números pseudoaleatórios incorporados e assumir que o período é 2 16 ou mais.

Pontuação

Este é um concurso de popularidade, para que a resposta mais votada ganhe.

Passatempos de Calvin
fonte
PerlinNoise + truncamento + sombreamento
Octopus
21
Não consigo criar imagens de parede, então, faça um quadrinho !
Sp3000
8
@ Sp3000 Foi assim que aconteceu. Embora Se eu foi olhando para cima, eu teria provavelmente escolhido o meu teto , o que poderia funcionar tão bem ...
Calvin Hobbies

Respostas:

65

GLSL (+ JavaScript + WebGL)

insira a descrição da imagem aqui

Demonstração ao vivo | Repositório do GitHub

Como usar

Recarregue a página para uma nova imagem aleatória. Se você deseja alimentar uma semente específica, abra o console do navegador e ligue drawScreen(seed). O console deve exibir a semente usada no carregamento.

Eu realmente não testei isso em muitas plataformas, então, deixe-me saber se não funciona para você. Obviamente, seu navegador precisa oferecer suporte ao WebGL. Os erros são exibidos na coluna à esquerda ou no console do navegador (dependendo do tipo de erro).

Novo: agora você pode dar vida às paredes um pouco, marcando a caixa de seleção "fonte de luz móvel".

O que é essa feitiçaria?

Eu tenho esse código padrão do WebGL flutuando em torno da minha conta do GitHub , que eu uso de vez em quando para prototipar rapidamente algumas coisas de gráficos 2D no WebGL. Com um pouco de mágica do shader, também podemos fazer com que pareça um pouco 3D, então achei que era a maneira mais rápida de obter bons efeitos. A maior parte da configuração é desse código padrão, e eu estou considerando que uma biblioteca para este envio não será incluída neste post. Se você estiver interessado, dê uma olhada no main.js no GitHub (e nos outros arquivos dessa pasta).

Tudo o que o JavaScript faz é configurar um contexto WebGL, armazenar a semente de maneira uniforme para o sombreador e, em seguida, renderizar um quad único em todo o contexto. O sombreador de vértice é um sombreador de passagem simples, portanto toda a mágica acontece no sombreador de fragmento. Por isso chamei isso de submissão GLSL.

A maior parte do código é realmente gerar o ruído Simplex, que eu encontrei no GitHub . Então, eu estou omitindo isso também na lista de códigos abaixo. A parte importante é que ela define uma função snoise(vec2 coords)que retorna ruído simplex sem usar uma textura ou pesquisa de matriz. Não é semeado, então o truque para obter um ruído diferente é usar a semente para determinar onde fazer a pesquisa.

Então aqui vai:

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

É isso aí. Posso acrescentar mais explicações amanhã, mas a ideia básica é:

  • Escolha uma posição aleatória da luz.
  • Adicione algumas oitavas de ruído para gerar o padrão fractal.
  • Quadrado o ruído para manter o fundo áspero.
  • Alimente o ruído tanhpara nivelar o topo.
  • Adicione mais duas oitavas para obter um pouco mais de textura na camada superior.
  • Calcule as normais da superfície resultante.
  • Execute um sombreamento Phong simples sobre essa superfície, com luzes especulares e difusas. As cores são escolhidas com base em algumas cores aleatórias que escolhi na primeira imagem de exemplo.
Martin Ender
fonte
17
Isso é mais realista do que o próprio muro: o
Quentin
11
Um pouco mais de "veias" / "cobras" / "vermes" tornariam essa imagem mais adequada para a "parede". Mas ainda é legal.
Nova
33

Mathematica Spackling

O aplicativo abaixo aplica manchas em uma imagem aleatória. Clicar em "novo patch" gera uma nova imagem aleatória para trabalhar e depois aplica os efeitos de acordo com as configurações atuais. Os efeitos são pintura a óleo, filtro gaussiano, posterização e gravação em relevo. Cada efeito pode ser ajustado independentemente. A semente do gerador de números aleatórios pode ser qualquer número inteiro de 1 a 2 ^ 16.

Atualização : o filtro gaussiano, que suaviza as bordas, agora é o último efeito de imagem aplicado. Com essa modificação, o efeito de posterização não era mais necessário e, portanto, removido.

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


Explicação

A explicação é baseada em uma versão ligeiramente diferente, na qual a posterização foi empregada e GaussianFilteraplicada desde o início. Mas ainda serve para esclarecer como cada efeito de imagem altera uma imagem. O resultado final é uma textura de tinta com bordas mais nítidas. Quando o filtro gaussiano é aplicado apenas no final, o resultado será mais suave, como mostra a imagem acima.

Vejamos alguns efeitos de imagem, um de cada vez.

Gere uma imagem inicial.

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

imagem aleatória


Lena nos mostrará como cada efeito de imagem transforma uma imagem realista.

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

Lena


Um efeito de pintura a óleo aplicado a Lena.

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

óleo de lena

Um efeito de pintura a óleo aplicado à nossa imagem aleatória. O efeito foi intensificado (16 em vez de 8).

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

óleo


Um efeito de filtro gaussiano aplicado a Lena (não à versão com efeito de pintura a óleo de Lena). O raio é de 10 pixels. (Na versão final, na parte superior desta entrada, o GaussianFilter é aplicado como efeito final.)

 GaussianFilter[Lena, 10]

lena gaussian.


Um efeito de filtro gaussiano um pouco mais suave aplicado a r1. O raio é de 5 pixels.

 r2 = GaussianFilter[r1, 5]

gauss


Um intenso efeito de posterização aplicado a Lena. (Na versão final do aplicativo, removi a posterização. Mas vamos deixá-lo na análise, pois os exemplos na análise foram baseados em uma versão anterior com posterização.)

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

lena posterize


Um efeito de posterização aplicado a r2.

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

posterizar


Lena em relevo

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

lena em relevo


A gravação de r3 completa o processamento da imagem. Isso tem a intenção de parecer com o teto do OP.

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

gravar


Para os curiosos, aqui está Lena com os mesmos efeitos de imagem aplicados.

lena4

DavidC
fonte
... há um built-in para obter a imagem Lena? RI MUITO.
precisa saber é o seguinte
7
Sim. Existem aproximadamente 30 fotos embutidas no Mathematica usadas para testes de processamento de imagem.
DavidC
Com uma ligeira modificação, pode-se alimentar RandomIntegeruma semente, garantindo assim uma produção específica. Ou você quer dizer outra coisa, como uma imagem inicial não aleatória à qual efeitos são aplicados?
DavidC
11
Agora ele aceita uma semente de 1 a 2 ^ 16.
DavidC 7/15
11
+1 porque Lena will show us how each image effect transforms a life-like pictureme fez rir. O estranho é que a imagem final de Lena parece ter um asteca ou inca voltado para a esquerda, vestindo um cocar e segurando um galho como se fosse uma arma.
Level River St
13

POV-Ray

Muito potencial de golfe, corra com povray /RENDER wall.pov -h512 -w512 -K234543 insira a descrição da imagem aqui

Primeiro, ele cria uma textura aleatória, mas, em vez de parar por aí, transforma a textura em um campo de altura 3D para tornar as sombras radiais da câmera mais realistas. E, para uma boa medida, adiciona outra textura de pequenas saliências no topo.
A única maneira de codificar a semente aleatória é usar a clockvariável destinada às animações, que é passada com a -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
fonte
Não há necessidade de jogar isso. Bons resultados! :)
Martin Ender