Проблемы с алгоритмом трассировки лучей - PullRequest
1 голос
/ 06 июня 2019

Изучаю алгоритм трассировки лучей от наблюдателя к объекту в процессе компьютерной графики. Сделано руководство по трассировке лучей для сферы с отражением, зеркальностью и тремя типами источников света

Мои знания по математике не очень глубокие. Весь вчера я пытался найти трассировку для куба (как просил мой инструктор), но я вообще не могу этого сделать.

Как получить куб на картинке в моем случае?

Также я бы не отказался помочь в добавлении прозрачности объектов и преломлении луча.

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

...