볼륨 렌더링
볼륨 렌더링을 익숙하지 않은 경우 개요를 읽어보는 것이 좋습니다.
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' 큐브가 있다고 가정합니다. 이 큐브는 점을 배치하기 위한 참조로 사용할 수 있습니다.
// 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
혼합 해상도 장면 렌더링
낮은 해상도로 장면의 일부를 렌더링하고 다시 배치하는 방법:
- 두 개의 오프 스크린 카메라 설정, 각 프레임을 업데이트 하는 각 눈을 따라 하나
- 카메라가 렌더링하는 두 개의 저해상도 렌더링 대상(즉, 각각 200x200)을 설정합니다.
- 사용자 앞에서 이동하는 쿼드 설정
각 프레임:
- 저해상도(볼륨 데이터, 비용이 많이 드는 셰이더 등)에서 각 눈의 렌더링 대상을 그립니다.
- 일반적으로 장면을 전체 해상도(메시, UI 등)로 그립니다.
- 장면 위에 사용자 앞에 쿼드를 그리고 저해선 렌더링을 해당 위치에 프로젝션합니다.
- 결과: 해상도가 낮지만 고밀도 볼륨 데이터와 전체 해상도 요소의 시각적 조합