Skip to content

Instantly share code, notes, and snippets.

@lyuma
Created August 6, 2019 03:21
Show Gist options
  • Save lyuma/8ce4d44ffc0ef1225c2c9bdf21e4afa7 to your computer and use it in GitHub Desktop.
Save lyuma/8ce4d44ffc0ef1225c2c9bdf21e4afa7 to your computer and use it in GitHub Desktop.
// Copyright (C) 2019 Lyuma (Lyuma#0781) (xn.lyuma@gmail.com)
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
Shader "LyumaShader/Thiccnessv2Combined" {
// This differs from Thiccness v1 by flipping which channel stores thickness and normal data.
// See the previous version here:
// https://gist.github.com/lyuma/c552d926815c4f9eeb590e439a642349
// CAVEAT: This will not work if any other objects in the scene use the same shader.
// To fix, this must be split up into three materials/queues, with the GrabPass
// combined with rendering in the last queue.
Properties
{
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent+30"}
LOD 100
Lighting Off
CGINCLUDE
#pragma target 4.5
#include "UnityCG.cginc"
struct thickness_v2f {
float4 pos : SV_Position;
float3 viewNormal : TEXCOORD0;
float viewDepthFromCenter : TEXCOORD1;
float4 screenPos : TEXCOORD2;
};
#define ANGLE_STAGES 13
#define EPSILON 0.0078125
#define HALF_MINUS_EPSILON 0.4921875
#define THICKNESS_SCALE 2.0
// Half precision has:
// 1 bit sign
// 5 bits exponent (cannot be 0:denorm or 31:infinity/NaN)
// 10 bits mantissa (10 bits split into angle and length)
#define NORMAL_ANGLE_BITS 4 // 256
#define LENGTH_ANGLE_BITS (10 - NORMAL_ANGLE_BITS) // 4
#define NORMAL_ANGLE_MULT (1 << (NORMAL_ANGLE_BITS - 1)) // 256
#define LENGTH_ANGLE_MULT (1 << (LENGTH_ANGLE_BITS - 1)) // 4
thickness_v2f vert(appdata_full v) {
thickness_v2f o = (thickness_v2f)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.viewNormal = mul((float3x3)UNITY_MATRIX_V, mul((float3x3)unity_ObjectToWorld, v.normal.xyz));
float3 viewPos = UnityObjectToViewPos(v.vertex).xyz;
float3 centerViewPos = UnityObjectToViewPos(float4(0,0,0,1)).xyz;
o.viewDepthFromCenter = centerViewPos.z - viewPos.z;
o.screenPos = ComputeGrabScreenPos(o.pos);
return o;
}
// Normal encoding as power of two (exponent portion)
// ALU noise in Next-gen post processing in COD:AW
float InterleavedGradientNoise( float2 screenUV )
{
float3 magic = { 0.06711056, 0.00583715, 52.9829189 };
return frac( magic.z * frac( dot( screenUV, magic.xy ) ) );
}
float encodeBackfaceNormal(float3 viewNormal, float2 ditherUV) {
float2 normalXY = normalize(viewNormal.xy);
float angle = atan2(normalXY.y, normalXY.x);
float leng = length(viewNormal.xy);
float anglePortion = clamp((angle + UNITY_PI) * NORMAL_ANGLE_MULT / UNITY_TWO_PI, 0, (NORMAL_ANGLE_MULT));
float lengthPortion = clamp(abs(leng) * LENGTH_ANGLE_MULT, 0, (LENGTH_ANGLE_MULT - 1));
float noise01 = InterleavedGradientNoise(ditherUV);
precise uint angleUint = uint((noise01 < frac(anglePortion) ? 1.0 : 0.0) + floor(anglePortion)) & (NORMAL_ANGLE_MULT - 1);
precise uint lengthUint = uint((noise01 < frac(lengthPortion) ? 1.0 : 0.0) + floor(lengthPortion));
precise uint bits = 0xbf800000 | ((angleUint << (13 + LENGTH_ANGLE_BITS)) | (lengthUint << 13));
// -1.angle|length
return asfloat(bits);
}
float3 decodeBackfaceNormal(float alphaChannel) {
precise uint bits = asuint(alphaChannel);
precise uint angleUint = (bits >> (13 + LENGTH_ANGLE_BITS)) & (NORMAL_ANGLE_MULT - 1);
precise uint lengthUint = (bits >> 13) & (LENGTH_ANGLE_MULT - 1);
float angle = float(angleUint) * UNITY_TWO_PI / NORMAL_ANGLE_MULT;
float leng = float(lengthUint) / LENGTH_ANGLE_MULT;
float2 sc;
sincos(angle, sc.x, sc.y);
float3 normal = normalize(float3(sc.yx * leng, -sqrt(1 - leng * leng)));
//float3 normal = normalize(float3(sc.yx, -1.0));
return normal;
}
// Thickness encoding as fractional part:
float encodeViewDistance(float viewDepthFromCenter, float2 ditherUV) {
float dist = 127 + ANGLE_STAGES * clamp(viewDepthFromCenter / THICKNESS_SCALE, -0.99, 0.99);
float noise01 = InterleavedGradientNoise(ditherUV);
float quantizedDist = (noise01 < frac(dist) ? 1.0 : 0.0) + floor(dist);
precise uint exponentBits = (((uint)quantizedDist) << 23);
precise float powOfTwo = asfloat(exponentBits);
return powOfTwo;
}
float decodeThickness(float alphaChannel) {
precise uint bits = asuint(alphaChannel);
float exponent = float((bits >> 23) & 0xff) - 128;
float quantizedThickness = exponent / ANGLE_STAGES;
return quantizedThickness * THICKNESS_SCALE;
}
ENDCG
Pass {
// Backface Stencil prepass
ZWrite On
ColorMask A
Cull Front
Stencil { Ref 167 Comp Always Pass Replace }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 frag(thickness_v2f i) : SV_Target {
return float4(1,0,0,0);
}
ENDCG
}
Pass {
// Backface Depth Prepass: we can only afford to subtract depth once
ZWrite On
ColorMask 0
ZTest Greater
Cull Front
Stencil { Ref 167 Comp Equal Pass Keep }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 frag(thickness_v2f i) : SV_Target {
return float4(1,1,1,0);
}
ENDCG
}
Pass {
// Compute frontface depth in alpha exponent channel
ZWrite Off
ZTest LEqual
Cull Back
ColorMask A
Blend One One
BlendOp Min
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 frag(thickness_v2f i) : SV_Target {
float2 ditherUV = _ScreenParams.xy * (i.screenPos.xy / i.screenPos.w);
return float4(1,0,0, -encodeViewDistance(-i.viewDepthFromCenter, ditherUV * 0.9173));
}
ENDCG
}
Pass {
// Divide backface depth in alpha exponent channel
// Store backface normal direction in alpha mantissa channel
ZWrite Off
ZTest Equal
Cull Front
ColorMask A
Stencil { Ref 167 Comp Always Pass Zero }
Blend DstAlpha Zero
BlendOp Add
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 frag(thickness_v2f i) : SV_Target {
float2 ditherUV = _ScreenParams.xy * (i.screenPos.xy / i.screenPos.w);
return float4(1,0,0, encodeViewDistance(i.viewDepthFromCenter, ditherUV * 0.9622) * encodeBackfaceNormal(i.viewNormal, ditherUV));
}
ENDCG
}
GrabPass {
"_ColorNormalThickness"
// Grab screen
}
Pass {
// Frontface Depth prepass. Just for fun.
ZWrite On
ColorMask 0
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 frag(thickness_v2f i) : SV_Target {
return float4(1,0,0,0);
}
ENDCG
}
Pass {
// FInal Render
ZWrite Off
ZTest Equal
Cull Back
ColorMask RGBA
Blend One Zero
BlendOp Add
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _ColorNormalThickness;
float4 frag(thickness_v2f i) : SV_Target {
float4 texRead = tex2Dproj(_ColorNormalThickness, i.screenPos);
if (0) {//texRead.a >= 0) {
// We did not write any data.
return texRead;
} else {
float3 backfaceNormal = decodeBackfaceNormal(texRead.a);
float thickness = decodeThickness(texRead.a);
// Show all parts visually.
//return dot(i.viewNormal.y, float3(-.86,.86,0)) * .5 + .5 * float4(.5 - .5 * backfaceNormal.xyz, 1.0);
//return float4(thickness.rrr / THICKNESS_SCALE, 1.0);
return float4(texRead.rgb * 0 + 1 * ((thickness / THICKNESS_SCALE) * (.5 + .5 * (i.viewNormal.xyz - backfaceNormal.xyz))), 1.0);
}
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment