天天看点

Unity Shader学习:体积光/体积阴影Unity Shader学习:体积光/体积阴影

Unity Shader学习:体积光/体积阴影

在前向渲染下实现平行光的体积光影效果,需要全屏深度图,延迟渲染会更划算。

思路:通过ray marching的步进点位置计算该点是否在阴影中,采样阴影贴图,通过dither+blur优化性能,叠加原图和光影图。步进策略直接采用等距,有其他策略性能表现也会更好,阴影级联 (Shadow Cascade)选择了无,注意如果开启了多个的话采样阴影算法需要调整。

参考:

https://blog.csdn.net/puppet_master/article/details/79859678

https://zhuanlan.zhihu.com/p/37624886

https://catlikecoding.com/unity/tutorials/rendering/part-7/

原图:

Unity Shader学习:体积光/体积阴影Unity Shader学习:体积光/体积阴影

体积光:

Unity Shader学习:体积光/体积阴影Unity Shader学习:体积光/体积阴影

体积阴影:

Unity Shader学习:体积光/体积阴影Unity Shader学习:体积光/体积阴影

体积光+体积阴影

Unity Shader学习:体积光/体积阴影Unity Shader学习:体积光/体积阴影

从上方打光:

Unity Shader学习:体积光/体积阴影Unity Shader学习:体积光/体积阴影

c#部分:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class VolumetricShadow : MonoBehaviour {
    private Matrix4x4 frustumCorners = Matrix4x4.identity;
    private Transform camTransform;
    private Camera cam;
    private RenderTexture marchingRT;
    private RenderTexture tempRT;

    public Material mat;
    [Range(0,5)]
    public int downSample=2;
    [Range(0f, 5f)]
    public float samplerScale = 1f;
    [Range(0,256)]
    public int rayMarchingStep=16;
    [Range(0f,100f)]
    public float maxRayLength=15f;
    [Range(0f, 2f)]
    public float volumetricLightIntenstiy = 0.05f;
    [Range(0f, 2f)]
    public float lightScatteringFactor = 0.5f;
    [Range(0f, 5f)]
    public float volumetricShadowIntenstiy = 0f;
    [Range(0f, 0.1f)]
    public float shadowAttenuation = 0.08f;
    [Range(0f, 1f)]
    public float minShadow = 0.5f;

    void Start () {
        camTransform = transform;
        cam = GetComponent<Camera>();
        cam.depthTextureMode = DepthTextureMode.Depth;
        mat.SetTexture("_DitherMap", GenerateDitherMap());
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {        
        //field of view
        float fov = cam.fieldOfView;
        //近裁面距离
        float near = cam.nearClipPlane;
        //横纵比
        float aspect = cam.aspect;
        //近裁面一半的高度
        float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
        //向上和向右的向量
        Vector3 toRight = cam.transform.right * halfHeight * aspect;
        Vector3 toTop = cam.transform.up * halfHeight;

        //分别得到相机到近裁面四个角的向量
        //depth/dist=near/|topLeft|
        //dist=depth*(|TL|/near)
        //scale=|TL|/near
        Vector3 topLeft = camTransform.forward * near + toTop - toRight;
        float scale = topLeft.magnitude / near;

        topLeft.Normalize();
        topLeft *= scale;

        Vector3 topRight = camTransform.forward * near + toTop + toRight;
        topRight.Normalize();
        topRight *= scale;

        Vector3 bottomLeft = camTransform.forward * near - toTop - toRight;
        bottomLeft.Normalize();
        bottomLeft *= scale;

        Vector3 bottomRight = camTransform.forward * near - toTop + toRight;
        bottomRight.Normalize();
        bottomRight *= scale;

        frustumCorners.SetRow(0, bottomLeft);
        frustumCorners.SetRow(1, bottomRight);
        frustumCorners.SetRow(3, topRight);
        frustumCorners.SetRow(2, topLeft);

        mat.SetMatrix("_FrustumCornorsRay", frustumCorners);
        mat.SetInt("_RayMarchingStep", rayMarchingStep);
        mat.SetFloat("_MaxRayLength", maxRayLength);
        mat.SetFloat("_VolumetricLightIntensity", volumetricLightIntenstiy);
        mat.SetFloat("_VolumetricShadowIntenstiy", volumetricShadowIntenstiy);
        mat.SetFloat("_ScatteringFactor", lightScatteringFactor);
        mat.SetFloat("_MinShadow", minShadow);
        mat.SetFloat("_ShadowAttenuation", shadowAttenuation);

        marchingRT = RenderTexture.GetTemporary(Screen.width >> downSample, Screen.height >> downSample, 0, source.format);
        tempRT = RenderTexture.GetTemporary(Screen.width >> downSample, Screen.height >> downSample, 0, source.format);

        //计算阴影
        Graphics.Blit(source, marchingRT, mat, 0);

        //模糊阴影信息
        mat.SetVector("_Offsets", new Vector4(0, samplerScale, 0, 0));
        Graphics.Blit(marchingRT, tempRT,mat,1);
        mat.SetVector("_Offsets", new Vector4(samplerScale, 0, 0, 0));
        Graphics.Blit(tempRT, marchingRT, mat, 1);
        mat.SetVector("_Offsets", new Vector4(0, samplerScale, 0, 0));
        Graphics.Blit(marchingRT, tempRT, mat, 1);
        mat.SetVector("_Offsets", new Vector4(samplerScale, 0, 0, 0));
        Graphics.Blit(tempRT, marchingRT, mat, 1);

        //合并
        mat.SetTexture("_MarchingTex", marchingRT);
        Graphics.Blit(source, destination, mat, 2);

        RenderTexture.ReleaseTemporary(marchingRT);
        RenderTexture.ReleaseTemporary(tempRT);
    }

    //Guerrilla Games 分享 DitherMap
    private Texture2D GenerateDitherMap()
    {
        int texSize = 4;
        Texture2D ditherMap = new Texture2D(texSize, texSize, TextureFormat.Alpha8, false, true);
        ditherMap.filterMode = FilterMode.Point;
        Color32[] colors = new Color32[texSize * texSize];

        colors[0] = GetDitherColor(0.0f);
        colors[1] = GetDitherColor(8.0f);
        colors[2] = GetDitherColor(2.0f);
        colors[3] = GetDitherColor(10.0f);

        colors[4] = GetDitherColor(12.0f);
        colors[5] = GetDitherColor(4.0f);
        colors[6] = GetDitherColor(14.0f);
        colors[7] = GetDitherColor(6.0f);

        colors[8] = GetDitherColor(3.0f);
        colors[9] = GetDitherColor(11.0f);
        colors[10] = GetDitherColor(1.0f);
        colors[11] = GetDitherColor(9.0f);

        colors[12] = GetDitherColor(15.0f);
        colors[13] = GetDitherColor(7.0f);
        colors[14] = GetDitherColor(13.0f);
        colors[15] = GetDitherColor(5.0f);

        ditherMap.SetPixels32(colors);
        ditherMap.Apply();
        return ditherMap;
    }

    private Color32 GetDitherColor(float value)
    {
        byte byteValue = (byte)(value / 16.0f * 255);
        return new Color32(byteValue, byteValue, byteValue, byteValue);
    }
}
           

