Tuesday, November 8, 2011

Renderman RSL-2.5

Functions

        Програмын код бичих нь  цаг хугацаа шаарддаг , төвөгтэй ажил төдийгүй  нэгэн хэвийн , давтамж ихтэй үйлдлүүдтэй байнга таардаг . Визуал эффект хийдэг студи, продакшнуудын эрчимтэй ажиллагаанд татагдан орсон хүмүүст өөрийн шэйдрийг хөгжүүлэх  цаг хугацаа бараг олддоггүй. Амжилттай шэйдр зохиогч болохын тулд ухаалаг, үр дүн сайтай  ажиллаж сурах хэрэгтэй.Ийм учраас техникийн удирдагчид (TD) функцыг оновчтой бичих болон зөв зохион байгуулах чадварыг сайтар эзэмшсэн байх хэрэгтэй.

     Функц бол дахин дахин ашиглагдах зориулалттай багц код юм. Функцыг хоёр аргаар ашиглаж болно. Шэйдрийн  эх код  дотор функцыг шууд тавиад дуудаж хэрэглэх бололцоотой бол нөгөөх нь header файлд хадгалаад түүнээсээ дуудаж хэрэглэх арга. Header файлд байгаа функцыг #include препроцессорын директивийг ашиглаж дуудна. RSL-д функыг  дараахь бичлэгийн дүрмээр зарлана.
    Төрөл ФункцНэр (параметрууд)
             {
               код...;
               return value;
            }
 Төрөл нь массиваас бусад бүх төрлийн өгөгдөл байж болно. Мөн void ( null ) гэх нэмэлт төрлийг тодорхойлж өгсөн. Үүний дараагаар функцын нэр, парамерууд байрлана. Функцын параметрууд нь шэйдрийн параметруудтай яг адил зарчмаар зарлагдана. Ганц ялгаа нь функцын параметр  анхны утгагүй байж болно.Параметрын дараа функцын их бие байрлана. Хэрвээ  void төрлийн функц биш бол их биеийн төгсгөлд return  заавал байх ёстой. Буцаах утгын төрөл нь функцыг зарлахад оноосон төрөлтэй адил байна. Өгөгдсөн тооны квадратыг олдог функцын жишээг авч үзье.
     float sqr ( float val) 
       {
           return val * val;
      }
Дээрхи функцыг дараахь маягаар хаана ч  ашиглаж болно.
     float x = sqr( 4);
 Утга буцаах үйлдэл функц дотор нэг л байх ёстой. Тиймээс функц доторхи логик үйлдлээс шалтгаалан өөр өөр утга буцаах шаардлага тулгарвал түр зуурын хувьсагчид утгыг хадгалж байгаад төгсгөлд буцаана. Жишээ:
color getTexture (string texturename; float u, v, scaleU, scaleV, flipU, flipV)
    {
        float uu = u * scaleU;
        if (flipU = 1)  {  uu = (1 - u) * scaleU; }
        float vv = v * scaleV;
        if (flipV == 1 ) { vv = (1 - v) * scaleV; }
        color Cout;
        if (texturename != "")
                 {  Cout = texture(texturename, uu, vv);   }
        else  {  Cout = color (1,0,0); }
        return Cout;
}

The C Preprocessor

 RSL нь бусад програмчлалын хэлтэй адил шэйдрийг хөрвүүлэхийн өмнө препроцессор програмыг биелүүлдэг. Препроцессор нь эх код дотор агуулагдах нь нэрнээсээ тодорхой харагдаж байна. Үүний үндсэн үүрэг нь програмд хэрэг болох  хэд хэдэн тэмдэгтүүдийг сольж, өргөтгөх юм. Өргөтгөсөн текст нь хөрвүүлэгчтэй stream маягаар холбогдоно. Препроцессорыг зөв ашиглаж сурах нь маш чухал бөгөөд шэйдр бүтээх ажиллагааг тун хялбарчилна.  Препроцессор нь тогтмол, команд , макро төрлүүдийг дэмждэг.
