Really? Can we access that from a shader?
Yep, there’s the 3D texture used for the shadows called _DitherMaskLOD, and a 2D one used for the LOD crossfade which I don’t remember the name of.
So I can use dither sampling for a stochastic blend? I tried to sample the 3D one but I wasn’t sure which input coordinates to use and even then what exactly do you do? Just clip based on the dither? These are looking pretty good though - here’s the update.
So I got MSAA / Alpha to Coverage working! Transparency that actually works!
The one thing I think would be better is if we could do it in terms of cam world position rather than literal view direction, the next video shows why I think it may be better.
Basically, your peripherals are at maybe up to 45 degrees different angle than the actual view direction, so you end up actually seeing the edge blending happening. If it was based solely on camera position, this would be fixed.
Edit:
I tried that, it looked super weird.
Anyway, I saw bgolus on your medium post in the sharpening section that you were talking about how to fix the fact that layered semitransparent pixels in alpha to coverage don’t add up their opacities like you would get in a blend - is there some way to just set all the leaves to say a 0.5 alpha and get the A2C to add the alphas so that the heavily layered areas end up fully opaque and random 1-layer leaves would have a very low final alpha?
What you’d want to do is base it on the direction from the camera to the center of each quad / polygon shape. That would give you the best of both worlds, but that would require storing the center position in the vertex data, or using complicated geometry shaders (and using some features of geometry shaders that don’t work in Unity), or some really complicated fragment shader magic.
It’s a ton of work, and really no one is going to notice either way.
Yes-ish.
You can randomize the coverage samples used. I posted an example that allows you to use different samples per material:
https://discussions.unity.com/t/669461/6
For what you’d want you could change between coverage samples via noise and / or primitive id. Inigo Quilez (best known for shadertoy.com) posted something of an example for that here:
https://twitter.com/iquilezles/status/947717661496041472
That’s written for OpenGL, but it works the same for Direct3D / Unity ShaderLab with some minor tweaks to naming. The “primitive id” comes from a uint primitiveID : SV_PrimitiveID
fragment input, the “pixel” comes from int2 vpos : VPOS
fragment input. The frame id and blue noise functions are custom things and would either need to be replaced with something similar or can be skipped entirely.
I’d be very grateful to hear any advice you have to offer in this area.
My advice is don’t use a surface shader.
More realistically, the solution is to not use addshadow
in your surface shader, and instead write a custom shadowcaster pass manually that just uses your basic alpha test cutoff.
Ben do you know is it possible to recreate the effects listed in this thread via shadergraph?
Nope, it is not. There’s no option for enabling alpha to coverage with Shader Graph. You’re stuck with alpha blend or alpha test, and that’s it.
The HD pipeline is setup assuming deferred rendering w/ temporal anti-aliasing, which can’t use alpha to coverage since deferred excludes the use of MSAA. Since the direction Unity has gone with Shader Graph has been to limit it to only features that both the HD and Lightweight pipelines have, it makes sense they would not include alpha to coverage. I think that direction is totally wrong, but that’s where we are.
Okay thanks, I guess for now we will have to keep writing shaders by hand but hope they add this to shadergraph at some point!
In case anyone else follows in my path, what I was missing was the keepalpha
parameter for the #pragma surface
directive.
Hi,
You (hardcorebadger, AcidArrow) combined two features:
One is to fade out away the face when almost perpedicular to the camera view.
The other is to use a normal vector which roughly point away from the center of model.
So we need two normals per vertex.
I want to combine these features in my tree. My idea is to simply interpret the local position of each vertex as its second normal! The origin of the fluffy tree mesh top will be somewhere inside the trunk, it think on the ground. I tried it in Unlit and Surface Shader, but it did not work.
Can somebody help?
Kind Regards,
Chris
The idea isn’t a bad option, and it’s how it is sometimes accomplished, but you should show the shader code from your attempts.
Shader "Custom/Double Sided Alpha Cutout Fresnet Sphere Normals"
{
// https://docs.unity3d.com/Manual/SL-VertexFragmentShaderExamples.html
Properties
{
_MainTex("Texture", 2D) = "white" {}
_Cutoff("Alpha cutoff", Range(0.0, 1.0)) = 0.4
_AlphaOffset("Alpha offset", Range(-1.0, 1.0)) = 0
_NormalsOffset("Normals offset", Vector) = (0,0,0)
}
SubShader
{
Tags
{
//"RenderType"="Transparent"
"RenderType"="TransparentCutout"
"Queue"="AlphaTest"
"IgnoreProjector"="True"
}
LOD 100
Cull Off
Pass
{
// try use Alpha To Coverage if multisample anti-aliasing is set to 4 (MSAA)
//AlphaToMask On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
//#pragma multi_compile_fog
#include "UnityCG.cginc"
#include "Lighting.cginc" // for _LightColor0
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
half3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
//UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
half3 worldNormal : NORMAL;
fixed4 diff : COLOR0;
float fresnel : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Cutoff;
float _AlphaOffset;
float3 _NormalsOffset;
v2f vert (appdata v)
{
v2f o;
// transform from local to world space
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
// precalc camera-perpedicular alpha
// this works in test meshes but not good with real tree model
float3 view = normalize(ObjSpaceViewDir(v.vertex));
float vn = dot(view, v.normal);
// double sided, flip normal if necessary
//o.worldNormal *= sign(o.worldNormal);
vn *= sign(vn);
//o.fresnel = _FresnelBias + _FresnelScale * pow(1 + vn, _FresnelPower);
o.fresnel = saturate(_AlphaOffset + vn);
// overwrite normal with the normals on a sphere
// this should work too but not good, sometimes light source direction flips
// attention: verctor 0,0,0 can result to NAN
o.worldNormal = UnityObjectToWorldNormal(normalize(_NormalsOffset + v.vertex.xyz));
// dot product between normal and light direction for standard diffuse (Lambert) lighting
half nl = max(0, dot(o.worldNormal, _WorldSpaceLightPos0.xyz));
// factor in the light color
o.diff = nl * _LightColor0;
// in addition to the diffuse lighting from the main light, add illumination from ambient or light probes
// ShadeSH9 function from UnityCG.cginc evaluates it, using world space normal
o.diff.rgb += ShadeSH9(half4(o.worldNormal, 1));
o.diff.a = 1;
//UNITY_TRANSFER_FOG(o, o.vertex);
return o;
}
fixed4 frag (v2f i /*, fixed facing : VFACE*/) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// multiply by diffuse lighting
col *= saturate(i.diff);
// calc alpha
col.a *= i.fresnel;
// test show normals
// if enabled the normals look correct
//col.rgb = i.worldNormal * 0.5 + 0.5;
// apply fog
//UNITY_APPLY_FOG(i.fogCoord, col);
clip(col.a - _Cutoff);
return col;
}
ENDCG
}
}
}
My shader is kind of working in general but not from all view directions.
I would also be happy to use a third-party shader. I already tried and bought a some tree shader assets from the Unity Asset Store, but there were looking bad.
Do you have any links?
You may want to move many of the dot products into the fragment shader rather than the vertex shader as they’ll yield slightly nicer looking results, though that does mean needing to pass the two normals and the (un-normalized!) view direction from the vertex to the fragment.
The main issue you’re having is you’re trying to use lighting on a shader not using a LightMode tag. Without setting a light mode your directional light information will be randomly flipping, and you may be missing ambient lighting data (ShadeSH9 may return black).
Add this at line 27
Tags { “LightMode” = “ForwardBase” }
Hey bgolus,
That line was the key! Thanks!
Most of the calculation is intentionally in the vertex shader. It think it matches the wanted look better and is a bit faster. I will make a more pixel shader heavy version and compare them.
Kind Regards,
Chris
This is the current version of my shader. Instead of a shadow pass rendering the tree mesh again I place one quad with a tree shaped texture on it in the middle of the tree. The shadow looks very similar to the “real” shadow. The variables DARKEN can be ignored, i tried to make the tree darker in the inside of the tree. The shader did not make it into our current game, but maybe the code it can help someone.
I will add pictures soon.
Shader "Custom/Double Sided Alpha Cutout Fresnel Sphere Normals Shadows"
{
// https://docs.unity3d.com/Manual/SL-VertexFragmentShaderExamples.html
// https://discussions.unity.com/t/692652/36
// https://pastebin.com/u0JjSjAC
// https://catlikecoding.com/unity/tutorials/rendering/part-18/Rendering-18.pdf
// https://catlikecoding.com/unity/tutorials/rendering/part-12/
// https://docs.unity3d.com/Manual/SL-ShaderSemantics.html
// moved most possible calculation from fragment to vertex shader for perfomance
// DOUBLESIDES rendering and lightning
// LIGHTNING diffuse Lambert shading including lightning/reflection probes and lightmaps
// ALPHA uses cutout for transparency, full screen anti-aliasing effect recommended
// FRESNEL hides faces almost perdendicular to camera using simple fresnel rim
// FLUFFYNORMALS calculates normal vectors pointing from mesh origin away for fluffy effect
// FOG disabled for performance
// SHADOWS casting disabled for performance
// INNERDARKEN allows darker pixels closer to origin of the mesh
// DITHER order independent transparency (OIT) alpha two pass or dithering
// TODO
// SOFT_PARTICLES soft particles, but needs a costly extra grab pass
// LOD_FADE_CROSSFADE support LOD cross fading
Properties
{
_MainTex("Texture", 2D) = "white" {}
// ALPHA
_Cutoff("Alpha cutoff", Range(0.0, 1.0)) = 0.4
// FRESNEL
_AlphaOffset("Alpha offset", Range(-1.0, 1.0)) = 0
// DITHER
_AlphaDither("Alpha dither", Range(0.0, 1.0)) = 0.2
// FLUFFYNORMALS
_NormalsOffset("Normals offset", Vector) = (0,0,0)
// INNERDARKEN
_DarkenRadius("Inner Darken Radius", Range(0.0, 10.0)) = 1.0
_DarkenHeight("Inner Darken Height", Range(-10.0, 10.0)) = 0.0
_DarkenPower("Inner Darken Power", Range(0.0, 1.0)) = 0.0
}
SubShader
{
Tags
{
//"RenderType"="Transparent"
"RenderType"="TransparentCutout"
"Queue"="AlphaTest"
"IgnoreProjector"="True"
}
LOD 100
Cull Off
Pass
{
// try use Alpha To Coverage if multisample anti-aliasing is set to 4 (MSAA)
//AlphaToMask On
Tags
{
"LightMode" = "ForwardBase" // for ShadeSH9
//"LightMode" = "ShadowCaster"
}
CGPROGRAM
// Upgrade NOTE: excluded shader from DX11; has structs without semantics (struct v2f members vertex)
//#pragma exclude_renderers d3d11
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0 // for VPOS
// FOG make fog work
//#pragma multi_compile_fog
// LOD
#pragma multi_compile _ LOD_FADE_CROSSFADE
//#pragma multi_compile _ LOD_FADE_CROSSFADE DITHER_ALPA
//#pragma shader_feature _ _RENDERING_CUTOUT _RENDERING_FADE _RENDERING_TRANSPARENT
#include "UnityCG.cginc" // for UnityObjectToWorldNormal
#include "Lighting.cginc" // for _LightColor0
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
half3 normal : NORMAL;
};
// note: no SV_POSITION in this struct
struct v2f
{
float2 uv : TEXCOORD0;
fixed4 diff : COLOR0;
// FOG
//UNITY_FOG_COORDS(1)
// FRESNEL LOD
//#if SHADOWS_SEMITRANSPARENT || defined(LOD_FADE_CROSSFADE)
// UNITY_VPOS_TYPE vpos : VPOS;
//#else
// float4 vertex : POSITION; // deprecated? SV_POSITION to ensure compatibility with xbox
//#endif
half3 worldNormal : NORMAL;
float fresnel : TEXCOORD1;
};
// LIGHTING
sampler2D _MainTex;
float4 _MainTex_ST;
// FRESNEL
float _Cutoff;
float _AlphaOffset;
float _AlphaDither;
float3 _NormalsOffset;
// INNER DARKEN
float _DarkenRadius;
float _DarkenHeight;
float _DarkenPower;
// FRESNEL LOD automatically assigned by unity
sampler3D _DitherMaskLOD;
v2f vert (
appdata v,
out float4 outpos : SV_POSITION // clip space position output
)
{
v2f o;
// transform from local to world space
//o.vertex = UnityObjectToClipPos(v.vertex);
float4 worldpos = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
//o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// FRESNEL precalc camera-perpedicular alpha
//float3 view = normalize(ObjSpaceViewDir(v.vertex));
//float vn = dot(view, v.normal);
float vn = dot(o.worldNormal, normalize(_WorldSpaceCameraPos.xyz - worldpos.xyz));
// DOUBLESIDED flip normal if necessary
//o.worldNormal *= sign(o.worldNormal);
vn *= sign(vn);
//o.fresnel = _FresnelBias + _FresnelScale * pow(1 + vn, _FresnelPower);
o.fresnel = saturate(_AlphaOffset + vn);
// FLUFFY NORMALS overwrite normal with the normals on a sphere
// attention: vector 0,0,0 can result to NAN
o.worldNormal = UnityObjectToWorldNormal(normalize(_NormalsOffset + v.vertex.xyz));
//o.worldnormal = normalize(mul(_NormalsOffset + v.vertex.xyz, (float3x3)unity_WorldToObject));
// LIGHTING dot product between normal and light direction for standard diffuse (Lambert) lighting
half nl = max(0, dot(o.worldNormal, _WorldSpaceLightPos0.xyz));
// factor in the light color
o.diff = nl * _LightColor0;
// in addition to the diffuse lighting from the main light, add illumination from ambient or light probes
// ShadeSH9 function from UnityCG.cginc evaluates it, using world space normal
o.diff.rgb += ShadeSH9(half4(o.worldNormal, 1));
o.diff.a = 1;
// INNER DARKER make darker in the middle, attention does not calculate scale
float dist = length(v.vertex);
//float grad = saturate(dist * (1.0 / _DarkenRadius)) * (_DarkenPower - 1.0);
float grad = saturate(((dist -_DarkenRadius) * _DarkenPower) + 1.0);
o.diff.rgb *= grad;
// HEIGHT DARKER make darker in the bottom, attention does not calculate scale
grad = saturate(((v.vertex.y - _DarkenHeight) * _DarkenPower) + 1.0);
o.diff.rgb *= grad;
// this would be more performant, but darkens the result a bit. why?
//o.diff = saturate(o.diff);
// DITHER pass coordinates to calculate screen pixel position
//outpos = UnityObjectToClipPos(v.vertex);
outpos = mul(UNITY_MATRIX_VP, worldpos); // performance optimization
// FOG
//UNITY_TRANSFER_FOG(o, outpos);
return o;
}
fixed4 frag (
v2f i,
/*fixed facing : VFACE,*/
UNITY_VPOS_TYPE screenPos : VPOS
) : SV_Target
{
// LIGHTING sample the texture, diffuse lighting
fixed4 col = tex2D(_MainTex, i.uv);
//col *= i.diff;
col *= saturate(i.diff);
// FRESNEL calc alpha
col.a *= i.fresnel;
// test show normals
//col.rgb = i.worldNormal * 0.5 + 0.5;
// APLHA apply alpha offset
col.a -= _Cutoff;
// ALPHA LOD
//#if defined(LOD_FADE_CROSSFADE)
// UnityApplyDitherCrossFade(i.vpos);
//#else
// note: clip HLSL instruction stops rendering a pixel if value is negative
// FRESNEL DITHER if pixel is clipped by fresnel effect replace it by dither
if (i.fresnel < _AlphaDither)
{
// test with big checkerboard pattern
//screenPos.xy = floor(screenPos.xy * 0.25) * 0.5;
// test with pixel checkerboard pattern
//screenPos.xy = floor(screenPos.xy) * 0.5;
//float checker = -frac(screenPos.r + screenPos.g);
//clip(checker);
// query a texture with 16 dither pattern from full transparent (0) to full opaque (0.9375) in steps of 0.625
// the border to the non-dithered area is mapped to reach full opaque
float dither = tex3D(_DitherMaskLOD, float3(screenPos.xy * 0.25, i.fresnel / _AlphaDither * 0.9375)).a;
clip(dither - 0.01);
}
// ALPHA cutout discard pixel when low alpha
//if defined(_RENDERING_CUTOUT)
clip(col.a);
//#endif
//#endif
// FOG apply
//UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
// pull in shadow caster from VertexLit built-in shader
// SHADOWS enable casting here, might look wrong, depends on tree
//UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
/*
// using macros from UnityCG.cginc
Pass
{
Tags {"LightMode" = "ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct v2f {
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v)
{
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
return o;
}
float4 frag(v2f i) : SV_Target
{
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
*/
}
//Fallback "Standard"
}
Hey guys, I have just read through this and the work you guys did is incredible. I’m completely new to shaders and am wondering if it’s possible to achieve this effect with the Amplify Shader Editor?
Probably? But that’s a third party asset and not an officially built in Unity thing, so try asking on their forum thread: