Ćwiczenia10

Teksturowanie proceduralne II**

Spójrzmy na tekstury obiekty jakie możemy znaleźć w naturze:

Drewno, kora, skały chmury czy góry wykazują się swego rodzaju losowością, Nie są one całkowicie losowe, mają skomplikowaną strukturę. W tej części zajęć skupimy sie na stworzeniu procedruralnego szumu, który będzie tworzył podobne wzory.

Ta sekcja bazuje na thebookofshaders.com rozdziałach 10, 11 oraz 13.

Biały szum

pierwszym krokiem jest stworzenie funkcji pseudolosowej. Klasycznie generator liczb pseudolosowych wyrzuca za każdym razem inną wartość. W naszym przypadku będziemy generować losową wartość bazując na wejściu w postaci współrzędnych (współrzędnych obiektu, lub tekstury w zależności od potrzeby).

Do generowania liczb losowych możemy wykorzystać część ułamkową jakieś skomplikowanej funkcji. jedną z nich może być $sin$ wtedy funkcją losową bęzie

$$fract(sin(x\cdot A)\cdot B)$$ Gdzie $A$ i $B$ to są stałe z odpowiedniego zakresu.

Zadanie

W tej części będziemy korzystać z narzędzia https://thebookofshaders.com/edit.php. Przekopiuj tam poniższy kod. zastąp wyjście funkcji function powyższym wzorem, dobierz odpowiednie $A$ i $B$.


#ifdef GL_ES
precision mediump float;
#endif

#define PI 3.14159265359

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;


float function(in float x) {
    float y = 0.000;
    return x;
}



float lineJitter = 0.5;
float lineWidth = 7.0;
float gridWidth = 1.7;
float scale = 0.0013;
float zoom = 2.5;
vec2 offset = vec2(0.5);

float rand (in float _x) {
    return fract(sin(_x)*1e4);
}

float rand (in vec2 co) {
    return fract(sin(dot(co.xy,vec2(12.9898,78.233)))*43758.5453);
}
vec3 plot2D(in vec2 _st, in float _width ) {
    const float samples = 3.0;

    vec2 steping = _width*vec2(scale)/samples;
    
    float count = 0.0;
    float mySamples = 0.0;
    for (float i = 0.0; i < samples; i++) {
        for (float j = 0.0;j < samples; j++) {
            if (i*i+j*j>samples*samples) 
                continue;
            mySamples++;
            float ii = i + lineJitter*fract(sin(dot(vec2(_st.x+ i*steping.x,_st.y+ j*steping.y),vec2(12.9898,78.233)))*43758.5453);
            float jj = j + lineJitter*fract(sin(dot(vec2(_st.y + i*steping.x,_st.x+ j*steping.y),vec2(12.9898,78.233)))*43758.5453);
            float f = function(_st.x+ ii*steping.x)-(_st.y+ jj*steping.y);
            count += (f>0.) ? 1.0 : -1.0;
        }
    }
    vec3 color = vec3(1.0);
    if (abs(count)!=mySamples)
        color = vec3(abs(float(count))/float(mySamples));
    return color;
}

vec3 grid2D( in vec2 _st, in float _width ) {
    float axisDetail = _width*scale;
    if (abs(_st.x)<axisDetail || abs(_st.y)<axisDetail) 
        return 1.0-vec3(0.65,0.65,1.0);
    if (abs(mod(_st.x,1.0))<axisDetail || abs(mod(_st.y,1.0))<axisDetail) 
        return 1.0-vec3(0.80,0.80,1.0);
    if (abs(mod(_st.x,0.25))<axisDetail || abs(mod(_st.y,0.25))<axisDetail) 
        return 1.0-vec3(0.95,0.95,1.0);
    return vec3(0.0);
}

void main(){
    vec2 st = (gl_FragCoord.xy/u_resolution.xy)-offset;
    st.x *= u_resolution.x/u_resolution.y;

    scale *= zoom;
    st *= zoom;

    vec3 color = plot2D(st,lineWidth);
    color -= grid2D(st,gridWidth);

    gl_FragColor = vec4(color,1.0);
}

Zadanie

Wzór $fract(sin(x\cdot A)\cdot B)$ Można uogólnić na wektory danego wymiaru $n$ wystarczy za $A$ wziąć n-wymiarowy wektor a zamiast zwykłego mnożenia między $x$ i $A$ iloczyn skalarny.

utwórz nowy shader napisz w nim funkcję, która zwróci wartość losową od vec2 i narysuj wynik od współrzędnych, żeby stworzyć teksturę szumu.

Zadanie

napisz shader, który będzie tworzył piksele o dowolnej wielkości.

Szum gradientowy przez interpolację

Udało nam się uzyskać losową teksturę, jednak jest on zbyt chaotyczny, by przedstawić naturalne zjawiska, gdzie często w teksturach występuje jakaś forma gładkości. W 1-wymiarowym przypadku możemy próbkować w równych i uśrednić otrzymane wartości, jednak w w przypadku większej liczby wymiarów siatka, po której będziemy próbkować będzie powodowała pojawienie się artefaktów.

