Displacement Map
สร้างฉากใต้ทะเล โดยใช้ Displacement Map
Chamferbox
ขั้นตอนการสร้าง เตียง ผ้าคลุมเตียง หมอนใบนั้น
Compesite Map
ขั้นตอนการสร้างวัสดุ น้ำ Compesite Map

[PRMan Memo page 4: Basic of Subsurface Scattering]
Subsurface Scattering (SSS)
เป็นที่รู้จักกันมาพอสมควรในระยะหลัง เนื่องจากมีภาพยนต์หลายเรื่องนำเทคนิคนี้ไปใช ้เช่นทำ Shader ผิวหนังของกอลลั่มใน The Lord of the Rings หรือ ผิวคนในเรื่อง Finding Nemo เป็นต้น
มาทำความเข้าใจกันในเรื่องการสะท้อนและดูดซับแสงของวัสดุต่างๆกันนิดนึงครับ

ภาพ (a) เป็นภาพที่แสดงถึงการสะท้อนแสงแบบ BRDF (Bidirectional Reflectance Distribution Function) เช่น Shader แบบ Blin หรือ Phong ที่เราใช้กันอย ู่ก็เป็นการ ประมาณค่าสะท้อนแสงโดยใช้หลักการ BRDF หลักการนี้สมมติว่าแสงกระทบวัตถุแล้ว สะท้อนออกจากวัตถุจากจุดเดียวกัน ไม่มีการทะลุผ่านเข้ากระเจิงในวัตถุวัตถุที่มีลักษณะแบบนี้เป็นวัตถุทึบแสงเช่น โลหะ แต่วัตถุอย่างอื่นเช่น ผลไม ้ผิวหนัง เทียน ฯลฯ แสงจะทะลุเข้าไปภายในและเกิดการกระเจิงของแสงขึ้น

ภาพเห็ดด้านบนนี้ก็เป็นตัวอย่างได้ดีสังเกตมีการทะลุผ่านของแสงใต้ใบเห็ด
การที่จะทำให้เกิดภาพในลักษณะนี้ได้จะต้องใช้หารคำนวณแสงตามหลัก BSSRDF (Bidirectional Surface Scattering Distribution Function) จากภาพ (b) เราจะเห็นแสง บางส่วนทะลุเข้าไปในเนื้อของวัตถุและสะท้อนกลับออกมาไม่ตรงกับตำแหน่งตกกระทบ และการกระเจิงของแสงมีความไม่สมํ่าเสมอ ในการคำนวณ BSSRDF ให้ได้ตามหลักฟิสิกส ์มีตัวแปลที่เกี่ยวข้องจำนวนมากที่จำเป็นต้องหาจึงมีการประมาณค่าในหลายๆขั้นตอน เช่นในเอกสารของ Henrik Wann Jensen จากงาน SIGGRAPH 2001 ได้แบ่ง BSSRDF Model ออกเป็นสองส่วนคือ Diffusion Approximation และ Single Scattering

ด้านบนนี้เป็นตัวอย่างของการคำนวณ Single Scattering (first-order scattering) โดยคำนวณ แสงที่หักเหผ่านมาตัวกับแนวหักเหของแสงที่สะท้อนเข้ากล้อง คำนวณค่าแสงที่จุด Xi โดยลดค่าของแสงที่จุด Xi ตามระยะ S (โดยใช้ความสัมพันธ์กับค่าคุณสมบัติต่างๆของวัสดุ) สะสมค่าโดย เปลี่ยนตำแหน่งของ Xi ตามจุดตัดต่างๆบนแนวหักเหของแสงที่สะท้อน เข้ากล้อง (Monte Carlo Integration)

การคำนวณ Diffusion Scattering Eason และ Farrell et al ได้พัฒนาวิธีในการประมาณค่าขึ้นมา โดยสร้างแหล่งแสงเสมือนขึ้นมาสองอัน อันแรกแสงจริง วางอยู่ด้านล่างของผิว วัตถุและอีกอัน เป็นแสงแบบลบ วางด้านบนผิววัตถุและนำความสัมพันธ์ไปแทนค่า เพื่อประมาณค่าจากสมการ โดยเรียกวิธีนี้ว่า Dipole method
ผมจะลองให้ดูตัวอย่างการเขียน Shader เพื่อจำลองลักษณะ BSSRDF ขึ้นมาครับ โดยใช้ตัวอย่างจาก Document ของ RAT
surface
subsurf_bruteforce(float Ka = 1, Kd = 1, Ks = 1, roughness = 0.1;
float Ksub = 1, extcoeff = 1, samples = 16)
{
normal Nn = normalize(N);
vector Vn = -normalize(I);
uniform string raylabel;
rayinfo("label", raylabel);
if (raylabel == "subsurface") { /* subsurface ray from inside object */
/* Compute direct illumination (no specular here) */
Ci = (Ka*ambient() + Kd*diffuse(Nn)) * Cs;
} else { /* regular shading (from outside object) */
color raycolor = 0, sum = 0, irrad;
float dist = 0;
/* Compute direct illumination (ambient, diffuse, specular) */
Ci = (Ka*ambient() + Kd*diffuse(Nn)) * Cs
+ Ks*specular(Nn,Vn,roughness);
/* Add subsurface scattering */
if (Ksub > 0) {
gather("illuminance", P, -Nn, PI/2, samples,
"label", "subsurface",
"surface:Ci", raycolor, "ray:length", dist) {

sum += exp(-extcoeff*dist) * raycolor;
}
irrad = sum / samples;
Ci += Ksub * irrad * Cs;
}
}
Ci *= Os;
Oi = Os;
}

