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/
原图:
体积光:
体积阴影:
体积光+体积阴影
从上方打光:
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
}
}
}
版权声明:本文为qq_36107199原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。