ในบทที่แล้วได้ แนะนำสิ่งที่เรียกว่า NURBS (Non-Uniform Rational Basis Spline) ไปแล้ว โดยได้พูดถึงเส้นโค้ง NURBS ไปก่อน ส่วนในบทนี้จะพูดถึงพื้นผิว NURBS

คำสั่งสร้างพื้นผิว NURBS นั้นมีอยู่หลากหลายเช่นเดียวกับการสร้างโพลิกอน สามารถเริ่มสร้างจากรูปทรงต่างๆได้หลายชนิด

ในจำนวนนั้นที่ดูเป็นพื้นฐานที่สุดก็คือระนาบสี่เหลี่ยม ซึ่งสร้างได้ง่ายโดยฟังก์ชัน nurbsPlane()

ลองสร้างขึ้นมาดูเลย
mc.nurbsPlane(w=10,lr=0.8,n='np01')



จะเห็นว่าคล้ายกับระนาบโพลิกอน สามารถกำหนดขนาดด้านได้ด้วยแฟล็ก เพียงแต่ว่าต่างกันที่ว่ากำหนดแค่ด้านแนวนอนด้วยแฟล็ก w (width) ส่วนอีกด้านใช้แฟล็ก lr (lengthRatio) ซึ่งคือค่าสัดส่วนระหว่างด้านแนวตั้งกับแนวนอน ในที่นี้เป็น 0.8 จึงสูง 0.8*10 = 8

โครงสร้างผิว NURBS นั้นประกอบขึ้นจากเส้น NURBS หลายเส้นมาสานกันเหมือนการนำเส้นใยมาทอเป็นผ้า ดังนั้นรูปร่างของผิว NURBS จะถูกควบคุมโดยเส้นต่างๆที่มาสานทอ

อนึ่ง ในทางกราฟิกแล้ว อักษร u กับ v มักถูกใช้แทนพิกัดของพื้นผิวสองมิติ โดย u เป็นแนวนอน v เป็นแนวตั้ง เรียกว่าเป็นพิกัด uv (ซึ่งไม่มีความเกี่ยวข้องกับรังสี uv แต่อย่างใด)

การเข้าถึงจุด cv ของผิว NURBS ทำได้โดยพิมพ์ชื่อวัตถุแล้วตามด้วย .cv[u][v] โดย u และ v ในที่นี้คือพิกัดในแนวตั้งและแนวนอน พิกัดแบ่งเป็นสองมิติชัดเจนผิดกับของโพลิกอนที่นับเรียงไปเรื่อยๆ

หากใส่เป็นดอกจันทน์ * ไปจะเป็นการเลือกทั้งหมด

ลอง
mc.select('np01.cv[*][*]')



ก็จะเห็นจุด cv ทั้งหมดของผิวนี้ ซึ่งมีทั้งหมดแนวตั้งและแนวนอนอย่างละ ๔ จุด รวมเป็น ๑๖ จุด โดยไล่จาก 0 ไปถึง 3 ในแต่ละแนว

จำนวนจุด cv จะขึ้นอยู่กับดีกรีบวกกับจำนวนช่วงแบ่ง โดยแต่ละแนวคิดแยกกัน ค่าตั้งต้นนั้นดีกรีเป็น 3 และช่วงแบ่งเป็น 1 คือไม่มีการแบ่งส่วนย่อย ดังนั้นจำนวนจุด cv จึงเป็น 3+1=4

ดีกรีสามารถกำหนดได้โดยแฟล็ก d (degree) ถ้าไม่ใส่ก็จะเป็น 3 ซึ่งเป็นค่าที่กำลังดีอยู่แล้วจึงมักไม่จำเป็นต้องปรับ

ส่วนจำนวนช่วงแบ่งกำหนดโดยแฟล็ก u (patchesU) ในแนวนอน และ v (patchesV) ในแนวตั้ง

ลองกำหนดแฟล็ก u กับ v ดู
mc.nurbsPlane(w=16,lr=1,u=4,v=4,n='np02')
mc.select('np02.cv[*][*]')



จะเห็นว่าผิวถูกแบ่งออกเป็น 4x4=16 ส่วน และมี cv 7x7=49 จุด

