<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Olga Khusainova</title><author><name>Olga Khusainova</name></author><id>https://teletype.in/atom/khusainova</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/khusainova?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@khusainova?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=khusainova"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/khusainova?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-06-24T14:15:41.645Z</updated><entry><id>khusainova:qrfN2NsIPJT</id><link rel="alternate" type="text/html" href="https://teletype.in/@khusainova/qrfN2NsIPJT?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=khusainova"></link><title>RGBM-кодировка цвета</title><published>2023-03-15T19:25:34.077Z</published><updated>2023-03-15T19:25:34.077Z</updated><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/4d/10/4d108452-5518-4066-bfa9-1de51d165147.png&quot;&gt;Хочется хранить цвета в вершинах как byte4, но значения у них могут быть сильно выше единицы. Следовательно, нужно как-то нормализовать.
Можно просто поделить на условную максимальную яркость и затем умножить в шейдере.
А можно использовать более хитрую кодировку в rgbm. Преимущество rgbm в том, что чем меньше значения цвета, тем меньше разница между оригиналом и декодированным значением, ошибка растет пропорционально яркости.</summary><content type="html">
  &lt;p id=&quot;GM89&quot;&gt;Хочется хранить цвета в вершинах как byte4, но значения у них могут быть сильно выше единицы. Следовательно, нужно как-то нормализовать.&lt;br /&gt;Можно просто поделить на условную максимальную яркость и затем умножить в шейдере.&lt;br /&gt;А можно использовать более хитрую кодировку в rgbm. Преимущество rgbm в том, что чем меньше значения цвета, тем меньше разница между оригиналом и декодированным значением, ошибка растет пропорционально яркости.&lt;/p&gt;
  &lt;figure id=&quot;LXCg&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/4d/10/4d108452-5518-4066-bfa9-1de51d165147.png&quot; width=&quot;604&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;eVx4&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a2/a6/a2a6d014-710f-4de0-ad23-292dd8535efd.png&quot; width=&quot;606&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;SnUv&quot;&gt;Кодировка цвета в скрипте&lt;/p&gt;
  &lt;pre id=&quot;Iwlf&quot;&gt;private static float MAX_BRIGHTNESS = 60;
private static Byte4 ColorToRGBM(Color color) {   
    float y = color.maxColorComponent;   
    y = math.clamp(math.ceil(y * 255 / MAX_BRIGHTNESS), 1, 255);   
    color *= 255 * 255 / (y * MAX_BRIGHTNESS);   
    Byte4 i = new Byte4((byte)color.r, (byte)color.g, (byte)color.b, (byte)y);   
    return i;
}&lt;/pre&gt;
  &lt;p id=&quot;OkWn&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;OD56&quot;&gt;Декодировка в шейдере:&lt;/p&gt;
  &lt;pre id=&quot;5Ls2&quot;&gt;#define MAX_BRIGHTNESS 60
