[Depth Shadow Pass]
render เงา เป็นเรื่องที่มีความสำคัญกับงาน 3D แทบจะขาดกันไม่ได้เลยทีเดียว จึงได้มีการพัฒนาเทคนิก การสร้างเงาขึ้นมาหลายแบบ ส่วนตัวแล้วผมชอบใช Shadow Map หรือ Depth Map Shadow เป็นหลัก เนื่องจากเร็ว และสามารถปรับแต่งเล่น ได้หลายอย่าง
ครั้งนี้จะเขียนถึง sl สำหรับ render เงาอย่างเดียวครับ เนื่องจาก RAT ไม่มีShader ที่ทำงานคลาย Use Background ของ Maya มาให ้จึงต้องเขียนขึ้นมาใช้งาน เอง แต่ก็ไม่ยากเลยครับ
ลองมาดูcode ของ แสงแบบ spotlight ที่RAT มีมาให้กันดีกว่าครับ

ตัดมาบางส่วน ในกรอบแดงที่วงเอาไว้เป็นตัวแปลที่เราจะนำมาใช้งาน ตัวแปลชื่อ __inShadowC นี้จะเก็บค่าปริมาณเงาซึ่งถูกส่งมาจาก shader เงาอีกทีโดยค่า 0 คือไม่มีเงา ส่วนค่า 1 คือมีเงาทึบ
ตัวแปลตัวนี้มีการให้ค่าในแสงดังนั้น จึงจำเป็นต้องเรียกคำสั่ง lightsource เพื่อเรียกค่าที่เก็บเอาไว้มาใช้งาน และ lightsource ใช้งานได้เฉพาะใน illuminance loop เท่านั้น
slim 1 extensions pixardb {
extensions pixar pxsl {
template shadingmodel shdshade {
label "ShadowShader"
collection shadingmodel shadingmodel {
access output
display hidden
parameter color CI {
detail varying
default "0 .25 1"
access output
}
parameter color OI {
detail varying
default "1 1 1"
access output
}
}
RSLFunction {
void
pxslshdshade(
output color CI;
output color OI; )
{
extern point P;
color sfColor=0;
float numLight=0;
illuminance(P) {
numLight += 1;
color shdColor=1;
lightsource( "__inShadowC", shdColor );
sfColor += shdColor;
}
if (numLight>0) {
sfColor /= numLight;
}
CI = sfColor;
OI = sfColor;
}
}
}
}
}
code ด้านบนนี้เป็น sl ซึ่งเรียก illuminance loop นับจำนวนแสง เก็บเงา แล้วก็เฉลี่ยค่าเงา เรียก lightsource เพื่อขอค่า __inShadowC นำค่า __inShadowC ที่ได้มาเพิ่มค่า sfColor ขึ้นเรื่อยๆ เมื่อออกจาก Loop ก็นำ numLight ซึ่งเป็นจำนวนแสงที่นับได้มา หาร เป็นการ normalized ให้อยู่ในช่วง 0 ถึง 1
ทดลองนำมาใช้งานครับ


ฉากง่ายๆและแสงหนึ่งดวง ให้เงาแบบ Shadow Map ลงไปที่แสง

attach shader ลงไปที่พื้น หรือ object ที่ต้องการให้รับเงา และเอา primary visibility ของตัวที่ทำให้เกิดเงาออก ในที่นี้คือแก้วนํ้า

ลอง render

จะได้ภาพแบบนี้ออกมาครับ สามารถนำไป composite เพื่อสร้างเงาได้ง่ายๆ
เงาที่ได้กลับขาวเป็นดำเพื่อประโยชน์ในการ composite แต่ถ้าลอง กลับสีของเงาให้เป็นสีเดิม ควรจะได้ภาพคล้ายกับ lambert และมีเงาด้วย
slim 1 extensions pixardb {
extensions pixar pxsl {
template shadingmodel aocshade {
label "DepthBufferShader"
collection shadingmodel shadingmodel {
access output
display hidden
parameter color CI {
detail varying
default "0 .25 1"
access output
}
parameter color OI {
detail varying
default "1 1 1"
access output
}
}
RSLFunction {
void
pxslaocshade(
output color CI;
output color OI; )
{
extern point P;
color sfColor=0;
float numLight=0;
illuminance(P) {
color shdColor=0;
lightsource( "__inShadowC", shdColor );
sfColor += shdColor;
numLight += 1;
}
if (numLight>0) {
sfColor /= numLight;
}
CI = 1-sfColor;
OI = 1;
}
}
}
}
}
ลองกลับสีของเราด้วยการนำค่าที่ได้ไปลบออกจาก 1 ลอง render อีกครั้งโดยครั้งนี้ไม่ต้องเอา primary visibility ออก

