共用方式為


磁碟區轉譯概觀

如需醫療 MRI 或工程磁碟區,請參閱 Wikipedia 上的磁碟區轉譯。 這些「磁碟區影像」包含整個磁碟區中具有不透明度和色彩的豐富信息,無法輕易地以 多邊形網格等表面表示。

改善效能的重要解決方案

  1. 錯誤:分類方法:顯示整個磁碟區,通常執行速度太慢
  2. 良好:剪切平面:只顯示磁碟區的單一配量
  3. 良好:剪下子磁碟區:只顯示磁碟區的幾個層
  4. GOOD:降低磁碟區轉譯的解析度 (請參閱)

在任何特定畫面格中,只有一定數量的資訊可以從應用程式傳輸到畫面,也就是總記憶體頻寬。 此外,任何處理 (或「底紋」) 轉換該數據以進行呈現所需的時間。 執行磁碟區轉譯時的主要考慮如下:

  • Screen-Width * Screen-Height * Screen-Count * Volume-Layers-on-that-Pixel = Total-Volume-Samples-Per-Frame
  • 1028 * 720 * 2 * 256 = 378961920 (100%) (完整重設磁碟區:太多樣本)
  • 1028 * 720 * 2 * 1 = 1480320 (0.3% 的完整) (細配量:每個圖元 1 個樣本,順暢地執行)
  • 1028 * 720 * 2 * 10 = 14803200 (3.9% 的完整) (子數據行配量:每個圖元 10 個樣本,執行相當順暢,看起來是 3d)
  • 200 * 200 * 2 * 256 = 20480000 (5% 的完整) (較低的重設磁碟區:圖元較少、完整磁碟區、看起來為 3d 但有點模糊)

代表 3D 紋理

在 CPU 上:

public struct Int3 { public int X, Y, Z; /* ... */ }
 public class VolumeHeader  {
   public readonly Int3 Size;
   public VolumeHeader(Int3 size) { this.Size = size;  }
   public int CubicToLinearIndex(Int3 index) {
     return index.X + (index.Y * (Size.X)) + (index.Z * (Size.X * Size.Y));
   }
   public Int3 LinearToCubicIndex(int linearIndex)
   {
     return new Int3((linearIndex / 1) % Size.X,
       (linearIndex / Size.X) % Size.Y,
       (linearIndex / (Size.X * Size.Y)) % Size.Z);
   }
   /* ... */
 }
 public class VolumeBuffer<T> {
   public readonly VolumeHeader Header;
   public readonly T[] DataArray;
   public T GetVoxel(Int3 pos)        {
     return this.DataArray[this.Header.CubicToLinearIndex(pos)];
   }
   public void SetVoxel(Int3 pos, T val)        {
     this.DataArray[this.Header.CubicToLinearIndex(pos)] = val;
   }
   public T this[Int3 pos] {
     get { return this.GetVoxel(pos); }
     set { this.SetVoxel(pos, value); }
   }
   /* ... */
 }

在 GPU 上:

float3 _VolBufferSize;
 int3 UnitVolumeToIntVolume(float3 coord) {
   return (int3)( coord * _VolBufferSize.xyz );
 }
 int IntVolumeToLinearIndex(int3 coord, int3 size) {
   return coord.x + ( coord.y * size.x ) + ( coord.z * ( size.x * size.y ) );
 }
 uniform StructuredBuffer<float> _VolBuffer;
 float SampleVol(float3 coord3 ) {
   int3 intIndex3 = UnitVolumeToIntVolume( coord3 );
   int index1D = IntVolumeToLinearIndex( intIndex3, _VolBufferSize.xyz);
   return __VolBuffer[index1D];
 }

底紋和漸層

如何著色磁碟區,例如 MRI,以獲得實用的視覺效果。 主要方法是讓「強度視窗」 (您想要查看其內強度的最小和最大) ,並只縮放到該空間以查看黑白濃度。 接著,可以將「色彩坡道」套用至該範圍內的值,並儲存為紋理,讓強度頻譜的不同部分可以有不同的色彩著色:

