46.htm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <!--
  2. demo.design 3D programming FAQ
  3. Idea, texts, screenshots:
  4. Andrew A. Aksyonoff,
  5. shodan@chat.ru
  6. Web-design, illustrations:
  7. Andrey Samoilov,
  8. asy@sense.simbirsk.su
  9. -->
  10. <html>
  11. <head>
  12. <title>demo.design 3D programming FAQ. Текстурирование. Мипмэппинг.</title>
  13. <link rel=stylesheet href="../style.css" type="text/css">
  14. </head>
  15. <script language="javascript">
  16. <!--//
  17. browser = navigator.appName;
  18. version = parseFloat(navigator.appVersion);
  19. if (browser == "Netscape" && version >= 3.0) { jsenabled = 1; } else
  20. if (browser == "Microsoft Internet Explorer" && version >= 3.0) { jsenabled = 1; } else { jsenabled = 0; }
  21. function swap(img,ref) { if (jsenabled) {document.images[img].src = ref;} }
  22. function loadtocache(img,ref) { cache[img] = new Image(); cache[img].src = ref; }
  23. if (jsenabled) {
  24. cache = new Array();
  25. loadtocache(0,"../img/xdl.gif");
  26. loadtocache(1,"../img/xfaq.gif");
  27. loadtocache(2,"../img/xlinks.gif");
  28. loadtocache(3,"../img/xauthor.gif");
  29. loadtocache(4,"../img/xe.gif");
  30. loadtocache(5,"../img/xprev.gif");
  31. loadtocache(6,"../img/xnext.gif");}
  32. //-->
  33. </script>
  34. <body bgcolor=white><center>
  35. <!-- Title -->
  36. <img src="../img/b.gif" width=500 height=1 alt=""><br>
  37. <img src="../img/t.gif" width=500 height=1 alt=""><br>
  38. <img src="../img/b.gif" width=500 height=1 alt=""><br>
  39. <img src="../img/t.gif" width=500 height=2 alt=""><br>
  40. <table width=500 cellpadding=0 cellspacing=0 border=0>
  41. <td><img src="../img/t.gif" width=5 height=1 alt=""><a href="../main.htm" onmouseover="swap('logo','../img/xe.gif');" onmouseout="swap('logo','../img/e.gif');"><img src="../img/e.gif" name=logo width=60 height=50 hspace=10 border=0 alt=" в самое начало "></a></td>
  42. <td><p class=pagetitle><img src="../img/t.gif" width=265 height=1 alt=""><br>demo.design<br>3D programming FAQ</td>
  43. <td align=center><p class=navy><a href="../download.htm" onmouseover="swap('dl','../img/xdl.gif');" onmouseout="swap('dl','../img/dl.gif');"><img src="../img/dl.gif" name=dl width=40 height=40 border=0 hspace=5 alt=" download "></a><br>download</td>
  44. <td align=center><p class=navy><a href="../links.htm" onmouseover="swap('links','../img/xlinks.gif');" onmouseout="swap('links','../img/links.gif');"><img src="../img/links.gif" name=links width=40 height=40 border=0 hspace=5 alt=" коллекция линков "></a><br>links</td>
  45. <td align=center><p class=navy><a href="../author.htm" onmouseover="swap('author','../img/xauthor.gif');" onmouseout="swap('author','../img/author.gif');"><img src="../img/author.gif" name=author width=40 height=40 border=0 hspace=5 alt=" автора! "></a><br>author</td>
  46. </table>
  47. <img src="../img/t.gif" width=500 height=4 alt=""><br><img src="../img/b.gif" width=500 height=1 alt=""><br>
  48. <!-- Head -->
  49. <table width=500 cellpadding=0 cellspacing=10 border=0><td><div align=justify>
  50. <p class=title>
  51. <img src="../img/b4.gif" width=70 height=70 align=left hspace=0 alt="">
  52. <img src="../img/t.gif" width=5 height=70 align=left hspace=0 alt="">
  53. ТЕКСТУРИРОВАНИЕ<br>4.6. Мипмэппинг
  54. <!-- Article -->
  55. <p>Если полигон относительно сильно удален или повернут, так, что соседним
  56. пикселам на экране соотвествуют сильно разнесенные точки текстуры, то
  57. возникают всякие неприятные артефакты - можно считать, что потому, что при
  58. текстурировании мы выбираем лишь какую-то одну точку текстуры, а реально в
  59. экранный пиксел будет проецироваться несколько текселов (точек текстуры).
  60. Вообще идеальным методом было бы следующее: провести до пересечения с гранью
  61. 3D-пирамиду с вершиной в камере и основанием-пикселом, выбрать все точки
  62. текстуры, попадающие в наш пиксел, и усреднить значения их цветов. Вот
  63. только вычислительные затраты на одну точку в этом случае окажутся просто
  64. фантастическими.
  65. <p>Поэтому для удаления артефактов используется значительно более простая вещь,
  66. а именно мипмэппинг. Идея, как обычно, проста. Для каждой текстуры заранее
  67. создается несколько ее копий уменьшенного размера (1/2, 1/4, и так далее),
  68. а далее при текстурировании используется либо сама текстура, либо подходящая
  69. уменьшенная копия. Памяти при этом расходуется на 25-33% больше, чем без
  70. мипмэппинга, но зато, вроде бы, увеличивается качество изображения.
  71. <p>Как создать уменьшенную в два раза копию текстуры? Здесь мы опишем три
  72. метода, два из них очевидны, третий позаимствован у Crystal Space. Методы
  73. расположены в порядке уменьшения скорости и увеличения качества уменьшенной
  74. текстуры.
  75. <p>Метод 1. Выкинуть все пикселы текстуры с нечетными координатами. Самый
  76. простой, самый быстрый, но дает не очень хорошо выглядящие результаты.
  77. <p>Метод 2. Оставить точки с четными координатами, в каждой точке усреднить
  78. значения цвета в этой точке и ее трех соседях (справа, снизу и справа-снизу).
  79. <p>Метод 3. Оставить точки с четными координатами, использовав в каждой точке
  80. фильтр, заданный вот такой матрицей:
  81. <pre class=formula>
  82. [ 1 2 1 ]
  83. 1/16 * [ 2 4 2 ]
  84. [ 1 2 1 ]
  85. </pre>
  86. <p>В виде формул для каждой из компонент цвета точки уменьшенной в два раза
  87. копии текстуры эти методы запишутся, соответственно, так:
  88. <pre class=source>
  89. mip1[x][y] = tex[2*x][2*y]; // метод 1
  90. mip2[x][y] = ( // метод 2
  91. tex[2*x ][2*y ] +
  92. tex[2*x+1][2*y ] +
  93. tex[2*x ][2*y+1] +
  94. tex[2*x+1][2*y+1]) / 4;
  95. mip3[x][y] = ( // метод 3
  96. 1 * tex[2*x-1][2*y-1] +
  97. 2 * tex[2*x ][2*y-1] +
  98. 1 * tex[2*x+1][2*y-1] +
  99. 2 * tex[2*x-1][2*y ] +
  100. 4 * tex[2*x ][2*y ] +
  101. 2 * tex[2*x+1][2*y ] +
  102. 1 * tex[2*x-1][2*y+1] +
  103. 2 * tex[2*x ][2*y+1] +
  104. 1 * tex[2*x+1][2*y+1]) / 16;
  105. </pre>
  106. <p>Последовательно применяя любой из описанных методов, мы можем построить набор
  107. уменьшенных текстур. Остается выяснить, какую именно из них надо выбрать при
  108. текстурировании. Здесь опять будет описано два достаточно простых метода; а
  109. вообще, конечно, их можно придумать значительно больше.
  110. <p>Метод 1: полигональный мипмэппинг. В этом случае мы считаем площадь полигона
  111. на экране в пикселах и его же площадь в текстуре в текселах (последнюю обычно
  112. можно посчитать заранее), определяем по ним примерное количество пикселов,
  113. соотвествующих одному пикселу и выбираем нужный уровень уменьшения текстуры
  114. по следующей формуле:
  115. <pre class=source>miplevel = floor(log2(screenArea / textureArea) / 2);</pre>
  116. <p>здесь<br>
  117. <p><table width=480 cellpadding=0 cellspacing=0 border=0 align=center>
  118. <tr><td><p class=expression>screenArea&nbsp;</td><td>площадь грани на экране (в пикселах)</tr>
  119. <tr><td><p class=expression>textureArea&nbsp;</td><td>площадь грани в текстуре (в текселах)</tr>
  120. <tr><td><p class=expression>log2()&nbsp;</td><td>функция двоичного логарифма (для Watcom C стандартная)</tr>
  121. <tr><td valign=top><p class=expression>miplevel&nbsp;</td><td>уровень уменьшения; выбираемая текстура должна быть
  122. сжата по обеим осям в (2^miplevel) раз</tr>
  123. </table>
  124. <p>Поскольку бесконечное количество уменьшенных копий текстуры никто хранить
  125. не будет, да и увеличенные текстуры тоже обычно не хранят, а miplevel может
  126. получится любым действительным числом, надо, конечно, поставить заглушку:
  127. <pre class=source>
  128. miplevel = floor(log2(screenArea / textureArea) / 2);
  129. if (miplevel < 0) miplevel = 0;
  130. if (miplevel > MAXMIPLEVEL) miplevel = MAXMIPLEVEL;
  131. </pre>
  132. <p>screenArea и textureArea проще всего, по-моему, посчитать по формуле Герона
  133. для площади треугольника:
  134. <pre class=source>
  135. // a, b, c - стороны треугольника; p - периметр
  136. a = sqrt((v2.sx-v1.sx)*(v2.sx-v1.sx) + (v2.sy-v1.sy)*(v2.sy-v1.sy));
  137. b = sqrt((v3.sx-v1.sx)*(v3.sx-v1.sx) + (v3.sy-v1.sy)*(v3.sy-v1.sy));
  138. c = sqrt((v3.sx-v2.sx)*(v3.sx-v2.sx) + (v3.sy-v2.sy)*(v3.sy-v2.sy));
  139. p = (a + b + c);
  140. screenArea = sqrt(p * (p-a) * (p-b) * (p-c));
  141. a = sqrt((v2.u-v1.u)*(v2.u-v1.u) + (v2.v-v1.v)*(v2.v-v1.v));
  142. b = sqrt((v3.u-v1.u)*(v3.u-v1.u) + (v3.v-v1.v)*(v3.v-v1.v));
  143. c = sqrt((v3.u-v2.u)*(v3.u-v2.u) + (v3.v-v2.v)*(v3.v-v2.v));
  144. p = (a + b + c);
  145. textureArea = sqrt(p * (p-a) * (p-b) * (p-c));
  146. </pre>
  147. <p>Этот метод практически не требует вычислительных затрат, так как все операции
  148. проделываются один раз на грань. С другой стороны, здесь использутся один и
  149. тот же уровень уменьшения (он же уровень детализации, LOD, level of detail)
  150. для всего полигона, а разным пикселам может соответствовать разное количество
  151. текселов. Есть и более неприятное следствие - уровни уменьшения для двух
  152. соседних полигонов меняются скачком, а это не очень хорошо выглядит.
  153. <p>Метод 2: попиксельный мипмэппинг. В этом случае нужный уровень уменьшения
  154. считается для каждого пиксела и выбирается на основе максимального шага в
  155. текстуре из соответствующих переходу к соседнему пикселу:
  156. <pre class=source>
  157. textureStep = max(
  158. sqrt(dudx * dudx + dvdx * dvdx),
  159. sqrt(dudy * dudy + dvdy * dvdy));
  160. miplevel = floor(log2(textureStep));
  161. </pre>
  162. <p>Подобную операцию для каждого пиксела проводить, конечно, накладно. Но при
  163. аффинном текстурировании dudx, dvdx, dudy и dvdy постоянны для всех пикселов,
  164. так что попиксельный мэппинг становится полигонным, только с другой методикой
  165. расчета уровня уменьшения. Для перспективно-корректного же текстурирования
  166. dudx, dvdx, dudy и dvdy постоянны для всех пикселов одного кусочка (span'а),
  167. так что уровень уменьшения считается раз в несколько пикселов.
  168. <p>Впрочем, даже раз в несколько пикселов подобное (два корня и один логарифм)
  169. считать будет достаточно медленно. Поэтому займемся небольшой оптимизацией:
  170. во-первых, для скорости можно сделать упрощение и считать, что
  171. <pre class=source>textureStep = sqrt(dudx * dudx + dvdx * dvdx);</pre>
  172. <p>Далее, заметим, что log2(sqrt(x)) = log2(x) / 2, откуда
  173. <pre class=source>miplevel = floor(log2(dudx * dudx + dvdx * dvdx) / 2);</pre>
  174. <p>Осталась, практически, одна трудоемкая операция - взятие логарифма. Но и ее
  175. можно убрать. Дело в том, что числа с плавающей запятой (float'ы) как раз и
  176. хранятся в логарифмической форме, и floor(log2(x)) можно посчитать вот так:
  177. <pre class=source>
  178. float x;
  179. int floor_log2_x;
  180. x = 123456;
  181. floor_log2_x = ((*((int*)&x)) - (127 << 23)) >> 23; // чистый C
  182. floor_log2_x = (((int&)x) - (127 << 23)) >> 23; // C++
  183. </pre>
  184. <p>Соответственно, floor(log2(sqrt(x))) = floor(log2(x) / 2) считаем как
  185. <pre class=source>
  186. miplevel = ((*((int*)&x)) - (127 << 23)) >> 24; // чистый C
  187. miplevel = (((int&)x) - (127 << 23)) >> 24; // C++
  188. </pre>
  189. <p>Естественно, что этот трюк можно применить и в случае полигонного мипмэпинга
  190. для полного устранения всяческих медленых операций типа sqrt(), log2(). Вот,
  191. в общем-то, и все.
  192. </div>
  193. </td></table>
  194. <!-- Bottom Navigation -->
  195. <img src="../img/b.gif" width=500 height=1 alt=""><br><img src="../img/t.gif" width=500 height=2 alt=""><br>
  196. <table width=500 cellpadding=0 cellspacing=0 border=0>
  197. <td><img src="../img/t.gif" width=5 height=1 alt=""><a href="../main.htm" onmouseover="swap('logo2','../img/xe.gif');" onmouseout="swap('logo2','../img/e.gif');"><img src="../img/e.gif" name=logo2 width=60 height=50 hspace=10 border=0 alt=" в самое начало "></a></td>
  198. <td><p class=pagetitle><img src="../img/t.gif" width=265 height=1 alt=""><br>demo.design<br>3D programming FAQ</td>
  199. <td align=center><p class=navy><a href="45.htm" onmouseover="swap('prev','../img/xprev.gif');" onmouseout="swap('prev','../img/prev.gif');"><img src="../img/prev.gif" name=prev width=40 height=40 border=0 hspace=5 alt=" предыдущая статья "></a><br>previous</td>
  200. <td align=center><p class=navy><a href="../content.htm" onmouseover="swap('faq','../img/xfaq.gif');" onmouseout="swap('faq','../img/faq.gif');"><img src="../img/faq.gif" name=faq width=40 height=40 border=0 hspace=5 alt=" содержание "></a><br>content</td>
  201. <td align=center><p class=navy><a href="51.htm" onmouseover="swap('next','../img/xnext.gif');" onmouseout="swap('next','../img/next.gif');"><img src="../img/next.gif" name=next width=40 height=40 border=0 hspace=5 alt=" следующая статья "></a><br>next</td>
  202. </table>
  203. <img src="../img/t.gif" width=500 height=4 alt=""><br>
  204. <img src="../img/b.gif" width=500 height=1 alt=""><br>
  205. <img src="../img/t.gif" width=500 height=1 alt=""><br>
  206. <img src="../img/b.gif" width=500 height=1 alt=""><br>
  207. </center></body>
  208. </html>