Volymåtergivning

Om du inte har använt volymrendering tidigare rekommenderar vi att du läser vår översikt.

Representerar 3D-texturer

På CPU:en:

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

På GPU:n:

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

Skuggning och toningar

Så här skuggar du en volym, till exempel MRI, för användbar visualisering. Den primära metoden är att ha ett "intensitetsfönster" (ett min och max) som du vill se intensiteter inom och helt enkelt skala in i det utrymmet för att se den svartvita intensiteten. En "färgramp" kan sedan tillämpas på värdena inom intervallet och lagras som en struktur, så att olika delar av intensitetsspektrumet kan skuggas olika färger:

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

I många av våra program lagrar vi i vår volym både ett råintensitetsvärde och ett "segmenteringsindex" (för att segmentera olika delar som hud och ben; dessa segment skapas av experter på dedikerade verktyg). Detta kan kombineras med metoden ovan för att sätta en annan färg, eller till och med olika färgramp för varje segmentindex:

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

Volymsnitt i en skuggning

Ett bra första steg är att skapa ett "segmenteringsplan" som kan flyttas genom volymen, "segmentera den" och hur genomsökningsvärdena vid varje punkt. Detta förutsätter att det finns en VolumeSpace-kub, som representerar var volymen finns i världsrymden, som kan användas som referens för att placera punkterna:

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

Volymspårning i skuggor

Så här använder du GPU:n för att utföra subvolumespårning (går några voxels djupt och lagrar sedan data från bak till fram):

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

Hel volymåtergivning

När vi ändrar undervolume-koden ovan får vi:

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

Scenåtergivning med blandad upplösning

Så här renderar du en del av scenen med låg upplösning och sätter tillbaka den:

  1. Konfigurera två kameror utanför skärmen, en för att följa varje öga som uppdaterar varje bildruta
  2. Konfigurera två mål för lågupplösningsåtergivning (d.a. 200x200 vardera) som kamerorna återger till
  3. Konfigurera en quad som flyttas framför användaren

Varje ram:

  1. Rita återgivningsmålen för varje öga med låg upplösning (volymdata, dyra skuggor och så vidare)
  2. Rita scenen normalt som full upplösning (nät, användargränssnitt och så vidare)
  3. Rita en quad framför användaren, över scenen, och projicera low-res renderas på den
  4. Resultat: visuell kombination av fullupplösningselement med lågupplösning men högdensitetsvolymdata