[PRMan Memo page 6: Writing shadeop]
การเขียน shading language ของ renderman นั้นมีข้อเสียอยู่บางอย่าง ที่ชัดเจนคือ shading language ที่ซับซ้อนและมัการคำนวณจำนวนมากมักต้องการ SL Function เพื่อให้การทำงานเป็น module และง่ายต่อการพัฒนา แต SL Function ก็มีข้อจำกัด เช่นช้ากว่า code ที่compile เป็น machine code แล้ว มีข้อจำกัด ในเรื่อง data structure ยากในการเรียกใช file ภายนอก เหล่านี้เป็นต้น
เราสามารถเขียน SL Function ภายนอก shading language ด้วย C หรือ C++ แล้ว link แบบ dynamic เมื่อมีการเรียกใช ้หรือเป็นที่รู้จักกันดีในชื่อของ .dll (dynamic link library)
.dll มีข้อดีหลายอย่างด้วยกัน ที่แน่ๆคือทำงานได้เร็วกว่าการทำงานด้วย SL Function ที่เขียนใน shading language สามารถใช library function ต่างๆของภาษา C ได ้สามารถใช้ความสามารถของ C หรือ C++ ในเรื่องของการสร้าง data structure ที่ซับซ้อนได ้เป็นต้น
Writing shadeop
เราสามารถใช้ตัว compiler เป็น C หรือ C++ ก็ได ้แต่ต้อง link แบบ C เท่านั้น ในกรณีของเราใช Windows ตัว compiler ที่สามารถจะ compile shadeop ได้มีเพียง Visual C++ 6.0 เท่านั้น (ผู้เขียนยังไม่มีข้อมูลสำหรับการเขียน shadeop ใน version ใหม่ของ renderman ว่า support compiler ตัวอื่นแล้วหรือยัง)
ทุกๆ shadop จะมี3 C Function คือ method, init และ shutdown (หรือ cleanup) โดยแต่ละอันมีรูปแบบ
/* Prototype for DSO shadeop "implementation method" */
#define SHADEOP(name) DLLEXPORT int name (void *initdata, int argc, void *argv[])
/* Prototype for DSO shadeop "initialization routine" */
#define SHADEOP_INIT(name) DLLEXPORT void * name (int ctx, void *texturectx)
/* Prototype for DSO shadeop "cleanup routine" */
#define SHADEOP_CLEANUP(name) DLLEXPORT void name (void *initdata)
เริ่มจากบรรทัดแรก SHADEOP เป็น macro รูปแบบการสร้าง method หรือส่วนที่ทำงานของ shadeop ซึ่งจะถูกเรียกทุกครั้ง ที่มีการเรียกใช้งาน shapeop ทุกๆ shadeop function จำเป็นต้องมีmethod ตัวนี้ซึ่งประกอบด้วย 3 ตัวแปล ตัวแรก initdata เป็นตัวแปลที่ส่งค่ากลับมาจาก init function, argc เป็นตัวแปลซึ่งจะ บอกว่ามีการผ่าน argument เข้ามาดี่ตัวส่วน argv[] คือ array เก็บค่าของ argument ต่างๆที่ผ่านเข้ามาใน shadeop เนื่องจาก shadeop ไม่สามารถเรียกจัวแปล ของ shading language มาใช้งานโดยจรงได ้จึงต้องมีการผ่านเข้ามาในรูปจของ argument เช่นผ่าน global variable P เข้ามาเผื่อให้ตำแหน่งจุด P เข้ามาคำนวณ เป็นต้น
SHADEOP_INIT คือรูปแบบของ init function ของ shadeop ซึ่งจะถูกเรียกใช้งานเพียงครั้งเดียวเมื่อมีการเรียกใช้งาน shadeop ครั้งแรกเท่านั้น เราสามารถใช function นี้ในการเริ่มต้นค่าต่างๆ เช่นการอ่าน file ที่ตอ้งการแล้วสร้าง data structure เอาไว ้หรือใช้เพื่อกำหนดตำแหน่งของ memory เพื่อการใช้งาน (memory allocation) และสามารถคืนค่ากลับไปเพื่อใช้งานใน method ได
SHADEOP_CLEANUP เป็น function ที่ถูกเรียกใช้เมื่อการ render เสร็จสิ้นลงเราอาจจะใช้เพื่อเขียนข้อมูลลง file หรือเพื่อ free memory ที่เราเรียกได้เรียกใช ้งานไปแล้ว
shadeop table
/* A handy macro for declaration of the shadeop table. */
#define SHADEOP_TABLE(opname) DLLEXPORT SHADEOP_SPEC opname ## _shadeops[]
/* Here is an example of using this macro:
*
*
SHADEOP_TABLE(sqr) = { { "float sqr_f(float)", "", "" },
*
{ "color sqr_c(color)", "", "" },
*
{ "", "", "" } // Blanks indicate end of list
*
};
*/
shadeop มีความสามารถของการเขียน program แบบ OOP เช่นการมีสิ่งที่คล้าย construtor และ destructor อย่าง init function และ cleanup function นอก จากนี้ยังสามารถเขียน function ในลักษณะ polymorphic ได้ด้วย function ในลักษณะแบบนี้จะสามารถรับตัวแปลได้หลายแบบ โดยยังสามารถเรียกได้โดยใช้ชื่อเดิม เช่น function sqrt เพื่อหาค่ารากที่สองของตัวเลขที่ได้รับ สามารถควณตัวเลขที่เป็น float ก็ได้หรือ vector ก็ได ้เป็นต้น
เพื่อให้มีคุณสมบัติเหล่านี้จำเป็นต้องมีการกำหนดที่ชัดเจน และ renderman ได้ให้รูปแบบการเขียนเอาใช้ในการเขียน shadeop table เช่น
SHADEOP_TABLE(sqr) =
{
{ "float sqr_f (float)", "", "" },
{ "point sqr_triple (point)", "", "" },
{ "vector sqr_triple (vector)", "", "" },
{ "normal sqr_triple (normal)", "", "" },
{ "color sqr_triple (color)", "", "" },
{ "" }
};
สำหรับ function ที่ชื่อ sqr เพื่อกาค่ายกกำลังสอง ก็จะมีsqr_f สำหรับหาค่าของ float มีsqr_triple สำหรับหาค่าของ point, vector และ color แบบนี้เป็นต้น เครื่อง หมายคำพูดเปิดปิด สอง อันต่อท้ายคือ ช่องสำหรับใส init และ cleanup ซึ่งในกรณีนี้ไม่มีก็เว้นไป ส่วนเครื่องหมาย คำพูดอันล่างสุดใส่ไว้เพื่อบอกว่า จบ shadeop table แล้ว
ลองมาดูตัวอย่างแบบซักตัวอย่างครับ