ตัวอย่างนี้เป็นการประมาณค่า BSSRDF แบบง่ายๆจากภาพจุด P คือจุดที่ทำการคำนวณสีอยู่ในขณะนั้น มีการยิงแนวแสงเข้าไป (สีส้ม) ภายในวัตถุหาจุดตัด (Pi) และคำนวนค่าแสง (Irradiance) ที่จุดนั้น แล้วนำมาลดค่าลงแบบ Exponential ตามระยะโดยมีค่า extcoeff (Extinction Coeffician) เป็นตัวควบคุม การประมาณค่าแบบนี้ไม่ได้นำคุณสมบัติของวัตถุหลายอย่างมาคิดดังนั้นการจำลองให้เหมือนวัสดุแบบเจาะจงจึงทำได้ยาก แต่ก็สามารถคำนวณแสงที่กระเจิงมาปรากฏด้านหลังวัตถุได้ในระดับหนึ่ง
gather("illuminance", P, -Nn, PI/2, samples, "label", "subsurface", "surface:Ci", raycolor, "ray:length", dist) { sum += exp(-extcoeff*dist) * raycolor;
}
ใน Shader ตัวนี้มีการเรียกใช gather loop ซึ่งมีหน้าที่ในการยิงแสงออกจากจุด P ในทิศทาง -Nn กระจากออกไป PI/2 เรเดียน และยิงออกไปจำนวน samples เนื่องจากเมื่อ แสงกระทบจะเรียกสีของ surface (Ci) จึงต้องมีการคำนวณสีของผิวนั้นอีกครั้ง (เรียก Shader ตัวนี้อีกครั้ง) ดังนั้นจึงจ้องมีการกำหนด label ให shader รู้ว่าเป็นการคำนวณในกรณีใด
จากนั้นทำการเขียน slim template และโหลดเข้าไปใน slim ครับ
## MTOR SLIM description file generated by Cutter version 3.2.9
## on 5.14.2004 11:34:57 PM. Cutter software by Malcolm Kesson
## (all rights reserved). This file is maintained by Cutter.
## Only edit a copy of this file.
slim 1 extensions pixardb {
extensions Cutter cutr {
# A template is an appearance that is used to generate functions and
# is fundamental to Slim's shader creation features. Templates are
# containers of properties.
template shadingmodel Subsurf_bruteforce {
# Parameters are used to collect all information associated
# with a single parameter of an appearance.
parameter float Ka {
description "no description"
label "Ka"
detail varying
default 1
subtype slider
range {0 1}
}
parameter float Kd {
description "no description"
label "Kd"
detail varying

default 1
subtype slider
range {0 1}
}
parameter float Ks {
description "no description"
label "Ks"
detail varying
default 1
subtype slider
range {0 1}
}
parameter float roughness {
description "no description"
label "roughness"
detail varying
default 0.1
subtype slider
range {0 1}
}
parameter float Ksub {
description "no description"
label "Ksub"
detail varying
default 1
subtype slider
range {-2.0 2.0}
}
parameter float extcoeff {
description "no description"
label "extcoeff"
detail varying
default 1
subtype slider
range {-2.0 2.0}
}
parameter float samples {
description "no description"
label "samples"
detail varying
default 16
subtype slider
range {-32.0 32.0}
}
# The collection context is valid within the appearance and
# collection contexts. Collections are containers for
# parameters and can have a custom UI. Collections are also
# used to represent higher order datatypes like shadingmodels
# and arrays.
collection shadingmodel shadingmodel {
access output
display hidden
# The parameters CI and OI are the outputs of the shading
# function. They are equivalent to the global variables
# Ci and Oi.
parameter color CI {
access output
}
parameter color OI {
access output
}
}
RSLFunction {
void
cutrSubsurf_bruteforce (
float Ka;
float Kd;
float Ks;
float roughness;
float Ksub;
float extcoeff;
uniform float samples;
output varying color CI;
output varying color OI;
)
{
extern color Cs;
extern color Os;
extern point P;
extern vector dPdu;
extern vector dPdv;
extern normal N;
extern normal Ng;
extern float u,v;
extern float du,dv;
extern float s,t;
extern vector L;
extern vector I;
extern point E;
color TEMP_OI;

color TEMP_CI;
normal Nn = normalize(N);
vector Vn = -normalize(I);
uniform string raylabel;
rayinfo("label", raylabel);
if (raylabel == "subsurface") { /* subsurface ray from inside object */
/* Compute direct illumination (no specular here) */
TEMP_CI = (Ka*ambient() + Kd*diffuse(Nn)) * Cs;
} else { /* regular shading (from outside object) */
color raycolor = 0, sum = 0, irrad;
float dist = 0;
TEMP_CI = 0;
/* Add subsurface scattering */
if (Ksub > 0) {
gather("illuminance", P, -Nn, PI/2, samples, "label", "subsurface", "surface:Ci", raycolor, "ray:length", dist) {
sum += exp(-extcoeff*dist) * raycolor;
}
irrad = sum / samples;
TEMP_CI += Ksub * irrad * Cs;
}
}
TEMP_CI *= Os;
TEMP_OI = Os;
OI = TEMP_OI;
CI = TEMP_CI;
}
}
}
}
}
ผมใช cutter เป็นตัว editor มีเครื่องมือช่วยในการสร้าง slim สะดวกดีพอสมควรครับ แต slim template ที่cutter สร้างขึ้นมาให้มักจะมีผิดบ้าง ต้องตรวจสอบกันเองครับ :)

