volume render

数学原理这篇文章讲得非常清楚,https://zhuanlan.zhihu.com/p/56710440

现在我们来一步步搭建一个体渲染的场景
首先我们创建一个新的场景,放一个小球,给小球放上一个半兰伯特光照的材质
同时加上阴影代码

Shader "Lit/halfLambert"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _wrap ("wrap", Float) = 0.5
    }
    SubShader
    {

        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Tags{ "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float3 normal : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _wrap;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);

                float3 worldNormal = normalize(i.normal);
                float product = dot(worldNormal, normalize(_WorldSpaceLightPos0));

                float wrap_diffuse = max(0, (product + _wrap) / (1 + _wrap));

                col = col * wrap_diffuse;
                
                return col;
            }
            ENDCG
        }

            Pass
            {
                Tags{ "LightMode" = "ShadowCaster" }

                ZWrite On ZTest LEqual Cull Off

                CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"

                struct v2f {
                V2F_SHADOW_CASTER;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                    return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
                ENDCG
            }
    }
}

image.png

然后我们需要的是两张图,一张是主摄像机的深度图,一般来说,这张图就是用来从屏幕空间还原到世界坐标的。另一张是shadowmap。我们需要知道世界坐标下的某个点是否在阴影当中。

首先搞定阴影图,这个我们可以利用unity内置的阴影图。
首先创建一个脚本ShadowMapCollector 绑在主光上

public class ShadowMapCollector : MonoBehaviour {
    private CommandBuffer _cascadeShadowCommandBuffer;
    Light _light;
    // Use this for initialization
    void Start()
    {

        _light = GetComponent<Light>();
        _cascadeShadowCommandBuffer = new CommandBuffer();
        _cascadeShadowCommandBuffer.name = "Dir Light Command Buffer";
        _cascadeShadowCommandBuffer.SetGlobalTexture("_CascadeShadowMapTexture", UnityEngine.Rendering.BuiltinRenderTextureType.CurrentActive);

        _light.AddCommandBuffer(LightEvent.AfterShadowMap, _cascadeShadowCommandBuffer);

    }    
}

它的作用是创建一个cmd buffer, 加入到灯光的LightEvent.AfterShadowMap事件之后,这样当前的活动RenderTexture就是CSM。把这个CSM设置到我们的shader纹理_CascadeShadowMapTexture中

为了采样阴影,我们先创建一个公共文件

UNITY_DECLARE_SHADOWMAP(_CascadeShadowMapTexture);

//-----------------------------------------------------------------------------------------
// GetCascadeWeights_SplitSpheres
//-----------------------------------------------------------------------------------------
inline float4 GetCascadeWeights_SplitSpheres(float3 wpos)
{
    float3 fromCenter0 = wpos.xyz - unity_ShadowSplitSpheres[0].xyz;
    float3 fromCenter1 = wpos.xyz - unity_ShadowSplitSpheres[1].xyz;
    float3 fromCenter2 = wpos.xyz - unity_ShadowSplitSpheres[2].xyz;
    float3 fromCenter3 = wpos.xyz - unity_ShadowSplitSpheres[3].xyz;
    float4 distances2 = float4(dot(fromCenter0, fromCenter0), dot(fromCenter1, fromCenter1), dot(fromCenter2, fromCenter2), dot(fromCenter3, fromCenter3));

    float4 weights = float4(distances2 < unity_ShadowSplitSqRadii);
    weights.yzw = saturate(weights.yzw - weights.xyz);
    return weights;
}


//-----------------------------------------------------------------------------------------
// GetCascadeShadowCoord
//-----------------------------------------------------------------------------------------
inline float3 GetCascadeShadowCoord(float4 wpos, float4 cascadeWeights)
{
    float3 sc0 = mul(unity_WorldToShadow[0], wpos).xyz;
    float3 sc1 = mul(unity_WorldToShadow[1], wpos).xyz;
    float3 sc2 = mul(unity_WorldToShadow[2], wpos).xyz;
    float3 sc3 = mul(unity_WorldToShadow[3], wpos).xyz;

    float3 shadowMapCoordinate = float3(sc0 * cascadeWeights[0] + sc1 * cascadeWeights[1] + sc2 * cascadeWeights[2] + sc3 * cascadeWeights[3]);
#if defined(UNITY_REVERSED_Z)
    float  noCascadeWeights = 1 - dot(cascadeWeights, 1);
    shadowMapCoordinate.z += noCascadeWeights;
#endif
    return shadowMapCoordinate;
}

//-----------------------------------------------------------------------------------------
// GetLightAttenuation
//-----------------------------------------------------------------------------------------
float GetLightAttenuation(float3 wpos)
{
    float atten = 1;
    //#if SHADOWS_DEPTH_ON
    // sample cascade shadow map
    float4 cascadeWeights = GetCascadeWeights_SplitSpheres(wpos);

    float3 samplePos = GetCascadeShadowCoord(float4(wpos, 1), cascadeWeights);
    atten = UNITY_SAMPLE_SHADOW(_CascadeShadowMapTexture, samplePos.xyz);

    //#endif
    return atten;
}


它提供了GetLightAttenuation方法,来传入世界坐标,采样CSM
我们写一个接受阴影的材质来试一试

Shader "Lit/ShadowReciever"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            
            #include "UnityCG.cginc"
            #include "CascadeShadow.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float4 worldPos :TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                float atten = GetLightAttenuation(i.worldPos);
                return col * atten;
            }
            ENDCG
        }
    }
}

