Skip to content

Instantly share code, notes, and snippets.

@aras-p
Last active April 15, 2022 17:50
Show Gist options
  • Save aras-p/03fa46fc0e92b9431806e61a5d26737c to your computer and use it in GitHub Desktop.
Save aras-p/03fa46fc0e92b9431806e61a5d26737c to your computer and use it in GitHub Desktop.
Unity command buffer that modifies screenspace shadow mask
Super small example of a command buffer based approach that modifies screenspace shadow mask.
1. computes (very crude & simple) SSAO from the depth buffer, after depth is rendered.
2. modifies screenspace shadow texture (from the main directional light), my multiplying in the SSAO term into regular shadow term.
3. now all objects that receive shadows also get illumination darkened by SSAO; it essentially becomes "part of the shadow"
Shader "Hidden/ComputeOcclusion"
{
Properties
{
_MainTex ("", 2D) = "white" {}
}
SubShader
{
Pass
{
Cull Off ZWrite Off ZTest Always
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (appdata_t v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv;
return o;
}
sampler2D_float _CameraDepthTexture;
// Fairly bad & noisy "SSAO" computation from the depth
// texture alone.
float3 normal_from_depth(float depth, float2 uv)
{
float2 offset1 = float2(0.0,_ScreenParams.w-1);
float2 offset2 = float2(_ScreenParams.z-1,0.0);
float depth1 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv + offset1);
depth1 = LinearEyeDepth (depth1);
float depth2 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv + offset2);
depth2 = LinearEyeDepth (depth2);
float3 p1 = float3(offset1, depth1 - depth);
float3 p2 = float3(offset2, depth2 - depth);
float3 normal = cross(p1, p2);
normal.z = -normal.z;
return normalize(normal);
}
float rand(float2 co){
return frac(sin(dot(co ,float2(12.9898,78.233))) * 43758.5453);
}
fixed4 frag (v2f i) : SV_Target
{
const float base = 0.2;
const float falloff = 0.001;
const float radius = 0.3;
const int kSampleCount = 16;
float3 kSamples[kSampleCount] = {
float3( 0.5381, 0.1856,-0.4319), float3( 0.1379, 0.2486, 0.4430),
float3( 0.3371, 0.5679,-0.0057), float3(-0.6999,-0.0451,-0.0019),
float3( 0.0689,-0.1598,-0.8547), float3( 0.0560, 0.0069,-0.1843),
float3(-0.0146, 0.1402, 0.0762), float3( 0.0100,-0.1924,-0.0344),
float3(-0.3577,-0.5301,-0.4358), float3(-0.3169, 0.1063, 0.0158),
float3( 0.0103,-0.5869, 0.0046), float3(-0.0897,-0.4940, 0.3287),
float3( 0.7119,-0.0154,-0.0918), float3(-0.0533, 0.0596,-0.5411),
float3( 0.0352,-0.0631, 0.5460), float3(-0.4776, 0.2847,-0.0271)
};
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.xy);
depth = LinearEyeDepth (depth);
float3 position = float3(i.uv, depth);
float3 normal = normal_from_depth(depth, i.uv);
float3 random = normalize(float3(rand(i.uv), rand(i.uv.yx), 0));
float r = radius/depth;
float occ = 0.0;
for(int i=0; i < kSampleCount; i++)
{
float3 ray = r * reflect(kSamples[i], random);
float3 hemi_ray = position + sign(dot(ray,normal)) * ray;
float sd = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, saturate(hemi_ray.xy));
sd = LinearEyeDepth (sd);
float diff = depth - sd;
occ += step(falloff, diff);
}
float ao = 1.0 - occ / kSampleCount;
ao += base;
ao *= ao;
return ao;
}
ENDCG
}
}
}
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
public class LightCommandBufferSSAO : MonoBehaviour
{
public Mesh m_Quad;
public Shader m_OcclusionShader;
private Material m_OcclusionMaterial;
public Shader m_ShadowMaskModulateShader;
private Material m_ShadowMaskModulateMaterial;
private CommandBuffer m_Buffer;
private CommandBuffer m_OcclusionBuffer;
private Light m_Light;
private Camera m_Camera;
private void Cleanup()
{
if (m_Buffer != null)
m_Light.RemoveCommandBuffer (LightEvent.AfterScreenspaceMask, m_Buffer);
m_Buffer = null;
if (m_OcclusionBuffer != null && m_Camera != null)
m_Camera.RemoveCommandBuffer (CameraEvent.AfterDepthTexture, m_OcclusionBuffer);
m_OcclusionBuffer = null;
Object.DestroyImmediate (m_OcclusionMaterial);
Object.DestroyImmediate (m_ShadowMaskModulateMaterial);
}
void OnDisable()
{
Cleanup();
}
void OnEnable ()
{
Cleanup();
m_Light = GetComponent<Light>();
m_Camera = Camera.main;
if (!m_ShadowMaskModulateMaterial)
{
m_ShadowMaskModulateMaterial = new Material(m_ShadowMaskModulateShader);
m_ShadowMaskModulateMaterial.hideFlags = HideFlags.HideAndDontSave;
}
if (!m_OcclusionMaterial)
{
m_OcclusionMaterial = new Material(m_OcclusionShader);
m_OcclusionMaterial.hideFlags = HideFlags.HideAndDontSave;
}
if (m_OcclusionBuffer == null)
{
// compute occlusion after camera's depth texture is done
m_OcclusionBuffer = new CommandBuffer();
m_OcclusionBuffer.name = "Compute ambient occlusion";
int texID = Shader.PropertyToID("_GlobalOcclusionTexture");
m_OcclusionBuffer.GetTemporaryRT (texID, -1, -1, 0, FilterMode.Bilinear);
m_OcclusionBuffer.Blit (BuiltinRenderTextureType.CurrentActive, texID, m_OcclusionMaterial);
m_Camera.AddCommandBuffer (CameraEvent.AfterDepthTexture, m_OcclusionBuffer);
}
if (m_Buffer == null)
{
// after light's screenspace shadow mask is computed, modulate it by occlusion
// texture
m_Buffer = new CommandBuffer();
m_Buffer.name = "Modify screenspace mask";
m_Buffer.DrawMesh(m_Quad, Matrix4x4.identity, m_ShadowMaskModulateMaterial);
int screenCopyID = Shader.PropertyToID("_ShadowMapCopy");
m_Buffer.GetTemporaryRT (screenCopyID, -1, -1, 0, FilterMode.Bilinear);
m_Buffer.Blit (BuiltinRenderTextureType.CurrentActive, screenCopyID);
m_Light.AddCommandBuffer (LightEvent.AfterScreenspaceMask, m_Buffer);
}
}
}
Shader "Hidden/MultiplyShadowMaskWithOcclusion"
{
SubShader
{
Pass
{
Cull Off ZWrite Off ZTest Always
Blend DstColor Zero
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata_t {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (appdata_t v)
{
v2f o;
v.vertex.xy += 0.5;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _GlobalOcclusionTexture;
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_GlobalOcclusionTexture, i.uv);
return col;
}
ENDCG
}
}
}
@jjxtra
Copy link

jjxtra commented Dec 12, 2018

Does not work in single pass or single pass instanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment