Monday, July 18, 2011

Encoding normals for Gbuffer

Deferred shading has nowadays become nearly ubiquitous in real-time rendering due to the separation of geometry and lighting passes. The general idea of this technique is to render all important properties (color, normal, reflectivity and so on) of all visible surfaces to a set of screen-sized textures. Later rendering passes (lighting) get surface parameters from those textures and thus complex geometries do not have to be rendered multiple times.

One design problem of Gbuffer implementation is the efficient encoding of surface normals. Normals in three-dimensional space have three components:

N = Nx,Ny,Nz

The most intuitive way would be to store those in three texture channels. But this would be wasteful, because we know, that:

Nx2 + Ny2 + Nz2 = 1

Gbuffer requires many texture channels for other data, thus we want to cut off as many unnecessary channels as possible to conserve memory and fillrate.

Another simple alternative would be to store only two components (Nx and Ny) and reconstruct third (Nz) in pixel shader. Unfortunately we will lose the sign of Nz in that process. While in ideal scene there should be no normals with negative Z (facing away from camera), they happen in real scenes because of realistic polygon counts.

There exist several techniques for encoding normals while (at least partially) preserving Z component. Unfortunately no one of those is ideal. Either they are not able to encode full range of Z (various projections) or waste precision and calculation (polar coordinates).

While implementing foliage shader for Khayyam I needed a way to encode full range of possible normals into two-component vector (that will later be stored in 4-component 8-bit RGBA texture of Gbuffer). The following is the brief introduction of the technique used. It solves all the problems I am aware of - with the price of slightly more shader instructions than simple projection encoders.

Efficient projection of normal space

We forget for awhile the need of storing the sign of Z component and look, how we can store X and Y components most efficently.
The trivial way is to simply forget Z and store X and Y. In geometric terms it means projecting the hemisphere of normal space to an unit circle on XY plane along Z vector as in following figure:

The trivial projection of normal space to XY plane

The problem with this projection is, that the uniform angular difference between normals near equator gives much smaller difference in X and Y coordinates than near poles. If the precision of normal texture is limited to 16 bits (4x8 bit or 2x16 bit integer buffers) or even less (FP16 buffers) , the loss of precision may become visible. For example, for 16 bit buffers, the minimum representable angle near equator is:

acos (1 - 2e-15) = 1.44°

Near poles it is much better:

asin (2e-15) = 0.002°

Clearly we are wasting precision near poles and introduce errors near equator.

To get better distribution of precision, we can switch projection from parallel to radial, with the projection center at the point (0,0,-1) as on the following figure:

The radial projection of normal space

The distribution of angles is still not uniform but is clearly much better (it differs only by the factor of 2 at poles and equator).
Actually, having the center of projection at (0,0,-sqrt(2)) would result in even better distribution, but I wanted to keep it at (0,0,-1) to simplify calculations a bit.

The hemisphere is still projected at unit circle at XY plane, although now we cannot project the normals on negative hemisphere directly but have to take the absolute value of Z coordinate before projecting.

This solves the problem of effecient use of normal texture precision. Now we can go on to encoding the sign of Z into the same buffer.

Encoding the sign of normal Z coordinate

Our positive hemisphere of normal space got projected to unit circle on XY plane as in following figure:


The normal space projection to XY plane and compaction schema


As we can see, we are actually wasting the numerical space of normal texture. The texture is able to encode values in unit square, but only unit circle is used - the possible values on dashed areas can never happen in our projected normals. We could store normals with negative Z in those areas, but unfortunately there is no easy algorithm to do that.

Now we do another conversion and pack all the values from unit circle into diamond-shaped area, given by the following formula:

|X| + |Y| <= 1

This can be done with the following algorithm (let the normal be Nx', Ny' and the projection be Nx'', Ny''):

r = sqrt (Nx' * Nx' + Ny' * Ny')
D = |Nx'| + |Ny'|
Nx" = Nx' * r / D
Ny" = Ny' * r / D

We lose some precision at  the normals close to 45° angles, but the maximum loss is by the factor of sqrt(2).

The resulting projection of normal space is shown in following figure:

Compacted normal space and Z flipping directions

Now the whole projection of the positive hemisphere of normal space takes exactly the half of the size of unit square. The extra space can be used to encode normals with negative Z.

We do this by mirroring the final normal projections by the closest one of diagonal lines (|x| + |y| = 1).

Thus we have encoded the full normal space to unit square with minimal loss of precision.

Decoding normals

To decode normals we have first to find the sign of Z coordinate. This is done with the following formula:

Zsign =  1 if (|x| + |y|) <= 1
Zsign = -1 if (|x| + |y|) >  1

