ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [NeRF 기본] 레이 마칭 알고리즘 (The Ray-Marching Algorithm)
    NeRF 2023. 8. 5. 11:14

    The almighty ray-marching algorithm

     

     

    backward ray-marching. Marching along the ray in small regular steps forward, from t1 to t0.

    내부 산란으로 인해 광선을 따라 들어오는 빛을 적분하려면, 광선이 통과하는 볼륨을 작은 볼륨 요소로 분해하고 각각의 작은 볼륨 요소의 기여를 전체 볼륨 개체에 결합한다. 이는 2D 편집 소프트웨어(예: Photoshop)에서 마스크 또는 알파 채널(일반적으로 개체의 불투명도를 나타냄)을 사용하여 이미지를 서로 겹쳐 놓는 것과 비슷하다. 앞으로 언급 될 리만 합에서 각각의 작은 볼륨 요소는 샘플을 나타낸다.
    To integrate incoming light along the ray due to in-scattering, we will break down the volume that the ray passes through into small volume elements and combine the contribution of each of these small volume elements to the overall volume object, a little bit like when we stack images with a mask or alpha channel (generally representing the objects' opacity) onto each other in a 2D editing software (such as Photoshop). That is why we spoke about the alpha compositing method in the first chapter. Each one of these small volume elements represents a sample in the Riemann sum mentioned in the first chapter.

     

    computing Li(x) requires us to trace a ray in the direction of the light to know how far the light beam had to travel through the volume to get to our sample point.

    알고리즘은 다음과 같이 작동합니다:
    The algorithm works as follows:

    • 카메라/눈 광선이 볼륨 개체에 들어오고 나가는 지점인 t0과 t1의 값을 찾습니다.
      Find the value for t0 and t1, the points where the camera/eye ray enters and leaves the volume object.
    • t0-t1으로 정의된 세그먼트를 동일한 크기의 X개 작은 세그먼트로 나눕니다. 일반적으로, 우리는 이것을 스텝 크기라고 부르는 것을 선택하여 수행합니다. 스텝 크기는 작은 세그먼트의 길이를 정의하는 부동 소수점 숫자입니다. 예를 들어, t0=2.5, t1=8.3, 그리고 스텝 크기 = 0.25이면, 우리는 t0-t1으로 정의된 세그먼트를 (8.3-2.5)/0.25=23개의 작은 세그먼트로 나눕니다(지금은 간단하게 하겠습니다. 소수점은 신경 쓰지 마세요).
      Divide the segment defined by t0-t1 into X number of smaller segments of identical size. Generally, we do so by choosing what we call a step size, which is nothing else than a floating number defining the length of the smaller segment. So for instance, if t0=2.5, t1=8.3, and the step size = 0.25, we will divide the segment defined by t0-t1 by (8.3-2.5)/0.25=23 smaller segments (let's keep it simple for now, so don't worry about the decimals).
    • 다음으로, 카메라 광선을 따라 X번 "행진"합니다. t0 또는 t1에서 시작합니다(6번 항 참조).
      What you do next is "march" along the camera ray X times, starting from either t0 or t1 (see bullet point #6).
    • 매번 스텝을 수행할 때, 우리는 샘플 위치에서 광원 방향으로 빛을 쏜다. 빛 광선이 볼륨 경계와 교차하는 지점을 계산하고, Beer 법칙을 사용하여 산란으로 인한 샘플에 대한 기여를 계산한다. 광원에서 오는 빛은 샘플 지점까지 이동하는 동안 볼륨에 의해 흡수된다. 이것은 이전 장에서 언급한 리만 합의 $Li(x)$ 값이고, 해당 값을 스텝 크기로 곱한다. 리만 합에서, 스텝 크기는 dx 항, 즉 직사각형의 너비에 해당한다.
      Each time we take a step, we shoot a "light ray" starting from the middle of the step (our sample point) to the light. We compute where the light ray intersects (leaves) the volume element, and compute its contribution to the sample (due to in-scattering) using Beer's Law. Remember, light coming from the light source is absorbed by the volume as it travels through it to the sample point. This is the Li(x) value in the Riemann sum that we mentioned in the previous chapter. Don't forget that we need to multiply this value by step size which in the Riemann sum, corresponds to the dx term, the width of our rectangle. In pseudo-code we get:
    // compute Li(x) for current sample x
    float lgt_t0, lgt_t1; // parametric distance to the points where the light ray intersects the sphere
    volumeSphere->intersect(x, lgt_dir, t0, lgt_t1); // compute the intersection of the light ray with the sphere
    color Li_x = exp(-lgt_t1 * sigma_a) * light_color * step_size; // step_size is our dx
    ...

    As you can see in Figure 2, t0 for the light ray-sphere intersection test should always be 0 because the light ray starts from within the sphere, and t1 is the parametric distance to the point where the light ray intersects the sphere from our sample position x. So we can use that value in Beer's law equation for the distance over which light is absorbed by the volumetric object.

    • 물론, 작은 볼륨 요소(우리의 샘플)를 통과하는 빛은 샘플을 통과할 때 약해집니다. 따라서, Beer의 법칙 방정식에서 빛이 볼륨을 통과하는 거리의 값으로 스텝 크기를 사용하여 샘플의 전달 값을 계산합니다. 그런 다음, 이 전달 값에 의해 빛의 양(내부 산란)을 약화(곱함)합니다.
      Then of course light passing through the small volume element (our sample) is attenuated as it passes through the sample as well. So we compute the transmission value for our sample using the step size as the value for the distance traveled by the light beam through the volume in Beer's law equation. And then attenuate (multiply) the light amount (in-scattering) by this transmission value.
    • 마지막으로, 각 샘플을 결합하여 볼륨 개체의 전체 불투명도와 "색상"에 대한 각각의 기여를 고려해야 합니다. 실제로, 이 과정을 거꾸로 생각하면(그림 1과 같이), 첫 번째 볼륨 요소(t1에서 시작)는 두 번째 요소에 의해 가려지고, 두 번째 요소는 세 번째 요소에 의해 가려지고, 그 다음은 마지막 요소에 의해 가려집니다. "큐"(t0 바로 다음에 있는 샘플). 카메라 광선을 통해 보면 t1 바로 다음에 있는 요소는 앞에 있는 모든 요소에 의해 가려집니다. t0 바로 다음에 있는 샘플 바로 다음에 있는 샘플은 그 첫 번째 샘플에 의해 가려집니다.
      Finally we need to combine each one of the samples to account for their respective contribution to both the overall opacity and "color" of the volumetric object. Indeed, if you think of this process backward (like shown in Figure 1), the first volume element (starting from t1) is occluded by the second, which is itself occluded by the third one, etc. until we reach the last element in the "queue" (the sample that's directly next to t0). If you look "through the camera" ray, the element directly next to t1 is occluded by all the elements that are in front of it. The sample that's just after the sample that's directly next to t0 is occluded by that very first sample, etc.

    샘플을 결합하는 방법은 두 가지가 있습니다. t1에서 t0로 행진하는 후방 방식과 t0에서 t1로 행진하는 전방 방식입니다. 하나는 다른 것보다 낫습니다(일종의). 이제 그들이 어떻게 작동하는지 설명하겠습니다.
    We can combine samples in two ways: eitherbackward(marching from t1 to t0) orforward(marching from t0 to t1). One is better than the other (sort of). We will now describe how they work.

    Backward Ray-Marching

     

    to compute a sample, we need to account for light coming from the back (background color) and light from the light source due to in-scattering. Then account for the small volume element which is going to absorb part of these light contributions. You can see this as an addition of the background color and the color coming from the light source multiplied by the small volume element transparency value.

    후방 레이 마칭에서는 광선의 뒤에서 앞으로 행진합니다. 즉, t1에서 t0까지입니다. 이로 인해 샘플을 결합하여 최종 픽셀 불투명도 및 색상 값을 계산하는 방법이 변경됩니다.
    In backward ray-marching, we will march along the ray from back to front. In other words from t1 to t0. This changes the way we combine the samples to compute the final pixel opacity and color values.

    볼륨 개체(우리의 구)의 뒤쪽에서 시작하기 때문에 자연스럽게 픽셀 색상(그 카메라 광선에 대해 반환되는 색상)을 배경 색상(우리의 푸른색 색상)으로 초기화할 수 있습니다. 그러나 우리의 구현에서는 볼륨 개체 색상과 불투명도를 계산한 후에만 두 개를 결합할 것입니다. 이는 2D 편집 소프트웨어에서 두 이미지를 합성할 때와 비슷합니다.
    Quite naturally, since we start from the back of the volumetric object (our sphere), we could initialize our pixel color (the color returned for that camera ray) with the background color (our bluish color). But in our implementation, we will only combine the two at end of the process (once we have computed the volumetric object color and opacity), a little bit like when we compose two images in a 2D editing software.

    우리는 t1에서 시작하여 t0까지 규칙적인 단계(스텝 크기로 정의됨)를 수행하면서 볼륨에서 첫 번째 샘플(예: X0)의 기여를 계산할 것입니다.
    We will compute the contribution of our first sample (say X0) in the volume, starting as we mentioned from t1, and walk back to t0, taking regular steps (defined by step size).

    ...
    color Li_x0 = exp(-lgt_t1 * sigma_a) * light_color * step_size; // step_size is our dx
    color x0_contrib = Li_x0 * exp(-step_size * sigma_a);
    ...


    우리는 첫 번째 샘플 X0를 방금 계산했습니다. 그런 다음 두 번째 샘플(X1)로 이동하지만 이제 두 가지 광원 소스를 고려해야 합니다. 첫 번째 샘플 X0(이전 결과)에서 오는 빛 광선과 내부 산란으로 인해 두 번째 샘플을 통과하는 빛 광선인 X1입니다. 우리는 이전 것을 이미 계산했습니다(방금 말했듯이, 그것이 우리의 이전 결과입니다) 그리고 우리는 후자를 렌더링하는 방법을 알고 있습니다. 우리는 그것들을 합하고 이 합을 두 번째 샘플 전달 값으로 곱합니다. 이것이 우리의 새로운 결과가 됩니다. 그리고 우리는 결국 t0에 도달할 때까지 X2, X3, ...와 함께 이 과정을 계속 반복합니다. 최종 결과는 볼륨 개체가 현재 카메라 광선 픽셀의 색상에 기여하는 것입니다. 이 과정은 아래에 설명되어 있습니다.
    We've just computed our first sample X0. We then move to our second sample (X1), but now we have two sources of light that we need to account for: the light beam coming from the first sample X0 (our previous result), and the light beam passing through the second sample due to in-scattering, X1\. We already computed the former (as we just said, that's our previous result) and we know how to render the latter. We sum them up and multiply this sum by the second sample transmission value. This becomes our new result. And we keep repeating this process with X2, X3, ... until we eventually reach t0\. The final result is the contribution of the volumetric object to the current camera ray pixel's color. This process is illustrated below.

    위의 이미지에서 볼륨 전체 색상(result에 저장됨)과 전체 볼륨 투명도 두 가지 값을 계산한다는 것을 알 수 있습니다. 이 값을 1(완전히 투명함)으로 초기화하고 광선을 따라(t1에서 t0로) 행진하면서 각 샘플 투명도 값으로 값을 약화시킵니다. 그런 다음(마지막으로) 이 전체 투명도 값을 볼륨 개체를 배경 색상 위에 결합하는 데 사용할 수 있습니다. 우리는 다음과 같이 간단히 합니다:
    Note from the image above that we compute two values: the volume overall color (stored in the result) and the overall volume transparency. We initialize this value to 1 (fully transparent) and then attenuate the value with each one of the sample transparency values as we walk up (or down) the ray (from t1 to t0). We can then (finally) use this overall transparency value to combine the volume objectover our background color. We simply do:

    color final = background_color * transmission + result;

    합성 용어로, 우리는 "결과" 항은 이미 볼륨 전체 투명도에 사전 곱해져 있다고 말할 것입니다. 하지만 이것이 당신을 혼란스럽게 한다면, 우리는 다음 장에서 이 점을 명확히 할 것입니다. 그러니 지금은 이 부분에 너무 집중하지 마세요.
    In compositing terms, we would say that the "result" term is already pre-multiplied by the volume overall transparency. But if this is confusing to you, we will clarify this point in the next chapter. So don't focus on this too much for now.

    또한 위의 이미지와 아래의 코드에서 샘플의 감쇠 항은 항상 동일하다는 점에 유의하십시오. exp(-step_size * sigma_a). 물론 이것은 효율적이지 않습니다. 이 항을 한 번 계산하고 변수에 저장하고 그 변수를 사용하는 것이 좋습니다. 하지만 우리의 목표는 명확함이지 성능이 뛰어난 코드를 작성하는 것이 아닙니다. 게다가, 지금은 이 값이 광선을 따라 행진할 때 일정하지만 다음 장에서 이 값이 샘플마다 다를 것임을 알게 될 것입니다.
    Note also that both in the image above and in the code below the attenuation term of the samples is always the same:exp(-step_size * sigma_a). Of course, this is not efficient. You should compute this term once, store it in a variable, and use that variable instead. But clarity is our goal, not writing performant code. Besides, for now, this value is constant as we march along the ray but we will discover in the next chapters that it will eventually vary from sample to sample.

    constexpr vec3 background_color{ 0.572f, 0.772f, 0.921f };
    
    vec3 integrate(const vec3& ray_orig, const vec3& ray_dir, ...)
    {
        const Object* hit_object = nullptr;
        IsectData isect;
        for (const auto& object : objects) {
            IsectData isect_object;
            if (object->intersect(ray_orig, ray_dir, isectObject)) {
                hit_object = object.get();
                isect = isect_object;
            }
        }
    
        if (!hit_object) 
            return background_color;
    
        float step_size = 0.2;
        float sigma_a = 0.1; // absorption coefficient
        int ns = std::ceil((isect.t1 - isect.t0) / step_size);
        step_size = (isect.t1 - isect.t0) / ns;
    
        vec3 light_dir{ 0, 1, 0 };
        vec3 light_color{ 1.3, 0.3, 0.9 };
    
        float transparency = 1; // initialize transparency to 1
        vec3 result{ 0 }; // initialize the volume color to 0
    
        for (int n = 0; n < ns; ++n) {
            float t = isect.t1 - step_size * (n + 0.5);
            vec3 sample_pos= ray_orig + t * ray_dir; // sample position (middle of the step)
    
            // compute sample transparency using Beer's law
            float sample_transparency = exp(-step_size * sigma_a);
            
            // attenuate global transparency by sample transparency
            transparency *= sample_transparency;
    
            // In-scattering. Find the distance traveled by light through 
            // the volume to our sample point. Then apply Beer's law.
            IsectData isect_vol;
            if (hitObject->intersect(sample_pos, light_dir, isect_vol) && isect_vol.inside) {
                float light_attenuation = exp(-isect_vol.t1 * sigma_a);
                result += light_color * light_attenuation * step_size;
            }
    
            // finally attenuate the result by sample transparency
            result *= sample_transparency;
        }
    
        // combine with background color and return
        return background_color* transparency + result;
    }

     

    하지만 이 코드를 주의하십시오. 아직 정확하지 않습니다. 다음 장에서 살펴볼 몇 가지 용어가 누락되어 있습니다. 지금은 레이 마칭의 원리를 이해하기만을 원합니다. 그래도 이 코드는 설득력 있는 이미지를 생성할 것입니다.
    But mindful of this code. It's not accurate yet. It's missing a few terms that we will be looking at in the next chapter. For now, we just want you to understand the principle of ray-marching. Yet this code will produce a convincing image.

    이 예에서 우리는 상향식 원거리 광원(광선 방향은 y축을 따라 위쪽)을 사용하고 있음을 알 수 있습니다. 구의 붉은색은 광원의 색에서 비롯됩니다. 상단 절반의 구가 하단 절반보다 밝은 것을 볼 수 있습니다. 이미 그림자 효과가 보입니다.
    Note that in this example, we've been using a top-down distant light (light direction is upward along the y-axis). The reddish color of the sphere comes from the light color. You can see that the top half of the sphere is brighter than the bottom half. The shadowing effect is visible already.

    $$A = Li(X_{0}) * Att$$$$B = (A + Li(X_{1})) * Att$$$$B = (Li(X_{0}) * Att + Li(X_{1})) * Att$$$$B = (Li(X_{0}) * Att^{2} + Li(X_{1}) * Att$$$$C = (B + Li(X_{2})) * Att$$$$C = (Li(X_{0}) * Att^{2} + Li(X_{1}) * Att + Li(X_{2})) * Att$$$$C = (Li(X_{0}) * Att^{3} + Li(X_{1}) * Att^{2} + Li(X_{2}) * Att$$$$...$$

    루프를 진행하면서 단지 에 어떤 일이 일어나는지 살펴보면, 그것이 어떤 힘으로 제기된 샘플 감쇠로 곱된다는 것을 알 수 있습니다. 우리가 광선을 따라 행진할수록 지수(처음에는 1, 그 다음에는 2, 그 다음에는 3, ...)가 높아지고 결과가 작아집니다(감쇠 또는 샘플 투명도가 1보다 낮기 때문입니다). 즉, 볼륨에 의해 산란되는 전체 빛에 대한 첫 번째 샘플의 기여는 더 많은 샘플이 누적됨에 따라 감소합니다.
    If you look at what happens to justLi(X0)as we go through the loop, you can observe that it gets multiplied by the sample attenuation raised to some power. The more we march along the ray, the higher the exponent (first 1, then 2, then 3, ...) and thus the smaller the result (since the attenuation or sample transparency is lower than 1). In other words, the contribution of the first sample to the overall resulting light scattered by the volume decreases as more samples are accumulated.

    Forward Ray-Marching

    forward ray-marching. Marching along the ray in small regular steps forward, from t0 to t1.

    Li(x)와 샘플의 전달 값을 계산할 때 후방 레이 마칭과 차이가 없습니다. 다른 점은 이번에는 t0에서 t1(앞에서 뒤로)로 행진하기 때문에 샘플을 결합하는 방법입니다. 전방 레이 마칭에서, 샘플에 의해 산란된 빛의 기여는 지금까지 처리한 모든 샘플(현재 샘플 포함)의 전체 전달(투명도) 값에 의해 약화되어야 합니다. Li(X1)은 샘플 X0과 X1의 전달 값에 의해 약화되고, Li(X2)는 샘플 X0, X1, X2의 전달 값에 의해 가려집니다. 다음은 알고리즘의 설명입니다:
    There is no difference with backward ray-marching when it comes to computing Li(x) and the sample's transmission value. What is different is how we combine samples because this time around, we will march from t0 to t1 (from front to back). In forward ray-marching, the contribution of light scattered by a sample has to be attenuated by the overall transmission (transparency) value of all samples (including the current one) that we've been processing so far: Li(X1) is attenuated by the transmission values of the samples X0 and X1, Li(X2) is occluded by the transmission values of the samples X0, X1, and X2, etc. Here is a description of the algorithm:

    • Step 1: 레이 마칭 루프에 들어가기 전에: 전체 전달(투명도) 값을 1로 초기화하고 결과 색상 변수를 0으로 초기화합니다(현재 카메라 광선의 볼륨 개체 색상을 저장하는 변수)
      before we enter the ray-marching loop: initialize the overall transmission (transparency) value to 1 and the result color variable to 0 (the variable that stores the volumetric object color for the current camera ray): float transmission = 1; color result = 0;.
    • Step 2: 레이마칭 반복시 마다:
      for each iteration in the ray-marching loop:
      • $Li(x)$ - 현재 샘플에 대해 내부 산란도를 계산한다
        Compute in-scattering for the current sample: Li(x).
      • 전체 투과도 값을 현재 샘플에 대한 투과도 값을 바탕으로 업데이트한다:
        Update the overall transmission (transparency) value by multiplying it by the current sample transmission value: $transmission *= sample_transmission$.
      • $Li(x)$를 전체 전달(투명도) 값으로 곱합니다. 샘플에 의해 산란된 빛은 지금까지 처리해온 모든 샘플(현재 샘플 포함)에 의해 가려집니다. 결과를 현재 카메라 광선의 볼륨 색상을 저장하는 전역 변수에 추가합니다 ($result += Li(x) * transmission$).
        Multiply Li(x) by the overall transmission (transparency) value: light scattered by the sample is occluded by all samples (including the current sample) that we've been processing so far. Add the result to our global variable that stores the volume color for the current camera ray: result += Li(x) * transmission.

    ...
    vec3 integrate(const vec3& ray_orig, const vec3& ray_dir, ...)
    {
        ...
        float transparency = 1; // initialize transparency to 1
        vec3 result{ 0 }; // initialize the volume color to 0
    
        for (int n = 0; n < ns; ++n) {
            float t = isect.t0 + step_size * (n + 0.5);
            vec3 sample_pos = ray_orig + t * ray_dir;
    
            // current sample transparency
            float sample_attenuation = exp(-step_size * sigma_a);
    
            // attenuate volume object transparency by current sample transmission value
            transparency *= sample_attenuation;
    
            // In-Scattering. Find the distance traveled by light through 
            // the volume to our sample point. Then apply Beer's law.
            if (hit_object->intersect(sample_pos, light_dir, isect_vol) && isect_vol.inside) {
                float light_attenuation = exp(-isect_vol.t1 * sigma_a);
                // attenuate in-scattering contrib. by the transmission of all samples accumulated so far
                result += transparency * light_color * light_attenuation * step_size;
            }
        }
    
        // combine background color and volumetric object color
        return background_color * transparency + result;
    }

    이 코드에 유의하십시오. 아직 정확하지 않습니다. 다음 장에서 살펴볼 몇 가지 용어가 누락되어 있습니다. 지금은 레이 마칭의 원리를 이해하기만 하면 됩니다. 그래도 이 코드는 설득력 있는 이미지를 생성합니다.
    But mindful of this code. It's not accurate yet. It's missing a few terms that we will be looking at in the next chapter. For now, we just want you to understand the principle of ray-marching. Yet this code will produce a convincing image.

    여기서 이미지를 보여줄 필요는 없습니다. 잘 했다면 후방과 전방 레이 마칭은 같은 결과를 가져야 합니다. 네, 우리가 당연하게 받아들이지 않을 거라는 걸 알았으니 결과가 여기 있습니다.
    No need to show an image here. If we've done it right, backward and forward ray-marching should give the same result. Ok, we knew you wouldn't take this for granted, so here are the results.

     

    왜냐하면 볼륨의 투명도가 0에 매우 가까워지면(볼륨이 충분히 크고/또는 산란 계수가 충분히 높으면 발생할 수 있음) 레이 마칭을 중단할 수 있습니다. 그리고 이것은 당신이 앞으로 레이 마칭을 할 때만 가능합니다.
    Because we can stop ray-marching as soon as the transparency of the volume gets very close to 0 (which would happen if the volume is large enough and/or the scattering coefficient is high enough). And this is possible only if you ray-march moving forward.

    지금은 볼륨 구를 렌더링하는 것이 빠르지만 챕터를 진행하면서 결국 느려질 것입니다. 따라서, 우리가 광선을 따라 행진하면서 볼륨이 불투명하다는 것을 알게 된 지점에 도달했기 때문에 픽셀의 색상에 기여하지 않는 샘플을 계산하지 않을 수 있다면 좋은 최적화입니다.
    Right now, rendering our volume sphere is rather quick but you will see as we progress through the chapters that it will eventually get slow. So, if we can avoid computing samples that aren't contributing to the pixel's color because we reached a point as we march along the ray where we know that the volume is opaque, then it's a good optimization.

    Choosing the Step Size

     

    we are not capturing the small details in the volume because our step size is too small. Of course, this example is extreme, but it was designed to help you get the idea.
    though is example is also extreme (2 samples might never be enough to properly render the lighting of a volumetric object) you can see that we don't have enough samples to capture part of the volumetric object that is in the shadow of the solid object. We would need a much small step size.

    리만 합 방법을 사용하여 볼륨 렌더링에서 빛의 산란을 계산하기 위해 카메라 광선에 따라 작은 단계를 수행하는 것을 기억하십시오. 이전 장에서 설명한 바와 같이, 적분을 추정하는 데 사용되는 직사각형이 클수록 근사값이 덜 정확합니다. 반대로, 직사각형이 작을수록(단계 크기가 작을수록) 근사값이 더 정확하지만 계산하는 데 더 오래 걸립니다. 지금은 볼륨 스피어를 렌더링하는 것이 매우 빠르지만, 이 튜토리얼을 진행하면서 훨씬 느려지는 것을 볼 수 있습니다. 이것이 단계 크기를 선택하는 것이 속도와 정확성 사이의 절충안인 이유입니다.
    Remember that the reason why we ray-marching, take small steps from t0 to t1 is to estimate an integral (the amount of light scattered towards the eye along the camera ray due to in-scattering) using the Riemann sum method. As explained in the chapter before and in the lesson the mathematics of shading, the larger the rectangles (the width of the rectangles in our case are defined by the step size here) used to estimate the integral the less accurate the approximation. Or the other way around: the smaller the rectangle (the smaller the step size) the more accurate the estimation, but of course the longer it takes to calculate. For now, rendering our volumetric sphere is rather fast, but as we progress through the lesson, you will see that it will eventually get much much slower. This is why choosing the step size is a tradeoff between speed and accuracy.

    현재 볼륨 밀도도 균일하다고 가정합니다. 다음 장에서 우리는 구름이나 연기와 같은 볼륨 밀도를 렌더링하기 위해 밀도가 공간을 통해 변한다는 것을 알게 될 것입니다. 이러한 볼륨은 큰 주파수 특징뿐만 아니라 작은 특징도 포함합니다. 스텝 크기가 너무 크면 결국 이러한 작은 주파수 특징 중 일부를 포착하지 못할 수 있습니다(그림 5). 이것은 필터링 문제로, 중요한 주제이지만 그 자체로 복잡한 주제입니다.
    For now, we assume the volume density is also uniform. In the next chapters, we will see that to render volume density such as clouds or smoke, the density varies through space. Such volumes are made of large frequency features but also smaller ones. If the step size is too large it might eventually fail to capture some of these smaller frequency features (Figure 5). This is a filtering issue, an important but complex topic on its own.

    또 다른 문제는 스텝 크기를 조정해야 하는 그림자일 수 있습니다. 작은 고체 물체가 볼륨 개체에 그림자를 드리우고 있다면, 스텝 크기가 너무 크면 결국 그것들을 놓칠 것입니다 (그림 6).
    Another problem might arise that requires tweaking the step size:shadows. If small solid objects are casting shadows on the volumetric object, you will eventually miss them if the step size is too large (Figure 6).

    이 모든 것이 좋은 스텝 크기를 선택하는 방법을 알려주지 않습니다. 이론적으로는 규칙이 없습니다. 볼륨 개체의 크기에 대한 아이디어를 갖는 것이 좋습니다. 예를 들어, 방의 크기를 알면(그리고 1 단위 = 10cm와 같은 단위의 종류를 알면) 해당 방의 크기를 알 수 있습니다. 방이 100 단위라면, 0.1의 스텝 크기는 너무 작을 수 있지만 1 또는 2는 시작하기 좋은 곳입니다. 그런 다음 이전에 언급한 것처럼 속도와 정확성 사이의 좋은 절충안을 찾기 위해 약간의 조정을 해야 합니다.
    All that doesn't tell us how to choose a good step size. In theory, there's no rule. You should essentially have an idea of the size of your volumetric object. For example, if it's a rectangle filling up a room with some kind of uniform atmosphere, you should get an idea of the size of that room (and of the kind of units you use, like 1 unit = 10 centimeters for example). So if the room is 100 units large, a step size of 0.1 might be too small whereas 1 or 2 might be a good place to start. Then you need to fiddle around to find a good tradeoff between speed and accuracy as we mentioned before.

    물론, 이것도 완전히 사실은 아닙니다. 장면의 개체가 얼마나 큰지 고려하여 경험적으로 스텝 크기를 선택하는 동안, 그렇게 하는 더 합리적인 방법이 있어야 합니다. 한 가지 가능한 방법은 볼륨 개체에 들어갈 때 픽셀이 얼마나 "큰"지 고려하고 스텝 크기를 투영된 픽셀의 치수로 설정하는 것입니다. 실제로, 이산 개체인 픽셀은 그 크기보다 작은 장면의 세부 사항을 표현할 수 없습니다. 여기서는 필터링이 자체적으로 한 수업이기 때문에 더 자세히 설명하지 않겠습니다. 지금으로서는 카메라 광선이 볼륨과 교차하는 지점에서 투영된 픽셀의 크기에 가까운 좋은 스텝 크기라고 말할 수 있습니다. 이것은 다음과 같이 추정할 수 있습니다:
    Now, this is not entirely true either. While choosing the step size empirically by considering how big the objects are in the scene, there must be a more rational way of doing so. One possible way is to consider "how big" is the pixel at the distance where we enter the volume object and set the step size to the dimension of the projected pixel. Indeed, a pixel that is a discrete object can't represent details from the scene that are smaller than its size. We won't get into much more detail here because filtering is worth a lesson on its own. All we will say, for now, is that a good step size is close to the projected size of a pixel at the point where the camera ray intersects the volume. This can be estimated with something like:

    float projPixWidth = 2 * tanf(M_PI / 180 * fov / (2 * imageWidth)) * tmin;

    이 부분은 볼륨 렌더링에서 샘플링을 최적화하는 방법에 대한 설명입니다. tmin은 카메라 광선이 볼륨 개체와 교차하는 거리입니다. 마찬가지로, 광선이 볼륨을 떠나는 곳에서 투사된 픽셀 너비를 계산하고 tmin과 tmax에서 투사된 픽셀 너비를 선형으로 보간하여 광선을 따라 행진할 때 스텝 크기를 설정할 수 있습니다.
    Which you can optimize if you wish to. Where tmin is the distance where the camera ray intersects the volume object. One could similarly compute the projected pixel width where the ray leaves the volume and linearly interpolate the projected pixel width at tmin and tmax to set the step size as we march along the ray.

    Other considerations of interest

    생산 코드를 작성하려면 광선 데이터와 함께 광선 불투명도와 색상을 저장해야 합니다. 이렇게 하면 먼저 고체 개체를 광선 추적한 다음 볼륨 개체를 추적하고 결과를 결합할 수 있습니다(위의 예에서 배경 색상과 볼륨 구 개체를 결합한 것과 유사한 방식으로).
    Writing production code would require storing the ray opacity and color with the ray data. So that we can ray-trace the solid objects first, then the volumetric objects and combine the result as we go (in a similar fashion to what we did by combining the background color with the volumetric sphere object in the example above).

    여러 볼륨 개체가 카메라 광선의 경로에 있을 수 있습니다. 따라서, 레이 마칭을 진행하면서 투명도를 저장하고 연속적인 볼륨 개체의 투명도와 색상을 결합할 필요가 있습니다.
    Note that several volumetric objects can be on the camera ray's path. Hence the necessity of storing the opacity along the way and combining the consecutive volumetric objects' opacity and color as we ray-march them.

    볼륨 개체는 서로 겹치는 입방체나 구와 같은 결합된 개체의 모음으로 만들어질 수 있습니다. 이 경우, 우리는 그들을 일종의 집합 구조로 결합할 수 있습니다. 이러한 집합을 레이 마칭하려면 집합을 구성하는 개체의 교차 경계를 특별한주의를 기울여 계산해야 합니다.
    A volumetric object can be made of a collection of combined objects such as cubes or spheres overlapping each other. In this case, we may want to combine them in some sort of aggregate structure. Ray-marching such aggregates require computing the intersection boundaries of the objects making the aggregate with special care.

     

    참조:

     

    Volume Rendering for Developers: Foundations

    To integrate incoming light along the ray due to in-scattering, we will break down the volume that the ray passes through into small volume elements and combine the contribution of each of these small volume elements to the overall volume object, a little

    www.scratchapixel.com

     

     

Designed by Tistory.