понедельник, 20 июня 2011 г.

Практическая обработка глубины резкости (Перевод) Часть 3

 

Эрл Хаммон,
Инфинити Уорд

Пример 28-1. Шейдер, который инициализирует ближайшее CoC
  1.    // These are set by the game engine.  
  2.    // The render target size is one-quarter the scene rendering size.  
  3.    sampler colorSampler;  
  4. sampler depthSampler;  
  5. const float2 dofEqWorld;  
  6. const float2 dofEqWeapon;  
  7. const float2 dofRowDelta;  // float2( 0, 0.25 / renderTargetHeight )  
  8.    const float2 invRenderTargetSize;  
  9. const float4x4 worldViewProj;  
  10. struct PixelInput  
  11. {  
  12.   float4 position : POSITION;  
  13.   float2 tcColor0 : TEXCOORD0;  
  14.   float2 tcColor1 : TEXCOORD1;  
  15.   float2 tcDepth0 : TEXCOORD2;  
  16.   float2 tcDepth1 : TEXCOORD3;  
  17.   float2 tcDepth2 : TEXCOORD4;  
  18.   float2 tcDepth3 : TEXCOORD5;  
  19. };  
  20. PixelInput DofDownVS( float4 pos : POSITION, float2 tc : TEXCOORD0 )  
  21. {  
  22.   PixelInput pixel;  
  23.   pixel.position = mul( pos, worldViewProj );  
  24.   pixel.tcColor0 = tc + float2( -1.0, -1.0 ) * invRenderTargetSize;  
  25.   pixel.tcColor1 = tc + float2( +1.0, -1.0 ) * invRenderTargetSize;  
  26.   pixel.tcDepth0 = tc + float2( -1.5, -1.5 ) * invRenderTargetSize;  
  27.   pixel.tcDepth1 = tc + float2( -0.5, -1.5 ) * invRenderTargetSize;  
  28.   pixel.tcDepth2 = tc + float2( +0.5, -1.5 ) * invRenderTargetSize;  
  29.   pixel.tcDepth3 = tc + float2( +1.5, -1.5 ) * invRenderTargetSize;  
  30.   return pixel;  
  31. }  
  32. half4 DofDownPS( const PixelInput pixel ) : COLOR  
  33. {  
  34.   half3 color;  
  35.   half maxCoc;  
  36.   float4 depth;  
  37.   half4 viewCoc;  
  38.   half4 sceneCoc;  
  39.   half4 curCoc;  
  40.   half4 coc;  
  41.   float2 rowOfs[4];  
  42.   // "rowOfs" reduces how many moves PS2.0 uses to emulate swizzling.  
  43.   rowOfs[0] = 0;  
  44.   rowOfs[1] = dofRowDelta.xy;  
  45.   rowOfs[2] = dofRowDelta.xy * 2;  
  46.   rowOfs[3] = dofRowDelta.xy * 3;  
  47.   // Use bilinear filtering to average 4 color samples for free.  
  48.   color = 0;  
  49.   color += tex2D( colorSampler, pixel.tcColor0.xy + rowOfs[0] ).rgb;  
  50.   color += tex2D( colorSampler, pixel.tcColor1.xy + rowOfs[0] ).rgb;  
  51.   color += tex2D( colorSampler, pixel.tcColor0.xy + rowOfs[2] ).rgb;  
  52.   color += tex2D( colorSampler, pixel.tcColor1.xy + rowOfs[2] ).rgb;  
  53.   color /= 4;  
  54.   // Process 4 samples at a time to use vector hardware efficiently.  
  55.    // The CoC will be 1 if the depth is negative, so use "min" to pick  
  56.    // between "sceneCoc" and "viewCoc".  
  57.   depth[0] = tex2D( depthSampler, pixel.tcDepth0.xy + rowOfs[0] ).r;  
  58.   depth[1] = tex2D( depthSampler, pixel.tcDepth1.xy + rowOfs[0] ).r;  
  59.   depth[2] = tex2D( depthSampler, pixel.tcDepth2.xy + rowOfs[0] ).r;  
  60.   depth[3] = tex2D( depthSampler, pixel.tcDepth3.xy + rowOfs[0] ).r;  
  61.   viewCoc = saturate( dofEqWeapon.x * -depth + dofEqWeapon.y );  
  62.   sceneCoc = saturate( dofEqWorld.x * depth + dofEqWorld.y );  
  63.   curCoc = min( viewCoc, sceneCoc );  
  64.   coc = curCoc;  
  65.   depth[0] = tex2D( depthSampler, pixel.tcDepth0.xy + rowOfs[1] ).r;  
  66.   depth[1] = tex2D( depthSampler, pixel.tcDepth1.xy + rowOfs[1] ).r;  
  67.   depth[2] = tex2D( depthSampler, pixel.tcDepth2.xy + rowOfs[1] ).r;  
  68.   depth[3] = tex2D( depthSampler, pixel.tcDepth3.xy + rowOfs[1] ).r;  
  69.   viewCoc = saturate( dofEqWeapon.x * -depth + dofEqWeapon.y );  
  70.   sceneCoc = saturate( dofEqWorld.x * depth + dofEqWorld.y );  
  71.   curCoc = min( viewCoc, sceneCoc );  
  72.   coc = max( coc, curCoc );  
  73.   depth[0] = tex2D( depthSampler, pixel.tcDepth0.xy + rowOfs[2] ).r;  
  74.   depth[1] = tex2D( depthSampler, pixel.tcDepth1.xy + rowOfs[2] ).r;  
  75.   depth[2] = tex2D( depthSampler, pixel.tcDepth2.xy + rowOfs[2] ).r;  
  76.   depth[3] = tex2D( depthSampler, pixel.tcDepth3.xy + rowOfs[2] ).r;  
  77.   viewCoc = saturate( dofEqWeapon.x * -depth + dofEqWeapon.y );  
  78.   sceneCoc = saturate( dofEqWorld.x * depth + dofEqWorld.y );  
  79.   curCoc = min( viewCoc, sceneCoc );  
  80.   coc = max( coc, curCoc );  
  81.   depth[0] = tex2D( depthSampler, pixel.tcDepth0.xy + rowOfs[3] ).r;  
  82.   depth[1] = tex2D( depthSampler, pixel.tcDepth1.xy + rowOfs[3] ).r;  
  83.   depth[2] = tex2D( depthSampler, pixel.tcDepth2.xy + rowOfs[3] ).r;  
  84.   depth[3] = tex2D( depthSampler, pixel.tcDepth3.xy + rowOfs[3] ).r;  
  85.   viewCoc = saturate( dofEqWeapon.x * -depth + dofEqWeapon.y );  
  86.   sceneCoc = saturate( dofEqWorld.x * depth + dofEqWorld.y );  
  87.   curCoc = min( viewCoc, sceneCoc );  
  88.   coc = max( coc, curCoc );  
  89.   maxCoc = max( max( coc[0], coc[1] ), max( coc[2], coc[3] ) );  
  90.   return half4( color, maxCoc );  
  91. }  
