Representación de volúmenesVolume rendering

En el caso de los volúmenes de resonancia médica o de ingeniería, consulte representación de volumen en Wikipedia.For medical MRI or engineering volumes, see Volume Rendering on Wikipedia. Estas ' imágenes volumétricas ' contienen información enriquecida con opacidad y color en todo el volumen que no se puede expresar fácilmente como superficies como mallas poligonales.These 'volumetric images' contain rich information with opacity and color throughout the volume that cannot be easily expressed as surfaces such as polygonal meshes.

Soluciones clave para mejorar el rendimientoKey solutions to improve performance

  1. BAD: Naive enfoque: Mostrar todo el volumen; por lo general, se ejecuta demasiado lentamente.BAD: Naïve Approach: Show Whole Volume, generally runs too slowly
  2. BUENA: plano de corte: mostrar solo un segmento del volumenGOOD: Cutting Plane: Show only a single slice of the volume
  3. BUENO: recortar el Subvolumen: mostrar solo algunas capas del volumenGOOD: Cutting Sub-Volume: Show only a few layers of the volume
  4. BUENA: reducir la resolución de la representación de volumen (consulte "representación de escenas de resolución mixta")GOOD: Lower the resolution of the volume rendering (see 'Mixed Resolution Scene Rendering')

Solo hay una cierta cantidad de información que se puede transferir desde la aplicación a la pantalla en cualquier fotograma determinado, que es el ancho de banda total de la 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. Además, cualquier procesamiento (o ' sombreado ') necesario para transformar los datos para la presentación requiere tiempo.Also, any processing (or 'shading') required to transform that data for presentation requires time. Las principales consideraciones a la hora de realizar la representación de volúmenes son las siguientes:The primary considerations when doing volume rendering are as such:

  • Screen-Width * Screen-Height * Screen-Count * Volume-Layers-on-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%) (volumen de res completo: demasiados ejemplos)1028 * 720 * 2 * 256 = 378961920 (100%) (full res volume: too many samples)
  • 1028 * 720 * 2 * 1 = 1480320 (0,3% de Full) (segmento fino: 1 muestra por píxel, se ejecuta sin problemas)1028 * 720 * 2 * 1 = 1480320 (0.3% of full) (thin slice: 1 sample per pixel, runs smoothly)
  • 1028 * 720 * 2 * 10 = 14803200 (3,9% de Full) (segmento de Subvolumen: 10 muestras por píxel, se ejecuta con bastante suavidad, parece 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% de Full) (volumen de res inferior: menos píxeles, volumen completo, apariencia 3D pero un poco borroso)200 * 200 * 2 * 256 = 20480000 (5% of full) (lower res volume: fewer pixels, full volume, looks 3d but a bit blurry)

Representar texturas 3DRepresenting 3D Textures

En la 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); }
   }
   /* ... */
 }

En la 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];
 }

Sombreado y degradadosShading and Gradients

Cómo sombrear un volumen, como resonancia magnética, para una visualización útil.How to shade a volume, such as MRI, for useful visualization. El método principal consiste en tener una ' ventana de intensidad ' (un mínimo y un máximo) en el que desea ver las intensidades y, simplemente, escalar en ese espacio para ver la intensidad de blanco y negro.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. A continuación, se puede aplicar una "rampa de color" a los valores dentro de ese intervalo y almacenarse como una textura, de modo que las distintas partes del espectro de intensidad pueden sombrear diferentes colores: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 ) );

En muchas de nuestras aplicaciones, almacenamos en nuestro volumen un valor de intensidad sin procesar y un "índice de segmentación" (para segmentar partes diferentes como la piel y el hueso; estos segmentos los crean expertos en herramientas dedicadas).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). Se puede combinar con el enfoque anterior para colocar un color diferente o incluso una rampa de color diferente para cada índice de 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

Segmentación de volumen en un sombreadorVolume Slicing in a Shader

Un primer paso es crear un "plano de segmentación" que pueda desplazarse por el volumen, ' segmentarlo ' y cómo examinar los valores en cada 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. Se supone que hay un cubo "VolumeSpace", que representa el lugar en el que el volumen está en el espacio universal, que se puede usar como referencia para colocar los puntos: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 ) );

Seguimiento de volumen en sombreadoresVolume Tracing in Shaders

Cómo usar la GPU para realizar el seguimiento de subvolumens (recorra unos pocos voxels de profundidad y, a continuación, capas en los datos de vuelta al frente):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 );

Representación de todo el volumenWhole Volume Rendering

Al modificar el código de Subvolumen anterior, obtenemos lo siguiente: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

Representación de escenas de resolución mixtaMixed Resolution Scene Rendering

Cómo representar una parte de la escena con una resolución baja y volver a colocarla en su lugar:How to render a part of the scene with a low resolution and put it back in place:

  1. Configuración de dos cámaras fuera de pantalla, una para cada ojo que actualice cada fotogramaSetup two off-screen cameras, one to follow each eye that update each frame
  2. Configuración de dos destinos de representación de baja resolución (es decir, 200 x 200 cada uno) que las cámaras representan enSetup two low-resolution render targets (that is, 200x200 each) that the cameras render into
  3. Configurar un cuádruple que se mueva delante del usuarioSet up a quad that moves in front of the user

Cada fotograma:Each Frame:

  1. Dibuje los objetivos de representación de cada ojo a baja resolución (datos de volumen, sombreadores costosos, etc.)Draw the render targets for each eye at low-resolution (volume data, expensive shaders, and so on)
  2. Dibuje la escena normalmente como resolución completa (mallas, interfaz de usuario, etc.)Draw the scene normally as full resolution (meshes, UI, and so on)
  3. Dibuje un cuádruple delante del usuario, a través de la escena, y el proyecto de las representaciones de baja res en eseDraw a quad in front of the user, over the scene, and project the low-res renders onto that
  4. Resultado: combinación visual de elementos de resolución completa con datos de volumen de baja resolución pero de alta densidadResult: visual combination of full-resolution elements with low-resolution but high-density volume data