3D 텍스처는 표준 2차원이 아닌 3차원 정보를 포함하는 비트맵 이미지입니다. 3D 텍스처는 일반적으로 안개 또는 연기와 같은 볼류메트릭 효과를 시뮬레이션하거나 볼류메트릭 3D 메시를 근사화하거나 애니메이션 텍스처를 저장하고 서로 간에 부드럽게 블렌딩하는 데 사용됩니다.
Unity 프로젝트에서 Unity 에디터는 텍스처 에셋으로 3D 텍스처를 나타냅니다. 텍스처 에셋의 임포트 설정을 구성하기 위해 텍스처 에셋을 선택하고 인스펙터를 사용하거나 TextureImporter API를 사용하는 스크립트를 작성할 수 있습니다.
Unity 엔진에서 Unity는 Texture3D 클래스를 사용하여 3D 텍스처를 나타냅니다. 이 클래스를 사용하여 C# 스크립트로 3D 텍스처와 상호작용합니다.
3D 텍스처의 최대 해상도는 2048x2048x2048입니다.
메모리와 디스크의 3D 텍스처 크기는 해상도가 증가함에 따라 빠르게 증가합니다. 밉맵이 없고 해상도가 16x 16x16인 RGBA32 3D 텍스처는 크기가 128KB이지만, 해상도가 256x256x256인 경우에는 크기가 512MB입니다.
프로젝트에 3D 텍스처를 만들려면 반드시 스크립트를 사용해야 합니다.
다음 예시는 Texture3D 클래스의 인스턴스를 만들고 컬러 데이터로 채워서 프로젝트에 텍스처 에셋으로 저장하는 에디터 스크립트입니다.
using UnityEditor;
using UnityEngine;
public class ExampleEditorScript : MonoBehaviour
{
[MenuItem("CreateExamples/3DTexture")]
static void CreateTexture3D()
{
// Configure the texture
int size = 32;
TextureFormat format = TextureFormat.RGBA32;
TextureWrapMode wrapMode = TextureWrapMode.Clamp;
// Create the texture and apply the configuration
Texture3D texture = new Texture3D(size, size, size, format, false);
texture.wrapMode = wrapMode;
// Create a 3-dimensional array to store color data
Color[] colors = new Color[size * size * size];
// Populate the array so that the x, y, and z values of the texture will map to red, blue, and green colors
float inverseResolution = 1.0f / (size - 1.0f);
for (int z = 0; z < size; z++)
{
int zOffset = z * size * size;
for (int y = 0; y < size; y++)
{
int yOffset = y * size;
for (int x = 0; x < size; x++)
{
colors[x + yOffset + zOffset] = new Color(x * inverseResolution,
y * inverseResolution, z * inverseResolution, 1.0f);
}
}
}
// Copy the color values to the texture
texture.SetPixels(colors);
// Apply the changes to the texture and upload the updated texture to the GPU
texture.Apply();
// Save the texture to your Unity Project
AssetDatabase.CreateAsset(texture, "Assets/Example3DTexture.asset");
}
}
다음은 3D 텍스처를 사용하여 볼륨을 시각화하는 간단한 레이마칭 셰이더의 예제입니다.
Shader "Unlit/VolumeShader\"
{
Properties
{
_MainTex ("Texture", 3D) = "white" {}
_Alpha ("Alpha", float) = 0.02
_StepSize ("Step Size", float) = 0.01
}
SubShader
{
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
Blend One OneMinusSrcAlpha
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// Maximum amount of raymarching samples
#define MAX_STEP_COUNT 128
// Allowed floating point inaccuracy
#define EPSILON 0.00001f
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 objectVertex : TEXCOORD0;
float3 vectorToSurface : TEXCOORD1;
};
sampler3D _MainTex;
float4 _MainTex_ST;
float _Alpha;
float _StepSize;
v2f vert (appdata v)
{
v2f o;
// Vertex in object space this will be the starting point of raymarching
o.objectVertex = v.vertex;
// Calculate vector from camera to vertex in world space
float3 worldVertex = mul(unity_ObjectToWorld, v.vertex).xyz;
o.vectorToSurface = worldVertex - _WorldSpaceCameraPos;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
float4 BlendUnder(float4 color, float4 newColor)
{
color.rgb += (1.0 - color.a) * newColor.a * newColor.rgb;
color.a += (1.0 - color.a) * newColor.a;
return color;
}
fixed4 frag(v2f i) : SV_Target
{
// Start raymarching at the front surface of the object
float3 rayOrigin = i.objectVertex;
// Use vector from camera to object surface to get ray direction
float3 rayDirection = mul(unity_WorldToObject, float4(normalize(i.vectorToSurface), 1));
float4 color = float4(0, 0, 0, 0);
float3 samplePosition = rayOrigin;
// Raymarch through object space
for (int i = 0; i < MAX_STEP_COUNT; i++)
{
// Accumulate color only within unit cube bounds
if(max(abs(samplePosition.x), max(abs(samplePosition.y), abs(samplePosition.z))) < 0.5f + EPSILON)
{
float4 sampledColor = tex3D(_MainTex, samplePosition + float3(0.5f, 0.5f, 0.5f));
sampledColor.a *= _Alpha;
color = BlendUnder(color, sampledColor);
samplePosition += rayDirection * _StepSize;
}
}
return color;
}
ENDCG
}
}
}
이 셰이더를 페이지 상단의 예제에서 생성된 3D 텍스처와 함께 사용하면 다음의 결과를 얻을 수 있습니다.