shader部分:

Shader "Unlit/VolumetricShadow"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		//1.ray marching && get shadow info
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"
			#include "AutoLight.cginc"

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

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				float4 interpolatedRay:TEXCOORD2;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;			
			float _ScatteringFactor;
			float4x4 _FrustumCornorsRay;
			sampler2D _CameraDepthTexture;
			sampler2D _ShadowMapTexture;
			sampler2D _DitherMap;
			int _RayMarchingStep;
			float _MaxRayLength;
			float _VolumetricLightIntensity;
			float _VolumetricShadowIntenstiy;
			float _MinShadow;		
			float _ShadowAttenuation;

			//重映射
			float Remap(float x,float from1,float to1,float from2,float to2) {
				return (x - from1) / (to1 - from1) * (to2 - from2) + from2;
			}

			//判断该点是否在阴影
			float2 GetShadow(float3 worldPos) {
				//比较灯光空间深度
				float4 lightPos = mul(unity_WorldToShadow[0], float4(worldPos, 1));
				float shadow = UNITY_SAMPLE_DEPTH(tex2Dlod(_ShadowMapTexture, float4(lightPos.xy,0,0)));
				float depth = lightPos.z ;
				float shadowValue = step(shadow, depth);
				//阴影的衰减
				float dis = abs(depth - shadow);								
				shadowValue += clamp(Remap(dis, _ShadowAttenuation,0.1,0,1),0,1)*(1-shadowValue);
				return shadowValue;
			}

			v2f vert(appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				
				//四个顶点对应的相机近裁面向量
				int index = step(0.5, v.uv.x) + step(0.5, v.uv.y)*2;

				//int index = 0;
				/*if (v.uv.x < 0.5&&v.uv.y < 0.5)
				{
					index = 0;
				}
				else if (v.uv.x > 0.5&&v.uv.y < 0.5) {
					index = 1;
				}
				else if (v.uv.x > 0.5&&v.uv.y > 0.5) {
					index = 2;
				}
				else {
					index = 3;
				}*/

				o.interpolatedRay = _FrustumCornorsRay[index];	
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				//获得世界坐标
				float depthTextureValue = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
				float linearEyeDepth = LinearEyeDepth(depthTextureValue);
				//限制获取到的最远距离
				linearEyeDepth = clamp(linearEyeDepth,0, _MaxRayLength);
				float3 worldPos = _WorldSpaceCameraPos + linearEyeDepth * i.interpolatedRay.xyz;

				float vShadow = 1;
				float vLight = 0;
				
				float3 rayOri = _WorldSpaceCameraPos;				
				float3 rayDir = i.interpolatedRay.xyz;

				float disCam2World = length(worldPos - _WorldSpaceCameraPos);

				//dither扰动采样点
				float2 offsetUV = fmod(floor(i.vertex.xy), 4.0);
				float ditherValue = tex2D(_DitherMap, offsetUV*0.25).a;
				rayOri += ditherValue * rayDir;

				//防止背光时也产生影响
				float3 toLight = normalize(_WorldSpaceLightPos0);
				float dotLightRayDir = dot(toLight, rayDir)*0.5 + 0.5;				
				float scatteringLight = smoothstep(0.5, 1, dotLightRayDir);
								
				float3 currentPos;

				//固定的步数得到步长
				float marchStep = disCam2World / _RayMarchingStep;

				UNITY_LOOP
				for (int j = 0; j < _RayMarchingStep; j++)
				{					
					currentPos = rayOri + i.interpolatedRay.xyz * marchStep * j;
					
					float disCam2Current = length(currentPos- _WorldSpaceCameraPos);

					//对比光线是否超过了深度
					float outOfRange = step(disCam2Current, disCam2World);

					//if (disCam2World>disCam2Current)
					//{					
						float getShadow = GetShadow(currentPos);
						vShadow -= (1- getShadow) * _VolumetricShadowIntenstiy  / _RayMarchingStep * (j+3)/_RayMarchingStep * outOfRange;
						vLight += getShadow * _VolumetricLightIntensity * scatteringLight / _RayMarchingStep * (j-3)/_RayMarchingStep * outOfRange;
					//}
					//else
					//{
					//	break;
					//}
				}

				vShadow = clamp(vShadow, _MinShadow, 1);
				vLight = pow(clamp(vLight, 0, 1),_ScatteringFactor);

				float4 col = float4(vLight, vShadow, 0, 1);				
				return col;
			}
			ENDCG
		}

		//2.blur
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

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

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float2 uv : TEXCOORD0;
				float4 uv01 : TEXCOORD1;
				float4 uv23 : TEXCOORD2;
				float4 uv45 : TEXCOORD3;
			};

			sampler2D _MainTex;
			float4 _MainTex_TexelSize;
			float4 _Offsets;

			v2f vert(appdata v) {
				v2f o;
				_Offsets *= _MainTex_TexelSize.xyxy;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				o.uv01 = v.uv.xyxy + _Offsets.xyxy*float4(1, 1, -1, -1);
				o.uv23 = v.uv.xyxy + _Offsets.xyxy*float4(1, 1, -1, -1)*2.0;
				o.uv45 = v.uv.xyxy + _Offsets.xyxy*float4(1, 1, -1, -1)*3.0;
				return o;
			}

			float4 frag(v2f i) :SV_Target{
				float4 color = float4(0,0,0,0);
				color += 0.40*tex2D(_MainTex, i.uv);
				color += 0.15*tex2D(_MainTex, i.uv01.xy);
				color += 0.15*tex2D(_MainTex, i.uv01.zw);
				color += 0.10*tex2D(_MainTex, i.uv23.xy);
				color += 0.10*tex2D(_MainTex, i.uv23.zw);
				color += 0.05*tex2D(_MainTex, i.uv45.xy);
				color += 0.05*tex2D(_MainTex, i.uv45.zw);
				return color;
			}
			ENDCG
		}

		//3.combine
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"
			#include "Lighting.cginc"

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

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

			sampler2D _MainTex;
			sampler2D _MarchingTex;

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

			float4 frag(v2f i) :SV_Target{
				float4 finalColor = 1;
				float4 ori = tex2D(_MainTex,i.uv);
				float4 marching = tex2D(_MarchingTex, i.uv);
				finalColor.rgb = clamp(ori.rgb + marching.r*_LightColor0.rgb,0,1) * marching.g;
				return finalColor;
			}
			ENDCG
		}
	}
}
           

继续阅读