Renderização de volumeVolume rendering

Para MRI médico ou volumes de engenharia, confira renderização de volume na Wikipédia.For medical MRI or engineering volumes, see Volume Rendering on Wikipedia. Essas "imagens volumétricoss" contêm informações avançadas com opacidade e cor em todo o volume que não podem ser facilmente expressas como superfícies como malhas poligonal.These 'volumetric images' contain rich information with opacity and color throughout the volume that cannot be easily expressed as surfaces such as polygonal meshes.

Principais soluções para melhorar o desempenhoKey solutions to improve performance

  1. MÁ: abordagem ingênua: mostrar volume inteiro, geralmente é executado muito lentamenteBAD: Naïve Approach: Show Whole Volume, generally runs too slowly
  2. BOM: plano de corte: mostra apenas uma única fatia do volumeGOOD: Cutting Plane: Show only a single slice of the volume
  3. BOA: recortando o subvolume: mostrar apenas algumas camadas do volumeGOOD: Cutting Sub-Volume: Show only a few layers of the volume
  4. BOM: Reduza a resolução da renderização de volume (consulte ' renderização de cena de resolução mista ')GOOD: Lower the resolution of the volume rendering (see 'Mixed Resolution Scene Rendering')

Há apenas uma determinada quantidade de informações que podem ser transferidas do aplicativo para a tela em qualquer quadro específico, que é a largura de banda de memória total.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. Além disso, qualquer processamento (ou ' sombreamento ') necessário para transformar os dados para apresentação requer tempo.Also, any processing (or 'shading') required to transform that data for presentation requires time. As principais considerações ao fazer a renderização de volume são:The primary considerations when doing volume rendering are as such:

  • Screen-Width * Screen-Height * Screen-Count * volume-camadas-em-pixel = total-volume-amostras por quadroScreen-Width * Screen-Height * Screen-Count * Volume-Layers-On-That-Pixel = Total-Volume-Samples-Per-Frame
  • 1028 * 720 * 2 * 256 = 378961920 (100%) (volume de resolução completa: muitos exemplos)1028 * 720 * 2 * 256 = 378961920 (100%) (full res volume: too many samples)
  • 1028 * 720 * 2 * 1 = 1480320 (0,3% de completo) (fatia fina: 1 amostra por pixel, é executado sem 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 completo) (fatia do subvolume: 10 amostras por pixel, é executada razoavelmente suavemente, 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 completo) (volume de resolução inferior: menos pixels, volume completo, parece 3D, mas um pouco borrado)200 * 200 * 2 * 256 = 20480000 (5% of full) (lower res volume: fewer pixels, full volume, looks 3d but a bit blurry)

Representando texturas 3DRepresenting 3D Textures

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

Na 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];
 }

Sombreamento e gradientesShading and Gradients

Como sombrear um volume, como MRI, para visualização útil.How to shade a volume, such as MRI, for useful visualization. O método principal é ter uma ' janela de intensidade ' (um mínimo e um máximo) que você deseja ver as intensidades dentro e simplesmente dimensionar esse espaço para ver a intensidade de preto e branco.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. Um ' Ramp de cores ' pode ser aplicado aos valores dentro desse intervalo e armazenado como uma textura, de modo que partes diferentes do espectro de intensidade possam ser sombreadas em cores diferentes: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 ) );

Em muitos de nossos aplicativos, armazenamos em nosso volume um valor de intensidade bruta e um "índice de segmentação" (para segmentar partes diferentes, como Skin e Bone; esses segmentos são criados por especialistas em ferramentas 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). Isso pode ser combinado com a abordagem acima para colocar uma cor diferente ou até mesmo uma rampa de cores 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

Divisão de volume em um sombreadorVolume Slicing in a Shader

Uma ótima primeira etapa é criar um "plano de divisão" que possa passar pelo volume, "dividindo-o" e como os valores de verificação em cada ponto.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. Isso pressupõe que há um cubo ' VolumeSpace ', que representa onde o volume está no espaço de mundo, que pode ser usado como uma referência para colocar os pontos: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 ) );

Rastreamento de volume em sombreadoresVolume Tracing in Shaders

Como usar a GPU para realizar o rastreamento de subvolume (percorre algumas voxels de fundo e camadas nos dados de volta para 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 );

Renderização de volume inteiraWhole Volume Rendering

Modificando o código do subvolume acima, obtemos: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

Renderização de cena de resolução mistaMixed Resolution Scene Rendering

Como renderizar uma parte da cena com uma baixa resolução e colocá-la de volta em vigor:How to render a part of the scene with a low resolution and put it back in place:

  1. Configurar duas câmeras fora da tela, uma para seguir cada olho que atualiza cada quadroSetup two off-screen cameras, one to follow each eye that update each frame
  2. Configurar dois destinos de renderização de baixa resolução (ou seja, 200 x 200 cada) em que as câmeras são renderizadasSetup two low-resolution render targets (that is, 200x200 each) that the cameras render into
  3. Configurar um quad que se move para a frente do usuárioSet up a quad that moves in front of the user

Cada quadro:Each Frame:

  1. Desenhe os destinos de renderização para cada olho em baixa resolução (dados de volume, sombreadores caros e assim por diante)Draw the render targets for each eye at low-resolution (volume data, expensive shaders, and so on)
  2. Desenhe a cena normalmente como resolução completa (malhas, interface do usuário e assim por diante)Draw the scene normally as full resolution (meshes, UI, and so on)
  3. Desenhe um quad na frente do usuário, por meio da cena, e projeto os renderizadores de baixa res para issoDraw a quad in front of the user, over the scene, and project the low-res renders onto that
  4. Resultado: combinação visual de elementos de resolução completa com dados de volume de baixa resolução, mas de alta densidadeResult: visual combination of full-resolution elements with low-resolution but high-density volume data