3D 纹理是位图图像,其中包含三维信息,而不是标准的二维信息。3D 纹理通常用于仿真诸如雾或烟的体积效果,模拟体积 3D 网格,或存储动画纹理并在这些动画纹理之间平滑混合。
在 Unity 项目中,Unity Editor 将 3D 纹理表示为纹理资源。要配置纹理资源的导入设置,可选择该纹理资源并使用 Inspector,或者编写一个使用 TextureImporter API 的脚本。
在 Unity 引擎中,Unity 使用 Texture3D 类来表示 3D 纹理。使用此类可以在 C# 脚本中与 3D 纹理进行交互。
3D 纹理的最大分辨率为 2048 x 2048 x 2048。
请注意,随着分辨率的提高,3D 纹理在内存中和磁盘上的大小会迅速增加。一个没有 Mipmap 且分辨率为 16 x 16 x 16 的 RGBA32 3D 纹理具有 128KB 的大小,而分辨率为 256 x 256 x 256 时则具有 512MB 的大小。
要在项目中创建 3D 纹理,必须使用脚本。
下面的示例是一个 Editor 脚本,该脚本创建一个 Texture3D 类的实例,用颜色数据填充该实例,然后作为纹理资源保存到项目中。
using UnityEditor;
using UnityEngine;
public class ExampleEditorScript : MonoBehaviour
{
[MenuItem("CreateExamples/3DTexture")]
static void CreateTexture3D()
{
// 配置纹理
int size = 32;
TextureFormat format = TextureFormat.RGBA32;
TextureWrapMode wrapMode = TextureWrapMode.Clamp;
// 创建纹理并应用配置
Texture3D texture = new Texture3D(size, size, size, format, false);
texture.wrapMode = wrapMode;
// 创建 3 维数组以存储颜色数据
Color[] colors = new Color[size * size * size];
// 填充数组,使纹理的 x、y 和 z 值映射为红色、蓝色和绿色
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);
}
}
}
// 将颜色值复制到纹理
texture.SetPixels(colors);
// 将更改应用到纹理,然后将更新的纹理上传到 GPU
texture.Apply();
// 将纹理保存到 Unity 项目
AssetDatabase.CreateAsset(texture, "Assets/Example3DTexture.asset");
}
}
Unity 编辑器具有三种不同的可视化模式,可用于预览 3D 纹理:
您可以在 Inspector 中预览 3D 纹理,或者编写脚本以使用 Handles API 在 Scene 视图中预览。使用 Inspector 既快捷又方便,但不允许使用自定义渐变。Handles API 允许您根据您的确切需求来配置预览,并允许使用自定义渐变。
要在 Inspector 窗口中预览 3D 纹理:
在此可视化模式中,Unity 将 3D 纹理渲染为半透明立方体。
工具栏中会显示以下控件:
控件: | 功能: |
---|---|
Ramp | 启用和禁用颜色渐变可视化。如果图像包含很多细微的细节,请启用 Ramp 使这些细节更容易看到。 |
Quality | 设置每纹理像素样本数。该值越高渲染质量越好。 |
Alpha | 控制可视化的不透明度。值为 1 表示完全不透明,值为 0 表示完全透明。调整以查看内部像素。 |
在这种可视化模式下,Unity 渲染 3D 纹理的每个轴平面的切片。
工具栏中会显示以下控件:
控件: | 功能: |
---|---|
Ramp | 启用和禁用颜色渐变可视化。如果图像包含很多细微的细节,请启用 Ramp 使这些细节更容易看到。 |
X | 设置 x 轴上的切片位置,以纹理像素为单位。调整以查看特定切片。 |
Y | 设置 y 轴上的切片位置,以纹理像素为单位。调整以查看特定切片。 |
Z | 设置 z 轴上的切片位置,以纹理像素为单位。调整以查看特定切片。 |
在这种可视化模式下,Unity 在 3D 空间中使用有向距离场渲染模式来渲染 3D 纹理。 请注意,此可视化模式仅支持非定向的有向距离场。
工具栏中会显示以下控件:
控件: | 功能: |
---|---|
Scale | 与射线步长相乘的数字。射线步长是两个相邻像素之间的距离。 如果可视化的远处部分被截断,请尝试增加该值。如果根本没有渲染可视化,请尝试减少该值。 |
Offset | 渲染表面的像素强度。当此值为正时,Unity 将扩展渲染表面。当此值为负时,Unity 会将空白空间渲染为曲面,将曲面渲染为空白空间。 |
有关使用 Handles API 预览 3D 纹理及代码示例的信息,请参阅以下文档:
下面是一个使用 3D 纹理来可视化体积的简单光线追踪 (Raymarching) 着色器示例。
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"
// 最大光线追踪样本数
#define MAX_STEP_COUNT 128
// 允许的浮点数误差
#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;
// 对象空间中的顶点将成为光线追踪的起点
o.objectVertex = v.vertex;
// 计算世界空间中从摄像机到顶点的矢量
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
{
// 开始在对象的正面进行光线追踪
float3 rayOrigin = i.objectVertex;
// 使用摄像机到对象表面的矢量获取射线方向
float3 rayDirection = mul(unity_WorldToObject, float4(normalize(i.vectorToSurface), 1));
float4 color = float4(0, 0, 0, 0);
float3 samplePosition = rayOrigin;
// 穿过对象空间进行光线追踪
for (int i = 0; i < MAX_STEP_COUNT; i++)
{
// 仅在单位立方体边界内累积颜色
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 纹理,则结果将如下所示: