광선 추적 3D 엔진 처음부터 3부분: 평면 및 반사

우리는 구를 만들 수 있지만, 이것은 우리가 원하는 대부분의 대상을 정의하는 방식이 아니다.우리가 진정으로 원하는 것은 평면으로 진정한 격자를 세우는 것이다.

깨끗이 정리하다


우리는 이전에 createMeshes방법이 있었는데, 그것은 WebGL 엔진의 보존이다.광선 추적기의 기능이 격자에 그치지 않기 때문에 나는 이 속성과 상응하는 this.meshes 속성을 각각 createObjectsthis.objects으로 바꾸었다.
내가 한 또 다른 일은 multiplyVectorscaleVector으로 바꾸는 것이다.이것은 곱셈 연산을 더욱 잘 묘사했다. 왜냐하면 곱셈은 여러 가지 의미를 가질 수 있기 때문이다.
그 밖에 모든 대상은 현재 type 속성을 가지고 있으며, 우리가 정확한 알고리즘을 실행할 수 있도록 정확한 의미를 정의할 것이다.

교차 평면


다음 단계는 평면과 교차하는 것입니다.수학적으로 말하자면, 이것은 우리가 원에 대해 한 것과 같지만, 사실상 이것은 좀 더 쉽다.우리는 광선 origin + t * direction의 방정식을 평면 dot(position, normal) - constant의 방정식에 삽입하여 position의 값을 얻을 것이다.몇 가지 재조정 조항은 우리로 하여금
intersectPlane(ray, plane){
    return (plane.offset - dotVector(ray.origin, plane.normal)) / dotVector(ray.direction, plane.normal);
}
객체가 평면인 경우 새로운 메서드를 사용하려면 intersectObjects을 업데이트해야 합니다.
intersectObjects(ray) {
    let closest = { distance: Infinity, object: null };
    for (let object of Object.values(this.objects)) {
        let distance;
        switch(object.type){
            case "sphere": {
                distance = this.intersectSphere(ray, object);
                break;
            }
            case "plane": {
                distance = this.intersectPlane(ray, object);
                break;
            }
        }
        if (distance != undefined && distance < closest.distance && distance > 0.001) {
            closest = { distance, object };
        }
    }
    return closest;
}
주의,if조건 distance > 0.001에 새로운 용어를 추가했습니다.이것은 어떤 물건이 원점과 교차할 때 약간의 문제가 발생하는 것을 방지할 수 있다.만약 우리가 평면을 놓아서 카메라의 위치와 교차시키면, 우리는 기본적으로 검은 스크린을 얻을 수 있다. 왜냐하면 충돌은 카메라의 위치에서 발생하기 때문이다.강제 충돌을 통해 너무 가까워지지 않도록 함으로써 우리는 이런 상황을 방지할 수 있다.
다음에 우리는 비행기의 법선을 얻어야 한다.이것은 사실상 평면 방정식에 의해 제시된 것이지만, 우리는 그것이 표준화되었는지 확인해야 한다. 그렇지 않으면 그것이 정상적으로 작동하지 못할 것이다.나는 또 그것을 구면법향 논리와 결합시켜서, 이렇게 하면 우리는 임의의 법향을 얻을 수 있다.
getNormal(collisionPosition, object){
    switch(object.type){
        case "sphere": {
            return normalizeVector(subtractVector(collisionPosition, object.position));
        }
        case "plane": {
            return normalizeVector(object.normal);
        }
    }
}
마지막으로 getSurfaceInfo에서 우리는 하드코딩 구체법선이 아닌 새로운 방법을 사용했다.
getSurfaceInfo(collisionPosition, object, ray){
    let color = [0,0,0];
    for(const light of Object.values(this.lights)){
        if(this.isVisible(collisionPosition, light.position)){
            const normal = this.getNormal(collisionPosition, object);
            const toLight = subtractVector(light.position, collisionPosition);
            const lightAmount = multiplyVector(light.color, dotVector(toLight, normal));
            color = addVector(color, componentwiseMultiplyVector(object.color, lightAmount));
            if(object.specularity){
                const toCamera = normalizeVector(subtractVector(ray.origin, collisionPosition));
                const halfVector = normalizeVector(addVector(toLight, toCamera));
                const baseSpecular = clamp(dotVector(halfVector, normal), 0.0, 1.0);
                const specularMagnitude = baseSpecular ** object.gloss;
                const specularLight = componentwiseMultiplyVector(light.color, [specularMagnitude, specularMagnitude, specularMagnitude, 1.0]);
                color = addVector(color, specularLight);)
            }
        }
    }
    return [...color, 1];
}
장면에 면 하나를 추가합니다.
plane: {
    type: "plane",
    normal: [0,1,-1],
    offset: 0,
    color: [0.3,0.3,0.3,1]
}
봐라!이 모든 것은 단지 약간의 그림자 같은 것일 뿐이다.

반성하다


