Rendering dei volumiVolume rendering

Per i volumi di ingegneria o MRI medici, vedere rendering del volume in Wikipedia.For medical MRI or engineering volumes, see Volume Rendering on Wikipedia. Queste immagini volumetriche contengono informazioni dettagliate con opacità e colore in tutto il volume che non possono essere facilmente espresse come superfici come le mesh poligonali.These 'volumetric images' contain rich information with opacity and color throughout the volume that cannot be easily expressed as surfaces such as polygonal meshes.

Soluzioni principali per migliorare le prestazioniKey solutions to improve performance

  1. ERRATO: approccio ingenuo: Mostra volume intero, in genere troppo lentoBAD: Naïve Approach: Show Whole Volume, generally runs too slowly
  2. BENE: piano di taglio: Mostra solo una singola sezione del volumeGOOD: Cutting Plane: Show only a single slice of the volume
  3. BENE: riduzione del volume secondario: Mostra solo pochi livelli del volumeGOOD: Cutting Sub-Volume: Show only a few layers of the volume
  4. BENE: abbassare la risoluzione del rendering del volume (vedere il rendering di una scena di risoluzione mista)GOOD: Lower the resolution of the volume rendering (see 'Mixed Resolution Scene Rendering')

È disponibile solo una certa quantità di informazioni che possono essere trasferite dall'applicazione alla schermata in un particolare frame, ovvero la larghezza di banda totale della memoria.There's only a certain amount of information that can be transferred from the application to the screen in any particular frame, which is the total memory bandwidth. Inoltre, qualsiasi elaborazione (o ' ombreggiatura ') necessaria per trasformare i dati per la presentazione richiede tempo.Also, any processing (or 'shading') required to transform that data for presentation requires time. Le considerazioni principali per il rendering del volume sono le seguenti:The primary considerations when doing volume rendering are as such:

  • Screen-Width * Screen-Height * Screen-Count * volume-layers-on-that-pixel = Total-volume-Samples-per-frameScreen-Width * Screen-Height * Screen-Count * Volume-Layers-On-That-Pixel = Total-Volume-Samples-Per-Frame
  • 1028 * 720 * 2 * 256 = 378961920 (100%) (volume res completo: troppi esempi)1028 * 720 * 2 * 256 = 378961920 (100%) (full res volume: too many samples)
  • 1028 * 720 * 2 * 1 = 1480320 (0,3% of Full) (slice sottile: 1 campione per pixel, esecuzione senza problemi)1028 * 720 * 2 * 1 = 1480320 (0.3% of full) (thin slice: 1 sample per pixel, runs smoothly)
  • 1028 * 720 * 2 * 10 = 14803200 (3,9% of Full) (sezione subvolume: 10 campioni per pixel, esecuzione abbastanza agevole, aspetto 3D)1028 * 720 * 2 * 10 = 14803200 (3.9% of full) (subvolume slice: 10 samples per pixel, runs fairly smoothly, looks 3d)
  • 200 * 200 * 2 * 256 = 20480000 (5% di pieno) (volume res inferiore: un minor numero di pixel, un volume intero, appare 3D ma un bit sfocato)200 * 200 * 2 * 256 = 20480000 (5% of full) (lower res volume: fewer pixels, full volume, looks 3d but a bit blurry)

Rappresentazione di trame 3DRepresenting 3D Textures

Sulla CPU:On the 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); }
   }
   /* ... */
 }

Sulla GPU:On the 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];
 }

Ombreggiatura e sfumatureShading and Gradients

Come ombreggiare un volume, ad esempio MRI, per una visualizzazione utile.How to shade a volume, such as MRI, for useful visualization. Il metodo principale consiste nel disporre di una "finestra intensità" (un valore minimo e massimo) in cui si desidera visualizzare le intensità e semplicemente ridimensionare lo spazio per visualizzare l'intensità del nero e del bianco.The primary method is to have an 'intensity window' (a min and max) that you want to see intensities within, and simply scale into that space to see the black and white intensity. È quindi possibile applicare una "rampa di colore" ai valori all'interno di tale intervallo e archiviarli come trama, in modo che le diverse parti dello spettro di intensità possano ombreggiare colori diversi:A 'color ramp' can then be applied to the values within that range, and stored as a texture, so that different parts of the intensity spectrum can be shaded different colors:

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 ) );

In molte delle nostre applicazioni viene archiviato nel volume sia un valore di intensità RAW che un "indice di segmentazione" (per segmentare parti diverse, ad esempio Skin e Bone). questi segmenti sono creati da esperti in strumenti dedicati.In many of our applications, we store in our volume both a raw intensity value and a 'segmentation index' (to segment different parts such as skin and bone; these segments are created by experts in dedicated tools). Questa operazione può essere combinata con l'approccio precedente per inserire un colore diverso o persino una rampa di colore diversa per ogni indice di segmento:This can be combined with the approach above to put a different color, or even different color ramp for each segment index:

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

Sezionamento del volume in uno shaderVolume Slicing in a Shader

Il primo passaggio consiste nel creare un "piano di sezionamento" che può spostarsi tra il volume, "sezionamento" e come i valori di analisi in ogni punto.A great first step is to create a "slicing plane" that can move through the volume, 'slicing it', and how the scan values at each point. Si presuppone che esista un cubo ' VolumeSpace ', che rappresenta il punto in cui il volume si trova nello spazio globale, che può essere usato come riferimento per inserire i punti:This assumes that there's a 'VolumeSpace' cube, which represents where the volume is in world space, that can be used as a reference for placing the points:

// 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 ) );

Traccia del volume in shaderVolume Tracing in Shaders

Come usare la GPU per eseguire la traccia dei sottovolumi (voxel approfondimenti, quindi i livelli sui dati da un ritorno all'inizio):How to use the GPU to do subvolume tracing (walks a few voxels deep, then layers on the data from back to front):

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 );

Rendering intero del volumeWhole Volume Rendering

Modificando il codice del sottovolume riportato sopra, si ottiene:Modifying the subvolume code above, we get:

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

Rendering di scene con risoluzione mistaMixed Resolution Scene Rendering

Come eseguire il rendering di una parte della scena con una risoluzione bassa e inserirla di nuovo:How to render a part of the scene with a low resolution and put it back in place:

  1. Configurare due fotocamere fuori schermo, una per seguire ogni occhio che aggiorna ogni frameSetup two off-screen cameras, one to follow each eye that update each frame
  2. Configurare due destinazioni di rendering a bassa risoluzione (ovvero 200x200 ognuna) in cui vengono sottoposte a rendering le fotocamereSetup two low-resolution render targets (that is, 200x200 each) that the cameras render into
  3. Configurare un quad che si sposta davanti all'utenteSet up a quad that moves in front of the user

Ogni frame:Each Frame:

  1. Disegnare le destinazioni di rendering per ogni occhio a bassa risoluzione (dati del volume, shader costosi e così via)Draw the render targets for each eye at low-resolution (volume data, expensive shaders, and so on)
  2. Creare la scena normalmente come risoluzione completa (mesh, interfaccia utente e così via)Draw the scene normally as full resolution (meshes, UI, and so on)
  3. Disegnare un quad davanti all'utente, sulla scena e proiettare i rendering a bassa risoluzione su taleDraw a quad in front of the user, over the scene, and project the low-res renders onto that
  4. Risultato: combinazione visiva di elementi a risoluzione completa con dati di volume a bassa risoluzione ma a densità elevataResult: visual combination of full-resolution elements with low-resolution but high-density volume data