float4 ShadeVol( float intensity ) {
   float unitIntensity = saturate( intensity - IntensityMin / ( IntensityMax - IntensityMin ) );
   // Simple two point black and white intensity:
   color.rgba = unitIntensity;
   // Color ramp method:
   color.rgba = tex2d( ColorRampTexture, float2( unitIntensity, 0 ) );

在許多應用程式中,我們會將原始強度值和「分割索引」儲存在磁碟區中, (區隔不同部分,例如面板和門;這些區段是由專用工具) 中的專家所建立。 這可以與上述方法結合,以放置不同的色彩,或甚至是每個線段索引的不同色彩坡形:

// Change color to match segment index (fade each segment towards black):
 color.rgb = SegmentColors[ segment_index ] * color.a; // brighter alpha gives brighter color

著色器中的磁碟區切割

絕佳的第一個步驟是建立可移動磁碟區的「切割平面」、「切割」磁碟區,以及在每個點掃描值的方式。 這假設有一個 'VolumeSpace' Cube,代表磁碟區位於世界空間的位置,可用來作為放置點的參考:

// In the vertex shader:
 float4 worldPos = mul(_Object2World, float4(input.vertex.xyz, 1));
 float4 volSpace = mul(_WorldToVolume, float4(worldPos, 1));
// In the pixel shader:
 float4 color = ShadeVol( SampleVol( volSpace ) );

著色器中的磁碟區追蹤

如何使用 GPU 來執行子數據行追蹤 (深入幾個體素,然後將數據的圖層從後到前) :

float4 AlphaBlend(float4 dst, float4 src) {
   float4 res = (src * src.a) + (dst - dst * src.a);
   res.a = src.a + (dst.a - dst.a*src.a);
   return res;
 }
 float4 volTraceSubVolume(float3 objPosStart, float3 cameraPosVolSpace) {
   float maxDepth = 0.15; // depth in volume space, customize!!!
   float numLoops = 10; // can be 400 on nice PC
   float4 curColor = float4(0, 0, 0, 0);
   // Figure out front and back volume coords to walk through:
   float3 frontCoord = objPosStart;
   float3 backCoord = frontPos + (normalize(cameraPosVolSpace - objPosStart) * maxDepth);
   float3 stepCoord = (frontCoord - backCoord) / numLoops;
   float3 curCoord = backCoord;
   // Add per-pixel random offset, avoids layer aliasing:
   curCoord += stepCoord * RandomFromPositionFast(objPosStart);
   // Walk from back to front (to make front appear in-front of back):
   for (float i = 0; i < numLoops; i++) {
     float intensity = SampleVol(curCoord);
     float4 shaded = ShadeVol(intensity);
     curColor = AlphaBlend(curColor, shaded);
     curCoord += stepCoord;
   }
   return curColor;
 }
// In the vertex shader:
 float4 worldPos = mul(_Object2World, float4(input.vertex.xyz, 1));
 float4 volSpace = mul(_WorldToVolume, float4(worldPos.xyz, 1));
 float4 cameraInVolSpace = mul(_WorldToVolume, float4(_WorldSpaceCameraPos.xyz, 1));
// In the pixel shader:
 float4 color = volTraceSubVolume( volSpace, cameraInVolSpace );

整個磁碟區轉譯

修改上述子數據行程式代碼,我們會取得:

float4 volTraceSubVolume(float3 objPosStart, float3 cameraPosVolSpace) {
   float maxDepth = 1.73; // sqrt(3), max distance from point on cube to any other point on cube
   int maxSamples = 400; // just in case, keep this value within bounds
   // not shown: trim front and back positions to both be within the cube
   int distanceInVoxels = length(UnitVolumeToIntVolume(frontPos - backPos)); // measure distance in voxels
   int numLoops = min( distanceInVoxels, maxSamples ); // put a min on the voxels to sample

混合解析度場景轉譯

如何以低解析度轉譯場景的一部分,並將其放回原位:

  1. 設定兩個螢幕外相機,一個用來追蹤更新每個畫面的每個眼睛
  2. 設定兩個低解析度轉譯目標 (,也就是相機轉譯成每個) 200x200
  3. 設定在使用者前面移動的四邊形

每個框架:

  1. 以低解析度繪製每個眼睛的轉譯目標, (磁碟區數據、昂貴的著色器等等)
  2. 通常會以網格、UI 等 (完整解析度繪製場景)
  3. 在使用者前面、場景上繪製四邊形,並將低重度轉譯投影到該圖上
  4. 結果:全解析度元素與低解析度但高密度磁碟區數據的視覺組合

另請參閱