数学原理这篇文章讲得非常清楚,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
}
}
}
然后我们需要的是两张图,一张是主摄像机的深度图,一般来说,这张图就是用来从屏幕空间还原到世界坐标的。另一张是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
}
}
}
点击运行,效果如下。
说明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
}
}
}