MATH FOR SHADER WRITER
Математмк - та авьяаслаг шэйдр бичигч болохыг хүсвэл зайлшгүй мэдэж байх шаардлагатай зүйл бөгөөд ихэнхи хүмүүс сонсоод халширч зугтаадаг. Их дээд сургууль коллежд байхдаа энэхүү хичээлд төдийлэн анхаардаггүй байсан учраас шэйдр бичихэд ашиглагдаж байгаа математикийн нэр томьёог ойлгохгүй нь гэж бүү бодоорой. Энэ сэдэвт үзэх зүйлийг ойлгохын тулд математикийн гүнзгий мэдлэг шаардагдахгүй. Зарим онол концептууд нь ойлгоход төвөгтэй байж болох ч бид математикч биш учраас үүнийг батлаж, онол яаж хэрэгжиж байгааг мэдэх шаардлага огт байхгүй.
Бидний гол мэдэж байх гол зүйл бол тухайн нөхцөлд зохицуулж ямар үйлдэл, өгөгдлүүдийг оновчтой ашиглах юм. Зарим үйлдлийг ашиглаж сурахад практик ажиллагаа хэрэгтэй болно. Шэйдр TD-ийн өдөр болгон ашигладаг математик нь тун хязгаарлагдмал, хэдхэн төрлийн математикийн үйлдэл, өгөгдлүүдийг мэдэж байхад л хангалттай. Математикийн хэд хэдэн ойлгоход төвөгтэй салбарууд байдаг бөгөөд эдгээрийг хэр сайн ойлгосноос шалтгаалж шэйдр TD-ийн чадвар сайжирна. Гэхдээ энэ мэдлэг нь маш онцгой нөхцөлд л ашиглагдана. Энэ сэдвээр бид өдөр тутам хэрэглэгддэг математиктай танилцана.
Useful Operations
Бид эхлээд бага, дунд сургуульд үздэг байсан энгийн тоог үзэж эхлье. Аз болоход шэйдр хөгжүүлэхэд явцад 0-оос 1-ийн хоорондох тоог л ашигладаг. Заримдаа –1 ээс 1 хооронд ч болж өргөсөх тохиолдол байдаг. Энэхүү хүрээ маш чухал учир нь объектын параметрик болон текстурын координат энэ хүрээнд л байдаг. Түүнчлэн ихэнхи өнгө болон шуугианы талбай (noise fields) мөн энэ хүрээнд багтана. Эдгээр утгыг тааруулж, тохируулан өөрт хэрэгтэй үр дүнг гаргаж авах нь чухал юм. Дараахи хэсэгт байнга ашиглагддаг үйлдлүүдийг жагсаасан байна. Эдгээрийг марталгүй санаж байх нь маш хэрэг болно. Зураг 5.1-т 0-ээс 1-т харгалзах y = x шугаман функцыг харуулсан байна.
зураг 5.1
Inverting a Value (Утга хөрвүүлэх)
Хэрэв таны ашиглаж байгаа утга (одооноос утга гэдэг нь x гэж ойлгож болно) 0-ээс 1 хүрээнд байвал та 1-ээс утгыг хассанаар хөрвүүлэлт хийнэ. Хасах үйлдэл байрыг солиход өөр өөр үр дүн үзүүлэх учир y = 1 – x хэлбэртэй байна. Өөрөөр хэлбэл y = x – 1 ондоо үр дүн үзүүлнэ. Хэрэв х нь –1 ээс 1-ийн хооронд байвал үүнийг хөрвүүлэхийн тулд х-ийг -1 ээр үржүүлэх ба функц y = –1*x хэлбэртэй байна. Үржүүлэх үйлдэл учир байрлал чухал биш y = x * –1 гэж бичиж болно; Өөр нэгэн арга нь аргументын өмнө (–) тэмдэг байрлуулах. –x маягтай бичигдэнэ. Энэ бичиглэл нь -1 ээр үржүүлсэнтэй адил бөгөөд товч бичиглэл нь юм. Зураг 5.2 болон 5.3-т дээрхи хөрвүүлэлтийн функц яаж ажилладаг болохыг харуулав.
зураг 5.2
зураг 5.3
Signing a Value (Тэмдэглэх үйлдэл)
Заримдаа 0-ээс 1 хүрээг –1 ээс 1 хүрээ рүү хөрвүүлэх хэрэгтэй болдог. Энэ үйлдлийг тэмдэглэх гэдэг. 0-ээс 1 хүрээг тэмдэглэхэд аргументыг хоёроор үржүүлж нэгийг хасах хэрэгтэй. y = (2 x) –1; Тэмдэглэх үйлдэл noise-тай ажиллахад тун ашигтай. noise() функц нь 0.5 утгыг дундажлан үр дүн илгээдэг. Зарим тохиолдолд –0.5 аас 0.5 хооронд noise-ийн утга хэрэг болно. Ийм тохиолдолд тэмдэглэх үйлдлийг ашиглана. Зураг 5.4-т 0-ээс 1 хүрээг –1 ээс 1 хүрээнд тэмдэглэсэн функцыг харуулав.
зураг 5.4
Divide Value by 1
Шэйдр хөгжүүлж байх явцад ямар нэг тодорхой утгыг ашиглах шаардлага тулгарч тэр нь шэйдрийн параметр болж чадахгүй байх тохиолдол гардаг. Яг ийм жишээ текстурын хэмжээг өөрчлөх үед тохиолддог. Текстурын орон зай (s, t) нь 0-ээс 1 хүрээнд хамаардаг. Текстурын хэмжээг өөрчлөхийн тулд s эсвэл t-г txscale оролтын утганд хуваадаг. Энэ шалтгааны улмаас асуудал үүсдэг. Учир нь текстурын координат 0 байх үед үйлдэл гүйцэтгэхэд тоог тэгд хуваасан гэсэн алдаа заана. Доорхи хэлбэртэй код нь рэндэрийн алдаа үүсгэх өндөр магадлалтай.
float ss = s/txscale;
float tt = t/txscale;
Энэ асуудлыг шийдэхийн тулд арга барилаа өөрчилж алдаанаас сэргийлэх логикийг нэмж өгөх хэрэгтэй. Өөрөөр хэлбэл s болон t-г 1/txscale утгаар үржүүлнэ. Энэхүү арга нь текстурын координатын хэмжээг багасгаж үр дүнд нь текстур томорно. Тоог тэгд хуваах алдаанаас сэргийлэх үүднээс max() операторыг ашиглана. Ингэж өөрчлөлт хийсэн кодыг дор сийрүүлсэн байна.
float scaler = max(1/txscale,0.000001);
float ss = s * scaler;
float tt = t * scaler;
Parametrizing a Value
Өөр нэг түлхүү ашиглагддаг үйлдэл бол шэйдрийн параметрийг тохируулах юм. 0-ээс 1 хүртэлхи утга бүхий шэйдрийн параметрийг өөр ашигтай утгуудад харгалзуулан оноох буюу remap заримдаа параметрчлах үйлдэл ч гэж нэрлэдэг. Миний ойлгож байгаагаар параметрчлах гэсэн нь оновчтой нэр юм. Учир нь Remap нь lowA -аас hiA хүртэлхи утгуудыг lowB-ээс hiB хүртэлхи утгуудад харгалзуулах үйлдэл бөгөөд заавал 0-ээс 1-ийн хооронд байх шаардлага тавидаггүй.
Үүний нэгэн жишээ бол phong() гэрэлтүүлэлтийн загварыг дуудахад гялаан (specular) гэрлийн утгыг тохируулах үед хэрэглэнэ. 20 болон 45-ийн хоорондох утгыг дамжуулж сайн үр дүн гаргах бололцоотой гэж үзье. Шэйдрийн хэрэглэгч үүнийг хэрхэн мэдэх билээ? Хэрэглэгч бэлэн болсон шэйдрийг 0-ээс 10-ийн хооронд утга оноон туршаад олигтой үр дүн гарахгүй байгааг анзаарч шэйдрийг ашиглахаа больж шэйдрийн зохиогчийг энгийн phong() шэйдр бичиж чадахгүй гэж зэмлэж магадгүй.
Энэ асуудлыг шийдэхийн тулд их утгаас (40) бага утгыг(20) хасаж хэрэглэгчийн оруулсан оролт (x)-ээр үржүүлж бага утгыг дээр нь нэмж парамерчлана. Хэрэглэгчид зөвхөн 0-ээс 1-ийн хооронд утга оруулах ба цаана нь 20-45-ийн хооронд шугаман хамаарлаар харгалзах
утга нь оноогдоод явна. Препроцессор макро тодорхойлж өгөх нь утгыг нормалчлах үйлдлийг маш хялбарчилж өгдөг. Доор макрог тодорхойлсон ба хэрхэн ашиглах жишээг үзүүллээ.
#define parametrizeVal(x,lo,hi) lo + x * (hi - lo)float specval = parametrizeVal(specParam,20,45);
float phongValue = phong(N,I,specval);
bias() and gain()
We will now introduce a set of functions that are extremely useful because of theirproperties. They allow you to manipulate the values within the 0 to 1 range without
ever modifying the low or high value. These functions were developed by Ken Perlin
and are a must for every shader writer’s bag of tricks. These two functions need to
default to a value of 0.5, at which point they have no effect on the input value.
The bias() function behaves like a weight modifier: it allows you to control which
part of the 0 to 1 range has more weight. Here is the bias() function, followed by
Figures 5.5 and 5.6, which illustrate the output of applying the values 0.25 and
0.85 to the bias() function.
Одоо бид тун чухал шаардлагатай хэдэн функцтай танилцана. Эдгээр нь доод ба дээд утгуудыг өөрчлөхгүйгээр 0-ээс 1 хүрээний дундах утгуудыг өөрчлөхөд ашиглагддаг. Эдгээр функцуудыг Ken Perlin хөгжүүлсэн бөгөөд шэйдр бичигчийн үндсэн багажуудын нэг. Энэ хоёр функцийн анхны утга нь 0.5 бөгөөд ийм утгатай байх үедээ оролтын утганд ямар нэг нөлөө үзүүлэхгүй.
bias() функц нь жинг өөрчлөгч (weight modifier) маягаар ажилладаг ба 0-ээс 1 хоорондох утгын аль хэсэг нь илүү жинтэй байх вэ гэдгийг хянаж удирдах бололцоог олгоно. Зураг 5.5 ба 5.6-д
0.25 болон 0.85 гэсэн утгуудыг оноосон тохиолдолд bias() функцийн гаралт ямар байхыг харууллаа.
float bias(varying float val,b)
{
return (b > 0) ? pow(val,log(b) / log(0.5)): 0;
}
зураг 5.5
зураг 5.6
The gain() function (also known as a contrast function) has very different behavior.
It leaves the values at 0, 0.5, and 1 unchanged and manipulates the values in
the first segment (0 to 0.5) and the second segment (0.5, 1). Internally, it is calculated
as two bias() operations applied to the first and second segments. The
visual result of the gain() function is the increase or decrease of the value’s contrast.
Here is the function followed by Figure 5.7 and Figure 5.8, which are graphs
of gain() at 0.25 and 0.85.
gain() функц (контраст ч гэж нэрлэдэг) нь өмнөхөөс шал өөр зарчимаар ажилладаг. Энэ функц нь 0, 0.5, ба 1 гэсэн утгуудыг өөрчлөхгүйгээр (0 to 0.5) болон (0.5, 1) гэсэн хоёр сегментийн хоорондох утгыг өөрчилдөг. Өөрөөр хэлбэл хоёр сегментэд ажилладаг 2 bias() функц л гэсэн үг. gain() функцын визуал үр дүн нь контрастыг ихэсгэх болон бууруулах юм. Зураг 5.7 , 5.8-д gain() функцын 0.25 болон 0.85 утгад харгалзах графикийг харуулав.
float gain(float val,g){
return 0.5 * ((val < 0.5) ? bias(2 * val,1 - g) :
(2 - bias(2 - 2 * val,1 - g)));
}
It leaves the values at 0, 0.5, and 1 unchanged and manipulates the values in
the first segment (0 to 0.5) and the second segment (0.5, 1). Internally, it is calculated
as two bias() operations applied to the first and second segments. The
visual result of the gain() function is the increase or decrease of the value’s contrast.
Here is the function followed by Figure 5.7 and Figure 5.8, which are graphs
of gain() at 0.25 and 0.85.
gain() функц (контраст ч гэж нэрлэдэг) нь өмнөхөөс шал өөр зарчимаар ажилладаг. Энэ функц нь 0, 0.5, ба 1 гэсэн утгуудыг өөрчлөхгүйгээр (0 to 0.5) болон (0.5, 1) гэсэн хоёр сегментийн хоорондох утгыг өөрчилдөг. Өөрөөр хэлбэл хоёр сегментэд ажилладаг 2 bias() функц л гэсэн үг. gain() функцын визуал үр дүн нь контрастыг ихэсгэх болон бууруулах юм. Зураг 5.7 , 5.8-д gain() функцын 0.25 болон 0.85 утгад харгалзах графикийг харуулав.
float gain(float val,g){
return 0.5 * ((val < 0.5) ? bias(2 * val,1 - g) :
(2 - bias(2 - 2 * val,1 - g)));
}
зураг 5.7
зураг 5.8
gamma()
A similar function to bias() is the gamma() function. This function is used in
gamma correction, which is the standard way images are manipulated inside the
animation and VFX industries. A deeper discussion of gamma correction will be
presented later in this chapter when we get to color math and color spaces.
A gamma() function is extremely simple and is defined as y = pow(x,1/gammavalue).
Note that this operation returns a float value, so to use it to gamma-correct a color,
you will need to apply the gamma() function to all channels of the color.
#define gamma(x,value) pow(x,1/value)
color col1 = color (0.5,0.5,0.5);
color gammacol1 = color(gamma(comp(col1,0),gammaval),
gamma(comp(col1,1),gammaval),
gamma(comp(col1,2),gammaval));
You also could use the new color array notation, available since PRMan 13.0:
color col1 = color (0.5,0.5,0.5);
color gammacol1 = color(gamma(col1[0],gammaval),
gamma(col1[1],gammaval),
gamma(col1[2],gammaval));
Figures 5.9 and 5.10 show graphs of gamma() at 0.4545 and 2.2.
gamma correction, which is the standard way images are manipulated inside the
animation and VFX industries. A deeper discussion of gamma correction will be
presented later in this chapter when we get to color math and color spaces.
A gamma() function is extremely simple and is defined as y = pow(x,1/gammavalue).
Note that this operation returns a float value, so to use it to gamma-correct a color,
you will need to apply the gamma() function to all channels of the color.
#define gamma(x,value) pow(x,1/value)
color col1 = color (0.5,0.5,0.5);
color gammacol1 = color(gamma(comp(col1,0),gammaval),
gamma(comp(col1,1),gammaval),
gamma(comp(col1,2),gammaval));
You also could use the new color array notation, available since PRMan 13.0:
color col1 = color (0.5,0.5,0.5);
color gammacol1 = color(gamma(col1[0],gammaval),
gamma(col1[1],gammaval),
gamma(col1[2],gammaval));
Figures 5.9 and 5.10 show graphs of gamma() at 0.4545 and 2.2.
зураг 5.9
зураг 5.10
compress() and expand()
Another set of very useful operations is the compress() and expand() functions.
These functions allow you to manipulate the dynamic range of the 0 to 1 values.
The output of these two operations might seem similar to using gain() to increase
or decrease the contrast of the texture. The difference lies in the fact that compress()
and expand() actually change the range of the texture, while in gain(), 0 will remain
0, and 1 will remain 1. The compress() function allows you to tighten the dynamic
range of the texture. It adheres to the following syntax:
compress(value,lo,hi)
lo represents the lowest value that will be remapped to and hi the highest. Note that
compress() will remap the lo and hi values by pushing them into a lower position
in a value graph. Here is a graphic that represents the overall action compress() will
take over the mapped values. Setting the compress() values to compress(s,0,2) will
remap a value of 1 to 2 and 0.5 to 1 while leaving 0 unchanged. Changing the values
to compress(s,0.5,1) will remap the value of 0 to 0.5 and 0.5 to 0.75 while leaving
1 untouched. Figure 5.11 is a graph of compress at (x,0,2) and (x,0.25,0.75)
while Figures 5.12 to 5.14 demonstrate the output of using compress().
float compress(float x, float lo, float hi) {
return (hi-lo) * x + lo;
}
These functions allow you to manipulate the dynamic range of the 0 to 1 values.
The output of these two operations might seem similar to using gain() to increase
or decrease the contrast of the texture. The difference lies in the fact that compress()
and expand() actually change the range of the texture, while in gain(), 0 will remain
0, and 1 will remain 1. The compress() function allows you to tighten the dynamic
range of the texture. It adheres to the following syntax:
compress(value,lo,hi)
lo represents the lowest value that will be remapped to and hi the highest. Note that
compress() will remap the lo and hi values by pushing them into a lower position
in a value graph. Here is a graphic that represents the overall action compress() will
take over the mapped values. Setting the compress() values to compress(s,0,2) will
remap a value of 1 to 2 and 0.5 to 1 while leaving 0 unchanged. Changing the values
to compress(s,0.5,1) will remap the value of 0 to 0.5 and 0.5 to 0.75 while leaving
1 untouched. Figure 5.11 is a graph of compress at (x,0,2) and (x,0.25,0.75)
while Figures 5.12 to 5.14 demonstrate the output of using compress().
float compress(float x, float lo, float hi) {
return (hi-lo) * x + lo;
}
зураг 5.11
зураг 5.12
зураг 5.13
зураг 5.14
The expand() function does the exact opposite of compress(): It remaps the values
by pushing the values to a higher position in a value graph. Applying an expand()
function with the values expand(s,0.5,1) will leave 0 and 1 untouched, remapping
0.5 to 0. This means that the value of 0 is shifted up to the position of 0.5, therefore
remapping 0.5 to 0. Using the values expand(s,0,2) will leave 0 untouched
while remapping 1 to 0.5, because the value of 1 will be shifted up to the position
of 2. Figure 5.15 shows a graph of expand at (0,2) and (0.25,0.75), while Figures
5.16 and 5.17 illustrate the effects of expand() on the s coordinates.
float expand(float x, float lo, float hi) {
float retval = 0;
if (lo == hi)
retval = x < lo ? 0 : 1;
else
retval = (x-lo) / (hi-lo);
return retval;
}
by pushing the values to a higher position in a value graph. Applying an expand()
function with the values expand(s,0.5,1) will leave 0 and 1 untouched, remapping
0.5 to 0. This means that the value of 0 is shifted up to the position of 0.5, therefore
remapping 0.5 to 0. Using the values expand(s,0,2) will leave 0 untouched
while remapping 1 to 0.5, because the value of 1 will be shifted up to the position
of 2. Figure 5.15 shows a graph of expand at (0,2) and (0.25,0.75), while Figures
5.16 and 5.17 illustrate the effects of expand() on the s coordinates.
float expand(float x, float lo, float hi) {
float retval = 0;
if (lo == hi)
retval = x < lo ? 0 : 1;
else
retval = (x-lo) / (hi-lo);
return retval;
}
зураг 5.15
зураг 5.16
зураг 5.17
remap()
The final float operation we will go over is the remap() function. This operation
can also be referred to as a fit operation because it takes the low and high values
of x (referred as a1 and b1) and fits them in a linear fashion into a new low and
high value (referred to as a2 and b2). If you use the call remap(x,0,1,1,0), the value
of x will be inverted. Setting the function to remap(x,0,1,0.25,0.75) will remap
the output to 0.25–0.75 as shown in Figure 5.18.
float remap(float x,float a1,float b1,float a2,float b2) {
return (x*(b2-a2) - a1*b2 + b1*a2) / (b1-a1);
}
can also be referred to as a fit operation because it takes the low and high values
of x (referred as a1 and b1) and fits them in a linear fashion into a new low and
high value (referred to as a2 and b2). If you use the call remap(x,0,1,1,0), the value
of x will be inverted. Setting the function to remap(x,0,1,0.25,0.75) will remap
the output to 0.25–0.75 as shown in Figure 5.18.
float remap(float x,float a1,float b1,float a2,float b2) {
return (x*(b2-a2) - a1*b2 + b1*a2) / (b1-a1);
}
зураг 5.18
trigonometry. If I had known in high school that I wanted to be a TD, I would
have paid a lot of attention. But there is no reason to panic. Although trig can
seem hard, there are only a handful of formulas and properties that you need to
remember; the rest you can always look up in a trigonometry book or a Website.
Trigonometry is all about triangles, so let’s go over some important properties of
triangles. Of course, they have three sides. Where the sides touch each other, there
is an angle. The sum of the angles of a triangle will always be 180 degrees, or /2
in radians. There are many different types of triangles, but for the study of computer
graphics the most common is the right triangle. The right triangle has one
90-degree angle; the other two angles add up to 90 degrees. The longest side of a
triangle, which is always opposite the 90-degree angle, is known as the hypotenuse.
The other two sides are known as the legs of the triangle. The well-known
Pythagorean Theorem provides us with the following formula to calculate the
hypotenuse:
This formula can be manipulated to get the length of any of the three sides of a
right triangle, as long as you have the length of the other two sides.
Now let’s move over to other key aspects of trigonometry. The terms sine, cosine,
and tangent represent the measurement of certain areas of a triangle. Each of these
values can be found using the lengths of the sides, as follows:
sin=opp/hyp
cos=adj/hyp
tan=opp/adj
In these formulas opp represents the side opposite to the angle you are calculating,
adj is the adjacent side (a side that touches the angle), and hyp is the hypotenuse,
which once again is the longest side of a right triangle. Let’s look at a simple example.
In the following formula, we will first calculate the hypotenuse, and then we
will calculate the sine, cosine, and tangent of angle A. Finally, we will calculate the
angle of A. Figure 5.19 illustrates a right triangle.
зураг 5.19
These are the basic trigonometric operations that you will use most often. Now let’s
take a look at the sine, cosine, and tangent properties. The values of sine and cosine
always range from –1 to 1, progressing in a wave-like manner as the angle or radians
increase or decrease. They are also an interval apart, so when sin(x) is equal to
1, cos(x) will be equal to 0. A tangent travels from –infinity to infinity while remaining
on a tangent to the point where it touches the sin(X) curve, when x = 0. A tangent
of a curve is a line that touches the curve on one position but never penetrates
or crosses the curve. The repetitive nature of sine and cosine make them quite useful
for texture generation. Figure 5.20 shows a graph of sine, cosine, and tangent,
while Figures 5.21 and 5.22 show how sine can be used to generate textures.
зураг 5.20
зураг 5.21 Cs = sin(s * freq)
зураг 5.22 Cs = sin(distance(point(.5,.5,.5),point(s,t,0))* freq).
One common use for these trigonometric functions is to apply rotation to a twodimensional
texture coordinate. The following preprocessor macro allows you to
rotate points x and y using ox and oy as the pivot point. It will rotate the a and b
points the given rad amount in angles. The output of the rotation is stored in the
rx and ry variables.
/* rotate2d()
* from rmannotes.sl
* 2D rotation of point (x,y) about origin (ox,oy) by an angle rad.
* The resulting point is (rx, ry).
*
*/
#define rotate2d(x,y,rad,ox,oy,rx,ry) \
rx = ((x) - (ox)) * cos(rad) - ((y) - (oy)) * sin(rad) + (ox); \
ry = ((x) - (ox)) * sin(rad) + ((y) - (oy)) * cos(rad) + (oy)
graphics. Matrices are used to represent transformations such as translations, rotations,
and scaling. Vectors are used to represent positions, directions, normals,
and sometimes even colors. We will concentrate mostly on vectors because they
are a lot more useful to a shader writer. Vector properties and operations are
important for any TD in the industry, but they are essential for a shader developer.
If there is one section in this chapter that you need to understand completely,
it is this section.
Vectors are represented by a three-element, one-dimensional array. The three values
of the vector represent the x, y, and z values. As previously stated, RenderMan
has three different data types that are represented in a very similar three element
array (points, vectors, and normals) but they hold different information. For a
point, the three values represent a position in 3D space. For a vector, they represent
a direction defined by a line that travels from the origin of the current coordinate
system and the value passed to the vector. Normals also represent a
direction, but the origin is a surface position, not the origin of the coordinate system.
Vectors also contain a length or magnitude that is defined by the distance
from the origin to the vector location.
Now let’s take a look at the most important properties of vectors and the most
useful vector operations.
Vector Addition, Subtraction, and Scaling
Once again we will start by looking at addition and subtraction, as they are the
most basic operations that can be performed. Vector addition and subtraction are
performed in an element-by-element fashion, where addition of vectors A and B
would be performed as:
Visually, the result of addition can be seen as placing one vector on the tip of the
other vector. For ease of visualization, let’s see how addition of two 2-dimensional
vectors would take place. Let’s add the vectors Q = [2 2] and T = [–3 1].
Figure 5.23 illustrates this addition.
Figure 5.24 demonstrates a subtraction of the same Q and T vectors.
independently by each of the three vector components. Vector scalar multiplication,
as well as vector addition, is a commutative operation, so multiplying
Q i is the same as i Q. Multiplying the integer i =2 by the vector Q=[2 3 1]
will result in Figure 5.25.
of a vector is a very useful operation that can be applied to a lot of different uses.
Among those uses is the normalization of a vector, which will turn the vector’s
length to 1, thus becoming a unit vector or a normalized vector. We will explain
normalization in more depth later on. To calculate the length of a vector, use the
following formula:

