本文参考《GPU编程与CG语言之阳春白雪下里巴人》
光线投射方法是基于图像序列的直接体会之算法,从图像的每一个像素,沿固定方向(通常是视线方向)发射一条光线,光线穿越整个图像序列,并在这个过程中,对图像序列进行采样获取颜色信息,同时根据光线吸收模型将会颜色值进行累加,直至光线穿越整个图像序列,最后得到的颜色值就是渲染图像的颜色。
这部分首先根据一系列有序的texture2d生成texture3d,然后渲染cube的正反深度纹理,并将这些数据输入到核心shader中,并以此shader为基准渲染屏幕特效
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class RayMarching : MonoBehaviour
{
public Shader renderFrontDepthShader;
public Shader renderBackDepthShader;
public Shader rayMarchShader;
public Texture2D[] slices;
private Material _rayMarchMaterial;
private Camera _ppCamera;
private Texture3D _volumeBuffer;
private void Awake()
{
_rayMarchMaterial = new Material(rayMarchShader);
}
private void Start()
{
GenerateVolumeTexture();
}
private void OnDestroy()
{
if(_volumeBuffer != null)
{
Destroy(_volumeBuffer);
}
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
_rayMarchMaterial.SetTexture("_VolumeTex", _volumeBuffer);//设置3D纹理贴图
if(_ppCamera == null)
{
var go = new GameObject("PPCamera");
_ppCamera = go.AddComponent();
_ppCamera.enabled = false;
}
_ppCamera.CopyFrom(GetComponent());//模拟一个眼睛
RenderTexture frontDepth = RenderTexture.GetTemporary(source.width, source.height, 0, RenderTextureFormat.ARGBFloat);
RenderTexture backDepth = RenderTexture.GetTemporary(source.width, source.height, 0, RenderTextureFormat.ARGBFloat);
RenderTexture volumeTarget = RenderTexture.GetTemporary(source.width, source.height, 0);
// Render depths
_ppCamera.targetTexture = frontDepth;
_ppCamera.RenderWithShader(renderFrontDepthShader, "RenderType");
_ppCamera.targetTexture = backDepth;
_ppCamera.RenderWithShader(renderBackDepthShader, "RenderType");
// Render volume
_rayMarchMaterial.SetTexture("_FrontTex", frontDepth);
_rayMarchMaterial.SetTexture("_BackTex", backDepth);//将正反向渲染的深度图传入到Ray Marching Shader中
Graphics.Blit(source, destination, _rayMarchMaterial);//屏幕特效
//GameObject.Find("Plane").GetComponent().material = _rayMarchMaterial;附在物体材质上
//Release
RenderTexture.ReleaseTemporary(volumeTarget);
RenderTexture.ReleaseTemporary(frontDepth);
RenderTexture.ReleaseTemporary(backDepth);
}
private void GenerateVolumeTexture()
{
System.Array.Sort(slices, (x, y) => x.name.CompareTo(y.name));
_volumeBuffer = new Texture3D(128, 128, 128, TextureFormat.ARGB32, false);
int w = 128;
int h = 128;
int d = 128;
float countOffset = (slices.Length - 1) / (float)d;
Color[] volumeColors = new Color[w * h * d];
int sliceCount = 0;
float sliceCountFloat = 0f;
for(int z = 0; z < d; z++)
{
sliceCountFloat += countOffset;
sliceCount = Mathf.FloorToInt(sliceCountFloat);
for(int x = 0; x < w; x++)
{
for(int y = 0; y < h; y++)
{
var idx = x + (y * w) + (z * (w * h));
volumeColors[idx] = slices[sliceCount].GetPixelBilinear(x / (float)w, y / (float)h);
volumeColors[idx].a *= volumeColors[idx].r;
}
}
}
_volumeBuffer.SetPixels(volumeColors);
_volumeBuffer.Apply();
_rayMarchMaterial.SetTexture("_VolumeTex", _volumeBuffer);
}
}
Cube的shader代码比较简单,什么也不输出:
Shader "Ray Marching/Volume"
{
SubShader
{
Tags {"RenderType" = "Volume"}
ZWrite Off
Pass
{
ColorMask 0
}
}
FallBack Off
}
只需要 设置RenderType为Volume,指定它为渲染深度图的对象(最终图像的载体),然后ColorMask 0屏蔽所有通道(不可见)
渲染深度图的Shader代码:
Shader "Hidden/Ray Marching/Render Back Depth" {
CGINCLUDE
#include "UnityCG.cginc"
struct v2f {
float4 pos : POSITION;
float3 localPos : TEXCOORD0;
};
float4 _VolumeScale;
v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.localPos = v.vertex.xyz + 0.5;
return o;
}
half4 frag(v2f i) : COLOR
{
return float4(i.localPos, 1);
}
ENDCG
Subshader
{
Tags {"RenderType"="Volume"}
Fog { Mode Off }
Pass
{
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
正反只是Cull Front 和Cull Back的区别
核心Shader部分:
Shader "Hidden/Ray Marching/Ray Marching"
{
Properties
{
_Color("Color", color) = (1,1,1,1)
_Strength("Strength",range(0,5))=2
}
CGINCLUDE
#include "UnityCG.cginc"
#pragma target 3.0
struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
sampler3D _VolumeTex;
float4 _VolumeTex_TexelSize;
sampler2D _FrontTex;
sampler2D _BackTex;
fixed4 _Color;
float _Strength;
v2f vert( appdata_img v )
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord.xy;
return o;
}
half4 raymarch(v2f i)
{
float3 frontPos = tex2D(_FrontTex, i.uv).xyz;
float3 backPos = tex2D(_BackTex, i.uv).xyz;
float3 dir = backPos - frontPos;
float3 pos = frontPos;
float4 dst = 0;
float3 stepDist = dir /128;
for(int k = 0; k < 128; k++)
{
float4 src = tex3D(_VolumeTex, pos);
src.rgb *= src.a;
dst = (1.0f - dst.a) * src + dst;//blend
pos += stepDist;
}
return dst;
}
ENDCG
Subshader {
ZTest Always
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
half4 frag(v2f i) : COLOR
{
return raymarch(i)*_Color*_Strength;
}
ENDCG
}
}
Fallback off
}
在这个Shader里面我们需要用到摄像机渲染得到的深度图信息作为 tex3D函数的第二个参数,对C#代码计算得到的Texture3D进行采样,得到最终具有3D效果的图像。