จุด cv เราสามารถเลื่อนเพื่อปรับรูปร่างของผิวได้ตามต้องการ เช่นลองดึงจุดตรงกลางออกไปดู
mc.move(12,0,0,'np02.cv[3][3]')



จะเห็นว่าต่างจากโพลิกอนตรงที่ผิวที่ถูกดึงออกมานั้นจะมีลักษณะโค้ง ไม่ได้เป็นเหลี่ยม ทำให้ผิว NURBS เหมาะกับการทำงานกับวัตถุที่เป็นเส้นโค้งมากกว่า

นอกจากนี้ยังมี แฟล็ก ax (axis) สำหรับกำหนดทิศว่าผิวจะหันไปทางไหน โดยถ้าไม่กำหนดผิวจะหันโดยมีแนว u เป็นแนวนอนตามระนาบ z และผิว v เป็นแนวตั้งตามแกน y

สรุปแฟล็กของระนาบ NURBS ที่พูดถึงไป
w(width)ความกว้างค่าตั้งต้นคือ 1
lr(lengthRatio)สัดส่วนระหว่างด้าน แนวตั้งต่อแนวนอนค่าตั้งต้นคือ 1
d(degree)ดีกรีค่าตั้งต้นคือ 3
u(patchesU)จำนวนส่วนแบ่งแนวนอนค่าตั้งต้นคือ 1
v(patchesV)จำนวนส่วนแบ่งแนวตั้งค่าตั้งต้นคือ 1
ax(axis)แกนหันค่าตั้งต้นคือ 1,0,0

นอกจากนี้ยังมีคำสั่งสร้างผิว NURBS อีกหลายชนิด คล้ายโพลิกอน
nurbsCube() ทรงสี่เหลี่ยม
cylinder() ทรงกระบอก
sphere() ทรงกลม
cone() ทรงกรวย
torus() ทรงโดนัท

เพียงแต่ว่าจะต่างจากโพลิกอนพอสมควร

เช่น ลองสร้างทรงสี่เหลี่ยม จะเห็นว่าได้แผ่นสี่เหลี่ยมออกมา ๖ ชิ้น วัตถุที่ได้ไม่ได้เป็นชิ้นเดียวต่อกัน
mc.nurbsCube(w=10,lr=1.2,hr=1.5,n='songsiliam')



นั่นเป็นเพราะว่า NURBS นั้นมักใช้สร้างผิวโค้ง ถ้าเจอวัตถุที่มีการหักมุมแบบนี้ใช้แยกเป็นคนละผิวไปเลยจะง่ายกว่า

สำหรับทรงสี่เหลี่ยม
w(width)ความยาวด้านกว้างค่าตั้งต้นคือ 1
lr(lengthRatio)สัดส่วนด้านยาวต่อด้านกว้างค่าตั้งต้นคือ 1
hr(heightRatio)สัดส่วนด้านสูงต่อด้านกว้างค่าตั้งต้นคือ 1
ส่วน u v ax จะมีเหมือนกับของระนาบ

ส่วนทรงกระบอกนั้นจะมีคุณสมบัติบางอย่างที่น่าสนใจคือสามารถทำทรงที่ไม่ได้ห่อเต็มวงได้ ลอง
mc.cylinder(r=5,hr=3,nsp=10,s=5,ssw=0,esw=300)



แฟล็ก ssw (startSweep) และ esw (endSweep) นี้คือมุมกวาดเริ่มต้นและมุมกวาดปลาย โดยปกติถ้าเริ่มที่ 0 และจบที่ 360 ก็จะเป็นวงกลมพอดี แต่สามารถทำให้แหว่งได้ด้วยการกำหนดมุมกวาด
r(radius)รัศมีค่าตั้งต้นคือ 1
hr(heightRatio)สัดส่วนความสูงต่อรัศมีค่าตั้งต้นคือ 2
nsp(spans)จำนวนส่วนแบ่งแนว u (ตามความยาว)ค่าตั้งต้นคือ 1
s(sections)จำนวนส่วนแบ่งแนว v (วนรอบวง)ค่าตั้งต้นคือ 8
ssw(startSweep)มุมกวาดเริ่มต้นค่าตั้งต้นคือ 0
esw(endSweep)มุมกวาดปลายค่าตั้งต้นคือ 360 (2π)

ทรงกลมก็สามารถทำเป็นรูปทรงแหว่งได้เช่นกัน แบบนี้จึงสามารถสร้างแพ็กแมนขึ้นได้ง่ายๆ
mc.sphere(r=5,ssw=15,esw=345,n='pacman')



หากลองมาดูรายละเอียดโครงสร้างของแพ็กแมนตัวนี้ก็จะเห็นว่าแม้จะดูกลมๆแบบนี้ แต่ก็เกิดมาจากการที่เอาแผ่นสี่เหลี่ยมมาดัดให้โค้งแบบนี้นั่นเอง โครงสร้างของผิว NURBS มีพื้นฐานมาจากสี่เหลี่ยม ซึ่งต่างจากโพลิกอนที่อาจเป็นสามเหลี่ยมหรือสี่เหลี่ยมหรือหลายเหลี่ยมมาต่อกัน

นอกจากนี้ก็มี NURBS ทรงกรวย cone() กับทรงโดนัท torus() ซึ่งก็มีคุณสมบัติในทำนองเดียวกัน



ข้อดีของพื้นผิว NURBS อีกอย่างก็คือพื้นผิว NURBS สามารถแปลงเป็นโพลิกอนเมื่อไหร่ก็ได้ แต่โพลิกอนจะไม่สามารถแปลงเป็นพื้นผิว NURBSได้ ดังนั้นหากคิดว่ารูปทรงไหนใช้พื้นผิว NURBS แล้วสร้างง่ายกว่าก็อาจเริ่มสร้างเป็นพื้นผิว NURBS แล้วค่อยแปลงเป็นโพลิกอนทีหลังก็ได้

ในความเป็นจริงแล้วปกติพื้นผิว NURBS เวลาที่เรนเดอร์จะต้องถูกแปลงเป็นโพลิกอนโดยอัตโนมัติ

คำสั่งที่ใช้แปลงพื้นผิว NURBS เป็นโพลิกอนคือ nurbsToPoly()

การแปลงนั้นมีอยู่ ๓ โหมดให้เลือกด้วยกัน ซึ่งกำหนดโดยแฟล็ก f (format)
f=0 แปลงโดยกำหนดจำนวนโพลิกอนตายตัว
f=1 แปลงแบบปรับให้ตามความเหมาะสม
f=2 แปลงโดยปรับตาม cv

และต้องเลือกรูปแบบว่าจะแปลงเป็นโพลิกอนสามเหลี่ยมหรือสี่เหลี่ยมโดยแฟล็ก pt (polygonType)
pt=0 สามเหลี่ยม
pt=1 สี่เหลี่ยม

กรณีที่เลือก f=0 คือกำหนดจำนวนโพลิกอนตายตัวนั้นจะต้องกำหนดจำนวนโพลิกอนโดยใช้แฟล็ก pc (polygonCount) เวลาที่แปลงโปรแกรมจะพยายามแปลงให้ได้จำนวนโพลิกอนใกล้เคียงกับที่กำหนดมาก ที่สุด

ลองสร้างผิว NURBS ขึ้นมาแล้วสร้างใช้เป็นแบบโพลิกอนหลายๆอันเทียบกันดู
p = mc.nurbsPlane(w=10,ax=[0,1,0],u=2,v=3)[0]
mc.move(0,10,0,p+'.cv[2][2]',r=1)
mc.nurbsToPoly(p,f=0,pt=1,pc=30)
mc.move(0,0,-10)
mc.nurbsToPoly(p,f=0,pt=0,pc=30)
mc.move(0,0,10)
mc.nurbsToPoly(p,f=0,pt=1,pc=300)
mc.move(-10,0,0)
mc.nurbsToPoly(p,f=2,pt=0)
mc.move(10,0,0)



ภาพตรงกลางคือ NURBS ต้นแบบ ส่วนสี่อันรอบทิศคือโพลิกอนที่สร้างขึ้นโดยกำหนดแฟล็กต่างๆกัน

