ДР12/Т3 - Spherical triangles
Spherical triangles
В хода на лекция 12, първият вариант на pathtrace(), който написахме, работеше само с BRDF::spawnRay(). Всеки път се продължаваше на случаен принцип (след допитване на BRDF-а в каква посока да е продължението) и строежа на пътища приключваше едва когато ударим лампа.
Както помните, това е един изключително шумен подход, тъй като статистическата вероятност да ударим случайно лампа е малък. Затова добавихме частта, която изрично семплира лампите. При оценката за приноса на една лампа към осветлението проверявахме дали случайно избран семпъл от нея е препречен (за сенки), и ако не е, умножавахме резултата по нейната яркост. Единственият елемент от сметките, който кара светлините да затихват с увеличаване на разстоянието, е наличието на Light::solidAngle(x), който участва в смятането на chooseProbLight. Идеята на solidAngle в случая е да се оцени каква е площта на проекцията на лампата върху единичната полусфера над пресечната точка. Знаейки, че всички лъчи, изстреляни към лампата, ще пробождат тази проекция, то вероятностната плътност на тези лъчи е приблизително 1 / solidAngle - което е единият множител на chooseLightProb.
По-наблюдателните от вас сигурно забелязват, че дали ще делим на 1 / solidAngle, или ще умножаваме входната светлина по solidAngle е едно и също, а втората формулировка има повече интуитивен смисъл. Наистина, ако приемем, че осветлението от дадена лампа е lightColor * lightPower * solidAngle, то всичко става в хармония с известното ни от досегашните шейдъри. По-голяма по размер лампа ще е по-ярка (по-голяма проекция → по-голям solidAngle); по-далечна лампа ще е по-слаба (по-малка проекция, като при двукратно отдалечаване проекцията ще стане около 4 пъти по-малка, т.е. законът за обратна квадратичност работи as expected); и - по-голям power ще дава по-висок принос (линейна закономерност).
Първоначалният вариант на pathtrace пресмяташе вероятността, асоциирана със solidAngle чисто статистически: ако удари лампа, приносът е много ярък, но много малко от лъчите реално стигат до там, и усреднено резултатът е "нормален".
В резюме: Light::solidAngle() трябва да върне площта на проекцията на лампата върху единичната сфера около точката, която се шейдва (IntersectionInfo::ip).
Остава само да сметнем каква е площта на тази проекция при правоъгълната. Тази площ по дефиниция не може да надмине 2π. Сегашният код в RectLight::solidAngle() е много хакав - за отдалечени лампи дава верни резултати, но на близо се получават сериозни разминавания:
data/hw12/sphtri.hexray с 1000 paths/pixel
За сравнение, ако разкараме "Option B" пътя на pathtrace() изчисленията и разрешим отново директните попадения в лампа след дифузни отражения:
data/hw12/sphtri.hexray с първоначалния pathtrace() и 40k paths/pixel.
Тук монте-карло семплирането на лампата дава верен резултат, но с цената на 40 пъти по-голямо време за рендериране.
В tag homework12 съм добавил сцената от горните рендери - data/hw12/sphtri.hexray. Ползвайте нея. Направете в RectLight::beginFrame() да ви се смятат и записват четирите крайща на лампата, в световни координати, a, b, c, и d. След това модифицирайте solidAngle. За да сметнете площта на проекцията на лампата, проектирайте точките a, b, c, и d върху единична сфера около точката, която шейдваме (точката x). След което ползвайте формулата за лице на сферични многоъгълници от Total Compendium (28). За смятане на диедралните ъгли вижте в Wikipedia. Площта която сметнете (от 0 до 2π) е резултатът от solidAngle(). Рендерът трябва да прилича (на практика трябва да е идентичен) с втория пример тук.