광선 추적의 장점 중 하나는 실제 반사를 할 수 있다는 것이다.이를 위해서는 반사물체에 대한 빛의 반등을 계산해야 한다.
이것은 우리의 거울 반사 실현을 대체할 것이다. 왜냐하면 이것은 사실상 거울 반사가 어떻게 작동해야 하는지이기 때문이다.이것은 phong의 최초의 작업 원리와 같다. 단지 우리가 빛의 방향을 다음 곡면으로 반사한 다음에 다시 색을 계산할 뿐이다. (그리고 계속 추적하여 반사한다.)최종 색상은 서피스 반사율을 제어하는 미러 반사량으로 배율이 조정됩니다.즉, 거울의 반사도 1은 순수한 거울로 예상한 방식대로 작동하지 못할 수도 있다. (특히 만반사가 존재한다면 들어오는 빛보다 반사된 빛이 더 많다는 뜻이다.)
수직 주위의 반사는 다음과 같습니다.
//vector.js
export function reflectVector(vec, normal) {
    return [
        vec[0] - 2 * dotVector(vec, normal) * normal[0],
        vec[1] - 2 * dotVector(vec, normal) * normal[1],
        vec[2] - 2 * dotVector(vec, normal) * normal[2],
    ];
}
getSurfaceInfo(collisionPosition, object, ray, bounceCounter){
    let color = [0,0,0];
    for(const light of Object.values(this.lights)){
        if(this.isVisible(collisionPosition, light.position)){
            const normal = this.getNormal(collisionPosition, object);
            const toLight = normalizeVector(subtractVector(light.position, collisionPosition));
            const lightAmount = scaleVector(light.color, dotVector(toLight, normal));
            color = addVector(color, componentwiseMultiplyVector(object.color, lightAmount));
            if(object.specularity){
                const reflectionDirection = reflectVector(ray.direction, normal);
                const reflectionColor = this.raytrace({ origin: collisionPosition, direction: reflectionDirection }, bounceCounter - 1);
                const specularLight = clamp(scaleVector(reflectionColor, object.specularity), 0.0, 1.0);
                color = addVector(color, specularLight);
            }
        }
    }
    return [...color, 1];
}
제가 bounceCounter의 추가 매개 변수를 추가한 것을 알 수 있습니다.귀속 검색은 무한에 가까울 수 있기 때문에, 우리는 어느 점에서 그것을 차단해야 한다.Dell은 raytrace에 대해 다음과 같이 수정했습니다.
raytrace(ray, bounceCounter = MAX_BOUNCES){
    if(bounceCounter <= 0){
        return BACKGROUND_COLOR;
    }
    const intersection = this.intersectObjects(ray);
    if (intersection.distance === Infinity || intersection.distance === -Infinity) {
        return BACKGROUND_COLOR;
    }
    const collisionPoint = addVector(ray.origin, scaleVector(ray.direction, intersection.distance));
    return this.getSurfaceInfo(collisionPoint, intersection.object, ray, bounceCounter);
}
우리는 MAX_BOUNCES부터 카운트다운을 시작합니다. 만약 우리가 0에 도달한다면 기본 색만 되돌려줍니다.MAX_BOUNCES부터 3까지 정의했습니다.또한 배경색을 추출해서 쉽게 바꿀 수 있도록 했습니다. 반사 테스트를 할 때 흰색은 나쁜 선택입니다.
나는 배경을 파란색으로 만들어 카메라를 조정했다.구체(0,0,0)는 y=-1을 가리키는 평면 위에 있다.이 평면은 회색이고 거울의 반사도는 0.7이다.

우리는 비행기 위의 그림자를 볼 수 있다.

환경 조명


이 점에서 우리는 물체의 색깔을 제어하기 어렵다. 왜냐하면 우리는 어떠한 에너지도 사용하지 않았기 때문에 빛이 너무 밝은 곳까지 계속 불린다.나는 정말 그 벌레 항아리를 열고 싶지 않아서, 우리로 하여금 약간의 추가 작업 공간을 가지게 했다.우리는'환경'빛의 개념을 도입할 수 있다.이것은 사방팔방에서 온 빛이기 때문에, 그것은 단지 모든 것에 첨가되었을 뿐이다.이것은 그늘이 이렇게 눈부시게 변하는 것을 방지하는 데 도움이 된다.getSurfaceInfo에서 우리는 불빛을 처음의 색깔에 추가하기만 하면 된다. let color = componentwiseMultiplyVector(BACKGROUND_LIGHT, object.color);일반적으로 객체 재질 자체는 환경 광색과 만반사, 미러 반사를 지정하지만 그럴 필요는 없다고 생각합니다.

이렇게 하면 우리는 불빛을 방해하지 않는 상황에서 전체 장면을 밝게 비출 수 있다.

그것을 극도로 발휘하다


라이트와 구를 추가하고 배경을 더 선명한 색상으로 변경합니다.

반사, 반사의 반사, 음영, 음영의 반사, 만반사와 환경광.코드가 많지 않은 상황에서 우리가 얻은 결과는 여전히 매우 만족스럽다.
여기서 코드를 찾을 수 있습니다: https://github.com/ndesmic/geort/tree/V3

좋은 웹페이지 즐겨찾기