1、前言
和在opengl中实现一样,unity通过texture3D读取体数据,通过着色器处理,从而使用GPU进行计算渲染。
2、Texture3D的读取方式
代码生成Texture3D:
using UnityEngine;
public class Create3DTex : MonoBehaviour
{
[SerializeField]
int size = 16;
void Start()
{
var tex = new Texture3D(size, size, size, TextureFormat.ARGB32, true);
var colors = new Color[size * size * size];
float a = 1f / (size - 1);
int i = 0;
Color c = Color.white;
for (int z = 0; z < size; ++z)
{
for (int y = 0; y < size; ++y)
{
for (int x = 0; x < size; ++x, ++i)
{
c.r = ((x & 1) != 0) ? x * a : 1 - x * a;
c.g = ((y & 1) != 0) ? y * a : 1 - y * a;
c.b = ((z & 1) != 0) ? z * a : 1 - z * a;
colors[i] = c;
}
}
}
tex.SetPixels(colors);
tex.Apply();
var renderer = GetComponent<Renderer>();
renderer.material.SetTexture("_Volume", tex);
}
}
读取Texture3D
Shader "VolumeRendering/3D Texture"
{
Properties
{
_Volume("Volume", 3D) = "" {}
}
CGINCLUDE
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 uv : TEXCOORD0;
};
sampler3D _Volume;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.vertex.xyz * 0.5 + 0.5;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return tex3D(_Volume, i.uv);
}
ENDCG
SubShader
{
Tags
{
"RenderType"="Opaque"
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
多边形表面的3d纹理的颜色被描绘。像下面这样改变顶点着色器的代码,通过改变或移动图形的大小,就可以用世界坐标消减想看的3d纹理的部位。
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
float4 wpos = mul(unity_ObjectToWorld, v.vertex);
o.uv = wpos.xyz * 0.5 + 0.5;
return o;
}
3、直接读取3D体数据
将原始数据直接作为3d纹理使用。
using UnityEngine;
using UnityEditor;
using UnityEditor.Experimental.AssetImporters;
using System;
using System.IO;
[ScriptedImporter(1, "raw")]
public class PvmRawImporter : ScriptedImporter
{
public enum Bits
{
Eight = 1,
Sixteen = 2,
}
public int width = 256;
public int height = 256;
public int depth = 256;
public Bits bit = Bits.Eight;
int totalSize
{
get { return width * height * depth * (int)bit; }
}
int maxValueSize
{
get
{
switch (bit)
{
case Bits.Eight : return (int)Byte.MaxValue;
case Bits.Sixteen : return (int)UInt16.MaxValue;
default:
throw new Exception("bit is wrong.");
}
}
}
Texture3D GetTexture3D(string path)
{
using (var stream = new FileStream(path, FileMode.Open))
{
if (stream.Length != totalSize)
{
throw new Exception("Data size is wrong.");
}
int n = totalSize;
var colors = new Color[n];
float a = 1f / maxValueSize;
var buf = new byte[(int)bit];
for (int i = 0; i < n; ++i)
{
float value = 0f;
switch (bit)
{
case Bits.Eight:
var b = stream.ReadByte();
value = a * b;
break;
case Bits.Sixteen:
stream.Read(buf, 0, 2);
value = a * BitConverter.ToUInt16(buf, 0);
break;
}
colors[i] = new Color(value, value, value, value);
}
var tex3d = new Texture3D(width, height, depth, TextureFormat.RGBA32, false);
tex3d.SetPixels(colors, 0);
return tex3d;
}
}
public override void OnImportAsset(AssetImportContext ctx)
{
try
{
var tex3d = GetTexture3D(ctx.assetPath);
ctx.AddObjectToAsset("Volume", tex3d);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
4、raycasting进行体渲染
Shader "VolumeRendering/VolumeRendering"
{
Properties
{
_Volume("Volume", 3D) = "" {}
_Color("Color", Color) = (1, 1, 1, 1)
_Iteration("Iteration", Int) = 10
_Intensity("Intensity", Range(0, 1)) = 0.1
}
CGINCLUDE
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 localPos : TEXCOORD0;
float4 worldPos : TEXCOORD1;
};
sampler3D _Volume;
fixed4 _Color;
int _Iteration;
fixed _Intensity;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.localPos = v.vertex;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 wdir = i.worldPos - _WorldSpaceCameraPos;
float3 ldir = normalize(mul(unity_WorldToObject, wdir));
float3 lstep = ldir / _Iteration;
float3 lpos = i.localPos;
fixed output = 0.0;
[loop]
for (int i = 0; i < _Iteration; ++i)
{
fixed a = tex3D(_Volume, lpos + 0.5).r;
output += (1 - output) * a * _Intensity;
lpos += lstep;
if (!all(max(0.5 - abs(lpos), 0.0))) break;
}
return _Color * output;
}
ENDCG
SubShader
{
Tags
{
"Queue" = "Transparent"
"RenderType" = "Transparent"
}
Pass
{
Cull Back
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Lighting Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}