เมื่อใช้คำสั่งนี้เสร็จแล้ว NURBS ต้นฉบับนั้นจะยังไม่ได้หายไปไหน ถ้าไม่ต้องการแล้วก็ลบทิ้งได้

หากไม่ลบ NURBS ต้นฉบับทิ้งเวลาที่ NURBS นี้มีการเปลี่ยนแปลงหรือเคลื่อนไหวไปโพลิกอนที่สร้างขึ้นมาใหม่นี้ก็จะมีการ เปลี่ยนแปลงไปด้วย

แต่หากต้องการเก็บ NURBS ไว้โดยไม่มีผลกระทบต่อโพลิกอนอีกต่อไปก็ทำได้โดยใส่แฟล็ก ch (constructionHistory) เป็น ch=0 ซึ่งเป็นการลบประวัติศาสตร์ทิ้ง ไม่ให้โพลิกอนจดจำไว้ว่ามันถูกสร้างขึ้นมาจากไหน



ถ้ามีรายละเอียดมากก็ยิ่งควรใช้จำนวนโพลิกอนมาก โพลิกอนน้อยเกินจะทำให้รายละเอียดหายไป

ลองดูตัวอย่าง สร้างวัตถุ NURBS ที่มีลักษณะคล้ายถ้วยที่แหว่งไปเสี้ยวหนึ่งแล้วแปลงเป็นโพลิกอนโดยกำหนดจำนวนโพลิกอนต่างกัน
p = mc.cylinder(r=1,hr=3,nsp=10,s=18,ssw=0,esw=330,ax=[0,1,0])[0]
mc.scale(3,3,3,
[p+'.cv[%d][*]'%i for i in range(2,11,2)]+
[p+'.cv[*][%d]'%i for i in range(2,19,2)])
for i in range(1,7):
    mc.nurbsToPoly(p,f=0,pc=40*2**i,pt=0)
    mc.move(0,0,6*i)



จากหน้าไปหลังคือจำนวนโพลิกอน 2560 ลดลงทีละ 2 เท่าไปจนเหลือแค่ 80 ส่วนอันไกลสุดคือ NURBS ต้นแบบ

นอกจาก จะใช้คำสั่ง nurbsToPoly() แล้ว ยังมีอีกวิธีที่จะสร้างโพลิกอนขึ้นจากผิว NURBS นั่นคือตอนที่ใช้ฟังก์ชันต่างๆสร้างผิว NURBS ขึ้นมานั้นให้เติมแฟล็ก po (polygon) เป็น po=1 แบบนี้แล้ววัตถุที่ถูกสร้างขึ้นจะเปลี่ยนเป็นโพลิกอนทันที

เพียงแต่ว่าจำการกำหนดว่าจะเปลี่ยนเป็นโพลิกอนในรูปแบบไหนนั้นจำเป็นต้องกำหนดโดยฟังก์ชัน nurbsToPolygonsPref()

แฟล็กต่างๆของฟังก์ชันนี้จะตรงกับฟังก์ชัน nurbsToPoly() เพียงแต่ว่าฟังก์ชันนี้ไม่ได้ทำการเปลี่ยนเป็นโพลิกอนทันทีแต่มีไว้กำหนดรูป แบบของการแปลงโพลิกอนเวลาใช้แฟล็ก po เท่านั้น

ตัวอย่างการใช้ เช่นการสร้างส่วนของทรงกลม ซึ่งเป็นสิ่งที่ใช้โพลิกอนสร้างยาก แต่สำหรับ NURBS แล้วสร้างได้ง่าย ดังนั้นเราใช้ประโยชน์จากตรงนี้สร้างโพลิกอนของส่วนของทรงกลมได้ทันที
for i in range(1,13):
    mc.nurbsToPolygonsPref(f=0,pc=2**i,pt=1)
    p = mc.sphere(r=5,ssw=i*30,esw=i*30+15,po=1)

ในนี้เป็นการสร้างส่วนของทรงกลมมาทั้งหมด ๑๒ อัน โดยจำนวนโพลิกอนแปรผันตั้งแต่ 2 ไปจนถึง 4096





อ้างอิง