RSL provides a built-in function for calculating the length of a vector. This function
adheres to the syntax ln = length(vector V). Internally, it is calculated as
ln = sqrt(V.V);
The RenderMan developers decided to use a dot product operation using the vector
as both components so that the operation would be more compact. Dot products
will be reviewed later in this chapter, and you will see why you can replace
the vector to the with a dot product. Normalizing is an operation
where you take a vector of any length and turn it into a unit vector. Unit vectors
are extremely useful in CG programming and in shader programming. Several
mathematical operations are greatly simplified if the vectors used are normalized.
RSL takes this into consideration and implements certain built-in functions in a
very optimized way (such as the reflect() function) that requires the user to provide
a normalized vector to the function; otherwise, the result can be erroneous.
To normalize a vector, all you need to do is divide the value of each component
by the length of the vector. The formula would look like this:
falls within the 0 to 1 range. RSL provides a built-in function that handles normalization
quite efficiently. It follows the simple syntax normalize(vector V), which
is internally calculated as follows:
to measure the angle between two vectors (dot product) and to create a vector that
is parallel in a third dimension to the two provided vectors. Of these two, the most
useful to a shader writer is the dot product (identified in RSL with a period (.)
between two vectors). A dot product is calculated as follows:
Because of the law of cosines, the dot product between two vectors can also be
defined with this statement:
This tells us that the dot product is equal to the multiplication of the length of A
by the length of B by the cosine of the angle between the two vectors. Since most
of the time we will be dealing with normalized vectors, which by definition have
a length of 1, then we can state that A.B = cos(angle). The dot product also gives
us the cosine of the angle between two vectors. If the two vectors are parallel and
point toward each other, the angle between them is 0, and the dot product returns
1 (cosine of 0 – 1). If the vectors are perpendicular to each other with an angle of
90 degrees between them, then the dot product will be 0 (cosine of 90 = 0). Based
on these two cases, we can say that the dot product returns a facing ratio value,
where if two vectors face each other the value is 1, and as the vectors start pointing
away, the value decreases all the way to –1 when the vectors are parallel but
point in different directions. It is for this reason that this operation is constantly used in illumination functions such as a Lambertian diffuse model, which uses
the dot product of the surface normal N and the light vector L to calculate light
contribution. Figure 5.26 illustrates this concept.

