لدي فرع pbrt ، والذي أستخدمه لاختبار أفكار جديدة ، وتنفيذ أفكار مثيرة للاهتمام من المقالات العلمية ، وبشكل عام لدراسة كل ما ينتج عنه عادةً إصدار جديد من كتاب العرض
المستند إلى جسديًا . على عكس
pbrt-v3 ، الذي نسعى جاهدين لإبقائه قريبًا قدر الإمكان من النظام الموصوف في الكتاب ، يمكننا في هذا الموضوع تغيير أي شيء. سنرى اليوم كيف ستؤدي التغييرات الأكثر جذرية في النظام إلى الحد بشكل كبير من استخدام الذاكرة في المشهد مع الجزيرة من الرسوم المتحركة ديزني
"موانا" .
ملاحظة حول المنهجية: في الوظائف الثلاث السابقة ، تم قياس جميع الإحصائيات لإصدار WIP (العمل قيد التقدم) من المشهد الذي عملت معه قبل إصداره. في هذه المقالة ، سننتقل إلى الإصدار النهائي ، وهو أكثر تعقيدًا بعض الشيء.
عند عرض المشهد الأخير للجزيرة من
Moana ، تم استخدام 81 غيغابايت من ذاكرة الوصول العشوائي لتخزين وصف المشهد لـ pbrt-v3. حاليًا ، يستخدم pbrt-next 41 غيغابايت - حوالي النصف تقريبًا. للحصول على هذه النتيجة ، كان يكفي إجراء تغييرات صغيرة امتدت إلى عدة مئات من الأسطر من التعليمات البرمجية.
البدائية منخفضة
دعونا نتذكر أنه في pbrt
Primitive
هو مزيج من الهندسة والمواد ، ووظيفة الإشعاع (إذا كان مصدر ضوء) ويسجل عن البيئة داخل وخارج السطح. في pbrt-v3 ، يخزن
GeometricPrimitive
ما يلي:
std::shared_ptr<Shape> shape; std::shared_ptr<Material> material; std::shared_ptr<AreaLight> areaLight; MediumInterface mediumInterface;
كما
ذكرنا سابقًا ، فإن معظم
areaLight
هي
nullptr
، وتحتوي
MediumInterface
على زوج من
nullptr
. لذلك في pbrt-next أضفت خيارًا
SimplePrimitive
يسمى
SimplePrimitive
، والذي يخزن فقط المؤشرات إلى الهندسة والمواد. حيثما أمكن ، يتم استخدامه
GeometricPrimitive
أمكن بدلاً من
GeometricPrimitive
:
class SimplePrimitive : public Primitive {
بالنسبة لمثيلات الكائنات غير المتحركة ، لدينا الآن
TransformedPrimitive
، الذي يقوم فقط بتخزين مؤشر إلى البدائي والتحويل ، مما يوفر لنا حوالي 500 بايت من
المساحة المهدرة التي أضافها مثيل
AnimatedTransform
إلى جهاز
TransformedPrimitive
العارض pbrt-v3.
class TransformedPrimitive : public Primitive {
(يوجد
AnimatedPrimitive
في حال كنت بحاجة إلى تحويل متحرك إلى pbrt-next.)
بعد كل هذه التغييرات ، تشير الإحصاءات إلى أنه يتم استخدام 7.8 غيغابايت فقط في الإصدار
Primitive
، بدلاً من 28.9 غيغابايت المستخدمة في pbrt-v3. على الرغم من أنه من الرائع توفير 21 غيغابايت ، إلا أنه ليس بقدر الانخفاض الذي نتوقعه من التقديرات السابقة ؛ سنعود إلى هذا التناقض في نهاية هذا الجزء.
هندسة مخفضة
أيضًا ، قلل pbrt-next بشكل كبير من حجم الذاكرة التي تشغلها الهندسة: انخفضت المساحة المستخدمة لمثلثات الشبكة من 19.4 جيجا بايت إلى 9.9 جيجا بايت ، ومساحة التخزين للمنحنيات من 1.4 إلى 1.1 جيجا بايت. جاء ما يزيد قليلاً عن نصف هذه المدخرات من تبسيط فئة
Shape
الأساسي.
في pbrt-v3 ، يجلب
Shape
معه العديد من الأعضاء الذين ينتقلون إلى جميع تطبيقات
Shape
- هذه هي جوانب عديدة ملائمة للوصول إليها في تطبيقات
Shape
.
class Shape {
لفهم سبب تسبب متغيرات الأعضاء هذه في حدوث مشكلات ، سيكون من المفيد فهم كيفية تمثيل شبكات المثلث في pbrt. أولاً ، هناك فئة
TriangleMesh
، التي تقوم بتخزين الرؤوس والمخازن المؤقتة للمؤشر للشبكة بأكملها:
struct TriangleMesh { int nTriangles, nVertices; std::vector<int> vertexIndices; std::unique_ptr<Point3f[]> p; std::unique_ptr<Normal3f[]> n;
يتم تمثيل كل مثلث في الشبكة بفئة
Triangle
، التي ترث من
Shape
. تكمن الفكرة في إبقاء
Triangle
صغيرًا قدر الإمكان: فهم يخزنون فقط مؤشرًا على الشبكة التي يشكلون جزءًا منها ، ومؤشرًا للإزاحة في المخزن المؤقت للفهرس حيث تبدأ مؤشرات رؤوسه:
class Triangle : public Shape {
عندما تحتاج تطبيقات
Triangle
إلى العثور على مواضع رؤوسها ، فإنها تقوم بإجراء الفهرسة المقابلة للحصول عليها من
TriangleMesh
.
تكمن المشكلة في
Shape
pbrt-v3 في أن القيم المخزنة فيه هي نفسها لجميع مثلثات الشبكة ، لذا من الأفضل حفظها من كل شبكة كاملة في
TriangleMesh
، ومن ثم منح
Triangle
الوصول إلى نسخة واحدة من القيم المشتركة.
تم إصلاح هذه المشكلة في pbrt-next: لا تحتوي فئة
Shape
الأساسي في pbrt-next على مثل هؤلاء الأعضاء ، وبالتالي يكون كل
Triangle
أقل من 24 بايت. يستخدم Geometry
Curve
إستراتيجية مماثلة ويستفيد أيضًا من شكل أكثر إحكاما.
مخازن مثلث مشترك
على الرغم من حقيقة أن مشهد جزيرة
Moana يستخدم على نطاق واسع استنساخ الكائن لتكرار الهندسة بشكل صريح ، كنت أشعر بالفضول حول عدد المرات التي يتم فيها إعادة استخدام المخازن المؤقتة للفهرس ، والمخازن المؤقتة لإحداثيات النسيج ، وما إلى ذلك لاستخدامها في شبكات المثلث المختلفة.
لقد كتبت فئة صغيرة تقوم بتجزئة هذه المخازن المؤقتة عند الاستلام وتخزينها في ذاكرة التخزين المؤقت ، وقمت بتعديل
TriangleMesh
بحيث تقوم بفحص ذاكرة التخزين المؤقت وتستخدم النسخة المحفوظة بالفعل لأي مخزن مؤقت احتياطي يحتاج إليه. كان الربح جيدًا جدًا: تمكنت من التخلص من 4.7 غيغابايت من الحجم الزائد ، وهو أكثر بكثير مما توقعته.
تحطم مع std :: Shared_ptr
بعد كل هذه التغييرات ، تشير الإحصائيات إلى حوالي 36 جيجابايت من الذاكرة المخصصة المعروفة ، وفي بداية العرض ، يشير
top
إلى استخدام 53 جيجابايت. الشؤون.
كنت خائفة من سلسلة أخرى من
التجمعات البطيئة
massif
لمعرفة الذاكرة المخصصة المفقودة في الإحصائيات ، ولكن بعد ذلك ظهرت رسالة من
Arseny Kapulkin في صندوق البريد الخاص بي. شرح لي أرسيني أن تقديراتي
السابقة لاستخدام ذاكرة
GeometricPrimitive
كانت خاطئة للغاية. كان علي أن أفهم ذلك لفترة طويلة ، لكنني أدركت بعد ذلك ؛ شكرا جزيلا لآرسيني على الإشارة إلى الخطأ والتفسيرات التفصيلية.
قبل الكتابة إلى Arseny ، تخيلت تنفيذ
std::shared_ptr
النحو التالي: في هذه الأسطر يوجد واصف مشترك يقوم بتخزين العد المرجعي ومؤشر للكائن الموضوع نفسه:
template <typename T> class shared_ptr_info { std::atomic<int> refCount; T *ptr; };
ثم اقترحت أن مثيل
shared_ptr
يشير إليه فقط ويستخدمه:
template <typename T> class shared_ptr {
باختصار ، افترضت أن
sizeof(shared_ptr<>)
هو نفس حجم المؤشر ، وأن 16 بايت من المساحة الإضافية تضيع على كل مؤشر مشترك.
لكن الأمر ليس كذلك.
في تطبيق النظام الخاص بي ، يبلغ حجم الواصف المشترك 32 بايت و 16 بايت بحجم
sizeof(shared_ptr<>)
. لذلك ، فإن
GeometricPrimitive
، الذي يتكون أساسًا من
std::shared_ptr
، يبلغ حجمه ضعف التقديرات تقريبًا. إذا كنت تتساءل عن سبب حدوث ذلك ، فإن هاتين المنشورتين من Stack Overflow تشرحان الأسباب بتفصيل كبير:
1 و
2 .
في جميع حالات استخدام
std::shared_ptr
Shared_ptr تقريبًا في pbrt-next ، لا يجب أن تكون مؤشرات مشتركة. أثناء القيام بالقرصنة المجنونة ، استبدلت كل ما يمكنني باستخدام
std::unique_ptr
، والذي له في الواقع نفس حجم المؤشر العادي. على سبيل المثال ، إليك ما يبدو عليه
SimplePrimitive
الآن:
class SimplePrimitive : public Primitive {
تبين أن المكافأة أكبر مما كنت أتوقع: انخفض استخدام الذاكرة في بداية العرض من 53 جيجا بايت إلى 41 جيجا بايت - توفير 12 جيجا بايت ، غير متوقع تمامًا قبل أيام قليلة ، والمبلغ الإجمالي هو نصف ما يستخدمه pbrt-v3. عظيم!
في الجزء التالي ، سنكمل أخيرًا هذه السلسلة من المقالات - فحص سرعة العرض في pbrt-next ومناقشة الأفكار لطرق أخرى لتقليل حجم الذاكرة اللازمة لهذا المشهد.
الجزء 5
لتلخيص سلسلة المقالات هذه ، سنبدأ باستكشاف سرعة عرض مشهد الجزيرة من كارتون ديزني
"Moana" في pbrt-next - فرع pbrt الذي أستخدمه لاختبار الأفكار الجديدة. سنقوم بإجراء تغييرات جذرية أكثر مما هو ممكن في pbrt-v3 ، والذي يجب أن يلتزم بالنظام الموضح في كتابنا. نختتم بمناقشة المجالات لمزيد من التحسينات ، من أبسط إلى أقصى قليلا.
تقديم الوقت
قام Pbrt-next بإجراء العديد من التغييرات على خوارزميات نقل الضوء ، بما في ذلك التغييرات على أخذ عينات BSDF وتحسينات على خوارزميات الروليت الروسية. ونتيجة لذلك ، فإنه يتتبع أشعة أكثر من pbrt-v3 لتقديم هذا المشهد ، لذلك لا يمكن مقارنة وقت تنفيذ هذين العارضين بشكل مباشر. السرعة قريبة بشكل عام ، باستثناء واحد مهم: عند عرض مشهد جزيرة من
موانا ، كما هو موضح أدناه ، يقضي pbrt-v3 14.5 ٪ من وقت التنفيذ في البحث عن
مواد ptex . كان هذا يبدو عاديًا جدًا بالنسبة لي ، لكن pbrt-next لا ينفق سوى 2.2٪ من وقت التنفيذ. كل هذا مثير للاهتمام للغاية.
بعد دراسة الإحصائيات نحصل على
1 :
pbrt-v3:
Ptex 20828624
Ptex 712324767
pbrt-next:
Ptex 3378524
Ptex 825826507
كما نرى في pbrt-v3 ، تتم قراءة نسيج ptex من القرص في المتوسط كل 34 بحثًا عن النسيج. في pbrt-next ، تتم قراءته فقط بعد كل عمليات بحث 244 - أي أن القرص I / O قد انخفض بنحو 7 مرات. اقترحت أن يحدث هذا لأن pbrt-next يحسب اختلافات الأشعة للأشعة غير المباشرة ، وهذا يؤدي إلى الوصول إلى مستويات MIP أعلى من القوام ، مما يؤدي بدوره إلى إنشاء سلسلة أكثر تكاملاً من الوصول إلى ذاكرة التخزين المؤقت لنسيج ptex ، يقلل من عدد عمليات التخزين المؤقت المفقودة ، وبالتالي عدد عمليات الإدخال / الإخراج
2 . أكد فحص موجز تخميني: عندما تم إيقاف فرق الشعاع ، أصبحت سرعة ptex أسوأ بكثير.
لم تؤثر الزيادة في سرعة ptex على تكلفة الحوسبة وإدخال / إخراج فقط. في نظام 32-CPU ، تسارع pbrt-v3 14.9 مرة فقط بعد تحليل وصف المشهد. عادة ما يظهر pbrt بالقرب من المقياس المتوازي الخطي ، لذلك خاب أملي كثيرًا. نظرًا للعدد الأصغر بكثير من التعارضات أثناء الأقفال في ptex ، كان إصدار pbrt-next أسرع 29.2 مرة في نظام يحتوي على 32 وحدة معالجة مركزية وأسرع 94.9 مرة في نظام يحتوي على 96 وحدة معالجة مركزية - لقد عدنا إلى المؤشرات التي تناسبنا.
الجذور من مشهد جزيرة موانا التي قدمها pbrt بدقة 2048x858 عند 256 عينة لكل بكسل. إجمالي وقت العرض على مثيل Google Compute Engine مع 96 وحدة معالجة مركزية افتراضية بتردد 2 غيغاهرتز في pbrt-next هو 41 دقيقة و 22 ثانية. كان التسارع بسبب mulithreading أثناء التقديم 94.9 مرة. (أنا لا أفهم تمامًا ما يحدث في رسم خريطة التضاريس.)اعمل من أجل المستقبل
يعد تقليل حجم الذاكرة المستخدمة في مثل هذه المشاهد المعقدة تجربة مثيرة: حفظ بضعة غيغابايت بتغير صغير هو أكثر متعة من عشرات الميغابايت المحفوظة في مشهد أبسط. لدي قائمة جيدة لما آمل أن أتعلمه في المستقبل ، إذا سمح الوقت بذلك. هنا لمحة سريعة.
مزيد من تقليل ذاكرة التخزين المؤقت للمثلث
حتى مع الاستخدام المتكرر للمخازن المؤقتة التي تخزن نفس القيم للعديد من شبكات المثلث ، فإن الكثير من الذاكرة لا تزال تستخدم تحت المخازن المؤقتة للمثلث. فيما يلي تصنيف لاستخدام الذاكرة لأنواع مختلفة من المخازن المؤقتة للمثلث في المشهد:
اكتب | الذاكرة |
---|
البنود | 2.5 جيجابايت |
عادي | 2.5 جيجابايت |
الأشعة فوق البنفسجية | 98 ميجابايت |
المؤشرات | 252 ميجابايت |
أفهم أنه لا يمكن فعل أي شيء مع مواقع قمة الرأس المرسلة ، ولكن بالنسبة للبيانات الأخرى ، هناك وفورات. هناك العديد
من أنواع تمثيلات المتجهات العادية في شكل موفر للذاكرة يوفر مقايضات مختلفة بين حجم الذاكرة / عدد الحسابات. سيؤدي استخدام أحد تمثيلات 24 بت أو 32 بت إلى تقليل المساحة التي تشغلها المعايير العادية إلى 663 ميجابايت و 864 ميجابايت ، مما سيوفر لنا أكثر من 1.5 جيجابايت من ذاكرة الوصول العشوائي.
في هذا المشهد ، كمية الذاكرة المستخدمة لتخزين إحداثيات النسيج والمخازن المؤقتة للفهرس صغيرة بشكل مدهش. أفترض أن هذا حدث بسبب وجود العديد من النباتات المولدة إجرائيًا في المشهد ولأن جميع الاختلافات من نفس نوع النبات لها نفس الهيكل (وبالتالي المخزن المؤقت للفهرس) مع المعلمات (وبالتالي إحداثيات الأشعة فوق البنفسجية). في المقابل ، فإن إعادة استخدام المخازن المؤقتة المطابقة فعال للغاية.
بالنسبة للمشاهد الأخرى ، قد يكون أخذ عينات إحداثيات الأشعة فوق البنفسجية 16 بت من الأنسجة أو استخدام قيم تعويم نصف دقيقة ، اعتمادًا على نطاق قيمها ، مناسبًا تمامًا. يبدو أنه في هذا المشهد ، تكون جميع قيم إحداثيات النسيج صفرية أو واحدة ، مما يعني أنه يمكن تمثيلها
بتة واحدة - أي أنه من الممكن تقليل الذاكرة المشغولة بمقدار 32 مرة. ربما نشأت هذه الحالة بسبب استخدام تنسيق ptex في التركيب ، مما يلغي الحاجة إلى الأطالس فوق البنفسجية. بالنظر إلى الكمية الصغيرة التي تشغلها حاليًا إحداثيات النسيج ، فإن تنفيذ هذا التحسين ليس ضروريًا بشكل خاص.
يستخدم pbrt دائمًا أعدادًا صحيحة 32 بت للمخازن المؤقتة للفهرس. بالنسبة للشبكات الصغيرة التي يقل حجمها عن 256 قمة ، يكفي 8 بت فقط لكل فهرس ، ويمكن استخدام 16 بت للشبكات التي تقل عن 65،536 رأس. لن يكون تغيير pbrt لتكييفه مع هذا التنسيق أمرًا صعبًا للغاية. إذا أردنا التحسين إلى أقصى حد ، فيمكننا تحديد عدد البتات بالضبط حسب الحاجة لتمثيل النطاق المطلوب في المؤشرات ، في حين أن السعر سيكون لزيادة تعقيد العثور على قيمها. على الرغم من حقيقة أنه يتم الآن استخدام ربع غيغابايت فقط من الذاكرة لمؤشرات القمة ، فإن هذه المهمة لا تبدو مثيرة للاهتمام للغاية مقارنة بالمهام الأخرى.
ذروة BVH بناء استخدام الذاكرة
في وقت سابق ، لم نناقش تفاصيل أخرى حول استخدام الذاكرة: قبل العرض مباشرة ، تحدث ذروة قصيرة المدى تبلغ 10 جيجابايت من الذاكرة المستخدمة بشكل إضافي. يحدث هذا عندما يتم بناء BVH (الكبير) للمشهد بأكمله. تم كتابة رمز بناء BVH لجهاز تقديم pbrt ليتم تنفيذه على مرحلتين: أولاً ، يقوم بإنشاء BVH مع
التمثيل التقليدي : مؤشرين فرعيين لكل عقدة. بعد إنشاء الشجرة ، يتم تحويلها إلى
مخطط فعال للذاكرة حيث يقع الطفل الأول للعقدة خلفه مباشرة في الذاكرة ، ويتم تخزين الإزاحة إلى الطفل الثاني كعدد صحيح.
كان هذا الفصل ضروريًا من وجهة نظر الطلاب في التدريس - كان من الأسهل بكثير فهم خوارزميات بناء BVH دون الفوضى المرتبطة بالحاجة إلى تحويل الشجرة إلى شكل مضغوط أثناء عملية البناء. ومع ذلك ، تكون النتيجة هذه الذروة في استخدام الذاكرة؛ مع الأخذ في الاعتبار تأثيرها على المشهد ، يبدو القضاء على هذه المشكلة جذابة.
تحويل المؤشرات إلى أعداد صحيحة
في هياكل البيانات المختلفة ، هناك العديد من مؤشرات 64 بت التي يمكن تمثيلها كأعداد صحيحة 32 بت. على سبيل المثال ، يحتوي كل
SimplePrimitive
على مؤشر إلى
Material
. معظم حالات
Material
شائعة للعديد من البدائيين في المشهد ولا يوجد أكثر من بضعة آلاف ؛ لذلك ، يمكننا تخزين ناقل
vector
عالمي واحد لجميع المواد:
std::vector<Material *> allMaterials;
وقم فقط بتخزين إزاحة الأعداد الصحيحة 32 بت لهذا المتجه في
SimplePrimitive
، مما يوفر لنا 4 بايت. يمكن استخدام نفس الحيلة مع مؤشر
TriangleMesh
في كل
Triangle
، وكذلك في العديد من الأماكن الأخرى.
بعد هذا التغيير ، سيكون هناك تكرار طفيف في الوصول إلى العلامات نفسها ، وسيصبح النظام أقل قابلية للفهم للطلاب الذين يحاولون فهم عمله ؛ إلى جانب ذلك ، ربما يكون هذا هو الحال عندما يكون من الأفضل ، في سياق pbrt ، الحفاظ على التنفيذ أكثر قابلية للفهم ، وإن كان ذلك على حساب التحسين غير الكامل لاستخدام الذاكرة.
الإقامة على أساس الساحات (المناطق)
لكل
Triangle
فردي وبدائي ، يتم إجراء مكالمة منفصلة إلى
new
(في الواقع
make_unique
، ولكن هذا هو نفسه). تؤدي تخصيصات الذاكرة هذه إلى استخدام محاسبة موارد إضافية ، تحتل حوالي خمسة غيغابايت من الذاكرة ، غير محسوبة في الإحصاءات. نظرًا لأن عمر جميع هذه المواضع هو نفسه - حتى اكتمال العرض - يمكننا التخلص من هذا الحساب الإضافي عن طريق تحديدها من
ساحة الذاكرة .
كاكي vtable
فكرتي الأخيرة رهيبة ، وأعتذر عنها ، لكنها أثارت اهتمامي.
يحتوي كل مثلث في المشهد على حمولة إضافية لا تقل عن مؤشرين vtable: أحدهما
Triangle
والآخر لـ
SimplePrimitive
. هذا 16 بايت. يحتوي مشهد جزيرة
Moana على ما مجموعه 146162124 مثلثًا فريدًا ، مما يضيف ما يقرب من 2.2 غيغابايت من مؤشرات vtable الزائدة.
ماذا لو لم يكن لدينا فئة أساسية مجردة للشكل ولم يرث كل تطبيق هندسي أي شيء؟ هذا سيوفر لنا مساحة على مؤشرات vtable ، ولكن ، بالطبع ، عند تمرير المؤشر إلى الهندسة ، لن نعرف أي نوع من الهندسة ، أي أنه سيكون عديم الفائدة.
اتضح أنه في وحدات المعالجة المركزية الحديثة x86
، يتم استخدام 48 بت فقط من مؤشرات 64 بت . لذلك ، هناك 16 بت إضافية يمكننا اقتراضها لتخزين بعض المعلومات ... على سبيل المثال ، مثل الهندسة التي نشير إليها. في المقابل ، بإضافة القليل من العمل ، يمكننا العودة إلى إمكانية إنشاء تناظرية للمكالمات للوظائف الافتراضية.
إليك كيفية حدوث ذلك: أولاً نحدد بنية
ShapeMethods
التي تحتوي على مؤشرات للوظائف ، مثل ذلك
3 :
struct ShapeMethods { Bounds3f (*WorldBound)(void *);
سيقوم كل تطبيق هندسي بتطبيق دالة القيد ، ووظيفة التقاطع ، وما إلى ذلك ، وتلقي تناظري
this
المؤشر كوسيطة أولى:
Bounds3f TriangleWorldBound(void *t) {
سيكون لدينا جدول عالمي لهياكل
ShapeMethods
حيث
يكون العنصر
n لنوع هندسي مع فهرس
n :
ShapeMethods shapeMethods[] = { { TriangleWorldBound, }, { CurveWorldBound, };
عند إنشاء الشكل الهندسي ، نقوم بترميز نوعه في بعض البتات غير المستخدمة من مؤشر الإرجاع. بعد ذلك ، مع الأخذ في الاعتبار المؤشر إلى الهندسة التي نريد إجراء
shapeMethods
المحددة ، سنقوم باستخراج فهرس النوع هذا من المؤشر واستخدامه كمؤشر في
shapeMethods
للعثور على مؤشر الوظيفة المقابلة. بشكل أساسي ، سنقوم بتطبيق vtable يدويًا ، ومعالجة إرسال أنفسنا. إذا فعلنا ذلك لكل من الهندسة والأوليات ، فسنوفر 16 بايت لكل
Triangle
، لكننا في نفس الوقت قمنا بصعوبة.
أفترض أن مثل هذا الاختراق لتطبيق إدارة الوظائف الافتراضية ليس جديدًا ، ولكن لم أتمكن من العثور على روابط إليه على الإنترنت. فيما يلي صفحة ويكيبيديا حول
المؤشرات التي تم وضع علامة عليها ، لكنها تبحث في أشياء مثل عدد الروابط. إذا كنت تعرف رابط أفضل ، أرسل لي رسالة.
من خلال مشاركة هذا الاختراق المحرج ، يمكنني إنهاء سلسلة المنشورات. مرة أخرى ، شكرا جزيلا لديزني لنشر هذا المشهد. كان العمل معه أمرًا مثيرًا للدهشة ؛ تستمر التروس في رأسي في الدوران.
ملاحظات
- في النهاية ، يتتبع pbrt-next المزيد من الأشعة في هذا المشهد أكثر من pbrt-v3 ، وهو ما يفسر على الأرجح الزيادة في عدد عمليات البحث.
- يتم حساب الاختلافات الشعاعية للأشعة غير المباشرة في pbrt-next باستخدام نفس الاختراق المستخدم في امتداد ذاكرة التخزين المؤقت للنسيج لـ pbrt-v3. , , .
- Rayshade . , C . Rayshade .