หน้าตาของ cutter 3.2.9 เป็น Java App

ลองสร้างวัตถุขึ้นมาครับ แล้วจัดแสงง่ายๆโดยให้มีแสงที่สองเข้าด้านหลังด้วย แล้วก็จัดการหร้าง shader ธรรมดาแล้ว Attach เข้าไปครับ

ใส texture ลงไปเล็กน้อย แล้วก ็render แยก 2 ตัว ถ้าอยากลองแยกหลายๆตัวเช่น diffuse, specular, shadow etc. ก็ลองดูครับ

อันนี้ผมแยกแค่สองตัว วัตถุและ พื้น (รวมเงา) จากนั้นก็ลองใส Shader ที่เราสร้างขึ้นมาครับ

ผมโหลดเข้ามาสองตัวครับ เป็น .slo และ .slim

อันนี้เป็น .slo

เราใช้ตัวนี้ครับ .slim ตั้งค่า extcoeff น้อยหน่อย เพื่อให้เห็นชัดเจน และตั้ง samples เยอะหน่อยซัก 128 หรือ 256 เพื่อให้ได้คุณภาพดีแต่ถ้าอยากลอง draft ดูก่อนก็ตั้งน้อยๆ หน่อยครับ ซัก 16-64 ก็พอมองออก
ตั้งค่า Render Global ให Raytrace ด้วย และตั้งค่า Irradiance ซัก 0.1 หรือ 0.2 ให้ได้คุณภาพซักเล็กน้อย แล้วก็จัดการ render เลยครับ

ซ่อนพื้นไปก่อน Render

มีแสงด้านหลังดวงเดียว

มีแสงทั้งด้านหลังและด้านหน้า ทั้งสองภาพนี้ใช้เวลา render ภาพละประมาณ 30 นาทีนานพอดูลองเอามาประกอบกันใน Shake ครับ

แบบยังไม่เพิ่ม SSS

อันนี้เพิ่ม sss เข้าไปแล้ว
การ render เพื่อให้ได้ภาพแบบ SSS นั้นต้องการเทคนิคหลายอย่างเพื่อที่จะทำให้การ render เร็วและ ดีขึ้น วิธีการข้างต้นเป็นการ Raytrace แบบง่ายๆ ไม่มีการ Optimized จึงใช ้เวลาค่อยข้างนานในการ render เราสามารถลดเวลาได้หลายวิธีเช่นการสร้าง Irradiance cache หรือการสร้าง Shadow map buffer ใช้แทน และทำให shader คำนวณผลได ้แม่นยำขึ้น โดยใช้วิธีที่ใกล้เคียงหลักฟิสิกส์มากกว่านี้ผมจะพูดถึงวิธีการต่างๆเหล่านี้ในโอกาศต่อๆไปครับ

<<Back