half3 DecodeColor(half4 data) {        
    return data.rgb * data.a * MAX_BRIGHTNESS;
}&lt;/pre&gt;
  &lt;p id=&quot;kboQ&quot; data-align=&quot;right&quot;&gt;&lt;a href=&quot;https://t.me/lab_sborki&quot; target=&quot;_blank&quot;&gt;https://t.me/lab_sborki&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>khusainova:RLXTMHMuH3j</id><link rel="alternate" type="text/html" href="https://teletype.in/@khusainova/RLXTMHMuH3j?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=khusainova"></link><title>Blizzard BlendAdd shading</title><published>2022-09-02T13:13:24.760Z</published><updated>2023-02-27T06:03:23.298Z</updated><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/83/6b/836b6b61-2942-45a1-8eaf-b3333720546e.png&quot;&gt;В этом занимательном видео начиная с 19:45 разработчик из Близзард рассказывает о способе рисовать эффекты так, чтобы они хорошо выглядели и в темном и в светлом окружении, не уходя слишком в белый, но и не теряя свечения. Этот способ они называют BlendAdd. </summary><content type="html">
  &lt;p id=&quot;xrdQ&quot;&gt;В этом занимательном &lt;a href=&quot;https://www.youtube.com/watch?v=YPy2hytwDLM&quot; target=&quot;_blank&quot;&gt;видео &lt;/a&gt;начиная с 19:45 разработчик из Близзард рассказывает о способе рисовать эффекты так, чтобы они хорошо выглядели и в темном и в светлом окружении, не уходя слишком в белый, но и не теряя свечения. Этот способ они называют &lt;em&gt;BlendAdd&lt;/em&gt;. &lt;/p&gt;
  &lt;p id=&quot;XyOl&quot;&gt;Идея состоит в том, что  в &lt;em&gt;rgb &lt;/em&gt;мы храним изображение эффекта на черном фоне, а альфа-канал определяет насколько аддитивно, а насколько через бленд должно накладываться изображение. &lt;em&gt;1&lt;/em&gt; - полностью через &lt;em&gt;blend&lt;/em&gt;, &lt;em&gt;0&lt;/em&gt; - полностью через &lt;em&gt;add&lt;/em&gt;. &lt;/p&gt;
  &lt;p id=&quot;tuGC&quot;&gt;Но как же это конкретно реализовать?&lt;/p&gt;
  &lt;p id=&quot;WMz6&quot;&gt;Запишем выражение и раскроем скобочки:&lt;/p&gt;
  &lt;figure id=&quot;JWhZ&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/83/6b/836b6b61-2942-45a1-8eaf-b3333720546e.png&quot; width=&quot;649&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;pk6V&quot;&gt;Осталось воплотить эту формулу через параметры бленда и операции в шейдере. К &lt;em&gt;dst &lt;/em&gt;тут явно просится &lt;em&gt;OneMinusSrcAlpha&lt;/em&gt;, но для соответствия в альфа-канал фрагмента надо записать квадрат альфы. A для &lt;em&gt;src &lt;/em&gt;установим &lt;em&gt;One &lt;/em&gt;и прямо в шейдере домножим на &lt;em&gt;(1 - alpha + alpha^2)&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;lLfN&quot;&gt;Shader &amp;quot;EffectBlendAdd&amp;quot; {
   ...
   SubShader {
       ...
       Blend One OneMinusSrcAlpha
       ...
       
       Pass {   
           HLSLPROGRAM
           ...
           half4 Fragment() {
              ...
               output.rgb *= 1 - a + a * a;
               output.a = a * a;
               return outputColor;
           }
           ENDHLSL
       }
           
   }
}&lt;/pre&gt;
  &lt;p id=&quot;A6OV&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Qpjb&quot; data-align=&quot;right&quot;&gt;&lt;a href=&quot;https://t.me/lab_sborki&quot; target=&quot;_blank&quot;&gt;https://t.me/lab_sborki&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>khusainova:dTgCDEpJ89R</id><link rel="alternate" type="text/html" href="https://teletype.in/@khusainova/dTgCDEpJ89R?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=khusainova"></link><title>Генерация случайных точек на поверхности мешей с помощью линий</title><published>2022-08-26T12:17:24.542Z</published><updated>2023-02-27T06:14:36.164Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/ac/82/ac82b98a-aa61-4693-baea-78c4930de1a9.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/64/82/6482b55d-1d7c-4071-bdec-3f952e6072e4.png&quot;&gt;В этом посте речь пойдет о способе покрыть случайными равномерно-распределенными точками только видимые поверхности сцены, исключая поверхности внутри пересечений мешей.  </summary><content type="html">
  &lt;p id=&quot;FG26&quot;&gt;В этом посте речь пойдет о способе покрыть случайными равномерно-распределенными точками только видимые поверхности сцены, исключая поверхности внутри пересечений мешей.  &lt;/p&gt;
  &lt;p id=&quot;WzWt&quot;&gt;Метод можно погуглить как &amp;quot;Sampling with Global Lines&amp;quot; или &amp;quot;Sampling with Uniformly Distributed Lines&amp;quot;. Есть несколько интересных вариаций, расскажу о самой простой.&lt;/p&gt;
  &lt;p id=&quot;k7sl&quot;&gt;Итак:&lt;/p&gt;
  &lt;ol id=&quot;RVDj&quot;&gt;
    &lt;li id=&quot;bhYt&quot;&gt;Формируем баунд-сферу вокруг геометрии сцены.&lt;/li&gt;
    &lt;li id=&quot;8UNT&quot;&gt;Берем две случайные точки на поверхности баунд-сферы и делаем из них линию.&lt;/li&gt;
    &lt;li id=&quot;U9O4&quot;&gt;Находим точки пересечения линии с геометрией уровня. Это и будут наши искомые точки на поверхности.  &lt;/li&gt;
    &lt;li id=&quot;0zkg&quot;&gt;Повторяем 2-4.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;figure id=&quot;BSLH&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/64/82/6482b55d-1d7c-4071-bdec-3f952e6072e4.png&quot; width=&quot;543.1193181818182&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VWXm&quot;&gt;Если все меши на сцене замкнутые, то  исключить точки внутри пересечений мешей довольно легко, подсчитывая число входов и выходов луча в поверхность по ходу рейкаста.&lt;/p&gt;
  &lt;figure id=&quot;5164&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/35/0d/350d1024-cc4c-4c1c-8b86-a10286b9251d.png&quot; width=&quot;542.0359712230216&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;oWVu&quot;&gt;Результат такой же, как и в &lt;a href=&quot;https://teletype.in/@khusainova/duhWjKHJJv4&quot; target=&quot;_blank&quot;&gt;методе inverse cdf&lt;/a&gt;,  с той лишь разницей, что мы исключили невидимые точки&lt;/p&gt;
  &lt;figure id=&quot;wUbl&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/19/77/19774633-11b7-40a7-a49b-0d4c365665e1.png&quot; width=&quot;549&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;OjpL&quot; data-align=&quot;right&quot;&gt;&lt;a href=&quot;https://t.me/lab_sborki&quot; target=&quot;_blank&quot;&gt;https://t.me/lab_sborki&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>khusainova:duhWjKHJJv4</id><link rel="alternate" type="text/html" href="https://teletype.in/@khusainova/duhWjKHJJv4?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=khusainova"></link><title>Генерация случайных точек на поверхности меша</title><published>2022-06-20T10:21:02.641Z</published><updated>2023-02-27T06:14:15.246Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/8e/91/8e912a5d-c959-4f66-ad61-903fef824912.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/d6/3d/d63de89b-738e-4cb4-82ad-4b763d4773a3.png&quot;&gt;Задача: сгенерировать N равномерно распределенных точек по поверхности произвольного триангулированного меша.</summary><content type="html">
  &lt;p id=&quot;2x2C&quot;&gt;Задача: сгенерировать N равномерно распределенных точек по поверхности произвольного триангулированного меша.&lt;/p&gt;
  &lt;figure id=&quot;4phF&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d6/3d/d63de89b-738e-4cb4-82ad-4b763d4773a3.png&quot; width=&quot;426.712962962963&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;dRxr&quot;&gt;Общая идея решения: выбираем треугольник с вероятностью, пропорциональной его площади, и уже внутри выбранного треугольника берем равномерно распределенную случайную точку. &lt;/p&gt;
  &lt;h3 id=&quot;8ybq&quot;&gt;Как выбрать треугольник с вероятностью, пропорциональной его площади? &lt;/h3&gt;
  &lt;p id=&quot;Eqbi&quot;&gt;Воспользуемся методом, который называется inverse cdf sampling. Подробно можно почитать &lt;a href=&quot;https://www.pbr-book.org/3ed-2018/Monte_Carlo_Integration/Sampling_Random_Variables&quot; target=&quot;_blank&quot;&gt;здесь.&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;obK5&quot;&gt;Представим, что у нас есть случайная величина и она может принимать 4 возможных дискретных значения  &lt;em&gt;v1, v2, v3 &lt;/em&gt;и&lt;em&gt; v4&lt;/em&gt; с вероятностями &lt;em&gt;p1, p2, p3 &lt;/em&gt;и&lt;em&gt; p4 &lt;/em&gt;соответственно.  &lt;/p&gt;
  &lt;p id=&quot;xPBv&quot; data-align=&quot;center&quot;&gt;&lt;em&gt;p1 + p2 + p3 + p4 = 1&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;IugK&quot;&gt;Сконструируем дискретную функцию cdf, где&lt;em&gt; cdf[i] = p0 + p1 + ... + pi. &lt;/em&gt;&lt;/p&gt;
  &lt;figure id=&quot;Yqdr&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/39/2c/392c9be4-e36b-4543-a918-2dacd9a9524f.png&quot; width=&quot;769.2035398230089&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;we4y&quot;&gt;И теперь используя cdf мы можем замапить равномерно распределенную случайную величину на наши дискретные случайные величины&lt;em&gt; v0, v1, v2, v3&lt;/em&gt; с соответствующими вероятностями&lt;/p&gt;
  &lt;pre id=&quot;bxu0&quot;&gt;/// &amp;lt;summary&amp;gt; Inverse cdf sampling &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;cumulativeProbabilities&amp;quot;&amp;gt;дискретная cdf. 
