В этом посте мы собираемся сделать кое-что более увлекательное, используя те же строительные блоки, что и в предыдущем посте. Мы собираемся добавить несколько сфер в нашу сцену и добавить отражения.
1. Добавление нескольких сфер
Давайте сначала добавим несколько сфер и начнем с определения макроса для необходимого количества сфер.
#define NUM_SPHERES 5
Мы уже определили структуру для нашей сферы в предыдущем посте. Давайте воспользуемся этим и определим 4 новые сферы в функции initialize(), чтобы добавить их в нашу сцену.
// Create spheres spheres[0].center = vec3(-0.25, 1.5 * sin(uTime), -0.25); spheres[0].radius = 0.3; spheres[0].color = vec3(1.0, 0.0, 0.0); spheres[1].center = vec3(0.5 * sin(uTime), 0.25, 0.75); spheres[1].radius = 0.2; spheres[1].color = vec3(0.0, 1.0, 0.0); spheres[2].center = vec3(-0.75, 0.0, 0.5); spheres[2].radius = 0.2; spheres[2].color = vec3(0.0, 0.0, 1.0); spheres[3].center = vec3(-0.25 , 0.4 * sin(uTime), 0.1* sin(uTime)); spheres[3].radius = 0.25; spheres[3].color = vec3(0.0, 1.0, 1.0); spheres[4].center = vec3(-1.5, 0.15 * sin(uTime), 0.15 * sin(uTime)); spheres[4].radius = 0.35; spheres[4].color = vec3(1.0, 1.0, 0.0);
2. Рисование сфер
Теперь у нас есть несколько сфер, и мы должны отрисовывать их на экране, помня, что визуализируется сфера, ближайшая к началу камеры/луча. Поэтому я изменил нашу функцию getIntersection() из предыдущего поста, сделав ее более общей, беря сферу и луч и просто возвращая точку пересечения луча с этой сферой.
float getIntersection(Sphere sphere, Ray ray) { vec3 sphereCenter = sphere.center; vec3 colorOfSphere = sphere.color; float radius = sphere.radius; vec3 cameraSource = ray.origin; vec3 cameraDirection = ray.direction; vec3 distanceFromCenter = (cameraSource - sphereCenter); float B = 2.0 * dot(cameraDirection, distanceFromCenter); float C = dot(distanceFromCenter, distanceFromCenter) - pow(radius, 2.0); float delta = pow(B, 2.0) - 4.0 * C; float t = 0.0; if (delta > 0.0) { float sqRoot = sqrt(delta); float t1 = (-B + sqRoot) / 2.0; float t2 = (-B - sqRoot) / 2.0; t = min(t1, t2); } if (delta == 0.0) { t = -B / 2.0; } return t; }
Теперь эту функцию нужно вызывать для каждой сферы в сцене, а наименьшая точка пересечения — это то, что рассматривается в нашем фрагментном шейдере.
for (int i=0; i < NUM_SPHERES; i++) { float t = getIntersection(spheres[i], ray); if (t > 0.0 && t < minT) { minT = t; sphereToShow = spheres[i]; } }
Обратите внимание, что начальное значение minT равно INFINITY. Поэтому мы определяем макрос для этого. Пусть БЕСКОНЕЧНОСТЬ будет относительно большим числом для нашей сцены. Я определяю это как 100000.0
#define INFINITY 100000.0
Теперь, когда у нас есть sphereToShow для нашей сцены, мы можем вернуть цвет этой сферы в `gl_FragColor`. Но нам также нужно вернуть ReflectRay для этой точки вместе с цветом. Итак, я определил новую структуру под названием RayTracerOutput.
struct RayTracerOutput { Ray reflectedRay; vec3 color; };
3. Вычисление отраженного луча
Хорошее математическое объяснение вычисления отраженного луча можно найти здесь. По сути, если у нас есть направление входящего луча W и направление нормали к поверхности n, мы можем вычислить направление возникающего отражения следующим образом:
R = 2 (-W • n) n + W
Таким образом, мы можем сформировать новый луч, начиная с небольшого расстояния ε за пределами поверхности сферы, как:
Происхождение = S + ε R,где S — точка поверхности, а ε — очень малое число
Направление = R
Теперь наш новый луч (то есть отраженный луч) будет:
Источник + t * Направление
Вот так все это выглядит в коде.
if (minT > 0.0 && minT != INFINITY) { vec3 surfacePoint = cameraSource + (minT * cameraDirection); vec3 surfaceNormal = normalize(surfacePoint - sphereCenter); // Reflection vec3 reflection = 2.0 * dot(-ray.direction, surfaceNormal) * surfaceNormal + ray.direction; reflectionRay.origin = surfaceNormal + epsilon * reflection; reflectionRay.direction = reflection; color = colorOfSphere * (ambience + ((1.0 - ambience) * max(0.0, dot(surfaceNormal, lightSource)))); rayTracer.color = color; rayTracer.reflectedRay = reflectionRay; }
Я объединил все это в одну функцию под названием trace().
RayTracerOutput trace(Sphere spheres[NUM_SPHERES], Ray ray, Light light) { RayTracerOutput rayTracer; Ray reflectionRay; Sphere sphereToShow; float minT = INFINITY; vec3 cameraSource = ray.origin; vec3 cameraDirection = ray.direction; vec3 lightSource = light.position; float ambience = light.ambience; vec3 color = vec3(0.0, 0.0, 0.0); for (int i=0; i < NUM_SPHERES; i++) { float t = getIntersection(spheres[i], ray); if (t > 0.0 && t < minT) { minT = t; sphereToShow = spheres[i]; } } vec3 sphereCenter = sphereToShow.center; vec3 colorOfSphere = sphereToShow.color; if (minT > 0.0 && minT != INFINITY) { vec3 surfacePoint = cameraSource + (minT * cameraDirection); vec3 surfaceNormal = normalize(surfacePoint - sphereCenter); // Reflection vec3 reflection = 2.0 * dot(-ray.direction, surfaceNormal) * surfaceNormal + ray.direction; reflectionRay.origin = surfaceNormal + epsilon * reflection; reflectionRay.direction = reflection; color = colorOfSphere * (ambience + ((1.0 - ambience) * max(0.0, dot(surfaceNormal, lightSource)))); rayTracer.color = color; rayTracer.reflectedRay = reflectionRay; } return rayTracer; }
4. Добавление отраженного цвета другим сферам.
Теперь все, что нам нужно сделать, это вызвать trace() с отраженным лучом и тем же набором сфер. Но на этот раз нас интересует цвет сферы, на которую падает отраженный луч. Мы добавляем этот цвет к исходному цвету сферы из первого вызова trace(), чтобы увидеть отражения. Вот как выглядит функция main().
void main() { initialize(); RayTracer rayTracer = trace(spheres, rays[0], light[0]); // Second call to get reflections RayTracer reflection = trace(spheres, rayTracer.reflectedRay, light[0]); gl_FragColor = vec4(rayTracer.color + reflection.color, 1.0); }
Теперь у нас есть работающий трассировщик лучей с несколькими сферами и отражениями!
Если у вас есть какие-либо мысли, предложения или вы заметили ошибки в коде, пожалуйста, оставьте комментарий. Вы можете найти код здесь и его работающую версию здесь.