Poniższy shader tworzy teksturę w ten sposób i powoduje takie artefakty.

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

// 2D Random
float random (in vec2 st) {
    return fract(sin(dot(st.xy,
                         vec2(12.9898,78.233)))
                 * 43758.5453123);
}

// 2D Noise based on Morgan McGuire @morgan3d
// https://www.shadertoy.com/view/4dS3Wd
float noise (in vec2 st) {
    vec2 i = floor(st);
    vec2 f = fract(st);

    // Four corners in 2D of a tile
    float a = random(i);
    float b = random(i + vec2(1.0, 0.0));
    float c = random(i + vec2(0.0, 1.0));
    float d = random(i + vec2(1.0, 1.0));

    // Smooth Interpolation
    u = smoothstep(0.,1.,f);

    // Mix 4 coorners percentages
    float ab= mix(a, b, u.x);
    float cd = mix(c, d, u.x);
    return mix(ab,cd,u.y);
}

void main() {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;

    // Scale the coordinate system to see
    // some noise in action
    vec2 pos = vec2(st*5.0);

    // Use the noise function
    float n = noise(pos);

    gl_FragColor = vec4(vec3(n), 1.0);
}

Szum Perlina

Ken Perlin zaproponował jako rozwiązanie szum, który wytwarza szum bez takich artefaktów. Podobnie jak poprzednio polega on na uśrednieniu wartości pozyskanych na siatce, jednak nie są one bezpośrednio uzyskane z wartości losowych a z iloczynu skalarnych losowych wektorów.

Zakładamy, że siatka jest jest wymiarów 1 na 1. To znaczy punkty przecięcia to punkty całkowite. Wartość szumu będziemy obliczać dla danej współrzędnej dwuwymiarowej pos. Znajduje sie on pomiędzy czterema punktami siatki (float(pos), float(pos)+vec2(0,1),float(pos)+vec2(1,0) i float(pos)+vec2(1,1)) i to na nich będziemy liczyć w shaderze.

Obliczanie losowych wektorów

Dla każdego punktu przecięcia losujemy wektor jednostkowy (można to zrobić przez wylosowanie wartości z przedziału $[0,2\pi]$ i wybranie punktu na okręgu jednostkowym).

Obliczenie iloczynu skalarnego

Dla każdego punktu przecięcia obliczamy offset_vector, który jest różnicą między tym punkem przecięcia a pos nastęnie liczymy iloczyn skalarny między offset_vector a losowym wektorem tego punktu przecięcia.

Uśrednianie iloczynów

W poprzednim kroku otrzymaliśmy 4 wartości dla naszej pozycji pos musimy interpolować pomiędzy nimi korzystając z funkcji smoothstep

Zadanie

Bazując na kodzie szumu gradientowego powyżej i opisie szumu Perlina zaimplementuj shader, który będzie tworzył szum perlina.

Szum fraktalny

Szum Perlina daje nam naturalnie wyglądające zmiany, jednak można powiedzieć, że brakuje mu detali. W naturze możemy zaobserować coś co można określić samopowtarzalnością (spójrz na obrazek z początku). Podobny efekt możemy uzyskać przez nakładanie na siebie wielu warst przeskalowanego szumu Perlina.

animacja pokazuje szum perlina i szum fraktalny

Poniższa funkcja generuje szum fraktalny z szumu perlina

float fractalNoise(vec2 v){
	int OCTAVES = 5;
	float result = 0.;

	float gain = 0.5;
	float lacunarity = 2.0;

	float frequency = 1.;
	float amplitude=0.5;
	for (int i=0;i<OCTAVES;i++){
		result+=amplitude*perlinNoise(frequency*v);
		frequency *=lacunarity;
		amplitude *=gain;
	}
    return result;
}

funkcje zwracająca wartość simplex noise i fractal noise dla danej współrzędnej.

Zadanie

Szum fraktalny powstaje poprzez nałożenie wielu oktaw szumu gradientowego. Liczba oktaw oraz wartości gain i lacunarity starują zachowaniem szumu. Sprawdź jak zmieni się szum, gdy zmodyfikujesz te wartośći.

Zadanie*

Wykorzystaj powyższą funkcje szumu do teksturowania jednej z pozostałych planet. Pobaw się zachowaniem funkcji sumów: na przykład

  • Zobacz co się stanie gdy do wejścia funkcji dodasz jakieś zakłócenia.
  • Wykonaj obrazek zawierający gradient i wykorzystaj go jako LUT, to znaczy załaduj do shadera jako teksturę i pobieraj kolor w zależności od wyników funkcji szumów.
  • Zmodyfikuj parametry odbicia w cieniowaniu w zależności od wartości funkcji szumów. Wiele z tych zachowań możesz przetestować najpierw w shadertoy zanim dodasz je do projektu.

Zadanie**

Zaproponuj rozwiążanie, które pozwoli uniknąć artefaktów na biegunach planety