Constants
 Тогтмол  нь энгийн эсвэл объект төрлийн макрод хамаарах ба утга хадгалахын тулд хувьсагчтай ижил ойлгомжтой , тодорхой  нэр оноодог учир хувьсагчтай адил харагддаг. Хамгийн гол ялгаа нь шэйдрийн код дотор хувьсагчийн утгыг өөрчилж, шинээр утга олгох бололцоотой. Харин тогтмол нь эсрэгээрээ зөвхөн унших зориулалттай. Шинээр код бичиж сурч байгаа хүмүүс  яагаад хувьсагч биш заавал тогтмол ашиглах хэрэгтэй  гэж гайхдаг.
    Энд л хоёрдахь буюу хамгийн чухал ялгаа нь харагдана. Хувьсагч нь кодын нэгэн хэсэг бөгөөд хөрвүүлсэн шэйдрт  цуг агуулагдана. Тиймээс зурах процессын үед хувьсагчид нь санах ойд бага хэмжээний зайг эзэлдэг. Хэдийгээр эзлэх зай нь бага ч гэсэн шэйдр бүтээмжтэй байх нь үндсэн зорилт. Харин тогтмол нь шэйдрийг хөрвүүлхийн өмнө препроцессорт тодорхойлогдсон утгаар солигдох учраас өөрт агуулах өгөгдлөөс илүү санах ой шаардахгүй. Тогтмолыг ашиглах аргыг жишээн дээр харцгаая.
    #define RED (1,1,1)
    #define SKYTEX "/home/textures/sky.tx"
    #define HALFPI 1.5707963267848966
    Препроцессор бүр өмнөө заавал (#) тэмдэгттэй байх хэрэгтэй. Энэ тэмдэгтийг үндэслэн препроцессор нь мөрийг команд, тогтмол эсвэл макро болохыг танидаг. define командыг  ашиглан шинээр тогтмол үүсгэж байгааг препроцессорт мэдээлдэг. Энэ командын араас тогтмолын нэрийг зарлана. Зайлшгүй шаардлагатай биш ч гэсэн тогтмолын нэрийг том үсгээр бичдэг уламжлал тогтсон. Кодыг уншиж байгаа өөр хүн хувьсагч тогтмол хоёрын ялгааг шууд олж харахад зориулсан. Тогтмолын нэрийн араас түүнд оноох тогтмолын утгыг бичнэ. Энэ нь хөрвүүлэлт хийгдэхийн өмнө препроцессорын тусламжтай солигдох утга юм.
 Commands
     Препроцессор командууд нь эх кодыг хэрхэн өргөжүүлэх аргуудыг тодорхойлсон түлхүүр үгнүүд юм.  Препроцессор бүр нь төрөл бүрийн хэд хэдэн командуудтай байна. RenderMan зарчмаар ажилладаг  зурагч нар ихэвчлэн С препроцессор ашигладаг. Учир нь  RSL- ийг бүтээж байхад С хэл нь хамгийн түгээмэл хэл байлаа. С препроцессор нь олон командуудтай бөгөөд хамгийн их ашиглагддаг командуудыг хэрхэн хэрэглэхийг харцгаая.  
include командыг байнга ашигладаг. Энэ команд нь програмыг сайн зохион байгуулалт, удирдлагтай болгодог. Үүнгүйгээр өмнө бичсэн кодыг эх кодтой холбохдоо copy paste хийн хуулах шаардлагатай болно. Ингэснээр засвар хийх болгонд эх кодыг байнга өөрчилж  нэмэх шаардлага тулгарч хүн ойлгохын аргагүй замбараагүй зүйл болж хувирна. include команд нь RSL код агуулсан файлыг одоо хийж байгаа эх коддоо  хавсарган ашиглах бололцоог олгоно. Эдгээр файлууд нь header эсвэл сан гэж нэрлэгддэг ба ихэвчлэн .h өргөтгөлтэй байдаг. Энэ файл нь зөвхөн препроцессор тогтмол, макро болон RSL код л агуулсан байж болно. Тиймээс шилжүүлэлтийн, гадаргын, гэрлийн гэх мэт ямар нэг шэйдр файлд байж болохгүй. include командыг хоёр төрлийн аргаар хэрэглэнэ. Эхний арга нь :
     #include <filename>
Дээрхи аргаар зарлавал препроцессор нь хөрвүүлэх үед -l флагт заасан замын тусламжтай файлын нэрийг хайна . Хайлт нь үргэлжилсэн прогрессив хэлбэртэй. Нэр нь тохирох файл олдох хүртэл үргэлжлүүлэн хайж байгаад олонгуутаа  хайлтыг зогсооно. Өөр директорууд дотор ижил нэртэй файл байвал препроцессор нь буруу файлыг сонгох магадлалтайг анхаарах хэрэгтэй.
Хоёрдахь арга нь доорхи зарчмаар бичигдэнэ :
    #include "filename"
 Энэ тохиолдолд препроцессор нь эхлээд файлыг идэвхитэй байгаа директор дотор хайна. Файл олдоогүй тохиолдолд хөрвүүлэх үед -l флагт зааж өгсөн замаар  үргэлжлүүлэн  хайна . C болон  C++ дээр програм бичихэд толгой файл үүсгэж хавсаргах шаардлага байнга гардаг. Харин шэйдр бичихэд энэ арга тийм ч тохиромжтой биш.
If Defined ба If Not Defined
Толгой   буюу санг агуулсан код бичихэд  хэд хэдэн файлыг эх болон толгой файлдаа дуудаж ашиглах шаардлагатай болдог. Хэрэв шэйдрийг хөрвүүлэх явцад  шэйдрт багтсан хэд хэдэн файлаас өөр файлыг зэрэг дуудвал яах вэ? Препроцессор нь энэ файл аль хэдийн ачаалагдсан гэдгийг ойлгох чадваргүй. Тэгэхээр  толгой  файлуудыг нэгээс олон удаа давхардуулан ачаална гэсэн үг. Файл  ачаалагдсан үгүйг мэддэг механизмыг ашиглан асуудлыг шийдэх боломжтой.  #ifdef (if defined) болон #ifndef (if not defined) командыг ашиглан бага зэрэг логик илэрхийлэл нэмээд препроцессорт энэхүү механизмыг хэрэгжүүлж болно. Жишээн дээр харцгаая:
     #ifndef FLOATUTILS_H
     #define FLOATUTILS_H 1
      float afunction ()
                {
                     Код ...;
                };
     #endif // FLOATUTILS_H
Эхний мөр нь  препроцессороос FLOATUTILS_H тогтмол тодорхойлогдсон эсэхийг асууж байна. Хэрэв тодорхойлогдоогүй бол препроцессор нь дараагийн алхмыг хэрэгжүүлнэ. Тогтмолыг шинээр тодорхойлж floatutils.h файлд хамаарах  хэд л бол хэдэн функцыг зарлаж болно. Файл доторхи функцуудын дараагаар #ifndef нөхцлөөс гарах болсноо #endif команд ашиглан препроцессорт хэлнэ. Энэ нь препроцессорт FLOATUTILS_H тогтмол тодорхойлогдоогүй бол шинээр тодорхойлох хэрэгтэйг хэлж байна. Харин файлыг дахин ачаалахаас хэрхэн сэргийлэх вэ? Доорхи мөрийг л шэйдр болон толгой файл дотроо нэмээд өгөхөд л хангалттай.
      #include <floatutils.h>
Энэ код препроцессорт  floatutils.h файлыг нэмэх хэрэгтэйг хэлж өгнө. Эхний удаа файл ачаалагдахад  FLOATUTILS_H тогтмолыг тодорхойлж функцыг гүйцэтгэнэ. Дараа нь дахин ачаалах гэж оролдвол FLOATUTILS_H аль хэдийн тодорхойлогдсон учраас бүх үйлдэл функцуудыг алгасна.
if, else, elseif
Эдгээр команд нь RSL-ийн урсгалыг хянах командуудтай адилхан. Гол ялгаа нь  логик илэрхийллийг шэйдр ажиллах явцад гүйцэтгэхгүй өмнө нь гүйцэтгээд харгалзах утгыг хөрвүүлэгч рүү шууд дамжуулдаг. Шэйдрийг олон зорилгоор ашиглах бол энэ арга үр дүнтэй. RSL дээр байдагтай адилхан зарчимтай. Хэрэв нөхцөл нь үнэн утга буцаавал if-ийн ард байрлах кодыг хөрвүүлэгч рүү дамжуулна. Худал бол if-ийн ард байрлах кодыг алгасаж else-ийн ард байх кодыг дамжуулна.  Бичилтийн дүрэм нь:
  #if илэрхийлэл
     controlled text
  #endif  /* илэрхийлэл */
Илэрхийлэл нь доорхийг агуулсан  байж болно:
■ integer 0 or 1
■ Макро эсвэл тогтмол
■ Нэмэх, хасах, үржүүлэх, хуваах, бит шилжүүлэх , харьцуулах зэрэг математик  үйлдлүүд болон  (&& ба ||) гэсэн логик үйлдэл
■  ifdef-ийг ашиглан тодорхойлсон оператор
Macros
      Препроцессор макро нь аргумент авдаг болохоор функцтай төстэй. Байнга ашигладаг кодоо функц ашиглалгүйгээр нэмэх хамгийн тохиромжтой арга юм. Байнга давтагддаг кодын нэр ба хувьсагчид динамик өргөтгөл хийхэд хэрэглэдэг. Бичилтийн дүрэм нь:
#define MACRONAME (param1, param2,..)
Тогтмол зарлахтай адил боловч хаалтан дотор параметрууд агуулснаараа макро ялгардаг. Параметруудад хандахдаа шууд нэрийг нь бичиж хэрэглэнэ.
      #define FGAMMA (x,gamma) pow(x,1/gamma)
Түүнчлэн ##  операторын тусламжтай параметруудыг хослуулан шинэ нэр өгч болно. Нэгээс олон мөртэй макро бичих бол  (  \ ) тэмдэгтийг мөрийн төгсгөлд нэмж өгөх хэрэгтэй. Тэмдэгтийн ард ямар ч зай, өөр тэмдэгт байж болохгүй. Макрог хэрхэн ашиглахыг жишээн дээр харъя.
   #define MY_TEX_CALL(texname,texnum) \
   uniform string texname##texnum = ""; \
   uniform string texname##texnum##Filter = "gaussian";\
   uniform float texname##texnum##FilterSize = 1
   surface myshader (
      MY_TEX_CALL(colortex,1);
      MY_TEX_CALL(colortex,2);
      MY_TEX_CALL(spectex,1);
      MY_TEX_CALL(spectex,2);
С препроцессорын дэмждэг өөр олон командууд байдаг. Үүнийг хэрхэн ашигладаг талаар дэлгэрэнгүй мэдэхийг хүсвэл http://gcc.gnu.org/onlinedocs/cpp/.

No comments:

Post a Comment