Мы применяем гауссовское размытие к изображению, сгенерированное функцией DofDownsample(). Мы делаем это с помощью текста программы, который автоматически делит радиус размытия в оптимальной последовательности горизонтальных и вертикальных фильтров, которые используют билинейное фильтрование для чтения двух образцов одновременно. Кроме того, мы применяем единый неразделенный 2D проход, при использовании текстурных инструкций не более двух разделенных 1D проходов. В 2D каждая текстурная инструкция применяет четыре экземпляра. Листинг 28-2 содержит текст программы.
Пример 28-2. Шейдер пиксела, который вычисляет близлежащий CoC
  1.  // These are set by the game engine.  
  2.  sampler shrunkSampler;  // Output of DofDownsample()  
  3.  sampler blurredSampler; // Blurred version of the shrunk sampler  
  4.  // This is the pixel shader function that calculates the actual  
  5.  // value used for the near circle of confusion.  
  6.  // "texCoords" are 0 at the bottom left pixel and 1 at the top right.  
  7.  float4 DofNearCoc( const float2 texCoords )  
  8.   
  9. float3 color;  
  10. float coc;  
  11. half4 blurred;  
  12. half4 shrunk;  
  13. shrunk = tex2D( shrunkSampler, texCoords );  
  14. blurred = tex2D( blurredSampler, texCoords );  
  15. color = shrunk.rgb;  
  16. coc = 2 * max( blurred.a, shrunk.a ) - shrunk.a;  
  17. return float4( color, coc );  
В листинге 28-3 мы применяем небольшое размытие 3x3 к результату DofNearCoc(), чтобы сгладить любые разрывы.
Пример 28-3. Этот шейдер размывает близлежащее CoC и цветное изображение
    1.    // This vertex and pixel shader applies a 3 x 3 blur to the image in  
    2.    // colorMapSampler, which is the same size as the render target.  
    3.    // The sample weights are 1/16 in the corners, 2/16 on the edges,  
    4.    // and 4/16 in the center.  
    5.    sampler colorSampler;  // Output of DofNearCoc()  
    6.    float2 invRenderTargetSize;  
    7. struct PixelInput  
    8. {  
    9.   float4 position : POSITION;  
    10.   float4 texCoords : TEXCOORD0;  
    11. };  
    12. PixelInput SmallBlurVS( float4 position, float2 texCoords )  
    13. {  
    14.   PixelInput pixel;  
    15.   const float4 halfPixel = { -0.5, 0.5, -0.5, 0.5 };  
    16.   pixel.position = Transform_ObjectToClip( position );  
    17.   pixel.texCoords = texCoords.xxyy + halfPixel * invRenderTargetSize;  
    18.   return pixel;  
    19. }  
    20. float4 SmallBlurPS( const PixelInput pixel )  
    21. {  
    22.   float4 color;  
    23.   color = 0;  
    24.   color += tex2D( colorSampler, pixel.texCoords.xz );  
    25.   color += tex2D( colorSampler, pixel.texCoords.yz );  
    26.   color += tex2D( colorSampler, pixel.texCoords.xw );  
    27.   color += tex2D( colorSampler, pixel.texCoords.yw );  
    28.   return color / 4;  
    29. }
Наш последний шейдер в листинге 28-4 применяет размытие с переменной шириной на экране.
Пример 28-4. Этот шейдер пиксела соединяет дальнее CoC с ближним CoC и выводит его на экран
  1. sampler colorSampler;      // Исходное изображение
  2. sampler smallBlurSampler;  // Изображение, сгенированное SmallBlurPS()
  3. sampler largeBlurSampler;  // Размытое изображение DofDownsample()
  4. float2 invRenderTargetSize;
  5. float4 dofLerpScale;
  6. float4 dofLerpBias;
  7. float3 dofEqFar;
  8. float4 tex2Doffset( sampler s, float2 tc, float2 offset )
  9. {
  10. return tex2D( s, tc + offset * invRenderTargetSize );
  11. }
  12. half3 GetSmallBlurSample( float2 texCoords )
  13. {
  14.   half3 sum;
  15. const half weight = 4.0 / 17;
  16.   sum = 0;  // Unblurred sample done by alpha blending
  17.   sum += weight * tex2Doffset( colorSampler, tc, +0.5, -1.5 ).rgb;
  18.   sum += weight * tex2Doffset( colorSampler, tc, -1.5, -0.5 ).rgb;
  19.   sum += weight * tex2Doffset( colorSampler, tc, -0.5, +1.5 ).rgb;
  20.   sum += weight * tex2Doffset( colorSampler, tc, +1.5, +0.5 ).rgb;
  21. return sum;
  22. }
  23. half4 InterpolateDof( half3 small, half3 med, half3 large, half t )
  24. {
  25.   half4 weights;
  26.   half3 color;
  27.   half  alpha;
  28. // d0 + d1 + d2 = 1.
  29. // dofLerpScale = float4( -1 / d0, -1 / d1, -1 / d2, 1 / d2 );
  30. // dofLerpBias = float4( 1, (1 – d2) / d1, 1 / d2, (d2 – 1) / d2 );
  31.   weights = saturate( t * dofLerpScale + dofLerpBias );
  32.   weights.yz = min( weights.yz, 1 - weights.xy );
  33.   color = weights.y * small + weights.z * med + weights.w * large;
  34.   alpha = dot( weights.yzw, half3( 16.0 / 17, 1.0, 1.0 ) );
  35. return half4( color, alpha );
  36. }
  37. half4 ApplyDepthOfField( const float2 texCoords )
  38. {
  39.   half3 small;
  40.   half4 med;
  41.   half3 large;
  42.   half depth;
  43.   half nearCoc;
  44.   half farCoc;
  45.   half coc;
  46.   small = GetSmallBlurSample( texCoords );
  47.   med = tex2D( smallBlurSampler, texCoords );
  48.   large = tex2D( largeBlurSampler, texCoords ).rgb;
  49.   nearCoc = med.a;
  50.   depth = tex2D( depthSampler, texCoords ).r;
  51. if ( depth > 1.0e6 )
  52.   {
  53.     coc = nearCoc;
  54.   }
  55. else
  56.   {
  57.     farCoc = saturate( dofEqFar.x * depth + dofEqFar.y );
  58.     coc = max( nearCoc, farCoc * dofEqFar.z );
  59.   }
  60. return InterpolateDof( small, med.rgb, large, coc ); 
61. }