of the length() function with A.A. Here is an example using
T = [2 3 2].T.T = 2 2+3 3+2 2
T.T = 17
So to calculate the length of a vector all you need to do is solve .
One final manipulation to the dot product rule allows us to calculate the angle
between the two vectors. Remember that the position of the vector is not really
important. In fact, vectors have no position, only direction, so measuring the angle
between two vectors can be done regardless of where the origin of the vector is, as
long as both vectors are defined in the same coordinate system. Using simple algebra,
we can manipulate the dot product formula to give us the angle between the
two vectors:
As you can see, the dot product operation can be used to gather a lot of information
about vectors, which is why it is so important to understand its properties.A cross product is another important operation that can be performed within two
vectors. When performing a cross product operation (identified in RSL with a ^),
the result will be a vector that is perpendicular to both supplied vectors with a
length (or magnitude) equal to the area of the parallelogram they span. What this
means is that by applying the cross product to any two vectors that are not parallel
to each other, we will get a vector that is parallel to the plane described by the
original two vectors. The most common use for this operation in computer graphics
is to find the rendering normal of surfaces and to find the normal to any given
plane in space. The vector product is calculated with the following formula:
As you can see, the surface normal of a flat grid that lies on the XY plane is always
pointing to Z. Figure 5.27 represents a cross product operation.
зураг 5.27
refractions. Reflections are the rays that bounce away from a surface based on the
point of view. A good example of reflection is a shiny surface, a mirror, and polished
metal. The rays that go through a surface and in the process are bent into a
direction vector are refractions. Common examples of objects that demonstrate
refraction are a glass of water and a piece of solid crystal such as an ashtray.
A reflection is calculated using the following formula:
Where I is the incidence vector, a vector that travels from the eye to the surface
point, and is the normalized surface normal. RSL defines the built-in function
reflect(), which has the following syntax:
vector reflect( vector I,N)
{
return I – 2 (I.N)*N;
}
You can use the output of reflect() to do an environment texture lookup with
environment() or to look up a raytraced reflection with trace(), among other
things. Figure 5.28 demonstrates a sphere using the reflect() call.
зураг 5.28
The refraction vector is another very important ray that is essential to simulate
effects such as an object made of glass or a glass of water. The refraction vector is
also referred to as the transmission vector, and in most RenderMan shaders, people
use the letter T to identify the variable. The refraction vector is somewhat
harder to compute than a reflection, but it is still doable with simple math. The
formula for refraction is as follows:
T= eta* I ? ??1- eta2*?1- ?I.N ?2?- eta*?I.N ??* N
This could be simplified by computing each term into a set of variables:
IdotN= I.N
cos1=?1- eta2?1- ?IdotN?2?
T= eta* I ? ?cos1- eta* IdotN?* N
RSL provides a refract() built-in function that can be used whenever you need
to calculate the refracted ray. The refracted ray can be used to do an environment
map lookup or in any raytracing function. The refract() function has the following
syntax:
vector refract( vector I, N; float eta )
{
float IdotN = I.N; float k = 1 - eta*eta*(1 - IdotN*IdotN);
return k < 0 ? (0,0,0) : eta*I - (eta*IdotN + sqrt(k))*N;
}
I is the incidence vector, and N is the surface normal. eta is a float value that is the
ratio of the index of refraction in the volume containing the incident vector to
that of the volume being entered, this value determines how much the ray will be
bent as it goes through the rendering surface. Figure 5.29 demonstrates the effect
of refract().
зураг 5.29
The following are the refraction indexes for common materials:
■ Vacuum 1.00000 (exactly)
■ Air (STP) 1.00029
■ Acetone 1.36
■ Alcohol 1.329
■ Crown Glass 1.52
■ Crystal 2.00
■ Diamond 2.417
■ Emerald 1.57
■ Ethyl Alcohol 1.36
■ Flourite 1.434
■ Fused Quartz 1.46
■ Heaviest Flint Glass 1.89
■ Heavy Flint Glass 1.65
■ Glass 1.5
■ Ice 1.309
■ Iodine Crystal 3.34
■ Light Flint Glass 1.575
■ Liquid Carbon Dioxide 1.20
■ Polystyrene 1.55
■ Quartz 1 1.644
■ Quartz 2 1.553
■ Ruby 1.77
■ Sapphire 1.77
■ Sodium Chloride (Salt) 1 1.544
■ Sodium Chloride (Salt) 2 1.644
■ Sugar Solution(30%) 1.38
■ Sugar Solution (80%) 1.49
■ Topaz 1.61
■ Water (20 C) 1.333
To get the proper eta value just divide 1.00029 (air IOR) by the IOR of the material,
so the eta of glass is 0.66686. Another way to calculate the refracted ray is
through the use of the fresnel() function (pronounced freh-nel). The fresnel()
function is somewhat more complicated because it allows you to calculate several
interesting values with one function. It is an essential function to re-create the
appearance of certain materials such as shiny plastics. The fresnel() function
makes a surface more reflective as the viewing ray hits the surface point at a perpendicular
angle in relation to the surface normal. The more perpendicular (or
glazing) the angle at which the ray hits the surface, the more reflective the surface
becomes. The fresnel() function has the following syntax:
void fresnel( vector I, N; float eta, Kr, Kt [; output vector R, T] )
I is the incidence vector, N is the surface normal, eta, Kr, and Kt are floats, where
eta is the index of refraction that will affect the ray. The variables Kr and Kt will
store the values that control how much the reflection (Kr) and the refraction (Kt)
will increase as the vector I hits the surface at glazing angles. The optional vector
parameters R and T allow fresnel() to return the reflection vector (R) and the refraction
or transmission vector (T). Figures 5.30 and 5.31 demonstrate the effect of
fresnel().
зураг 5.30
зураг 5.31
Trigonometry
Stepping up one level in the complexity of math concepts, we will take a look attrigonometry. If I had known in high school that I wanted to be a TD, I would
have paid a lot of attention. But there is no reason to panic. Although trig can
seem hard, there are only a handful of formulas and properties that you need to
remember; the rest you can always look up in a trigonometry book or a Website.
Trigonometry is all about triangles, so let’s go over some important properties of
triangles. Of course, they have three sides. Where the sides touch each other, there
is an angle. The sum of the angles of a triangle will always be 180 degrees, or /2
in radians. There are many different types of triangles, but for the study of computer
graphics the most common is the right triangle. The right triangle has one
90-degree angle; the other two angles add up to 90 degrees. The longest side of a
triangle, which is always opposite the 90-degree angle, is known as the hypotenuse.
The other two sides are known as the legs of the triangle. The well-known
Pythagorean Theorem provides us with the following formula to calculate the
hypotenuse:
This formula can be manipulated to get the length of any of the three sides of a
right triangle, as long as you have the length of the other two sides.
Now let’s move over to other key aspects of trigonometry. The terms sine, cosine,
and tangent represent the measurement of certain areas of a triangle. Each of these
values can be found using the lengths of the sides, as follows:
sin=opp/hyp
cos=adj/hyp
tan=opp/adj
In these formulas opp represents the side opposite to the angle you are calculating,
adj is the adjacent side (a side that touches the angle), and hyp is the hypotenuse,
which once again is the longest side of a right triangle. Let’s look at a simple example.
In the following formula, we will first calculate the hypotenuse, and then we
will calculate the sine, cosine, and tangent of angle A. Finally, we will calculate the
angle of A. Figure 5.19 illustrates a right triangle.
зураг 5.19
These are the basic trigonometric operations that you will use most often. Now let’s
take a look at the sine, cosine, and tangent properties. The values of sine and cosine
always range from –1 to 1, progressing in a wave-like manner as the angle or radians
increase or decrease. They are also an interval apart, so when sin(x) is equal to
1, cos(x) will be equal to 0. A tangent travels from –infinity to infinity while remaining
on a tangent to the point where it touches the sin(X) curve, when x = 0. A tangent
of a curve is a line that touches the curve on one position but never penetrates
or crosses the curve. The repetitive nature of sine and cosine make them quite useful
for texture generation. Figure 5.20 shows a graph of sine, cosine, and tangent,
while Figures 5.21 and 5.22 show how sine can be used to generate textures.
зураг 5.20
зураг 5.22 Cs = sin(distance(point(.5,.5,.5),point(s,t,0))* freq).
One common use for these trigonometric functions is to apply rotation to a twodimensional
texture coordinate. The following preprocessor macro allows you to
rotate points x and y using ox and oy as the pivot point. It will rotate the a and b
points the given rad amount in angles. The output of the rotation is stored in the
rx and ry variables.
/* rotate2d()
* from rmannotes.sl
* 2D rotation of point (x,y) about origin (ox,oy) by an angle rad.
* The resulting point is (rx, ry).
*
*/
#define rotate2d(x,y,rad,ox,oy,rx,ry) \
rx = ((x) - (ox)) * cos(rad) - ((y) - (oy)) * sin(rad) + (ox); \
ry = ((x) - (ox)) * sin(rad) + ((y) - (oy)) * cos(rad) + (oy)
Vector Math
Vectors and matrices are among the most used mathematical entities in computergraphics. Matrices are used to represent transformations such as translations, rotations,
and scaling. Vectors are used to represent positions, directions, normals,
and sometimes even colors. We will concentrate mostly on vectors because they
are a lot more useful to a shader writer. Vector properties and operations are
important for any TD in the industry, but they are essential for a shader developer.
If there is one section in this chapter that you need to understand completely,
it is this section.
Vectors are represented by a three-element, one-dimensional array. The three values
of the vector represent the x, y, and z values. As previously stated, RenderMan
has three different data types that are represented in a very similar three element
array (points, vectors, and normals) but they hold different information. For a
point, the three values represent a position in 3D space. For a vector, they represent
a direction defined by a line that travels from the origin of the current coordinate
system and the value passed to the vector. Normals also represent a
direction, but the origin is a surface position, not the origin of the coordinate system.
Vectors also contain a length or magnitude that is defined by the distance
from the origin to the vector location.
Now let’s take a look at the most important properties of vectors and the most
useful vector operations.
Vector Addition, Subtraction, and Scaling
Once again we will start by looking at addition and subtraction, as they are the
most basic operations that can be performed. Vector addition and subtraction are
performed in an element-by-element fashion, where addition of vectors A and B
would be performed as:
Visually, the result of addition can be seen as placing one vector on the tip of the
other vector. For ease of visualization, let’s see how addition of two 2-dimensional
vectors would take place. Let’s add the vectors Q = [2 2] and T = [–3 1].
Figure 5.23 illustrates this addition.
зураг 5.23
The same way, vector subtraction is performed element by element. Visually, vector
subtraction connects the tips of both vectors as they emanate from the origin.Figure 5.24 demonstrates a subtraction of the same Q and T vectors.
зураг 5.24
Scaling the length of a vector is achieved by multiplying an integer or a float value
by the vector. When multiplying these two entities, the integer value will be multipliedindependently by each of the three vector components. Vector scalar multiplication,
as well as vector addition, is a commutative operation, so multiplying
Q i is the same as i Q. Multiplying the integer i =2 by the vector Q=[2 3 1]
will result in Figure 5.25.
зураг 5.25
Vector Length and Normalize
Vectors are defined by direction and length (or magnitude). Calculating the lengthof a vector is a very useful operation that can be applied to a lot of different uses.
Among those uses is the normalization of a vector, which will turn the vector’s
length to 1, thus becoming a unit vector or a normalized vector. We will explain
normalization in more depth later on. To calculate the length of a vector, use the
following formula:
So the length of a vector Q=[2 4 1] and a vector T=[6 16 3] would be
RSL provides a built-in function for calculating the length of a vector. This function
adheres to the syntax ln = length(vector V). Internally, it is calculated as
ln = sqrt(V.V);
The RenderMan developers decided to use a dot product operation using the vector
as both components so that the operation would be more compact. Dot products
will be reviewed later in this chapter, and you will see why you can replace
the vector to the with a dot product. Normalizing is an operation
where you take a vector of any length and turn it into a unit vector. Unit vectors
are extremely useful in CG programming and in shader programming. Several
mathematical operations are greatly simplified if the vectors used are normalized.
RSL takes this into consideration and implements certain built-in functions in a
very optimized way (such as the reflect() function) that requires the user to provide
a normalized vector to the function; otherwise, the result can be erroneous.
To normalize a vector, all you need to do is divide the value of each component
by the length of the vector. The formula would look like this:
So if you normalize the vector T = [6 16 3] you would get
This is a vector with the exact same direction as [6 16 3] but with a length thatfalls within the 0 to 1 range. RSL provides a built-in function that handles normalization
quite efficiently. It follows the simple syntax normalize(vector V), which
is internally calculated as follows:
Dot and Cross Product
We finally arrive at what are perhaps the most used mathematical expressions in
computer graphics. These two operations are very useful because they allow youto measure the angle between two vectors (dot product) and to create a vector that
is parallel in a third dimension to the two provided vectors. Of these two, the most
useful to a shader writer is the dot product (identified in RSL with a period (.)
between two vectors). A dot product is calculated as follows:
Because of the law of cosines, the dot product between two vectors can also be
defined with this statement:
by the length of B by the cosine of the angle between the two vectors. Since most
of the time we will be dealing with normalized vectors, which by definition have
a length of 1, then we can state that A.B = cos(angle). The dot product also gives
us the cosine of the angle between two vectors. If the two vectors are parallel and
point toward each other, the angle between them is 0, and the dot product returns
1 (cosine of 0 – 1). If the vectors are perpendicular to each other with an angle of
90 degrees between them, then the dot product will be 0 (cosine of 90 = 0). Based
on these two cases, we can say that the dot product returns a facing ratio value,
where if two vectors face each other the value is 1, and as the vectors start pointing
away, the value decreases all the way to –1 when the vectors are parallel but
point in different directions. It is for this reason that this operation is constantly used in illumination functions such as a Lambertian diffuse model, which uses
the dot product of the surface normal N and the light vector L to calculate light
contribution. Figure 5.26 illustrates this concept.
зураг 5.26
Another important property of the dot product is that when you apply it to a single
vector A.A, the result will always be the square of the length of that vector. It
is for this reason that RSL replaced the from the length equation
vector A.A, the result will always be the square of the length of that vector. It
is for this reason that RSL replaced the from the length equation
of the length() function with A.A. Here is an example using
T = [2 3 2].T.T = 2 2+3 3+2 2
T.T = 17
So to calculate the length of a vector all you need to do is solve .
One final manipulation to the dot product rule allows us to calculate the angle
between the two vectors. Remember that the position of the vector is not really
important. In fact, vectors have no position, only direction, so measuring the angle
between two vectors can be done regardless of where the origin of the vector is, as
long as both vectors are defined in the same coordinate system. Using simple algebra,
we can manipulate the dot product formula to give us the angle between the
two vectors:
As you can see, the dot product operation can be used to gather a lot of information
about vectors, which is why it is so important to understand its properties.A cross product is another important operation that can be performed within two
vectors. When performing a cross product operation (identified in RSL with a ^),
the result will be a vector that is perpendicular to both supplied vectors with a
length (or magnitude) equal to the area of the parallelogram they span. What this
means is that by applying the cross product to any two vectors that are not parallel
to each other, we will get a vector that is parallel to the plane described by the
original two vectors. The most common use for this operation in computer graphics
is to find the rendering normal of surfaces and to find the normal to any given
plane in space. The vector product is calculated with the following formula:
As you can see, the surface normal of a flat grid that lies on the XY plane is always
pointing to Z. Figure 5.27 represents a cross product operation.
зураг 5.27
Reflections and Refractions
The last of the most popular operations applied to vectors are reflections andrefractions. Reflections are the rays that bounce away from a surface based on the
point of view. A good example of reflection is a shiny surface, a mirror, and polished
metal. The rays that go through a surface and in the process are bent into a
direction vector are refractions. Common examples of objects that demonstrate
refraction are a glass of water and a piece of solid crystal such as an ashtray.
A reflection is calculated using the following formula:
Where I is the incidence vector, a vector that travels from the eye to the surface
point, and is the normalized surface normal. RSL defines the built-in function
reflect(), which has the following syntax:
vector reflect( vector I,N)
{
return I – 2 (I.N)*N;
}
You can use the output of reflect() to do an environment texture lookup with
environment() or to look up a raytraced reflection with trace(), among other
things. Figure 5.28 demonstrates a sphere using the reflect() call.
зураг 5.28
The refraction vector is another very important ray that is essential to simulate
effects such as an object made of glass or a glass of water. The refraction vector is
also referred to as the transmission vector, and in most RenderMan shaders, people
use the letter T to identify the variable. The refraction vector is somewhat
harder to compute than a reflection, but it is still doable with simple math. The
formula for refraction is as follows:
T= eta* I ? ??1- eta2*?1- ?I.N ?2?- eta*?I.N ??* N
This could be simplified by computing each term into a set of variables:
IdotN= I.N
cos1=?1- eta2?1- ?IdotN?2?
T= eta* I ? ?cos1- eta* IdotN?* N
RSL provides a refract() built-in function that can be used whenever you need
to calculate the refracted ray. The refracted ray can be used to do an environment
map lookup or in any raytracing function. The refract() function has the following
syntax:
vector refract( vector I, N; float eta )
{
float IdotN = I.N; float k = 1 - eta*eta*(1 - IdotN*IdotN);
return k < 0 ? (0,0,0) : eta*I - (eta*IdotN + sqrt(k))*N;
}
I is the incidence vector, and N is the surface normal. eta is a float value that is the
ratio of the index of refraction in the volume containing the incident vector to
that of the volume being entered, this value determines how much the ray will be
bent as it goes through the rendering surface. Figure 5.29 demonstrates the effect
of refract().
зураг 5.29
The following are the refraction indexes for common materials:
■ Vacuum 1.00000 (exactly)
■ Air (STP) 1.00029
■ Acetone 1.36
■ Alcohol 1.329
■ Crown Glass 1.52
■ Crystal 2.00
■ Diamond 2.417
■ Emerald 1.57
■ Ethyl Alcohol 1.36
■ Flourite 1.434
■ Fused Quartz 1.46
■ Heaviest Flint Glass 1.89
■ Heavy Flint Glass 1.65
■ Glass 1.5
■ Ice 1.309
■ Iodine Crystal 3.34
■ Light Flint Glass 1.575
■ Liquid Carbon Dioxide 1.20
■ Polystyrene 1.55
■ Quartz 1 1.644
■ Quartz 2 1.553
■ Ruby 1.77
■ Sapphire 1.77
■ Sodium Chloride (Salt) 1 1.544
■ Sodium Chloride (Salt) 2 1.644
■ Sugar Solution(30%) 1.38
■ Sugar Solution (80%) 1.49
■ Topaz 1.61
■ Water (20 C) 1.333
To get the proper eta value just divide 1.00029 (air IOR) by the IOR of the material,
so the eta of glass is 0.66686. Another way to calculate the refracted ray is
through the use of the fresnel() function (pronounced freh-nel). The fresnel()
function is somewhat more complicated because it allows you to calculate several
interesting values with one function. It is an essential function to re-create the
appearance of certain materials such as shiny plastics. The fresnel() function
makes a surface more reflective as the viewing ray hits the surface point at a perpendicular
angle in relation to the surface normal. The more perpendicular (or
glazing) the angle at which the ray hits the surface, the more reflective the surface
becomes. The fresnel() function has the following syntax:
void fresnel( vector I, N; float eta, Kr, Kt [; output vector R, T] )
I is the incidence vector, N is the surface normal, eta, Kr, and Kt are floats, where
eta is the index of refraction that will affect the ray. The variables Kr and Kt will
store the values that control how much the reflection (Kr) and the refraction (Kt)
will increase as the vector I hits the surface at glazing angles. The optional vector
parameters R and T allow fresnel() to return the reflection vector (R) and the refraction
or transmission vector (T). Figures 5.30 and 5.31 demonstrate the effect of
fresnel().
зураг 5.30
зураг 5.31
No comments:
Post a Comment