/// cumulativeProbabilities[0] - p0, где p0 - вероятность, что выпадет случайная величина с индексом 0 
/// cumulativeProbabilities[1] - p0 + p1, где p1 - вероятность, что выпадет случайная величина с индексом 1 
/// cumulativeProbabilities[2] - p0 + p1 + p2, где p2 - вероятность, что выпадет случайная величина с индексом 2 
/// ...
/// cumulativeProbabilities[cumulativeProbabilities.Length - 1] = p0 + p1 + p2 + ... + pLast = 1&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;randomValue&amp;quot;&amp;gt;равномерно распределенная случайная величина&amp;lt;/param&amp;gt;
/// &amp;lt;returns&amp;gt;индекс выпавшей случайной величины&amp;lt;/returns&amp;gt;
public static int InverseSample(float[] cumulativeProbabilities, float randomValue) {
   int maxIndex = cumulativeProbabilities.Length - 1;   
   int minIndex = 0;   
      
   for (int i = 0; i &amp;lt; cumulativeProbabilities.Length; i++) {      
       int index = minIndex + (int)(0.5f * (maxIndex - minIndex));      
       var pr = cumulativeProbabilities[index];      
       if (randomValue &amp;lt;= pr) {         
           maxIndex = index;      
       } else {         
           minIndex = index + 1;      
       }      
       if (minIndex == maxIndex) return maxIndex;             
   }
   
   throw new Exception(&amp;quot;Something was wrong&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;rJjp&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;6ELK&quot;&gt;Теперь остается сконструировать &lt;em&gt;cumulativeProbabilities &lt;/em&gt;для наших треугольников и, используя обычный &lt;em&gt;random &lt;/em&gt;и метод &lt;em&gt;InverseSample&lt;/em&gt;, получать индексы треугольников с вероятностью, пропорциональной их площади.&lt;/p&gt;
  &lt;pre id=&quot;JWkO&quot;&gt;float totalArea = 0;
for (var i = 0; i &amp;lt; triangles.Length; i++)
{    
    var triangle = triangles[i];    
    var triangleArea = CalculateTriangleArea(triangle);
    totalArea += triangleArea;
}  
var cumulativeProbabilities = new float[triangles.Length];
float probabilitySum = 0;
for (var i = 0; i &amp;lt; triangles.Length; i++)
{    
    var triangle = triangles[i];    
    var triangleArea = CalculateTriangleArea(triangle);
    var probability = triangleArea /totalArea;
    probabilitySum += probability;
    cumulativeProbabilities[i] = probabilitySum;    
}  
var triangleRandom = new Random(seed);
for (int i = 0; i &amp;lt; N; i++) {
    var index = InverseSample(cumulativeProbabilities, triangleRandom.NextFloat());
    var trianlge = triangles[index];
    ...
}
         &lt;/pre&gt;
  &lt;h3 id=&quot;W04a&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;Nsyy&quot;&gt;Как сгенерировать равномерно распределенную случайную точку внутри треугольника&lt;/h3&gt;
  &lt;pre id=&quot;m3u5&quot;&gt;float3 v0 = triangle.v0; // 1я вершина
float3 v1 = triangle.v1; // 2я вершина
float3 v2 = triangle.v2; // 3я вершина

var random = new Random(seed);
var r1 = random.NextValue();
var r2 = random.NextValue();
float sqrtR1 = math.sqrt(r1);
// Получим барицентрические координаты
w0 = 1 - sqrtR1;
w1 = (1 - r2) * sqrtR1;
w2 = r2 * sqrtR1;

var randomPoint = w0*v0 + w1*v1 + w2*v2; //Результат&lt;/pre&gt;
  &lt;p id=&quot;A7R9&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;8yJM&quot; data-align=&quot;right&quot;&gt;&lt;a href=&quot;https://t.me/lab_sborki&quot; target=&quot;_blank&quot;&gt;https://t.me/lab_sborki&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>khusainova:OSs7zrhdxsH</id><link rel="alternate" type="text/html" href="https://teletype.in/@khusainova/OSs7zrhdxsH?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=khusainova"></link><title>Трансформация нормалей</title><published>2022-05-26T12:29:55.091Z</published><updated>2023-02-27T06:04:26.766Z</updated><summary type="html">Для трансформации нормалей на объекте с отрицательным скейлом метод Unity transform.TransformDirection()  дает некорректный результат. Вместо него нужно использовать более дорогой, но правильный код</summary><content type="html">
  &lt;p id=&quot;ssG2&quot;&gt;Для трансформации нормалей на объекте с отрицательным скейлом метод Unity transform.TransformDirection()  дает некорректный результат. Вместо него нужно использовать более дорогой, но правильный код&lt;/p&gt;
  &lt;pre id=&quot;s7L1&quot;&gt;public static Vector3 TransformDir(Transform transform, Vector3 dir){
    var matrix = transform.localToWorldMatrix;    
    var matrixInverse = matrix.inverse;    
    var matrixTranspose = matrixInverse.transpose;    
    var n = (Vector3) (matrixTranspose * dir);    
    return n;
}&lt;/pre&gt;
  &lt;p id=&quot;0Gm7&quot;&gt;Подробное объяснение в главе 4.5 книги&lt;a href=&quot;https://www.mathfor3dgameprogramming.com/&quot; target=&quot;_blank&quot;&gt; Mathematics for 3D Game Programming and Computer Graphics&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;7O7N&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;qJkL&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;3sY5&quot; data-align=&quot;right&quot;&gt;&lt;a href=&quot;https://t.me/lab_sborki&quot; target=&quot;_blank&quot;&gt;https://t.me/lab_sborki&lt;/a&gt;&lt;/p&gt;

</content></entry></feed>