28.6 Заключение

Не так много арифметики уходит на эти шейдеры, поэтому их стоимость характеризуется текстурными инструкциями.
  • Сформированная четверть изображения использует четыре цветовых инструкции и 16 глубинных инструкций для каждого пикселя для четверти изображения, 1,25 инструкций на пиксель для исходного изображения.
  • Малый радиус размытия добавляет еще четыре инструкции на пиксель.
  • Применение размытия с переменной шириной требует чтения глубины и два посчитанных уровня размытия, которые добавляет до трех инструкций на пиксель.
  • Получение пятна рассеяния требует двух текстурных инструкций на пиксель для четверти изображения или 0.125 экземпляра на пиксел для исходного изображения.
  • Малое размытие применяется к близлежащему CoC изображению с использованием еще четырех текстурных инструкций для других 0.25 экземпляров на пиксел.
Это равняется 8,625 экземпляров на пиксель, не считая значения числа экземпляров, необходимых для применения большого гауссовского размытия. Применяемое в качестве делимого фильтра, размытие используется не более двух проходов с восемью текстурными инструкциями, которые дают 17-ое билинейное фильтрование. Это в среднем составляет один экземпляр на пиксель в исходном изображении. Ожидаемое число текстур инструкций на пиксель составляет около 9,6.
Пропускная способность буфера кадров требует отдельного анализа. Этот метод первый раз записывает четверть исходного изображения, а затем два раза использует большое гауссовское размытие. Есть еще два способа: применить уравнение 1 и незначительно размыть его результаты. Наконец, каждый пиксель исходного изображения единожды записывается в конечное изображение. Это составлят 1,3125 записей для каждого пикселя исходного изображения.
Измеренные характеристики затрат составляют 1 к 1,5 миллисекунд на 1024x768 на наших тестах на Radeon X1900 и GeForce 7900. Результаты на следующем поколении консолей схожи. Метод на самом деле более быстрый, чем первая реализация, основанная Шейерманном 2004, предположительно потому, что он использует меньше половины считывания текстур.

28.7 Ограничения и планы на будущее