ไม่เหมอน lambert ซะทีเดียว เนื่องจากไม่ได้นำการหันหน้าเข้าหาแสงมาคิดด้วย จาก shader ตรงนี้ถ้าสร้างแสงในลักษณะ รอบทิศ ขึ้นมา ควรจะได้ภาพที่คล้ายกับการคำนวณ Ambient Occlusion


ผมลองสร้างขึ้นมา 94 ดวง ใส shader ลงไปแล้วก ็render ครับ สำหรับ resolution ของเงา ตั้งไว้ตํ่าๆครับ เช่น 128 หรือ 256 เพื่อให้ประหยัดพื้นที่และลดเวลาใน การคำนวณ ตั้งค่า Blur เล็กน้อย เช่น 0.005

ภาพนี้ใช Depth Filter ของเงา เป็น min เอาที่ได้มืดมาก ลองเปลี่ยนเป็นแบบ midpoint ดูครับ

สว่างขึ้นแต่ก็แบนเกินไป เกิดจากไม่ได้คำนวณการหันหน้าเข้าแสงเลย ลองเปลี่ยน code ครับ
slim 1 extensions pixardb {
extensions pixar pxsl {
template shadingmodel aocshadedot {
label "DepthBufferShaderDot"
collection shadingmodel shadingmodel {
access output
display hidden
parameter color CI {
detail varying
default "0 .25 1"
access output
}
parameter color OI {
detail varying
default "1 1 1"
access output
}
}
RSLFunction {
void
pxslaocshadedot(
output color CI;
output color OI; )
{
extern point P;
normal Nn = faceforward( normalize(N), I );
color sfColor=0;
float numLight=0;
illuminance(P) {
vector Ln = normalize( L );
float NdotL = max((Nn . Ln), 0);
color shdColor=0;
lightsource( "__inShadowC", shdColor );
color aocColor = NdotL * (1 - shdColor);
sfColor += aocColor;
numLight += NdotL;
}
if (numLight>0) {
sfColor /= numLight;
}
CI = sfColor;
OI = 1;
}
}
}
}
}
นำค่า normal ของ surface (Nn) และค่าทิศทางของแสง (Ln) มาหา dot product แล้วในคูณกับค่าที่ได้จาก __inShadowC เพื่อกำหนดปริมาณเงาให้เปลี่ยนไป ตามการทำมุมกันของ Nn และ Ln ถ้า Nn และ Ln ไปทิศเดียวกันเงามีค่าสูงสุด และลดลงเรื่อยๆเมื่อ ทำมุมกันมากขึ้น คล้ายกับเป็นนํ้าหนัก (weight) ของเงานั่นเอง ดังนั้นจึงเพิ่มค่า numLight ทีละ 1 ไม่ได้แล้ว แต่ต้องเพิ่มครั้งละเท่ากับ Ln dot Nn

ได้ผลออกมาดูมีมิติมากขึ้นและสว่างมาขึ้น ลองมา render pass อื่นซัก 2 pass เพื่อมา composite ครับ


shader แบบ constant และใส texture ลงไปแล้ว render โดยลบแสงทิ้งทั้งหมด ได้เป็น flat color pass

shader แบบ matte แสง 1 ดวงพร้อมเงา ได้เป็น key lighting pass

ต่อใน shake โดยใช IMult และ IAdd จะได้ออกมาแบบนี้ครับ

การ render เงาแยก pass ออกมาเป็นสิ่งจำเป็นมากในการทำงาน เนื่องจากที่ให้เราสามารถควบคุมเงาได้อย่างอิสระ จึงควรทำให้เป็นนิสัยครับ ช่วงหลังเกี่ยวกับเรื่องการใช้งานเงากับแสงแบบ dome light บางคนอาจเรียกว่าการใช้งาน shadow buffer เพื่อสร้าง Ambient Occlusion Pass ก็ได้ครับการสร้าง
Ambient Occlusion Pass ด้วยเทกนิกนี้เหมาะกับงานทั่วไปที่ไม่ได้อยู่ภายในห้องครับ ใช้งานได้ดีทีเดียว และคำนวณเร็ว เมื่อเทียบกับ Ambient Occlusion Pass แบบ Raytraced เป็นอีกทางเลือกที่มักจะเป็นทางเลือกแรกก่อนจะไป Raytraced การ render เงาในแบบต่างเป็นสิ่งที่ควรศึกษาครับ เนื่องจากเป็นส่วนประกอบสำคัญที่จะทำให้งานมีคุณภาพมากขึ้น มีคำแนะนำ mail ตาม address ด้านล่างได้เลยครับ ยินดีรับฟังทุกความเห็นเพื่อการปรับปรุงครับ
<<Back
|