Так что это школьное задание. Я должен понять, как работает трассировка лучей И отладить эту программу, которую несколько лет назад сделал другой студент.
Трассировка лучей, кажется, работает правильно, но проблема заключается в количестве отскоков. Он предназначен для установки пользователем с помощью ползунка, чтобы он мог взаимодействовать и видеть эффекты отраженных лучей света.
Таким образом, отображаемое изображение кажется идеальным с одним отскоком, чего не должно быть. С нулевым отскоком ничего не отображается. Затем со значениями, такими как 2,3,5 или 10 или даже 1000, визуализированное изображение становится все хуже и хуже, а значение становится все выше и выше, что противоположно предполагаемому поведению.
Ошибка очень странная, потому что ядро трассировки лучей работает, но наоборот.
У меня есть файл диагностики в вики, в котором я перечислил то, что я тестировал до сих пор без какого-либо успеха.
https://github.com/ErwanBueche/webgl-pathtracing
Это мой фрагментный шейдер
<!-- vertex shader -->
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
uniform mat4 uPMatrix;
void main(void)
{
gl_Position = uPMatrix * vec4(aVertexPosition, 1.0);
}
</script>
<!-- fragment shader -->
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
const float pi = 3.14159265358;
const float sphereRayon = 0.25;
const float lightRayon = 0.01;
uniform mat3 uRepMatrix;
uniform float uAlpha;
uniform mat4 uMVMatrix;
uniform float uBounce;
vec3 uDir;
const vec3 uOri = vec3(0.0,0.0,2.0);
uniform vec3 uSpherePos1;
uniform vec3 uSpherePos2;
uniform vec3 uLightPos;
const vec3 boxMin = vec3(-1.0, -1.0, -1.0);
const vec3 boxMax = vec3(1.0, 1.0, 1.0);
//Return the distance between origin and a sphere. return 10000.0 if there is no intersection
float intersectSphere(vec3 dir, vec3 origin, vec3 spherePosition, float sphereRayon)
{
vec3 l = origin - spherePosition;
float a = dot(dir, dir);
float b = 2.0 * dot(l, dir);
float c = dot(l, l) - sphereRayon*sphereRayon;
float discriminant = b*b - 4.0*c*a;
if(discriminant > 0.0)
{
float t = (-b-sqrt(discriminant))/(2.0*a);
if(t > 0.0)
{
return t;
}
}
return 10000.0;
}
//retun the distance between origin and a cube
float intersectCube(vec3 dir, vec3 origin, vec3 cubeMin, vec3 cubeMax)
{
vec3 tMin = (cubeMin - origin) / dir;
vec3 tMax = (cubeMax - origin) / dir;
vec3 t1 = min(tMin, tMax);
vec3 t2 = max(tMin, tMax);
float tNear = max(max(t1.x, t1.y), t1.z);
float tFar = min(min(t2.x, t2.y), t2.z);
return (tNear >= tFar) ? tNear : tFar;
}
//return the distance between a point and a sphere
vec3 normalForSphere(vec3 hit, vec3 spherePosition, float sphereRayon)
{
return (hit - spherePosition) / sphereRayon;
}
//return the normal of a point on a cube
vec3 normalForCube(vec3 hit, vec3 cubeMin, vec3 cubeMax)
{
vec3 normal = vec3(0.0, 0.0, 0.0);
if(hit.x < cubeMin.x + 0.00001)
{
normal = vec3(-1.0, 0.0, 0.0);
}
else if (hit.x > cubeMax.x - 0.00001)
{
normal = vec3(1.0, 0.0, 0.0);
}
else if(hit.y < cubeMin.y + 0.00001)
{
normal = vec3(0.0, -1.0, 0.0);
}
if (hit.y > cubeMax.y - 0.00001)
{
normal = vec3(0.0, 1.0, 0.0);
}
else if(hit.z < cubeMin.z + 0.00001)
{
normal = vec3(0.0, 0.0, -1.0);
}
else if (hit.z > cubeMax.z - 0.00001)
{
normal = vec3(0.0, 0.0, 1.0);
}
return normal;
}
//retrun the shadow coefficient (0.1 if shadow, 1.0 if not shadow)
float shadow(vec3 origin, vec3 dir)
{
float tSphere1 = intersectSphere(dir, origin, uSpherePos1, sphereRayon);
float tSphere2 = intersectSphere(dir, origin, uSpherePos2, sphereRayon);
float tLight = intersectSphere(dir, origin, uLightPos, lightRayon);
if(tSphere1 < tLight)
{
return 0.1;
}
if(tSphere2 < tLight)
{
return 0.1;
}
else
{
return 1.0;
}
}
//return a random vector in a half sphere
vec3 uniformSampleHemisphere(float r1,float r2)
{
float sinTheta = sqrt(1.0 - r1 * r1);
float phi = pi * 2.0 * r2;
float x = sinTheta * cos(phi);
float z = sinTheta * sin(phi);
return vec3(x, r1, z);
}
//return a random number from seed
float random(vec3 scale, float seed)
{
return fract(sin(mod(dot(gl_FragCoord.xyz+seed, scale),pi)) * (43758.5453 + seed) );
}
//return the next direction of a ray of light in a scene in a diffuse way
vec3 getNextDir(vec3 normal, float seed)
{
vec3 Nt, Nb;
if(abs(normal.x) > abs(normal.y))
{
Nt = vec3(normal.z,0.0,-normal.x) / sqrt(normal.x * normal.x + normal.z * normal.z); ;
}
else
{
Nt = vec3(0.0,-normal.z,normal.y) / sqrt(normal.z * normal.z + normal.y * normal.y ); ;
}
Nb = cross(normal, Nt);
float r1 = random(vec3(13.9898, 78.233, 151.7182),seed);
float r2 = random(vec3(63.7264, 10.873, 623.6736),seed);
vec3 sampleRand = uniformSampleHemisphere(r1,r2);
return vec3(sampleRand.x * Nb.x + sampleRand.y * normal.x + sampleRand.z * Nt.x, sampleRand.x * Nb.y + sampleRand.y * normal.y + sampleRand.z * Nt.y, sampleRand.x * Nb.z + sampleRand.y * normal.z + sampleRand.z * Nt.z);
}
//Main ray tracing loop
vec3 raytrace(vec3 dir, vec3 origin, float index)
{
vec3 color = vec3(0.0);
vec3 surfaceColor = vec3(0.8);
float distanceFromTheLight = 0.0;
//tests if the ray intersects with the light source
float tlight = intersectSphere(dir, origin, uLightPos, lightRayon*5.0);
if(tlight < 10000.0)
{
//if intersect light source, pixel is white
return vec3(1.0);
}
//Start main loop
for(float i=1.0; i < 1000.0; i+=1.0)
{
//trick to test bounce number (we cant use a uniform variable in a loop)
if(i <= uBounce)
{
//Test all possible intersections with scene items
float tBox = intersectCube(dir, origin, boxMin, boxMax);
float tsphere1 = intersectSphere(dir, origin, uSpherePos1, sphereRayon);
float tsphere2 = intersectSphere(dir, origin, uSpherePos2, sphereRayon);
//test which intersections is the nearest
float t = 10000.0;
if(tBox < t)
{
t = tBox;
}
if(tsphere1 < t)
{
t = tsphere1;
}
if(tsphere2 < t)
{
t = tsphere2;
}
//coordinates of intersection
vec3 hit = origin + dir * t;
//Distance between lignt and point
vec3 lightToHit = normalize(uLightPos - hit);
if(i == 1.0)
{
distanceFromTheLight += length(uLightPos - hit);
}
else
{
distanceFromTheLight += length(origin - hit);
}
//normal at the intersection point
vec3 normal;
if (t == tBox)
{
//inversed, to have the inside of the cube
normal = -normalForCube(hit, boxMin, boxMax);
if(hit.x < boxMin.x + 0.00001)
{
//green
surfaceColor = vec3(0.2, 1.0, 0.2);
}
else if(hit.x > boxMax.x - 0.00001)
{
//red
surfaceColor = vec3(1.0, 0.1, 0.1);
}
else if(hit.z < boxMin.z + 0.00001)
{
//blue
surfaceColor = vec3(0.1, 0.5, 1.0);
}
else if(hit.z > boxMax.z - 0.00001)
{
//yellow
surfaceColor = vec3(1.0, 0.9, 0.1);
}
}
if(t == tsphere1)
{
normal = normalForSphere(hit, uSpherePos1, sphereRayon);
//white
surfaceColor = vec3(1.0);
}
if(t == tsphere2)
{
normal = normalForSphere(hit, uSpherePos2, sphereRayon);
//white
surfaceColor = vec3(1.0);
}
//We can process the color coefficients, brightness, diffusion and shadow intensity
float lightIntensity = 7.0 / (4.0 * pi * distanceFromTheLight);
float diffuse = max(0.0, dot(lightToHit, normal));
float shadowIntensity = shadow(hit + normal*0.00001, lightToHit);
//Accumulation of the color of the pixel
color += surfaceColor * diffuse * shadowIntensity * lightIntensity * 1.0/i;
//processing of the new direction and new point
dir = getNextDir(normal, i + index);
origin=hit;
}
else
{
break;
}
}
return color;
}
void main(void)
{
vec3 pixelScreen = uRepMatrix * vec3(gl_FragCoord.x,gl_FragCoord.y, 1.0);
vec3 pixelCamera = pixelScreen * vec3(uAlpha, uAlpha, -1.0);
uDir = normalize(pixelCamera);
vec3 dir = vec3(vec4(uDir,0)*uMVMatrix);
vec3 ori = vec3(vec4(uOri,0)*uMVMatrix);
//Multi sampling
vec3 ray = raytrace(dir,ori,0.0);
vec3 ray11 = raytrace(vec3(dir.x+0.001,dir.y+0.001,dir.z),ori,2.0);
vec3 ray10 = raytrace(vec3(dir.x+0.001,dir.y-0.001,dir.z),ori,3.0);
vec3 ray01 = raytrace(vec3(dir.x-0.001,dir.y+0.001,dir.z),ori,4.0);
vec3 ray00 = raytrace(vec3(dir.x-0.001,dir.y-0.001,dir.z),ori,5.0);
vec3 color = sqrt((ray11*ray11 + ray10*ray10 + ray01*ray01 + ray00*ray00 + ray*ray)/5.0);
gl_FragColor = vec4(color, 1.0);
}
</script>
Так что, если кто-то из вас, ребята, столкнулся с подобными проблемами, я был бы признателен за любую помощь в этой точке.
Я знаю, что код не идеален, он не мой, я просто хочу понять и исправить его, чтобы значение, заданное пользователем, заставляло рендеринг работать правильно, и чем больше отказов, тем лучше изображение.
Спасибо за вашу помощь!