새소식

인기 검색어

게임 개발/그래픽스

투명한 물체와 빛의 굴절

  • -

투명한 물체를 구현하기 위해서는 처음 물체와 부딪힌 지점 d1와 뚫고 지나가서 안에서 밖으로 나올때 만나는 지점 d2를 고려해야 한다. 이때, 계산을 위해서 여태까지 처음맞은 지점을 찾기 위해 빛의 시작점으로부터 맞은 지점까지의 거리를 찾아서 더 짧은쪽이 먼저 맞은점이라고 판단하고 그것만 사용했었다.

hit.d = glm::min(d1, d2);

이제는, 뚫고 안에 들어갔을때 빛을 계산할때는 더 먼지점인 d2를 사용해야 한다.

구 안에 있는 점에서 시작해서 d2까지를 계산할때는 d1이 시작점보다 뒤에 있기때문에 거리가 음수로 나올것이다.

// 물체 안에서 다시 밖으로 나가면서 충돌 가능
if (hit.d < 0.0f) // 
    hit.d = glm::max(d1, d2);

 

 

https://samdriver.xyz/article/refraction-sphere

 

Refraction in a Sphere

Refraction in a SphereIntroducing the basics of ray tracing to create a refraction effect in Unity. A classic demonstration of ray traced rendering is a distorted image caused by refraction. The effect can be reproduced without needing the expense of ray t

samdriver.xyz

https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/reflection-refraction-fresnel.html

 

Introduction to Shading

Reflection and refraction are very common in the real world and can be observed every day. Glass or water are two very common materials that exhibit both properties. Light can pass through them, a phenomenon we call transmission and they can reflect light

www.scratchapixel.com

우리가 목표하는 렌더링 결과

위 그림을 보면 바닥이 뒤집혀서 나오는 것을 볼 수 있다.

왜 그럴까? -> 굴절

빛의 굴절
투명한 물체의 굴절과 반사

굴절 유도 전 사전지식 정리

주의: theta 1 == -d 와 n 사이의 각도

과학자들이 알아낸 사실에 의하면 sinθ1/sinθ2 가 일정하더라.

예컨데 공기(진공)중에서의 유리 굴절은 sinθ1/sinθ2  = 1.5 (엄밀하게 따지면 공기중과 진공은 다르지만 거의 비슷함)

같은 원리로 유리->공기 의 경우는 1/1.5 이다.

참고로 공기에서 물은 1.3 정도 된다고 함

그렇다면 공기->유리 인지, 유리->공기 인지 그것은 어떻게 판단할까?

(밖에서 안으로 들어가는 상황인지, 안에서 밖으로 나가는 상황인지 판단 어떻게?)

-> d 와 n 을 dot product 하고 그것이 음수인지 양수인지를 보면 된다.

// 광선이 물체에 닿으면 그 물체의 색 반환
vec3 traceRay(Ray &ray, const int recurseLevel)
{
    if (recurseLevel < 0)
        return vec3(0.0f);

    // Render first hit
    const auto hit = FindClosestCollision(ray);

    if (hit.d >= 0.0f)
    {
        glm::vec3 color(0.0f);

        // Diffuse
        const vec3 dirToLight = glm::normalize(light.pos - hit.point);

        glm::vec3 phongColor(0.0f);

        const float diff = glm::max(dot(hit.normal, dirToLight), 0.0f);

        // Specular
        const vec3 reflectDir = hit.normal * 2.0f * dot(dirToLight, hit.normal) - dirToLight;
        const float specular = glm::pow(glm::max(glm::dot(-ray.dir, reflectDir), 0.0f), hit.obj->alpha);

        if (hit.obj->ambTexture)
        {
            phongColor += hit.obj->amb * hit.obj->ambTexture->SampleLinear(hit.uv);
        }
        else
        {
            phongColor += hit.obj->amb;
        }

        if (hit.obj->difTexture)
        {
            phongColor += diff * hit.obj->dif * hit.obj->difTexture->SampleLinear(hit.uv);
        }
        else
        {
            phongColor += diff * hit.obj->dif;
        }

        phongColor += hit.obj->spec * specular;

        color += phongColor * (1.0f - hit.obj->reflection - hit.obj->transparency);

        if (hit.obj->reflection)
        {
            const auto reflectedDirection = glm::normalize(2.0f * hit.normal * dot(-ray.dir, hit.normal) + ray.dir);
            Ray reflection_ray{hit.point + reflectedDirection * 1e-4f, reflectedDirection}; // add a small vector to avoid numerical issue

            color += traceRay(reflection_ray, recurseLevel - 1) * hit.obj->reflection;
        }

        // 참고
        // https://samdriver.xyz/article/refraction-sphere (그림들이 좋아요)
        // https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/reflection-refraction-fresnel (오류있음)
        // https://web.cse.ohio-state.edu/~shen.94/681/Site/Slides_files/reflection_refraction.pdf (슬라이드가 보기 좋지는 않지만 정확해요)
        if (hit.obj->transparency)
        {
            const float ior = 1.5f; // Index of refraction (유리: 1.5, 물: 1.3)

            float eta; // sinTheta1 / sinTheta2
            vec3 normal;

            if (glm::dot(ray.dir, hit.normal) < 0.0f) // 밖에서 안에서 들어가는 경우 (예: 공기->유리)
            {
                eta = ior;
                normal = hit.normal;
            }
            else // 안에서 밖으로 나가는 경우 (예: 유리->공기)
            {
                eta = 1.0f / ior;
                normal = -hit.normal;
            }

            const float cosTheta1 = -glm::dot(normal, ray.dir);
            const float sinTheta1 = glm::sqrt(1 - cosTheta1*cosTheta1); // cos^2 + sin^2 = 1
            const float sinTheta2 = sinTheta1 / eta;
            const float cosTheta2 = glm::sqrt(1 - sinTheta2*sinTheta2);

            const vec3 m = glm::normalize(glm::dot(-ray.dir, normal) * normal + ray.dir);
            const vec3 a = -normal * cosTheta2;
            const vec3 b = m * sinTheta2;
            const vec3 refractedDirection = glm::normalize(a + b); // transmission

            Ray refractedRay{ hit.point + refractedDirection * 0.0001f, refractedDirection };
            color += traceRay(refractedRay, recurseLevel - 1) * hit.obj->transparency;

            // Fresnel 효과는 생략되었습니다.
        }

        return color;
    }

    return vec3(0.0f);
}

코드 실행 결과

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.