1. Wstęp
Raymarching można go nazwać innym podejściem do Raytracingu, gdzie zamiast wyliczać punkt przecięcia promienia z obiektem na scenie wykonujemy spacer od obserwatora szukając miejsca przecięcia z obiektem sceny.
2. Algorytm Raymarching’u – spacer od obserwatora
Poniżej prezentuje kod shadera realizującego operację ray marchingu, przy jego pomocy otrzymujemy prosty sześcian (można zaszaleć).
W przykładowym kodzie najważniejsze są dwie funkcje:
rayMatching(vec3 ray, vec3 rayDir) – gdzie: ray – pozycja obserwatora (kamery), rayDir – kierunek patrzenia
map(vec3 p) – gdzie p – aktualny point w przestrzeni 3D.
#ifdef GL_ES precision mediump float; #endif uniform float time; uniform vec2 mouse; uniform vec2 resolution; const float PI = 3.14159; const int MAX_STEPS = 128; const float STEPS_DELTA=1.0/32.; const float MAX_DISTANCE = 100.0; const float EPSILON = 0.01; //------------------------------------- // Struktury danych //------------------------------------- struct Material { vec3 ambient; vec3 diffuse; vec3 specular; float shininess; int texture; }; struct Object { float distance; float step; vec3 pos; vec3 normal; bool hit; Material mat; }; //------------------------------------- // Obiekty //------------------------------------- vec2 box(vec3 p, vec3 pos, vec3 size, float material){ vec3 d = abs(p-pos) - size; float distance=min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)); return vec2(distance, material); } //------------------------------------- // Mapowanie sceny //------------------------------------- // // Zwraca wektor 2 wymiarowy gdzie: // x: dystans // y: id materiału // vec2 map(vec3 p) { return box(p, vec3(0.0,0.0,0.0), vec3(1.0,1.0,1.0), 0.0); } //------------------------------------- // Ray Marching //------------------------------------- vec3 pointNormal(vec3 pos) { vec3 eps = vec3(EPSILON, 0.0, 0.0 ); vec3 nor = vec3( map(pos+eps.xyy).x - map(pos-eps.xyy).x, map(pos+eps.yxy).x - map(pos-eps.yxy).x, map(pos+eps.yyx).x - map(pos-eps.yyx).x ); return normalize(nor); } Object rayMarching(vec3 ray, vec3 rayDir){ float distance = 0.0; Object sceneObject; vec2 mapOut; sceneObject.hit=false; for(int i=0; i < MAX_STEPS; ++i) { mapOut=map(ray); distance += mapOut.x; ray += rayDir * mapOut.x; sceneObject.step += STEPS_DELTA;; if(mapOut.x < EPSILON) { sceneObject.hit=true; distance=0.0; break; } if(distance > MAX_DISTANCE) { distance=MAX_DISTANCE; break; } } sceneObject.distance=distance/MAX_DISTANCE; if (sceneObject.hit){ sceneObject.normal=pointNormal(ray); sceneObject.pos=ray; sceneObject.mat.ambient=vec3(0.0); sceneObject.mat.diffuse=vec3(0.8); sceneObject.mat.specular=vec3(1.0); } return sceneObject; } void main() { vec2 pos = (gl_FragCoord.xy*2.0 - resolution.xy) / resolution.y; vec2 uv = pos * vec2(resolution.x/resolution.y,1.0); // // Konfiguracja kamery // vec3 camPos = vec3(5.0, 2.0, 4.0); vec3 camTarget = vec3(0.0, 0.0, 0.0); // // Obliczenia kierunku kamery // vec3 camUp = normalize(vec3(0.0, 1.0, 0.0)); vec3 camDir = normalize(camTarget - camPos); vec3 camSide = cross(camDir, camUp); float focus = 1.8; vec3 rayDir = normalize(camSide * pos.x + camUp * pos.y + camDir * focus); vec3 ray = camPos; // // RayMarching // Object sceneObject = rayMarching(ray, rayDir); vec3 outColor= vec3(sceneObject.step); gl_FragColor=vec4(outColor.r,outColor.g,outColor.b,1.0); } |
Wynikiem pracy tego programu jest, sześcian gdzie im jaśniejszy kolor, tym więcej korków potrzeba było do znalezienia punktu przecięcia.
Kroki działania programu:
1. Na podstawie pozycji kamery i pozycji „na co patrzy” wyliczamy kierunek kamery
vec3 camUp = normalize(vec3(0.0, 1.0, 0.0)); vec3 camDir = normalize(camTarget - camPos); vec3 camSide = cross(camDir, camUp); float focus = 1.8; vec3 rayDir = normalize(camSide * pos.x + camUp * pos.y + camDir * focus); vec3 ray = camPos; |
2. Inicjujemy dane:
float distance = 0.0;
Object sceneObject;
vec2 mapOut;
sceneObject.hit=false;
3. Przeprowadzamy właściwą operację RayMarchingu. Bieżącą pozycję punktu w przestrzeni 3D, mapujemy na odległość. Po tym mamy 3 warunki wyjścia z pętli wykonującej operację RayMarchingu:
- Przekroczyliśmy maksymalną ilość kroków pętli
- Dystans jest mniejszy od zakładanej dokładności – znaleźliśmy punkt przecięcia. W tym przypadku wykonujemy obliczenie wektora normalnego, nadania koloru, wyliczenia światła, czy inne operacje na znalezionym punkcie przecięcia.
- Przekroczyliśmy maksymalny wyliczany dystans punktu od obserwatora, nie wykonujemy dalszych obliczeń
for(int i=0; i < MAX_STEPS; ++i) { mapOut=map(ray); distance += mapOut.x; ray += rayDir * mapOut.x; sceneObject.step += STEPS_DELTA;; if(mapOut.x < EPSILON) { sceneObject.hit=true; distance=0.0; break; } if(distance > MAX_DISTANCE) { distance=MAX_DISTANCE; break; } } |
Graficzna wersja algorytmu:
Możliwość komentowania jest wyłączona.