1. Wstęp
Raymarching jest to technika generowania obrazów 3D, bazująca na zupełnie innym podejściu niż grafika 3D generowana na podstawie obiektów trójwymiarowych. Ta technika używana jest szczególnie w intrach 4kb, gdzie cała grafika jest generowana przy pomocy PixelShadera.
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:
-
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;
-
Inicjujemy dane:
float distance = 0.0; Object sceneObject; vec2 mapOut; sceneObject.hit=false;
-
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.