点击运行,效果如下。


image.png

说明shadowmap已经准备好,接下来就是重点做raymarch了。

而体积渲染都是由屏幕发出射线因此我们需要给camera挂上一个脚本。


public class CameraVolume : MonoBehaviour {

    Camera cam;
    public Material DepthRender;


    public Material BlitAddMat;

    [Range(0.0f, 0.999f)]
    public float MieG = 0.1f;
    static int _MieG;

    static int _Source;
    // Use this for initialization
    void Start()
    {

        cam = gameObject.GetComponent<Camera>();
        cam.depthTextureMode = DepthTextureMode.Depth;

        _MieG = Shader.PropertyToID("_MieG");
        _Source = Shader.PropertyToID("_SourceTex");
    }

    // Update is called once per frame
    void Update()
    {

    }

    public void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        Shader.SetGlobalMatrix("_InvVP", (GL.GetGPUProjectionMatrix(cam.projectionMatrix, false) * cam.worldToCameraMatrix).inverse);

        DepthRender.SetVector(_MieG, new Vector4(1 - (MieG * MieG), 1 + (MieG * MieG), 2 * MieG, 1.0f / (4.0f * Mathf.PI)));
        var fogText = RenderTexture.GetTemporary(Screen.width, Screen.height);

        Graphics.Blit(source, fogText, DepthRender);

        BlitAddMat.SetTexture(_Source, source);

        Graphics.Blit(fogText, destination, BlitAddMat);
        RenderTexture.ReleaseTemporary(fogText);
    }
}


然后添加一个材质,它的shader如下

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/depth_render"
{
    Properties
    {

        _LightFinalColor("Color", Color) = (2,2,2,1)
        _fogDensity("densigty", Float) = 1.0
        _MaxRayLength("MaxRayLength", Float) = 150.0
        _SampleCount("SampleCount", Int) = 100
    }
    
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always  
        
        Tags{ "RenderType" = "Opaque"        "LightMode" = "ForwardBase" }

        Pass
        {
            CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag

#include "UnityCG.cginc"
#include "CascadeShadow.cginc"
            sampler2D _CameraDepthTexture;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 worldPos :TEXCOORD1;
            };


            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }


            int _SampleCount = 50;
            float _fogDensity = 1;
            float4 _LightFinalColor = float4(2, 2, 2, 1);

            // x: 1 - g^2, y: 1 + g^2, z: 2*g, w: 1/4pi
            float4 _MieG;
            #define MieScattering(cosAngle, g) g.w * (g.x / (pow(g.y - g.z * cosAngle, 1.5)))


            float4 ScatterStep(float3 accumulatedLight, float accumulatedTransmittance, float sliceLight, float sliceDensity)
            {
                sliceDensity = max(sliceDensity, 0.00001);
                float sliceTransmittance = exp(-sliceDensity / _SampleCount);

                float sliceLightIntegral = sliceLight * (1 - sliceTransmittance) / sliceDensity;
                accumulatedLight += sliceLightIntegral * accumulatedTransmittance;
                accumulatedTransmittance *= sliceTransmittance;

                return float4(accumulatedLight, accumulatedTransmittance);
            }

            float4 RayMarch(float cosAngle, float3 origin, float3 rayDir, float3 finalPos, float rate)
            {
                float4 color = float4(0, 0, 0, 1);
                float stepLength = 1.0 / _SampleCount;

                rate *= _fogDensity;

                float att1 = rate;
                float4 accumulateLight = float4(0, 0, 0, 1);
                for (int i = 0; i <= _SampleCount; ++i)
                {
                    float3 worldPos = lerp(origin, finalPos, stepLength *i);

                    float atten = GetLightAttenuation(worldPos);

                    // apply phase function for dir light
                    atten *= MieScattering(cosAngle, _MieG);

                    accumulateLight = ScatterStep(accumulateLight.xyz, accumulateLight.w, atten, rate);

                    att1 = accumulateLight;
                }

                accumulateLight.rgb *= _LightFinalColor;
                accumulateLight.a = saturate(accumulateLight.a);

               // return float4(att1 , att1 , att1 , 1);
               return accumulateLight;
            }

            float _MaxRayLength = 50;
            float4x4 _InvVP;
            float4 frag(v2f i) : SV_Target
            {

                //还原世界坐标的射线
                float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
                float4 wpos = mul(_InvVP, float4(i.uv * 2 - 1, depth, 1));
                wpos /= wpos.w;

                float3 rayDir = wpos.xyz - _WorldSpaceCameraPos;
                float rayLength = length(rayDir);
                rayDir /= rayLength;
                rayLength = min(rayLength, _MaxRayLength);


                float3 lightDir = normalize(_WorldSpaceLightPos0);
                float cosAngle = dot(lightDir, rayDir);

                float4 color = RayMarch(cosAngle , _WorldSpaceCameraPos, rayDir, _WorldSpaceCameraPos + rayLength * rayDir, rayLength / _MaxRayLength);

                return color;


            }
            ENDCG
        }
    }
}

再创建一个blitadd材质如下

Shader "Unlit/BlitAdd"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Cull Off ZWrite Off ZTest Always
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;


            sampler2D _SourceTex;
            float4 _SourceTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 main = tex2D(_MainTex, i.uv);
                fixed4 source = tex2D(_SourceTex, i.uv);
                // apply fog
                return lerp(main, source, main.a) ;
                //return main ;
            }
            ENDCG
        }
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352