Now, if Z is negative, we have to mirror our encoded normal by the closest  diagonal line (|x| + |y| = 1) so the resulting normal is inside the diamond shaped area.

We unpack normals to unit circle with reverse packing algorithm:


d = sqrt (Nx" * Nx" + Ny" * Ny")
r = |Nx"| + |Ny"|
Nx' = Nx" * d / r
Ny' = Ny" * d / r

From normal projection on unit sphere (N') unit sphere we unproject to normal on positive hemisphere (N).

Finally we set the sign of the Z component of N from previously found Zsign.

And that is it.

Encoding quality

I ran simulated tests of this algorithm on CPU with 100 000 000 randomly generated normals, including normals with any 1 or 2 components zero. Encoded vector was written and then read back from 4-component unsigned char buffer to simulate storing of 2-component normal in RGBA8 texture.

The maximum angular error between original and retrieved normal was 0.028°.

GLSL shader code

For those interested, here is current GLSL shader code of libsehle implementation. It is a bit heavy, but if your lighting is fill-rate bound, as is mine, you can spare some GPU cycles.

Encoding.

vec2 encodeNormal (vec3 normal)
{
    // Project normal positive hemisphere to unit circle
    // We project from point (0,0,-1) to the plane [0,(0,0,-1)]
    // den = dot (l.d, p.n)
    // t = -(dot (p.n, l.p) + p.d) / den
    vec2 p = normal.xy / (abs (normal.z) + 1.0);

    // Convert unit circle to square
    // We add epsilon to avoid division by zero
    float d = abs (p.x) + abs (p.y) + EPSILON;
    float r = length (p);
    vec2 q = p * r / d;

    // Mirror triangles to outer edge if z is negative
    float z_is_negative = max (-sign (normal.z), 0.0);
    vec2 q_sign = sign (q);
    q_sign = sign (q_sign + vec2 (0.5, 0.5));
    // Reflection
    // qr = q - 2 * n * (dot (q, n) - d) / dot (n, n)
    q -= z_is_negative * (dot (q, q_sign) - 1.0) * q_sign;

    return q;
}

Decoding.

vec3 decodeNormal (vec2 encodedNormal)
{
    vec2 p = encodedNormal;
   
    // Find z sign
    float zsign = sign (1.0 - abs (p.x) - abs (p.y));
    // Map outer triangles to center if encoded z is negative
    float z_is_negative = max (-zsign, 0.0);
    vec2 p_sign = sign (p);
    p_sign = sign (p_sign + vec2 (0.5, 0.5));
    // Reflection
    // qr = q - 2 * n * (dot (q, n) - d) / dot (n, n)
    p -= z_is_negative * (dot (p, p_sign) - 1.0) * p_sign;

    // Convert square to unit circle
    // We add epsilon to avoid division by zero
    float r = abs (p.x) + abs (p.y);
    float d = length (p) + EPSILON;
    vec2 q = p * r / d;

    // Deproject unit circle to sphere
    float den = 2.0 / (dot (q, q) + 1.0);
    vec3 v = vec3(den * q, zsign * (den - 1.0));

    return v;
}

15 comments:

  1. Thanks for that awesome and insightful article!
    What I'm still wondering however is how you're dealing with negative XY-Values when storing in the usual RGBA8 UNSIGNED_BYTE-texture. Assuming you are packing x to 16bit RG components and y to 16bit BA,
    as far as I know, the usual way to go about this would be to scale all values by ( + 1 ) / 2 and unscale by * 2 + 1 during unpacking. Thus all above considerations would be essentially broken down to the first quadrant. And if I'm not mistaken, that would also speed up the z-sign reflection as xy' = 1 - xy.

    Or would you use e.g. the R-channel for -x, the G-channel for +x and thus restricting to only 8 bit precision?

    Or do you yo use Floating-point textures? ;)

    ReplyDelete
  2. To be honest I have not tried to optimize it much yet. At moment I am doing the usual (V + 1) / 2 transformation and I am not sure, whether moving everything to 0,0 - 1,1 quadrant during packing would make things faster.
    I tried FP16 buffers, but my card did not like mixing U8 and FP16 color buffers on the same render target. Thus I went back to RGBA8 and just pack normals into RG and BA parts.

    ReplyDelete
  3. اليك خدمات منزلية متقدمة تساعدنا علي الارتقاء بشركة ركن البيت في مدينة الدمام من خلال اعمالها المتميزة التي تقدمها في مجال النظافة والقضاء علي الحشرات فخدماتنا متنوعة مثل شركة تنظيف بالدمام التي تكون متخصصة في اعمال النظافة العامة التي تتمثل في المنشأت الكبيرة والصغيرة بجميع انواع واليك خدمة متميزة ايضا وهي شركة تنظيف فلل بالدمام التي تكون لديها كل الامكانيات المثالية التي تساعدنا في تنفيذ خدمات نظافة الفلل المستعملة والمفروشة بواسطة فريق فني لديه كل الامكانيات التي تساعده في تقديم ذلك ولديك خدمة ايضا نظافة الشقق بواسطة شركة تنظيف شقق بالدمام وعمل التنظيف اللازم لها بواسطة فريق لديه اعمال التميز والتنفيذ في تقديم الخدمات المخصصة في اعمال النظافة واليك خدمة مثالية نقدمها في القضاء علي الحشرات الصغيرة بواسطة مبيدات طبيعة تساعدنا في ذلك فتعاملك مع شركة مكافحة حشرات بالدمام لديها امكانيات متميزة تساعدك علي ان تخلصك من الحشرات لان تجد كل ذلك الا من خلال ركن البيت التي لديها كل الامكانيات في البحث عن شركة رش مبيدات بالدمام التي تقوم بالرش بواسطة فني مختص معه الادوات اللازمة للقيام بذلك وعطائك الضمانات اللازمة علي ذلك واليك خدمة نقل اثاث منزلية تساعدك في الوصول الي مكانك الجديد بواسطة شركة نقل اثاث بالدمام التي لديها كل الامكانيات والسيارات التي تساعدك علي تحقيق ذلك

    ReplyDelete
  4. كيفية الطرث الصحيحية للعنايه بالمنزل ونظافة بيتك مع شركة ابو سمره لجميع الخدمات المنزليه نقدم لكم شركة تنظيف بالرياض
    من اهم الاشياء الاهتمام بالبيت افكار رائعة للشقق والبيوت بالرياض طريقة ازاله الروائح الكريها من المنزل ونظافة المنزل جيداً شركة تنظيف شقق بالرياض اسرع طريقة لتنظيف المنزل بالرياض طرق النظافة العالميه في شركتنا وبس افضل طرق تنظيف السجاد والموجيت بالرياض شركة تنظيف فلل بالرياض مع شركتنا اسهل طرق للتنظيف شركة تنظيف موكيت بالرياض سوف تجد افضل الخدمات المنزليه في مدينة الرياض والمناطق المجاوره لدينا قسم خاص ل شركة تنظيف خزانات بالرياض وايضاً شركة عزل خزانات بالرياض من اهم الاشياء الضرور يه هيا كشف التسربات نقدم لكم شركة كشف تسربات المياه بالرياض شركة تسليك مجاري بالرياض شركة تنظيف بيارات بالرياض
    شركة رش مبيدات بالرياض
    شركة مكافحة حشرات بالرياض
    شركة نقل اثاث بالرياض
    شركة تخزين اثاث بالرياض
    شركة صيانة وترميم بالرياض مع شركة ابو سمره لجميع الخدمات المنزليه في مدينة الرياض سوف تجد افضل الخدمات المناسبة

    ReplyDelete
  5. شركة نقل عفش بالدمام
    عالميه الدمام- 0542613483 - افضل شركات نقل العفش بالمنطقه الشرقيه شركة نقل اثاث بالدمام
    ارخص شركة نقل اثاث بالدمام
    شركة نقل اثاث بالجبيل
    أن الشركة تتمتع بالالتزام التام بمواعيدها، فلا تعمل على مخالفتها تحت أي ظرف من الظروف وهذا بداية من عملية الاتصال وحتى إنهاء الخدمة تمامًا، كما أن فريق العمل ذو حجم كبير نسبيًا نقل عفش بالخبر
    شركة نقل عفش بالجبيل

    ReplyDelete
  6. شركتنا شركة نقل اثاث بالجبيل الأفضل كونها تنظم من عملها، وتنظم من عمالتها، فكل عامل له عمل مخصص يقوم به. نقل عفش بالدمام
    نقل اثاث بالدمام
    نقل عفش بالاحساء
    لن تجد أجدر من شركه نقل عفش بالدمام قادرة على إتمام تلك المهمة حيث ثقة عملائها الكرام على المحك ولن تقبل تحت أي حال من الأحوال أن يخيب ظن العملاء نقل اثاث بالاحساء

    ReplyDelete
  7. يمكن تعريف الشحن المحدود الذي تقدمه شركه نقل اثاث بالدمام بكونه ما يقتصر على نقل ما قد يحتاجه العميل من أثاث كالأجهزة الكهربائية أو الأثاث الخشبية حتى وإن كان لقطع غيرة ومدودة منه. شركة نقل عفش بالخبر
    نقل عفش بالخبر
    شركة نقل عفش فى بالخبر
    وجدير بالذكر أنه بعد الرفع يقوم العمال بتركيب الأثاث كما كان شركات نقل العفش بالخبر

    ReplyDelete
  8. انوار طيبة أفضل شركة نظافة بالدمام متخصصة في مجال التنظيف بالدمام والمناطق المجاورة فنحن شركة تنظيف مكيفات بالدمام نقوم أيضاً بتنظيف (فلل– شقق- منازل- بيوت- المجالس- العمائر- الشركات – المطاعم – الفنادق- المحال التجارية) وجميع الأماكن التي تحتاج للتنظيف، وتقوم الشركة بتنظيف منزلك بأحدث الماكينات شركة تنظيف مكيفات بالدمام
    شركة تنظيف مكيفات بالخبر
    شركة تنظيف بالخبر تقدم أسعار مميزة لعملائها فهي أرخص شركة تنظيف مكيفات حيث تقوم بتنظيف المنازل والشقق والفلل والكنب والسجاد والمسابح والمدارس والمساجد والخزانات بالدمام و بالخبر و بالجبيل و بالقطيف تستخدم أجود أنواع المنظفات فهي افضل شركة نظافة بالخبر شركة غسيل مكيفات بالدمام
    شركة تنظيف مكيفات بالقطيف
    مؤسسة انوار طيبة-ارخص شركة تنظيف مكيفات بالقطيف والدمام والجبيل – افضل شركة تنظيف مكيفات السبلت بالدمام والقطيف، وتنظيف وصيانة وغسيل المكيفات سبليت ومركزية ومخفي وكاست ودولابي وشباك وتركيب جميع المكيفات شركتنا افضل شركة غسيل مكيفات بالدمام والجبيل تتعامل معكم شركة تنظيف مكيفات برأس تنورة

    ReplyDelete
  9. ان حشرة البق تشكل خطورة كبيرة علي المنازل وحصوصا الاطفال لذلك يمكنك ايجاد الحل الامثل من خلال شركة مكافحة البق بالدمام حيث تضم الشركة افضل الخبراء فهي افضل شركة مكافحة البق بالدمام شركة مكافحة البق بالدمام
    شركة مكافحة البق بالخبر
    شركة مكافحة النمل الابيض بالدمام
    افضل شركة مكافحة النمل الابيض بالقطيف شركة  انوار طيبة ونصلك اينما كنت نستخدم افضل المبيدات للقضاء علي الحشرات النمل والصراصير وبق الفراش والوزع والنمل الابيض وجميع الحشرات الزاحفة ,مكافحة جميع انواع الحشرات باستخدام افضل المبيدات المستوردة والمحلية لضمان القضاء علي الحشرات مع الضمان وخصومات هائلة شركة مكافحة النمل الابيض بالقطيف
    شركات مكافحة حشرات ورش مبيدات بالدمام هل تبحث عن شركة مكافحة حشرات بالسعودية هل لديك نمل في المطبخ او حشرة البق في الخشب؟ هل عندك صراصير او نمل او فئران وتفسد عليك حياتك في منزلك او شركتك؟ لا تقلق فهناك العديد من شركات مكافحة الحشرات في السعودية وأيضا يوجد العديد من شركة رش مبيدات ومكافحة الحشرات بالدمام شركة رش مبيدات بالدمام

    ReplyDelete
  10. قبطان الخليج -0504353061-يُعد الأثاث من أكثر الأشياء التي تحتاج إلى حرص ودقة بالغة عند القيام بنقلها شركة نقل عفش بالظهران
    تحرص على تقديم الضمان اللازم عن العمل الذي تقوم به لعملائها الكرام، فالضمان بدوره يطمئن العميل وفي النفس الوقت يضمن مصداقية الشركة شركة نقل اثاث بالظهران

    ReplyDelete
  11. ان حشرة البق تشكل خطورة كبيرة علي المنازل وحصوصا الاطفال لذلك يمكنك ايجاد الحل الامثل من خلال شركة مكافحة البق بالدمام حيث تضم الشركة افضل الخبراء فهي افضل شركة مكافحة البق بالدمام شركة رش حشرات بالدمام
    شركة مكافحة حشرات بالدمام
    شركة مكافحة حشرات بالخبر تقدم لكم العديد من الخدمات المميزة، حيث أن الشركة تستطيع مكافحة النمل الابيض بالقطيف بالإضافة إلى كافة أنواع الحشرات الأخرى بكل سهولة وبأحدث الإمكانيات مثل مكافحة البق بالقطيف ومكافحة الصراصير بالقطيف ومكافحة الصراصير بالقطيف ولها فروع مثل شركة مكافحة حشرات بالدمام شركة مكافحة حشرات بالخبر
    شركة مكافحة البق بالدمام

    ReplyDelete
  12. We find lots of learning after reading this very useful article . cara menggugurkan hamil

    ReplyDelete