При использовании нашего подхода сфокусированные объекты будут “кровоточить” на несфокусированные объекты на заднем плане. Это наименее нежелательный дефект этого метода и DOF метода последующей обработки. Эти дефекты могут быть удалены с помощью сбора дополнительных экземпляров глубин для 17-ого размытия. Это должно быть достаточным, поскольку, как мы уже видели, на заднем плане необходимо использовать меньший радиус размытия, чем на переднем плане.
Второй дефект, присущий методу: вы не можете намного увеличить радиус размытия для близких объектов, так как станет очевидно, что метод фактически представляет размытие экрана, а не размытие несфокусированных объектов. Рисунок 28-7 является наихудшим примером данной проблемы, а рисунок 28-8 показывает более типичную ситуацию. Центральное изображение на рисунке 28-8 показывает типичный радиус размытия.
Изображение справа имеет радиус размытия в два раза больший, даже относительно крупный камень на заднем плане был размазан. Это особенно нежелательно, когда камера движется, показывая дымку вокруг объекта переднего плана.
clip_image001
Рисунок 28-7 Наихудший пример для нашего алгоритма
clip_image002
Рисунок 28-8 Если радиус размытия слишком велик, эффект неудачен
Радиус, с которым это произойдет, зависит от размеров объекта, не находящегося в фокусе, и от количества деталей, которые должны быть в фокусе. Мелкие объекты, расположенные не в фокусе, и сфокусированные объекты, которые имеют более высокие частоты, требуют меньшего максимального радиуса размытия. Это можно преодолеть с помощью рендеринга объектов переднего плана в отдельном буфере информации о цвете, который будет размыт. Это потребует тщательного рассмотрения недостающих пикселей.
Наконец, мы подробно не прорабатали прозрачность. Прозрачные поверхности используют CoC первого позадистоящего непрозрачного объекта. Чтобы это исправить, прозрачные объекты могут быть изображены после того, как глубина резкости будет применена ко всей непрозрачной геометрии. Они могут вызвать впечатления DOF путем смещения поиска текстуры к нижним уровням множественного отображения.
Прозрачные объекты, которые граничат с непрозрачными объектами, например, окнами, могут иметь дефекты на границе. Это делает метод интрузивным. Мы обнаружили, что полностью игнорируемая прозрачность работает достаточно хорошо в большинстве ситуаций.

28.8 Литература

Демерс, Джо. 2004. "Глубина резкости: обзор методов", с. 375–390. 375-390. Addison-Wesley.
Касс, Майкл Аарон Лефон и Джон Оуэнс. 2006. "Интерактивная глубина резкости, диффузионное моделирование". Технический отчет. Pixar Animation Studios. Можно ознакомиться на сайте http://graphics.pixar.com/DepthOfField/paper.pdf.
Козлов, Тодд Джером, и Брайан А. Барский. 2007. "Алгоритм рендеринга эффектов глубины резкости, основанные на тепловом моделировании". Технический отчет № UCB/EECS-2007-19. Можно ознакомиться на сайте http://www.eecs.berkeley.edu/Pubs/TechRpts/2007/EECS-2007-19.pdf.
Криванек, Ярослав Иванек и Кади Ботач. 2003. "Быстрый рендеринг глубины резкости с Surface Splatting". Выступление на международной конференции по компьютерной графике 2003. Можно ознакомиться на сайте http://www.cgg.cvut.cz/ ~ xkrivanj/papers/cgi2003/9-3_krivanek_j.pdf.
Малдер, Джуриан и Роберт ван Лири. 2000. "Быстрое восприятие на основе рендеринга глубины резкости изображения". Можно ознакомиться на сайте http://www.cwi.nl/ ~ robertl/papers/2000/vrst/paper.pdf.
Потмесил Майкл и Индранил Чакраварти. 1981. "Линза и диафрагма модели камеры для искусственно сгенерированных изображений". В материалах 8-й ежегодной конференции по компьютерной графике и интерактивным методам, с. 297–305. 297-305.

Шейерманн, Торстен. 2004. "Глубина резкости". Выступление на конференции разработчиков игр 2004. Можно ознакомиться на сайте http://ati.amd.com/developer/gdc/Scheuermann_DepthOfField.pdf.


Комментариев нет:

Отправить комментарий