#include <stdio.h>
#include <shadeop.h>
SHADEOP_TABLE(sqr) =
{
{ "float sqr_f (float)", "", "" },
{ "point sqr_triple (point)", "", "" },
{ "vector sqr_triple (vector)", "", "" },
{ "normal sqr_triple (normal)", "", "" },
{ "color sqr_triple (color)", "", "" },
{ "" }
};
SHADEOP (sqr_f)
{
float *result = (float *)argv[0];
float *x = (float *)argv[1];
*result = (*x) * (*x);
return 0;
}
SHADEOP (sqr_triple)
{
int i;
float *result = (float *)argv[0];
float *x = (float *)argv[1];
for (i = 0; i < 3; ++i, ++result, ++x)
{
*result = (*x) * (*x);
}
return 0;
}
listing เดียวกัน แต่แยกเป็นแบบมีสีซึ่ง capture มาจาก text editor ชื่อ Crimson ทำให้ดูง่าย และสามารถตรวจสอบ syntax error ได้อย่าง รวดเร็ว
บรรทัดที่2 เป็นการ include header ที่ชื่อ shadeop.h เข้าไปใช้งาน ซึ่งในนี้มีmacro ที่จำเป็นในการใช้งานหลายตัว เช่น SHADEOP หรือ shadeop table structure ที่กล่าวถึงก่อนหน้านี้ดังนั้น ต้องเพิ่ม include path ที่มีshadeop.h เข้าไปตอน compile ด้วย (ปกติอยู่ที่include path ของ prman)
บรรทัดที่4-12 กำหนดค่าต่างๆที่ต้องการให้กับ shadeop table ตามตัวอย่างเป็นการกำหนด function แบบ polymorphic จึงมีการแตก function เป็นหลายตัว ตาม ชนิดของข้อมูลที่จะได้รับ
บรรทัดที่14-20 รายละเอียดของ function ซึ่งรับค่าเป็น float
บรรทัดที่16 คือการรับค่า address สำหรับการส่งค่ากลับให้กับตัวแปลที่ชื่อ result ซึ่งเป็น pointer ชี้ไป float ดังนั้นเมื่อต้องการ return ค่ากลับก็จะส่งค่าให้กับตัว แปลตัวนี้(สังเกตการรับค่าจากตัวแปล argv ต้องมีการให้ชนิดข้อมูลเสมอ)
บรรทัดที่18 ส่งค่าผลลัพท์กลับ ได้ให้ค่ากับตัวแปลที่ชื่อ result
บรรทัดที่22-32 รายละเอียดของ function ซึ่งรับค่าเป็น vector หรือ point หรือ color ตัวแปลเหล่านี้มีfloat เรียงกัน 3 ตัวจึงต้องมีการวน loop for เพื่อถึงค่าออก มาให้ครบ และส่งผลลัพท์กลับไปที่result เช่นเดิม
Compilation...
เนื่องจากต้องการการ Link แบบ C จึงต้องใส option บางตัวลงไปใน command line เพื่อความสะดวกในการเรียกใช้งานจึงเขียนเป็น batch file ง่ายๆ เช่น
del dumpgrids.dll
cl -DNT -DDLL -c "-I%RMANTREE%\include" dumpgrids.c
link /out:dumpgrids.dll -dll "-LIBPATH:%RMANTREE%\lib" dumpgrids.obj Prman.lib
copy dumpgrids.dll "C:\Program Files\Pixar\rat-5.5.1\lib\slim\include\dumpgrids.dll"
copy dumpgrids.dll "C:\Program Files\Pixar\rat-5.5.1\lib\shaders\dumpgrids.dll"
copy dumpgrids.dll "D:\XTeapot\GALLERY\3DModel\PRManMemo\rmanshader\dumpgrids.dll"
เป็นการสั่ง compile ไฟล์ที่ชื่อ dumpgrids.c ด้วย "cl" ซึ่งเป็นตัว compiler ของ Visual C++ หลังจาก compile แล้วก็จะทำการ copy .dll ไปไว 3 ที่ด้วยกัน เพื่อ การทดสอบหลายๆแบบ การเขียน batch file แบบนี้สะดวกมากครับ เมื่อเขียนแล้วสามารถ run batch file ได้เลย และทดสอบผลลัพท์ได้ทันทีแต่การ debug อาจจะ ยุ่งยากนิดหน่อยครับ จะสะดวกขึ้นมากถ้าใช assert เข้ามาช่วย ตรวจสอบข้อมูล ในชวงต่างๆของ code
ลองมาดูอีกซกตัวอย่างครับ
/*
* From the original code by Matt Pharr <mmp@graphics.stanford.edu>
*/
#include <stdio.h>
#include <assert.h>
#include <shadeop.h>
SHADEOP_TABLE(gridStart) = {
{ "void gridStart (string)", "", "" }, { "" } };
SHADEOP_TABLE(processGrid) = {
{ "void processGrid (point, color, float, float, float, normal)", "", "" }, { "" } };
SHADEOP_TABLE(gridEnd) = {
{ "void gridEnd (float)", "initDump", "dumpCleanup" }, { "" } };
/* A safe(?) upper bound on the total number of vertices in a grid--i.e. Options "limits" "gridsize" [] */
#define MAX_SIZE 8192
static int pcPos = 0, uPos = 0, vPos = 0;
static float u[MAX_SIZE], v[MAX_SIZE], p[MAX_SIZE][3], c[MAX_SIZE][3], a[MAX_SIZE], n[MAX_SIZE][3];
static char *fileName;
SHADEOP_INIT(initDump) {
FILE *f = fopen(fileName, "w");
assert(f);
return f;
}
SHADEOP_CLEANUP(dumpCleanup) {
FILE *f = (FILE *)initdata;
fclose(f);
}
SHADEOP(gridStart) {
STRING_DESC *fName = (STRING_DESC *)argv[1];
fileName = fName->s;
pcPos = uPos = vPos = 0;
return 0;
}
SHADEOP(processGrid) {
float *pp = (float *)argv[1], *cp = (float *)argv[2];
float uv = *(float *)argv[3], vv = *(float *)argv[4];
float ap = *(float *)argv[5];
float *np = (float *)argv[6];
int i;
/* Store all position, color, area and normal vector values that we're called with */
assert(argc == 7);
for (i = 0; i < 3; ++i) {
p[pcPos][i] = pp[i];
c[pcPos][i] = cp[i];
n[pcPos][i] = np[i];
}
a[pcPos] = ap;
++pcPos;
assert(pcPos < MAX_SIZE);
/* Only store u and v values that we haven't seen before. By counting the number of unique u's and v's, we can infer the grid size. */
for (i = 0; i < uPos; ++i)
if (u[i] == uv) break;
if (i == uPos) {
u[i] = uv;
++uPos;
assert(uPos < MAX_SIZE);
}
for (i = 0; i < vPos; ++i)
if (v[i] == vv) break;
if (i == vPos) {
v[i] = vv;
++vPos;
assert(vPos < MAX_SIZE);
}
return 0;
}
SHADEOP(gridEnd) {
FILE *f = (FILE *)initdata;
int i, j;
float avp[3];
fprintf(f, "g %d %d %d %s [positoin XYZ, color RGB, area, normal ijk]\n", uPos-1, vPos-1, pcPos, fileName);
for (i = 0; i < pcPos; i++) {
fprintf(f, "mp %f %f %f %f %f %f %f %f %f %f\n",
p[i][0], p[i][1], p[i][2], // P position c[i][0], c[i][1], c[i][2], // Color (Diffuse) a[i], // Shading area n[i][0], n[i][1], n[i][2]); // Normalized Normal vector }
return 0;
}
และ slim template ซึ่งเรียกใช shadeop ตัวนี้...
slim 1 extensions pixardb {
extensions pixar pxsl {
template shadingmodel dumpgrids {
parameter color sfcolor {
label "Surface Color"
description {Surface Color}
detail varying
default "1 1 1"
}
parameter color sfopacity {
label "Surface Opacity"
description {Surface Opacity}
detail varying
default ".5 .5 .5"
}
parameter float Ka {
label {Ka}
description {Ka}
detail varying
default 1
}
parameter float Kd {
label {Kd}
description {Kd}
detail varying
default 1
}
parameter string fileName {
label {fileName}
description {fileName}
provider variable
subtype texture
default "c:/temp/dump.txt"
}
collection shadingmodel shadingmodel { access output display hidden parameter color CI { detail varying default {0 1 0} access output } parameter color OI { detail varying default {1 1 1} access output } }
RSLFunction {
void pxsldumpgrids(
color sfcolor;
color sfopacity;
float Ka;
float Kd;
string fileName;
output color CI;
output color OI;){
extern normal N;
extern vector I;
normal Nf = faceforward(normalize(N), I);
OI = Os*sfopacity;
CI = Os * Cs * (Ka * ambient() + sfcolor * Kd * diffuse(Nf));
float ap = area(P);
uniform float i = 0;
gridStart(fileName);
processGrid(P, CI, u, v, ap, normalize(N));
gridEnd(i);
}
}
}
}
} ตัวอย่างนี้เป็นตัวอย่างของ shadeop ซึ่งเขียนข้อมูลที่ได้มาขณะ render เช่น ตำแหนง xyz ของ point P ค่าสีของ point P เป็นต้น ออกมาเป็น text file รูปแบบหนึ่ง

หน้าตาของ slim เฉพาะกิจครับ save file ที่ต้องการไปที่c:/temp/dump.txt

จากมุมกล้องด้านบน render ขนาด 160x120 ออกมาประมาณนี้ละครับ ลองมาดูข้อมูลที่ได้ออกมาใน file ที่ชื่อ dump.txt ครับ

ได้ออกมาประมาณ 18000 บรรทัด ก็ประมาณ 18000 จุด ด้านบนนี้ตัดมาบางส่วนครับ เพื่อทดสอบว่าข้อมูลที่ได้มาถูกต้องแค่ไหน จึงเขียน mel script ง่ายๆเพื่ออ่าน file นี้และสร้าง particle ตามตำแหน่ง และให้สีลงไป ถ้าข้อมูลที่ได้ถูกต้องก็ควรได particle ซึ่งประกอบกันเป็นรูปเหมือนรูปแก้วนํ้าด้านบน

หลังจาก excecute แล้ว ได้ออกมาแบบนี้ครับ

ข้อมูลแบบนี้สามารถนำมาใช้งานได้หลายรูปแบบเลยครับ โดยเฉพาะนำมาใช้เป็นข้อมูลเพื่อการ sample หลายๆจุด แทนที่การเรียกการ sample ธรรมาดา เช่นคำสั่ง gather ซึ่งใช้เวลานาน ตอนหน้าเรามาดูกันครับว่า เราจะใช้ข้อมูลที่ได้มาทำอะไรได้บ้าง
shadeop เป็นการเขียน SL Function ในรูปแบบที่มีประโยชน ์และนำไปใช้งานได้หลากหลายมาก มีความโดดเด่นในเรื่องของความเร็ว และความยืดหยุ่นกับการ จัดการข้อมูล เนื่องจากเขียนภายนอก สามารถใช้ความสามารถของ ภาษา C หรือ C++ ได้อย่างเต็มที่renderman สามารถเขียน script เพิ่มเติมได้ในหลายลักษณะ ถ้าศึกษาให้ดีจะเกิดประโยชน์ต่อการประยุกต์ใช้งานอย่างมากครับ
<<Back |