Is there a way to access the shadows of the scene inside of a surface shader? The only way i know is to use a custom lighting model, where you have access to the attenuation value. But i would like to stay away from custom lighting functions for this one! I want to mask my fresnel effect to only appear in the shadows of my object-
If by "in the shadows" you mean "in the main directional light's shadow for desktop", then yes. To get the main shadow only really requires the screen position. Code (csharp): // input struct struct input { // other stuff float4 screenPos; } // in surf function float shadow = 1; #if defined(SHADOWS_SCREEN) && !defined(UNITY_NO_SCREENSPACE_SHADOWS); shadow = unitySampleShadow(IN.screenPos); #endif Alternatively you could use a custom lighting function that just calls the LightingStandard functions.
Yes, just the directional lights shadows! Nothing happend though- As far as my tests go, the if function never gets used. I also tried different setups like foward/deferred or baked/realtime shadows, but makes no difference. (In this case, the cube in the middle should start glowing only in the lit parts (just for testing purposes) Code (CSharp): Shader "Custom/testShadowMask" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; float3 viewDir; float4 screenPos; }; half _Glossiness; half _Metallic; fixed4 _Color; void surf (Input IN, inout SurfaceOutputStandard o) { float shadow = 1; #if defined(SHADOWS_SCREEN) && !defined(UNITY_NO_SCREENSPACE_SHADOWS) shadow = unitySampleShadow(IN.screenPos); o.Emission = 1 * shadow; #endif fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color * shadow; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
So, the above trick will only work with forward rendering and real time shadows. It takes a bit more work to get the lightmaps to check if you're in shadow, and it's impossible to get access to the shadows during the surface shader with deferred as those aren't rendered until long after the object. Also there's a "fun" feature with Surface Shaders. When it does the initial code generation, it looks over the shader to figure out what Input values and surf output values are actually used. When it does that look over the shader it doesn't setup most of the defines, so because IN.screenPos and o.Emission is only used inside that #if block it doesn't think the shader uses them and chooses to skip passing & setting the screenPos, or using the o.Emission... Like I said, "fun". There's some secret define to detect when you're doing the shader gen pass, but it got mentioned to me once on twitter like 3 years ago and I've never been able to find the tweet again. Anyway, the hacky work around is to use those values in a stupid way. Code (csharp): // outside a function define a dummy value, no need to put into the properties // this will always be initialized as 0.0 half _DummyZero; // in the surf function half fresnel = pow(1 - saturate(dot(IN.viewDir, IN.worldNormal)), 4); half3 edgeGlow = fresnel * _Emission; // set the emission to the screenPos multiplied by the dummy uniform // this will always be zero, because _DummyZero is always zero, but the shader gen doesn't know that // the result is the shader generator will make sure both screenPos and Emission code gets added o.Emission = IN.screenPos.x * _DummyZero; #if defined (SHADOWS_SCREEN) && !defined(UNITY_NO_SCREENSPACE_SHADOWS) half shadow = unitySampleShadow(IN.screenPos); o.Emission = edgeGlow * (1 - shadow); #endif
Also another approach right here with commandbuffers: I just found the solution! So I was also searching for a way to sample the screen space shadow texture with a commandbuffer and noticed, that they use this technique in the 3D GameKit from Unity! And it just seems to work straight out of the box this way! I saw many questions about this and nobody ever posted his working code, so here ya go! Code that should be attached to the main directional light: Code (CSharp): using UnityEditor; using UnityEngine; using UnityEngine.Rendering; namespace Gamekit3D { [ExecuteInEditMode] public class CopyShadowMap : MonoBehaviour { CommandBuffer cb = null; void OnEnable() { var light = GetComponent<Light>(); if (light) { cb = new CommandBuffer(); cb.name = "CopyShadowMap"; cb.SetGlobalTexture("_DirectionalShadowMask", new RenderTargetIdentifier(BuiltinRenderTextureType.CurrentActive)); light.AddCommandBuffer(UnityEngine.Rendering.LightEvent.AfterScreenspaceMask, cb); } } void OnDisable() { var light = GetComponent<Light>(); if (light) { light.RemoveCommandBuffer(UnityEngine.Rendering.LightEvent.AfterScreenspaceMask, cb); } } } } And in the shader, access the map likes this: Code (CSharp): sampler2D _DirectionalShadowMask; and in the surf/fragment shader: Code (CSharp): float shadowmask = tex2D(_DirectionalShadowMask,screenUV).b; Theres also this Macro written there, but I'm not sure for what it's used: Code (CSharp): UNITY_DECLARE_SHADOWMAP(_DirectionalShadowMap); (I just didnt used it and it still worked) Also thanks to @bgolus for clearing this up: That's for defining a shadow map texture object ... which technically the screen space shadow texture is not so can be ignored. You've already defined it as a sampler2D, which is what it is. Anyways, I hope this helps someone!