ในบทที่ผ่านๆมาเราพูดถึงแต่การสร้างวัตถุรูปร่างต่างๆรวมถึงการแต่งพื้นผิวแต่งสีสันไปแล้ว
แต่ยังมีอีกองค์ประกอบหนึ่งที่สำคัญสำหรับการทำงานกับภาพสามมิติ นั่นคือเรื่องของการเล่นแสงสีและเงา
โดยทั่วไปแล้วเวลาที่เริ่มฉากขึ้นมาใหม่ฉากจะประกอบไปด้วยแสงไฟตั้งต้นซึ่งจะ เปลี่ยนทิศทางไปตามมุมมอง สะดวกเวลาที่สนใจแค่การออกแบบวัตถุโดยที่ไม่สนเรื่องแสงเงา
แต่พอต้องการจะมายุ่งเรื่องแสงไฟก็จะต้องเปลี่ยนโหมดการแสดงผลทางหน้าจอ ให้กดที่ปุ่มตามในภาพนี้เพื่อให้แสดงผลแสงตามจริง
หรือไม่ก็แค่กดเลข 7 บนคีย์บอร์ดเท่านั้นก็ได้
หรืออาจทำได้ด้วยการใช้โค้ดไพธอน โดยพิมพ์
mc.modelEditor(mc.getPanel(wf=1),e=1,dl='all')
เวลาที่ยุ่งกับเรื่องของแสงและเงานั้น ภาพที่แสดงผลในหน้าจอควบคุมกับเวลาที่เรนเดอร์สร้างภาพออกมาอาจมีผลต่างกัน พอสมควร ในที่นี้จะยังไม่พูดถึงภาพเวลาเรนเดอร์ แต่พูดถึงแค่ภาพรวมที่ปรากฏในหน้าจอควบคุม
การสร้างแสงไฟขึ้นมานั้นอาจทำได้โดยสร้างโหนดที่อยู่ในหมวดหมู่แสงไฟขึ้นมาโดยใช้ฟังก์ชัน mc.shadingNode() แล้วใส่แฟล็ก al
การใส่แสงไฟทำได้โดยใช้ฟังก์ชันสำหรับสร้างไฟชนิดต่างๆ แสงไฟในมายามีอยู่ ๖ ชนิดด้วยกัน
แอมเบียนต์ไลต์ (アンビエントライト) แสงที่ส่องตลอดทั่วทั้งฉาก
mc.shadingNode('ambientLight',al=1)
ดีเร็กชันนัลไลต์ (ディレクショナル ライト) แสงที่มีทิศทางแน่นอน
mc.shadingNode('directionalLight',al=1)
พอยนต์ไลต์ (ポイント ライト) แสงที่มีจุดกำเนิดจากจุดหนึ่งและส่องออกไปรอบทิศทาง
mc.shadingNode('pointLight',al=1)
สป็อตไลต์ (スポット ライト) แสงที่ออกจากจุดหนึ่งและส่องออกไปเฉพาะขอบเขตในทิศทางหนึ่ง
mc.shadingNode('spotLight',al=1)
แอเรียไลต์ (エリア ライト) แสงที่ออกมาจากพื้นที่หนึ่งแล้วกระจายออกไป
mc.shadingNode('areaLight',al=1)
วอลุมไลต์ (ボリューム ライト) แสงที่ถูกจำกัดอยู่ภายในอาณาเขตปริมาตรหนึ่ง
mc.shadingNode('volumeLight',al=1)
ขอเริ่มอธิบายจากใช้ดีเร็กชันนัลไลต์ ซึ่งเป็นแสงที่เข้าใจง่ายที่สุด ส่วนแสงแบบอื่นจะว่ากันในบทถัดไป
แสงชนิดนี้เป็นแสงที่ส่องมาโดยมีทิศทางที่แน่นอน ตกกระทบลงบนทุกจุดเหมือนกันหมดโดยไม่ขึ้นกับตำแหน่ง ถ้าให้เทียบแล้วก็เหมือนกับดวงอาทิตย์ที่ส่องมาบนพื้นโลก เราจะรู้สึกว่ามันเป็นแสงขนานที่ไม่ว่าจะเคลื่อนไปไหนก็อยู่ทิศเดิม
การสร้างแสงแบบดีเร็กชันนัลไลต์นั้นนอกจากจะใช้ฟังก์ชัน shadingNode() แล้วก็ยังมีอีกฟังก์ชันหนึ่งที่ใช้เพื่อสร้างไฟชนิดนี้โดยเฉพาะ นั่นคือ directionalLight()
ผลที่ได้จะไม่ต่างกันมาก เพียงแต่ถ้าใช้ directionalLight() จะเป็นการสร้างตัววัตถุที่ใช้เป็นแหล่งกำเนิดแสง สามารถตั้งชื่อให้กับวัตถุนั้นได้ทันทีตอนสร้าง
ตัวอย่าง เริ่มแรกลองใส่แสงลงไปโดยตั้งชื่อให้ง่ายๆว่า "แสงไฟ"
mc.directionalLight(n='saengfai')
ตอนนี้จะยังไม่เห็นอะไร นั่นเป็นเพราะยังไม่มีวัตถุให้แสงมากระทบ แต่ลองขยายเข้าไปจะกลุ่มลูกศรเล็กๆชี้ไปทางเดียวกัน นี่คือวัตถุที่เป็นตัวแทนของแหล่งกำเนิดแสง มีไว้บ่งบอกว่าเราได้ใส่แสงลงไปตรงนี้ แต่เวลาเรนเดอร์จะมองไม่เห็น
หากเข้ามาที่แอตทริบิวต์อีดิเตอร์จะเห็นโหนดชื่อ saengfai ซึ่งเป็นโหนดหลัก กับอีกโหนดซึ่งเป็นโหนดกำหนดโครงสร้างคุณสมบัติของแสง ชื่อโหนดจะเป็นชื่อตามที่เราตั้งตามด้วยคำว่า Shape ดังนั้นโหนดนี้จึงชื่อว่า saengfaiShape
ในขณะที่ถ้าใช้ shadingNode() สร้างชื่อที่ใส่ลงไปจะกลายเป็นชื่อของโหนดรูปร่างแสง เช่น
mc.shadingNode('directionalLight',al=1,n='saengfai')
จะได้โหนดชื่อ directionalLight1 กับ saengfai
เพื่อให้เห็นภาพชัดขอยกตัวอย่างโดยจำลองสร้างภูมิประเทศที่มีลักษณะเป็นแท่งเสา ตะปุ่มตะป่ำคล้ายจางเจียเจี้ย (张家界) ที่มณฑลหูหนาน ประเทศจีน (ลบไฟอันเก่าออกก่อน)
import random
mc.polyPlane(w=1000,h=1000,sx=1,sy=1) # สร้างพื้นฉากหลัง
mc.polyPlane(w=200,h=200,sx=20,sy=20) # สร้างพื้นที่ราบสูง
for i in range(mc.polyEvaluate(v=1)):
if((i+1)%21>1 and i>20 and i <420):
mc.move(random.gauss(100,20)%100,'.vtx[%d]'%i,y=1) # ปรับความสูงจุดบนพื้นผิวแบบสุ่ม
mc.directionalLight(n='saengfai') # สร้างแสงไฟ
ตอนนี้จะเห็นว่าแสงส่องมาจากฝั่งเดียว
ลองปรับมุมของแสงดู
mc.setAttr('saengfai.rx',-30)
mc.setAttr('saengfai.ry',30)
จะเห็นแสงเปลี่ยนแปลงไป
สำหรับค่าองค์ประกอบที่ปรับได้ภายในโหนดหลักนี้มีแค่มุมเท่านั้นที่มีผล เพราะเป็นตัวบอกทิศทางของแสง ถ้าปรับตำแหน่งหรือมาตราส่วนจะพบว่าไม่มีความเปลี่ยนแปลงใดๆเพราะแสงชนิดนี้ ขึ้นกับทิศทางอย่างเดียว เพียงแต่หากย้ายตำแหน่งไปในที่ที่สะดวกหรือขยายรูปวัตถุให้ใหญ่ก็จะทำให้มอง เห็นสะดวกขึ้น แต่ก็จะไม่มีผลกับตอนเรนเดอร์
สำหรับการปรับรายละเอียดอื่นๆของแสงนั้นจะต้องไปปรับที่ตัวโหนด saengfaiShape
เช่น ความสว่างของแสง สามารถปรับได้ด้วยการปรับค่า in (intensity) โดยเริ่มแรกจะมีค่าเป็น 1 หากปรับให้สูงขึ้นก็จะสว่างขึ้น เช่น
mc.setAttr('saengfaiShape.in',2.5)
ทำแบบนี้แล้วจะพบว่าแสงสว่างจ้าขึ้นมามาก
ตอนนี้เห็นส่วนมืดส่วนสว่างแตกต่างกันภายในวัตถุแล้ว แต่ก็จะยังรู้สึกว่าไม่สมจริง ขาดอะไรไปบางอย่าง นั่นก็คือเงานั่นเอง ปกติแล้วเวลาที่มีแสงก็จะต้องมีเงา เพราะเป็นของคู่กัน
ก่อนอื่นต้องทำความเข้าใจก่อนว่าในโปรแกรมมายามีเงาอยู่ ๒ ชนิดคือ
-
เด็ปธ์แม็ปชาโดว (深度マップ シャドウ, depth map shadow) -
เรย์เทรซชาโดว (レイ トレース シャドウ, ray trace shadow) ๒ แบบนี้ไม่ได้ต่างกันมากมายนัก แต่เรย์เทรซชาโดวจะให้เงาที่ดูเป็นธรรมชาติมากกว่า แต่ก็จะใช้เวลาในการประมวลผลภาพนานกว่าด้วย
หากจะใช้เทรย์เทรซชาโดวต้องเปิดใช้งานความสามารถที่เรียกว่าเรย์เทรซ (ray trace) ซึ่งเป็นเรื่องระดับสูงขึ้นมา ในที่นี้จะยังไม่พูดถึง จะอธิบายแค่เด็ปธ์แม็ปชาโดวก่อน
โดยปกติเมื่อสร้างดวงไฟขึ้นมาค่าตั้งต้นจะเปิดใช้เรย์เทรซชาโดวอยู่ เราสามารถเปลี่ยนเป็นเด็ปธ์แม็ปชาโดวได้โดยตั้งค่าองค์ประกอบของแสงซึ่งอยู่ในโหนดที่เก็บรูปร่างแสง
ให้ตั้งที่ค่าองค์ประกอบ useDepthMapShadows หรือชื่อย่อว่า dms ปกติตอนแรกจะมีค่าเป็น 0 คือไม่ทำงาน ถ้าจะให้ทำงานก็ตั้งค่าเป็น 1
mc.setAttr('saengfaiShape.dms',1)
พอพิมพ์ไปแล้วก็จะพบว่ายังไม่เห็นความเปลี่ยนแปลงในทันที นั่นเพราะต้องปรับโหมดให้แสดงผลเงาก่อน โดยปกติเริ่มมาโปรแกรมจะไม่ได้อยู่ในโหมดที่แสดงเงา ต้องทำให้อยู่ในโหมดที่แสดงเงาด้วยก่อน โดยสามารถทำได้โดยกดปุ่มตามในภาพนี้
หรือพิมพ์โค้ด
mc.modelEditor(mc.getPanel(wf=1),e=1,sdw=1)
แล้วจะเห็นเงาปรากฏขึ้น อย่างไรก็ตามจะเห็นว่าเงานั้นดูขาดเป็นห้วงๆเหมือนภาพแตก นั่นเพราะกำลังแยกภาพต่ำเกินไป ต้องไปปรับค่าองค์ประกอบกำลังแยกภาพซึ่งชื่อ dr (dmapResolution) ให้สูงขึ้น
ลองพิมพ์
mc.setAttr('saengfaiShape.dr',8192)
เท่านี้ก็จะเห็นเงาที่ดูเป็นธรรมชาติขึ้น แต่แน่นอนว่าเครื่องก็จะทำงานหนักขึ้นตามมา โดยเฉพาะเวลาเรนเดอร์
สีของเงาโดยปกติจะเป็นสีดำ แต่ก็สามารถปรับเปลี่ยนได้ หากต้องการ โดยแก้ที่ค่าองค์ประกอบ sc (shadowColor) ซึ่งเป็นค่าของแม่สีทั้ง ๓ หรือถ้าจะปรับแยกทีละสีก็ใช้ scr (scrshadColorR), scg (shadColorG), และ scb (shadColorB)
เช่น ลองปรับเงาเป็นสีเขียว ซึ่งก็คงจะดูแปลกดี
mc.setAttr('saengfaiShape.sc',0,1,0,typ='double3')
สีของแสงไฟเองก็สามารถปรับได้เช่นกัน ค่าองค์ประกอบของสีแสงคือ cl (color)
สีของแสงสามารถใช้เป็นภาพก็ได้ เช่นเดียวกับการใส่ภาพที่ตัววัสดุ ซึ่งการใส่ภาพให้แสงก็จะได้ภาพเหมือนการฉายสไลด์ เพียงแต่ว่าภาพจะไม่แสดงให้เห็นในหน้าจอควบคุมแต่จะแสดงตอนเรนเดอร์เท่านั้น
นอกจากนี้ยังปรับอะไรได้อีกหลายอย่าง ลองพิมพ์
mc.listAttr('saengfaiShape')
ก็จะเห็นค่าองค์ประกอบอีกมากมายหลายอย่าง สามารถลองปรับๆกันดูได้
สรุปค่าต่างๆของแสงที่ได้พูดถึงไป
ชื่อในรายการ |
ชื่อเต็ม |
ชื่อย่อ |
ความหมาย |
カラー |
color |
cl |
สีของแสง แบ่งย่อยเป็น cr cg cb |
強度 |
intensity |
in |
ความเข้มแสง |
深度マップシャドウの使用 |
useDepthMapShadows |
dms |
เปิดปิดเงาเด็ปธ์แม็ปชาโดว |
レイ トレース シャドウの使用 |
useRayTraceShadows |
urs |
เปิดปิดเงาแบบเรย์เทรซชาโดว |
シャドウ カラー |
shadowColor |
sc |
สีของเงา แบ่งย่อยเป็น scr scg scb |
解像度 |
dmapResolution |
dr |
กำลังแยกภาพของเงา |
และอีกมากมายที่ไม่ได้พูดถึง
ค่าสี, ความเข้มแสง, เรย์เทรซชาโดว และสีของเงา สามารถตั้งได้ตั้งแต่ตอนที่ใช้ฟังก์ชัน directionalLight() สร้างแสงขึ้นมา ชื่อย่อแฟล็กเป็น rgb, i, rs และ rc ตามลำดับ
ค่ามุมของแสง, ความเข้มแสง, สีของแสง, สีของเงา, ฯลฯ สามารถตั้งคีย์เฟรมให้เปลี่ยนไปตามเวลาได้
ลองสร้างตัวอย่างขึ้นเพื่อแสดงถึงการประยุกต์ใช้แสง โดยจะจำลองแสงอาทิตย์ที่ส่องผ่านหุบเขาที่หน้าตาเหมือนจางเจียเจี้ยตั้งแต่ เช้าถึงเย็น
หากสมมุติว่าที่นี่เป็นจางเจียเจี้ยจริงๆ ซึ่งจางเจียเจี้ยนั้นตั้งอยู่เส้นรุ้ง ๒๘ องศาเหนือ และสมมุติว่าเป็นวันครีษมายัน (วันที่ซีกโลกเหนือกลางวันยาวที่สุด ประมาณ 21 มิ.ย.) ดวงอาทิตย์จะอยู่ห่างจากระนาบศูนย์สูตรฟ้า ๒๓.๕ องศา ดังนั้นดวงอาทิตย์จะขึ้นไปได้สูงสุด ๙๐ - ๒๘ + ๒๓.๕ = ๘๕.๕ องศา
import random
mc.polyPlane(w=1000,h=1000,sx=1,sy=1)
# สร้างหุบเขา
mc.polyPlane(w=200,h=200,sx=20,sy=20,n='zhangjiajie')
for i in range(mc.polyEvaluate(v=1)):
if((i+1)%21>1 and i>20 and i <420):
mc.move(random.gauss(100,20)%100,'.vtx[%d]'%i,y=1)
mc.shadingNode('blinn',asShader=1,n='phiuhupkhao')
mc.shadingNode('ramp',at=1,n='khiao_namtan') # ทำไล่สี
mc.setAttr('khiao_namtan.cel[0].ec',0.54,0.43,0.36,typ='double3') # น้ำตาล
mc.setAttr('khiao_namtan.cel[0].ep',0.58)
mc.setAttr('khiao_namtan.cel[1].ec',0.03,0.12,0.03,typ='double3') # เขียว
mc.setAttr('khiao_namtan.cel[1].ep',0.82)
mc.connectAttr('khiao_namtan.oc','phiuhupkhao.c')
mc.select('zhangjiajie')
mc.hyperShade(a='phiuhupkhao')
mc.polyProjection('zhangjiajie.f[0:399]',md='x') # ตั้งแนวฉายให้ไล่สีตามแนวตั้ง
# สร้างแสงอาทิตย์
mc.directionalLight(n='saeng')
mc.setAttr('.rx',-23.5) # มุมเอียงของแกนโลก
mc.setAttr('.rz',28) # ละติจูดของจางเจียเจี้ย
mc.setAttr('saengShape.dms',1) # ใส่เงา
mc.setAttr('saengShape.dr',8192) # เพิ่มกำลังแยกภาพของเงา
mc.setKeyframe(at='.ry',v=0,t=0) # มุมของแสงตอนเช้า
mc.setKeyframe(at='.ry',v=180,t=50) # มุมของแสงตอนเย็น
# ปรับสีของแสงให้เปลี่ยนไปตามเวลา
mc.setKeyframe('saengShape',at='.cg',v=0.7,t=0)
mc.setKeyframe('saengShape',at='.cb',v=0.4,t=0)
mc.setKeyframe('saengShape',at='.cg',v=1,t=10)
mc.setKeyframe('saengShape',at='.cb',v=1,t=10)
mc.setKeyframe('saengShape',at='.cg',v=1,t=40)
mc.setKeyframe('saengShape',at='.cb',v=1,t=40)
mc.setKeyframe('saengShape',at='.cg',v=0.7,t=50)
mc.setKeyframe('saengShape',at='.cb',v=0.4,t=50)
ลองเล่นแสงกับดวงจันทร์ที่สร้างขึ้นจาก
บทที่แล้วดูบ้าง ลองใช้โค้ดบทที่แล้วสร้างดวงจันทร์ขึ้นมาใหม่แล้วใส่แสงเพิ่มไปตามนี้
mc.directionalLight(n='saeng_athit')
mc.expression(s='saeng_athit.ry = 90+time*180')
mc.setAttr('saeng_athit.dms',1)
อย่างที่รู้กันว่าดวงจันทร์ข้างขึ้นข้างแรมเกิดจากการที่แสงอาทิตย์ส่องในมุมที่ ต่างกัน ดังนั้นจึงเห็นเต็มดวงบ้างเป็นเสี้ยวบ้างดังที่เห็นในรูปนี้
เรื่องของแสงเป็นอะไรที่ซับซ้อน ทั้งยังมีเรื่องของความต่างระหว่างสิ่งที่เห็นในหน้าจอกับเวลาที่เรนเดอร์ด้วย ที่อธิบายไปในนี้เป็นเพียงแค่เบื้องต้น ที่เหลือต้องไปประยุกต์ต่อกันดู
อ้างอิง