Unity之基于光线投射算法的体渲染技术(一)

本文参考《GPU编程与CG语言之阳春白雪下里巴人》

光线投射算法原理:

       光线投射方法是基于图像序列的直接体会之算法,从图像的每一个像素,沿固定方向(通常是视线方向)发射一条光线,光线穿越整个图像序列,并在这个过程中,对图像序列进行采样获取颜色信息,同时根据光线吸收模型将会颜色值进行累加,直至光线穿越整个图像序列,最后得到的颜色值就是渲染图像的颜色。

实现流程:

Unity之基于光线投射算法的体渲染技术(一)_第1张图片

C#代码部分:

这部分首先根据一系列有序的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);
    }
}

shader代码部分:

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效果的图像。

Unity之基于光线投射算法的体渲染技术(一)_第2张图片

 

你可能感兴趣的:(Unity,图形学)