Изучаю алгоритм трассировки лучей от наблюдателя к объекту в процессе компьютерной графики.
Сделано руководство по трассировке лучей для сферы с отражением, зеркальностью и тремя типами источников света
Мои знания по математике не очень глубокие. Весь вчера я пытался найти трассировку для куба (как просил мой инструктор), но я вообще не могу этого сделать.
Как получить куб на картинке в моем случае?
Также я бы не отказался помочь в добавлении прозрачности объектов и преломлении луча.
index.js
function findIntersectionRaySphere(origin, direction, sphere) {
const oc = differenceVectors(origin, sphere.center);
const k1 = dotProduct(direction, direction);
const k2 = 2 * dotProduct(oc, direction);
const k3 = dotProduct(oc, oc) - sphere.radius ** 2;
const discriminant = k2 ** 2 - 4 * k1 * k3;
if (discriminant < 0) {
return [Infinity, Infinity];
}
const t1 = (-k2 + Math.sqrt(discriminant)) / (2 * k1);
const t2 = (-k2 - Math.sqrt(discriminant)) / (2 * k1);
return [t1, t2];
}
function calculateLighting(point, normal, view, specular) {
const lengthNormal = length(normal);
const lengthView = length(view);
let intensity = 0;
lights.forEach(light => {
if (light.type === LIGHT_AMBIENT) {
intensity += light.intensity;
}
if (light.type !== LIGHT_AMBIENT) {
let lightVector, tMax;
if (light.type === LIGHT_POINT) {
lightVector = differenceVectors(light.position, point);
tMax = 1.0;
}
if (light.type === LIGHT_DIRECTIONAL) {
lightVector = light.position;
tMax = Infinity;
}
const blocker = findClosestIntersectionRaySphere(point, lightVector, EPSILON, tMax);
if (!blocker) {
const normalDotLight = dotProduct(normal, lightVector);
if (normalDotLight > 0) {
intensity += light.intensity * normalDotLight / (lengthNormal * length(lightVector));
}
if (specular !== -1) {
const rayVector = reflectRey(lightVector, normal);
const rayDotView = dotProduct(rayVector, view);
if (rayDotView > 0) {
intensity += light.intensity * Math.pow(rayDotView / (length(rayVector) * lengthView), specular);
}
}
}
}
});
return intensity;
}
function findClosestIntersectionRaySphere(origin, direction, tMin, tMax) {
let closestT = Infinity;
let closestSphere = null;
spheres.forEach(sphere => {
const [t1, t2] = findIntersectionRaySphere(origin, direction, sphere);
if (t1 < closestT && tMin < t1 && t1 < tMax) {
closestT = t1;
closestSphere = sphere;
}
if (t2 < closestT && tMin < t2 && t2 < tMax) {
closestT = t2;
closestSphere = sphere;
}
});
if (closestSphere) {
return [closestSphere, closestT];
}
return null;
}
function raycasting(origin, direction, tMin, tMax, depth) {
const intersection = findClosestIntersectionRaySphere(origin, direction, tMin, tMax);
if (!intersection) {
return backgroundColor;
}
const [closestSphere, closestT] = intersection;
const point = additionalVectors(origin, multiplyByScalar(closestT, direction));
let normal = differenceVectors(point, closestSphere.center);
normal = multiplyByScalar(1.0 / length(normal), normal);
const view = multiplyByScalar(-1, direction);
const lighting = calculateLighting(point, normal, view, closestSphere.specular);
const localColor = multiplyByScalar(lighting, closestSphere.color);
if (closestSphere.reflective <= 0 || depth <= 0) {
return localColor;
}
const reflectedRay = reflectRey(view, normal);
const reflectedColor = raycasting(point, reflectedRay, EPSILON, Infinity, depth - 1);
return additionalVectors(
multiplyByScalar(1 - closestSphere.reflective, localColor),
multiplyByScalar(closestSphere.reflective, reflectedColor));
}
function render() {
for (let x = -scene.width / 2; x < scene.width / 2; x++) {
for (let y = -scene.height / 2; y < scene.height / 2; y++) {
let direction = sceneToViewport([x, y]);
direction = matrixMultiplyVector(cameraRotation, direction);
const color = raycasting(cameraPosition, direction, 1, Infinity, RECURSION_DEPTH);
fillPixel(x, y, clamp(color));
}
}
updateScene();
}
render();
scene.js
export const scene = document.getElementById('scene');
export const context = scene.getContext('2d');
export const buffer = context.getImageData(0, 0, scene.width, scene.height);
export const pitch = buffer.width * 4;
export const EPSILON = .001;
export const RECURSION_DEPTH = 3;
export const FOV = 1;
export const cameraPosition = [3, 0, 1];
export const cameraRotation = [
[.7071, 0, -.7071],
[0, 1, 0],
[.7071, 0, .7071]
];
export const backgroundColor = [0, 0, 0];
export function fillPixel(x, y, color) {
x = scene.width / 2 + x;
y = scene.height / 2 - y - 1;
if (x < 0 || x >= scene.width) {
return;
}
if (y < 0 || y >= scene.height) {
return;
}
let offset = 4 * x + pitch * y;
color.forEach(rgb => {
buffer.data[offset++] = rgb;
});
buffer.data[offset] = 255;
}
export function updateScene() {
context.putImageData(buffer, 0, 0);
}
export function sceneToViewport(coordinates) {
return [
coordinates[0] * FOV / scene.width,
coordinates[1] * FOV / scene.height,
1
];
}
export const spheres = [
new Sphere([0, -1, 3], .5, [255, 0, 0], 500, .2),
new Sphere([2, 0, 4], 1, [0, 0, 255], 500, .3),
new Sphere([-2, 0, 4], 1, [0, 255, 0], 10, .4),
new Sphere([0, -5001, 0], 5000, [255, 255, 0], 1000, .5),
];
export const lights = [
new Light(LIGHT_AMBIENT, 0.2),
new Light(LIGHT_POINT, 0.6, [2, 1, 0]),
new Light(LIGHT_DIRECTIONAL, 0.2, [1, 4, 4])
];
core.js
export function Sphere(center, radius, color, specular, reflective){
this.center = center;
this.radius = radius;
this.color = color;
this.specular = specular;
this.reflective = reflective;
}
export function Light(type, intensity, position) {
this.type = type;
this.intensity = intensity;
this.position = position;
}
export const LIGHT_AMBIENT = 0;
export const LIGHT_POINT = 1;
export const LIGHT_DIRECTIONAL = 2;
export function dotProduct(firstVector, secondVector) {
return firstVector.reduce((prev, _, i) => prev + _ * secondVector[i], 0);
}
export function differenceVectors(firstVector, secondVector) {
return firstVector.map((_, i) => _ - secondVector[i]);
}
export function length(vector) {
return Math.sqrt(dotProduct(vector, vector));
}
export function multiplyByScalar(k, vector) {
return vector.map(_ => _ * k);
}
export function matrixMultiplyVector(matrix, vector) {
return matrix.map(_ => dotProduct(_, vector));
}
export function additionalVectors(firstVector, secondVector) {
return firstVector.map((_, i) => _ + secondVector[i]);
}
export function clamp(vector) {
return vector.map(_ => Math.min(255, Math.max(0, _)));
}
export function reflectRey(firstVector, secondVector) {
return differenceVectors(multiplyByScalar(2 * dotProduct(firstVector, secondVector), secondVector), firstVector);
}
Также прикрепите ссылку к jsfiddle