diff --git a/Assets/Fur/Shaders/Shell/Depth.hlsl b/Assets/Fur/Shaders/Shell/Depth.hlsl index d1d1118..5a99323 100644 --- a/Assets/Fur/Shaders/Shell/Depth.hlsl +++ b/Assets/Fur/Shaders/Shell/Depth.hlsl @@ -3,6 +3,8 @@ #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl" #include "./Param.hlsl" +// For VR single pass instance compability: +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { @@ -10,38 +12,108 @@ struct Attributes float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID }; -struct Varyings +struct v2g +{ + float4 positionOS : POSITION; + float3 normalOS : NORMAL; + float4 tangentOS : TANGENT; + float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID +}; + +struct g2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float fogCoord : TEXCOORD1; float layer : TEXCOORD2; + UNITY_VERTEX_INPUT_INSTANCE_ID + UNITY_VERTEX_OUTPUT_STEREO }; -Attributes vert(Attributes input) +v2g vert(Attributes input) { - return input; + v2g output = (v2g)0; + // setup the instanced id + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the "v2g output" to 0.0 + // This is the URP version of UNITY_INITIALIZE_OUTPUT() + ZERO_INITIALIZE(v2g, output); + // copy instance id in the "Attributes input" to the "v2g output" + UNITY_TRANSFER_INSTANCE_ID(input, output); + + output.positionOS = input.positionOS; + output.normalOS = input.normalOS; + output.tangentOS = input.tangentOS; + output.uv = input.uv; + return output; } -void AppendShellVertex(inout TriangleStream stream, Attributes input, int index) +void AppendShellVertex(inout TriangleStream stream, v2g input, int index) { - Varyings output = (Varyings)0; + g2f output = (g2f)0; + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the g2f output to 0.0 + ZERO_INITIALIZE(g2f, output); + + UNITY_TRANSFER_INSTANCE_ID(input, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS); - float moveFactor = pow(abs((float)index / _ShellAmount), _BaseMove.w); + float clampedShellAmount = clamp(_ShellAmount, 1, 13); + _ShellStep = _TotalShellStep / clampedShellAmount; + + float moveFactor = pow(abs((float)index / clamp(_ShellAmount, 1, 13)), _BaseMove.w); float3 posOS = input.positionOS.xyz; float3 windAngle = _Time.w * _WindFreq.xyz; float3 windMove = moveFactor * _WindMove.xyz * sin(windAngle + posOS * _WindMove.w); float3 move = moveFactor * _BaseMove.xyz; float3 shellDir = normalize(normalInput.normalWS + move + windMove); - float3 posWS = vertexInput.positionWS + shellDir * (_ShellStep * index); + float FurLength = SAMPLE_TEXTURE2D_LOD(_FurLengthMap, sampler_FurLengthMap, input.uv / _BaseMap_ST.xy, 0).x; + float3 posWS = vertexInput.positionWS + shellDir * (_ShellStep * index * FurLength * _FurLengthIntensity); float4 posCS = TransformWorldToHClip(posWS); + output.vertex = posCS; + output.uv = TRANSFORM_TEX(input.uv, _BaseMap); + output.fogCoord = ComputeFogFactor(posCS.z); + output.layer = (float)index / clamp(_ShellAmount, 1, 13); + + stream.Append(output); +} + +// For geometry shader instancing, no clamp on _ShellAmount. +void AppendShellVertexInstancing(inout TriangleStream stream, v2g input, int index) +{ + g2f output = (g2f)0; + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the g2f output to 0.0 + ZERO_INITIALIZE(g2f, output); + + UNITY_TRANSFER_INSTANCE_ID(input, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); + VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS); + + _ShellStep = _TotalShellStep / _ShellAmount; + + float moveFactor = pow(abs((float)index / _ShellAmount), _BaseMove.w); + float3 posOS = input.positionOS.xyz; + float3 windAngle = _Time.w * _WindFreq.xyz; + float3 windMove = moveFactor * _WindMove.xyz * sin(windAngle + posOS * _WindMove.w); + float3 move = moveFactor * _BaseMove.xyz; + + float3 shellDir = normalize(normalInput.normalWS + move + windMove); + float FurLength = SAMPLE_TEXTURE2D_LOD(_FurLengthMap, sampler_FurLengthMap, input.uv / _BaseMap_ST.xy, 0).x; + float3 posWS = vertexInput.positionWS + shellDir * (_ShellStep * index * FurLength * _FurLengthIntensity); + float4 posCS = TransformWorldToHClip(posWS); + output.vertex = posCS; output.uv = TRANSFORM_TEX(input.uv, _BaseMap); output.fogCoord = ComputeFogFactor(posCS.z); @@ -50,10 +122,33 @@ void AppendShellVertex(inout TriangleStream stream, Attributes input, stream.Append(output); } -[maxvertexcount(128)] -void geom(triangle Attributes input[3], inout TriangleStream stream) +//-----------------------------------(below) For Microsoft Shader Model > 4.1----------------------------------- +// See "Lit.hlsl" for more information. +#if defined(_GEOM_INSTANCING) +[instance(3)] +[maxvertexcount(39)] +void geom(triangle v2g input[3], inout TriangleStream stream, uint instanceID : SV_GSInstanceID) { - [loop] for (float i = 0; i < _ShellAmount; ++i) + // 13 is calculated manually, because "maxvertexcount" is 39 in "Lit.hlsl", keep all passes to have the smallest (39 now). + // If not, DepthNormals will be incorrect and Depth Priming (DepthNormal Mode) won't work. + // "39 / 3 = 13", 3 means 3 vertices of a tirangle. + [loop] for (float i = 0 + (instanceID * 13); i < _ShellAmount; ++i) + { + [unroll] for (float j = 0; j < 3; ++j) + { + AppendShellVertexInstancing(stream, input[j], i); + } + stream.RestartStrip(); + } +} +//-----------------------------------(above) For Microsoft Shader Model > 4.1----------------------------------- + +//-----------------------------------(below) For Microsoft Shader Model < 4.1----------------------------------- +#else +[maxvertexcount(39)] +void geom(triangle v2g input[3], inout TriangleStream stream) +{ + [loop] for (float i = 0; i < clamp(_ShellAmount, 1, 13); ++i) { [unroll] for (float j = 0; j < 3; ++j) { @@ -62,17 +157,19 @@ void geom(triangle Attributes input[3], inout TriangleStream stream) stream.RestartStrip(); } } +#endif +//-----------------------------------(above) For Microsoft Shader Model < 4.1----------------------------------- -void frag( - Varyings input, - out float4 outColor : SV_Target, - out float outDepth : SV_Depth) +// Previous frag() causes Depth Priming error (black pixels), +// when enabling "Depth Priming + MSAA" in URP 12.1. +float frag(g2f input) : SV_TARGET { - float4 furColor = SAMPLE_TEXTURE2D(_FurMap, sampler_FurMap, input.uv * _FurScale); + float4 furColor = SAMPLE_TEXTURE2D(_FurMap, sampler_FurMap, input.uv / _BaseMap_ST.xy * _FurScale); float alpha = furColor.r * (1.0 - input.layer); if (input.layer > 0.0 && alpha < _AlphaCutout) discard; - outColor = outDepth = input.vertex.z / input.vertex.w; + // Divided by w of PositionCS gets wrong depth when enabling depth priming (Depth Prepass) on URP 12.1. + return input.vertex.z; + //outColor = outDepth = input.vertex.z / input.vertex.w; } - #endif \ No newline at end of file diff --git a/Assets/Fur/Shaders/Shell/DepthNormals.hlsl b/Assets/Fur/Shaders/Shell/DepthNormals.hlsl new file mode 100644 index 0000000..e8199be --- /dev/null +++ b/Assets/Fur/Shaders/Shell/DepthNormals.hlsl @@ -0,0 +1,195 @@ +#ifndef FUR_SHELL_DEPTH_NORMALS_HLSL +#define FUR_SHELL_DEPTH_NORMALS_HLSL + +#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl" +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.hlsl" +#include "./Param.hlsl" +// For VR single pass instance compability: +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" + +struct Attributes +{ + half4 positionOS : POSITION; + half3 normalOS : NORMAL; + half4 tangentOS : TANGENT; + float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID +}; + +struct v2g +{ + half4 positionOS : POSITION; + half3 normalOS : NORMAL; + half4 tangentOS : TANGENT; + float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID +}; + +struct g2f +{ + float4 vertex : SV_POSITION; + float2 uv : TEXCOORD0; + //float fogCoord : TEXCOORD1; + float layer : TEXCOORD1; + float3 normalWS : TEXCOORD2; + float3 tangentWS : TEXCOORD3; + float3 posWS : TEXCOORD4; + UNITY_VERTEX_INPUT_INSTANCE_ID + UNITY_VERTEX_OUTPUT_STEREO +}; + +v2g vert(Attributes input) +{ + v2g output = (v2g)0; + // setup the instanced id + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the "v2g output" to 0.0 + // This is the URP version of UNITY_INITIALIZE_OUTPUT() + ZERO_INITIALIZE(v2g, output); + // copy instance id in the "Attributes input" to the "v2g output" + UNITY_TRANSFER_INSTANCE_ID(input, output); + + output.positionOS = input.positionOS; + output.normalOS = input.normalOS; + output.tangentOS = input.tangentOS; + output.uv = input.uv; + return output; +} + +void AppendShellVertex(inout TriangleStream stream, v2g input, int index) +{ + g2f output = (g2f)0; + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the g2f output to 0.0 + ZERO_INITIALIZE(g2f, output); + + UNITY_TRANSFER_INSTANCE_ID(input, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); + VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS); + + float clampedShellAmount = clamp(_ShellAmount, 1, 13); + _ShellStep = _TotalShellStep / clampedShellAmount; + + float moveFactor = pow(abs((float)index / clamp(_ShellAmount, 1, 13)), _BaseMove.w); + float3 posOS = input.positionOS.xyz; + float3 windAngle = _Time.w * _WindFreq.xyz; + float3 windMove = moveFactor * _WindMove.xyz * sin(windAngle + posOS * _WindMove.w); + float3 move = moveFactor * _BaseMove.xyz; + + float3 shellDir = SafeNormalize(normalInput.normalWS + move + windMove); + float FurLength = SAMPLE_TEXTURE2D_LOD(_FurLengthMap, sampler_FurLengthMap, input.uv / _BaseMap_ST.xy, 0).x; + float3 posWS = vertexInput.positionWS + shellDir * (_ShellStep * index * FurLength * _FurLengthIntensity); + float4 posCS = TransformWorldToHClip(posWS); + + output.vertex = posCS; + output.posWS = posWS; + output.uv = TRANSFORM_TEX(input.uv, _BaseMap); + output.layer = (float)index / clamp(_ShellAmount, 1, 13); + + output.normalWS = normalInput.normalWS; + output.tangentWS = normalInput.tangentWS; + + + stream.Append(output); +} + +// For geometry shader instancing, no clamp on _ShellAmount. +void AppendShellVertexInstancing(inout TriangleStream stream, v2g input, int index) +{ + g2f output = (g2f)0; + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the g2f output to 0.0 + ZERO_INITIALIZE(g2f, output); + + UNITY_TRANSFER_INSTANCE_ID(input, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); + VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS); + + _ShellStep = _TotalShellStep / _ShellAmount; + + float moveFactor = pow(abs((float)index / _ShellAmount), _BaseMove.w); + float3 posOS = input.positionOS.xyz; + float3 windAngle = _Time.w * _WindFreq.xyz; + float3 windMove = moveFactor * _WindMove.xyz * sin(windAngle + posOS * _WindMove.w); + float3 move = moveFactor * _BaseMove.xyz; + + float3 shellDir = SafeNormalize(normalInput.normalWS + move + windMove); + float FurLength = SAMPLE_TEXTURE2D_LOD(_FurLengthMap, sampler_FurLengthMap, input.uv / _BaseMap_ST.xy, 0).x; + float3 posWS = vertexInput.positionWS + shellDir * (_ShellStep * index * FurLength * _FurLengthIntensity); + float4 posCS = TransformWorldToHClip(posWS); + + output.vertex = posCS; + output.posWS = posWS; + output.uv = TRANSFORM_TEX(input.uv, _BaseMap); + output.layer = (float)index / _ShellAmount; + + output.normalWS = normalInput.normalWS; + output.tangentWS = normalInput.tangentWS; + + + stream.Append(output); +} + +//-----------------------------------(below) For Microsoft Shader Model > 4.1----------------------------------- +// See "Lit.hlsl" for more information. +#if defined(_GEOM_INSTANCING) +[instance(3)] +[maxvertexcount(39)] +void geom(triangle v2g input[3], inout TriangleStream stream, uint instanceID : SV_GSInstanceID) +{ + // 13 is calculated manually, because "maxvertexcount" is 39 in "Lit.hlsl", keep all passes to have the smallest (39 now). + // If not, DepthNormals will be incorrect and Depth Priming (DepthNormal Mode) won't work. + // "39 / 3 = 13", 3 means 3 vertices of a tirangle. + [loop] for (float i = 0 + (instanceID * 13); i < _ShellAmount; ++i) + { + [unroll] for (float j = 0; j < 3; ++j) + { + AppendShellVertexInstancing(stream, input[j], i); + } + stream.RestartStrip(); + } +} +//-----------------------------------(above) For Microsoft Shader Model > 4.1----------------------------------- + +//-----------------------------------(below) For Microsoft Shader Model < 4.1----------------------------------- +#else +[maxvertexcount(39)] +void geom(triangle v2g input[3], inout TriangleStream stream) +{ + [loop] for (float i = 0; i < clamp(_ShellAmount, 1, 13); ++i) + { + [unroll] for (float j = 0; j < 3; ++j) + { + AppendShellVertex(stream, input[j], i); + } + stream.RestartStrip(); + } +} +#endif +//-----------------------------------(above) For Microsoft Shader Model < 4.1----------------------------------- + +float4 frag(g2f input) : SV_Target +{ + float4 furColor = SAMPLE_TEXTURE2D(_FurMap, sampler_FurMap, input.uv / _BaseMap_ST.xy * _FurScale); + float alpha = furColor.r * (1.0 - input.layer); + if (input.layer > 0.0 && alpha < _AlphaCutout) discard; + + float3 viewDirWS = SafeNormalize(GetCameraPositionWS() - input.posWS); + half3 normalTS = UnpackNormalScale( + SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, input.uv / _BaseMap_ST.xy * _FurScale), + _NormalScale); + float3 bitangent = SafeNormalize(viewDirWS.y * cross(input.normalWS, input.tangentWS)); + float3 normalWS = SafeNormalize(TransformTangentToWorld( + normalTS, + float3x3(input.tangentWS, bitangent, input.normalWS))); + + // Stores World Space Normal to "_CameraNormalsTexture", no depth needed currently. + return float4(NormalizeNormalPerPixel(normalWS), 0.0); + +} + +#endif \ No newline at end of file diff --git a/Assets/Fur/Shaders/Shell/DepthNormals.hlsl.meta b/Assets/Fur/Shaders/Shell/DepthNormals.hlsl.meta new file mode 100644 index 0000000..98b85b0 --- /dev/null +++ b/Assets/Fur/Shaders/Shell/DepthNormals.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9cb306cee9064a94ab6fd8359189c7b4 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fur/Shaders/Shell/FurSpecular.hlsl b/Assets/Fur/Shaders/Shell/FurSpecular.hlsl new file mode 100644 index 0000000..e96aebd --- /dev/null +++ b/Assets/Fur/Shaders/Shell/FurSpecular.hlsl @@ -0,0 +1,177 @@ +#ifndef FUR_SPECULAR_HLSL +#define FUR_SPECULAR_HLSL + +//------------------------------------------------------------------------------------------------- +// Fur shading from "maajor"'s https://github.com/maajor/Marschner-Hair-Unity, licensed under MIT. +//------------------------------------------------------------------------------------------------- + +struct SurfaceOutputFur +// Upgrade NOTE: excluded shader from DX11, OpenGL ES 2.0 because it uses unsized arrays +//#pragma exclude_renderers d3d11 gles +{ + half3 Albedo; + half MedulaScatter; + half MedulaAbsorb; + half3 Normal;//Tangent actually + half3 VNormal;//vertext normal + half3 Emission; + half Alpha; + half Roughness; + half Specular; + half Layer; + half Kappa; +}; + +inline float square(float x) { + return x * x; +} + +float acosFast(float inX) +{ + float x = abs(inX); + float res = -0.156583f * x + (0.5 * PI); + res *= sqrt(1.0f - x); + return (inX >= 0) ? res : PI - res; +} + +#define SQRT2PI 2.50663 + +//Gaussian Distribution for M term +inline float Hair_G(float B, float Theta) +{ + return exp(-0.5 * square(Theta) / (B*B)) / (SQRT2PI * B); +} + + +inline float3 SpecularFresnel(float3 F0, float vDotH) { + return F0 + (1.0f - F0) * pow(1 - vDotH, 5); +} + +inline float3 SpecularFresnelLayer(float3 F0, float vDotH, float layer) { + float3 fresnel = SpecularFresnel(F0, vDotH); + return (fresnel * layer) / (1 + (layer-1) * fresnel); +} + +// Yan, Ling-Qi, etc, "An efficient and practical near and far field fur reflectance model." +float3 FurBSDFYan(SurfaceOutputFur s, float3 L, float3 V, float3 N, float Shadow, float Backlit, float Area) +{ + float3 S = 0; + + const float VoL = dot(V, L); + const float SinThetaL = dot(N, L); + const float SinThetaV = dot(N, V); + float cosThetaL = sqrt(max(0, 1 - SinThetaL * SinThetaL)); + float cosThetaV = sqrt(max(0, 1 - SinThetaV * SinThetaV)); + float CosThetaD = sqrt((1 + cosThetaL * cosThetaV + SinThetaV * SinThetaL) / 2.0); + + const float3 Lp = L - SinThetaL * N; + const float3 Vp = V - SinThetaV * N; + const float CosPhi = dot(Lp, Vp) * rsqrt(dot(Lp, Lp) * dot(Vp, Vp) + 1e-4); + const float CosHalfPhi = sqrt(saturate(0.5 + 0.5 * CosPhi)); + + float n_prime = 1.19 / CosThetaD + 0.36 * CosThetaD; + + float Shift = 0.0499f; + float Alpha[] = + { + -0.0998,//-Shift * 2, + 0.0499f,// Shift, + 0.1996 // Shift * 4 + }; + float B[] = + { + Area + square(s.Roughness), + Area + square(s.Roughness) / 2, + Area + square(s.Roughness) * 2 + }; + + //float F0 = square((1 - 1.55f) / (1 + 1.55f)); + float F0 = 0.04652;//eta=1.55f + + float3 Tp; + float Mp, Np, Fp, a, h, f; + float ThetaH = SinThetaL + SinThetaV; + // R + Mp = Hair_G(B[0], ThetaH - Alpha[0]); + Np = 0.25 * CosHalfPhi; + Fp = SpecularFresnelLayer(F0, sqrt(saturate(0.5 + 0.5 * VoL)), s.Layer).x; + S += (Mp * Np) * (Fp * lerp(1, Backlit, saturate(-VoL))); + + // TT + Mp = Hair_G(B[1], ThetaH - Alpha[1]); + a = rcp(n_prime); + h = CosHalfPhi * (1 + a * (0.6 - 0.8 * CosPhi)); + f = SpecularFresnelLayer(F0, CosThetaD * sqrt(saturate(1 - h * h)), s.Layer).x; + Fp = square(1 - f); + float sinGammaTSqr = square((h * a)); + float sm = sqrt(saturate(square(s.Kappa)-sinGammaTSqr)); + float sc = sqrt(1 - sinGammaTSqr) - sm; + Tp = pow(s.Albedo, 0.5 * sc / CosThetaD) * pow(s.MedulaAbsorb*s.MedulaScatter, 0.5 * sm / CosThetaD); + Np = exp(-3.65 * CosPhi - 3.98); + S += (Mp * Np) * (Fp * Tp) * Backlit; + + // TRT + Mp = Hair_G(B[2], ThetaH - Alpha[2]); + f = SpecularFresnelLayer(F0, CosThetaD * 0.5f, s.Layer).x; + Fp = square(1 - f) * f; + // assume h = sqrt(3)/2, calculate sm and sc + sm = sqrt(saturate(square(s.Kappa)-0.75f)); + sc = 0.5f - sm; + Tp = pow(s.Albedo, sc / CosThetaD) * pow(s.MedulaAbsorb*s.MedulaScatter, sm / CosThetaD); + Np = exp((6.3f*CosThetaD+0.7f)*CosPhi-(5*CosThetaD+2)); + + S += (Mp * Np) * (Fp * Tp); + + // TTs + // hacking approximate Cm + Mp = abs(cosThetaL)*0.5f; + // still assume h = sqrt(3)/2 + Tp = pow(s.Albedo, (sc+1-s.Kappa)/(4*CosThetaD)) * pow(s.MedulaAbsorb, s.Kappa / (4*CosThetaD)); + // hacking approximate pre-integrated Dtts based on Cn + Np = 0.05*(2*CosPhi*CosPhi - 1) + 0.16f;//0.05*std::cos(2*Phi) + 0.16f; + + S += (Mp * Np) * (f * Tp); + + //TRTs + float phi = acosFast(CosPhi); + // hacking approximate pre-integrated Dtrts based on Cn + Np = 0.05f * cos(1.5*phi+1.7) + 0.18f; + // still assume h = sqrt(3)/2 + Tp = pow(s.Albedo, (3*sc+1-s.Kappa)/(4*CosThetaD)) * pow(s.MedulaAbsorb, (2*sm+s.Kappa) / (4*CosThetaD)) * pow(s.MedulaScatter, sm/(8*CosThetaD)); + Fp = f * (1-f); + + S += (Mp * Np) * (Fp * Tp); + + return S; +} + +float3 FurDiffuseKajiya(SurfaceOutputFur s, float3 L, float3 V, float3 N, half Shadow, float Backlit, float Area) { + float3 S = 0; + float KajiyaDiffuse = 1 - abs(dot(N, L)); + + float3 FakeNormal = SafeNormalize(V - N * dot(V, N)); + N = FakeNormal; + + // Hack approximation for multiple scattering. + float Wrap = 1; + float NoL = saturate((dot(N, L) + Wrap) / square(1 + Wrap)); + float DiffuseScatter = (1 / PI) * lerp(NoL, KajiyaDiffuse, 0.33);// *s.Metallic; + float Luma = Luminance(s.Albedo); + float3 ScatterTint = pow(s.Albedo / Luma, 1 - Shadow); + S = sqrt(s.Albedo) * DiffuseScatter * ScatterTint; + return S; +} + +float3 FurBxDF(SurfaceOutputFur s, float3 N, half3 V, half3 L, float Shadow, float Backlit, float Area) +{ + float3 S = float3(0, 0, 0); + + S = FurBSDFYan(s, L, V, N, Shadow, Backlit, Area); + S += FurDiffuseKajiya(s, L, V, N, Shadow, Backlit, Area); + + S = -min(-S, 0.0); + + return S; +} + +#endif \ No newline at end of file diff --git a/Assets/Fur/Shaders/Shell/FurSpecular.hlsl.meta b/Assets/Fur/Shaders/Shell/FurSpecular.hlsl.meta new file mode 100644 index 0000000..740e419 --- /dev/null +++ b/Assets/Fur/Shaders/Shell/FurSpecular.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4a9c4eb5427894546b064ec7b5e51258 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fur/Shaders/Shell/Lit.hlsl b/Assets/Fur/Shaders/Shell/Lit.hlsl index 5a988c0..4981d1b 100644 --- a/Assets/Fur/Shaders/Shell/Lit.hlsl +++ b/Assets/Fur/Shaders/Shell/Lit.hlsl @@ -5,6 +5,11 @@ #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl" #include "./Param.hlsl" #include "../Common/Common.hlsl" +#if defined(_FUR_SPECULAR) +#include "./FurSpecular.hlsl" +#endif +// VR single pass instance compability: +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { @@ -16,7 +21,17 @@ struct Attributes UNITY_VERTEX_INPUT_INSTANCE_ID }; -struct Varyings +struct v2g +{ + float4 positionOS : POSITION; + float3 normalOS : NORMAL; + float4 tangentOS : TANGENT; + float2 texcoord : TEXCOORD0; + float2 lightmapUV : TEXCOORD1; + UNITY_VERTEX_INPUT_INSTANCE_ID +}; + +struct g2f { float4 positionCS : SV_POSITION; float3 positionWS : TEXCOORD0; @@ -26,29 +41,98 @@ struct Varyings DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 5); float4 fogFactorAndVertexLight : TEXCOORD6; // x: fogFactor, yzw: vertex light float layer : TEXCOORD7; + UNITY_VERTEX_INPUT_INSTANCE_ID + UNITY_VERTEX_OUTPUT_STEREO }; -Attributes vert(Attributes input) +v2g vert(Attributes input) { - return input; + v2g output = (v2g)0; + // setup the instanced id + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the "v2g output" to 0.0 + // This is the URP version of UNITY_INITIALIZE_OUTPUT() + ZERO_INITIALIZE(v2g, output); + // copy instance id in the "Attributes input" to the "v2g output" + UNITY_TRANSFER_INSTANCE_ID(input, output); + + output.positionOS = input.positionOS; + output.normalOS = input.normalOS; + output.tangentOS = input.tangentOS; + output.lightmapUV = input.lightmapUV; + output.texcoord = input.texcoord; + return output; } -void AppendShellVertex(inout TriangleStream stream, Attributes input, int index) +void AppendShellVertex(inout TriangleStream stream, v2g input, int index) { - Varyings output = (Varyings)0; + g2f output = (g2f)0; + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the g2f output to 0.0 + ZERO_INITIALIZE(g2f, output); + + UNITY_TRANSFER_INSTANCE_ID(input, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS); - float moveFactor = pow(abs((float)index / _ShellAmount), _BaseMove.w); + float clampedShellAmount = clamp(_ShellAmount, 1, 13); + _ShellStep = _TotalShellStep / clampedShellAmount; + + float moveFactor = pow(abs((float)index / clampedShellAmount), _BaseMove.w); float3 posOS = input.positionOS.xyz; float3 windAngle = _Time.w * _WindFreq.xyz; float3 windMove = moveFactor * _WindMove.xyz * sin(windAngle + posOS * _WindMove.w); float3 move = moveFactor * _BaseMove.xyz; float3 shellDir = SafeNormalize(normalInput.normalWS + move + windMove); float3 viewDirWS = GetCameraPositionWS() - vertexInput.positionWS; + float FurLength = SAMPLE_TEXTURE2D_LOD(_FurLengthMap, sampler_FurLengthMap, input.texcoord / _BaseMap_ST.xy, 0).x; - output.positionWS = vertexInput.positionWS + shellDir * (_ShellStep * index); + output.positionWS = vertexInput.positionWS + shellDir * (_ShellStep * index * FurLength * _FurLengthIntensity); + output.positionCS = TransformWorldToHClip(output.positionWS); + output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap); + output.normalWS = normalInput.normalWS; + output.tangentWS = normalInput.tangentWS; + output.layer = (float)index / clampedShellAmount; + + float3 vertexLight = VertexLighting(vertexInput.positionWS, normalInput.normalWS); + float fogFactor = ComputeFogFactor(vertexInput.positionCS.z); + output.fogFactorAndVertexLight = float4(fogFactor, vertexLight); + + OUTPUT_LIGHTMAP_UV(input.lightmapUV, unity_LightmapST, output.lightmapUV); + OUTPUT_SH(output.normalWS.xyz, output.vertexSH); + + + stream.Append(output); +} + +// For geometry shader instancing, no clamp on _ShellAmount. +void AppendShellVertexInstancing(inout TriangleStream stream, v2g input, int index) +{ + g2f output = (g2f)0; + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the g2f output to 0.0 + ZERO_INITIALIZE(g2f, output); + + UNITY_TRANSFER_INSTANCE_ID(input, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); + VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS); + + _ShellStep = _TotalShellStep / _ShellAmount; + + float moveFactor = pow(abs((float)index / _ShellAmount), _BaseMove.w); + float3 posOS = input.positionOS.xyz; + float3 windAngle = _Time.w * _WindFreq.xyz; + float3 windMove = moveFactor * _WindMove.xyz * sin(windAngle + posOS * _WindMove.w); + float3 move = moveFactor * _BaseMove.xyz; + float3 shellDir = SafeNormalize(normalInput.normalWS + move + windMove); + float3 viewDirWS = GetCameraPositionWS() - vertexInput.positionWS; + float FurLength = SAMPLE_TEXTURE2D_LOD(_FurLengthMap, sampler_FurLengthMap, input.texcoord / _BaseMap_ST.xy, 0).x; + + output.positionWS = vertexInput.positionWS + shellDir * (_ShellStep * index * FurLength * _FurLengthIntensity); output.positionCS = TransformWorldToHClip(output.positionWS); output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap); output.normalWS = normalInput.normalWS; @@ -62,13 +146,53 @@ void AppendShellVertex(inout TriangleStream stream, Attributes input, OUTPUT_LIGHTMAP_UV(input.lightmapUV, unity_LightmapST, output.lightmapUV); OUTPUT_SH(output.normalWS.xyz, output.vertexSH); + stream.Append(output); } -[maxvertexcount(42)] -void geom(triangle Attributes input[3], inout TriangleStream stream) +//-----------------------------------(below) For Microsoft Shader Model > 4.1----------------------------------- +// "[instance(3)]" = 3+1 geometry shader instances, so we can have at most 13x4 = 52 shells +// +// If you need 200 shells (too much for game): +// "200 / 13 = 15, remains 5" +// +// You will need "16" instances, "15" for 195 shells, "1" for 5 remaining shells. +// So, use [instance(15)] to execute "15+1" instances. +// +// IMPORTANT: if you set [instance(30)] for 200 shells, you will waste performance because +// "stream.RestartStrip()" will run on 14 istances (with empty output). +// +// Please keep "n" in [instance(n)] the same for all "passes.hlsl" (Lit, Depth, DepthNormals, Shadow,...) +// Or something will break! (post-processing in most cases) +// +// LIMIT: You can only have up to 32 instances, so (32+1)x13 = 429 shells at most. +#if defined(_GEOM_INSTANCING) +[instance(3)] +[maxvertexcount(39)] +void geom(triangle v2g input[3], inout TriangleStream stream, uint instanceID : SV_GSInstanceID) { - [loop] for (float i = 0; i < _ShellAmount; ++i) + // 13 is calculated manually, because "maxvertexcount" is 39 in "Lit.hlsl", keep all passes to have the smallest (39 now). + // If not, DepthNormals will be incorrect and Depth Priming (DepthNormal Mode) won't work. + // "39 / 3 = 13", 3 means 3 vertices of a tirangle. + [loop] for (float i = 0 + (instanceID * 13); i < _ShellAmount; ++i) + { + [unroll] for (float j = 0; j < 3; ++j) + { + AppendShellVertexInstancing(stream, input[j], i); + } + stream.RestartStrip(); + } +} +//-----------------------------------(above) For Microsoft Shader Model > 4.1----------------------------------- + +//-----------------------------------(below) For Microsoft Shader Model < 4.1----------------------------------- +// For device that does not support geometry shader instancing. +// Available since Microsoft Shader Model 4.1, it is "target 4.6" in Unity. +#else +[maxvertexcount(39)] +void geom(triangle v2g input[3], inout TriangleStream stream) +{ + [loop] for (float i = 0; i < clamp(_ShellAmount, 1, 13); ++i) { [unroll] for (float j = 0; j < 3; ++j) { @@ -77,14 +201,17 @@ void geom(triangle Attributes input[3], inout TriangleStream stream) stream.RestartStrip(); } } +#endif +//-----------------------------------(above) For Microsoft Shader Model < 4.1----------------------------------- inline float3 TransformHClipToWorld(float4 positionCS) { return mul(UNITY_MATRIX_I_VP, positionCS).xyz; } -float4 frag(Varyings input) : SV_Target +float4 frag(g2f input) : SV_Target { + float2 furUv = input.uv / _BaseMap_ST.xy * _FurScale; float4 furColor = SAMPLE_TEXTURE2D(_FurMap, sampler_FurMap, furUv); float alpha = furColor.r * (1.0 - input.layer); @@ -108,7 +235,7 @@ float4 frag(Varyings input) : SV_Target inputData.positionWS = input.positionWS; inputData.normalWS = normalWS; inputData.viewDirectionWS = viewDirWS; -#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF) +#if defined(_MAIN_LIGHT_SHADOWS) || defined(_MAIN_LIGHT_SHADOWS_CASCADE) || defined(_MAIN_LIGHT_SHADOWS_SCREEN) && !defined(_RECEIVE_SHADOWS_OFF) inputData.shadowCoord = TransformWorldToShadowCoord(input.positionWS); #else inputData.shadowCoord = float4(0, 0, 0, 0); @@ -119,11 +246,46 @@ float4 frag(Varyings input) : SV_Target float4 color = UniversalFragmentPBR(inputData, surfaceData); +#if defined(_FUR_SPECULAR) + // Use abs(f) to avoid warning messages that f should not be negative in pow(f, e). + SurfaceOutputFur s = (SurfaceOutputFur)0; + s.Albedo = abs(SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv).rgb * _BaseColor.rgb); + s.MedulaScatter = abs(_MedulaScatter); + s.MedulaAbsorb = abs(_MedulaAbsorb); + s.Normal = input.tangentWS; + s.VNormal = normalWS; + s.Alpha = 1.0; + // Convert smoothness to roughness, (1 - smoothness) is perceptual roughness. + s.Roughness = (1.0 - _Smoothness) * (1.0 - _Smoothness); + s.Emission = half3(0.0, 0.0, 0.0); + s.Specular = 0.1; + s.Layer = input.layer; + s.Kappa = (1.0 - _Kappa / 2.0); + + color.rgb += (GetMainLight().color.rgb * FurBSDFYan(s, GetMainLight().direction, viewDirWS, normalWS, 1.0, _Backlit, _Area)); + + // Too slow to calculate for all additional lights, you can enable it if needed. + /* + #ifdef _ADDITIONAL_LIGHTS + int additionalLightsCount = GetAdditionalLightsCount(); + for (int i = 0; i < additionalLightsCount; ++i) + { + int index = GetPerObjectLightIndex(i); + Light light = GetAdditionalPerObjectLight(index, input.positionWS); + color.rgb += (light.color.rgb * FurBSDFYan(s, light.direction, viewDirWS, normalWS, 1.0, _Backlit, _Area)); + } + #endif + */ + +#endif + ApplyRimLight(color.rgb, input.positionWS, viewDirWS, normalWS); color.rgb += _AmbientColor; + // Disable "Specular Highlights" instead of clamping, + // to avoid reducing Bloom and Scatter strength. color.rgb = MixFog(color.rgb, inputData.fogCoord); return color; } -#endif \ No newline at end of file +#endif diff --git a/Assets/Fur/Shaders/Shell/Lit.shader b/Assets/Fur/Shaders/Shell/Lit.shader index 5411b5b..fe69d94 100644 --- a/Assets/Fur/Shaders/Shell/Lit.shader +++ b/Assets/Fur/Shaders/Shell/Lit.shader @@ -10,23 +10,44 @@ Properties [Gamma] _Metallic("Metallic", Range(0.0, 1.0)) = 0.5 _Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5 + [Header(Reduce Flickering)][Space] + [ToggleOff] _SpecularHighlights("Specular Highlights", Float) = 0.0 + [Header(Fur)][Space] - _FurMap("Fur", 2D) = "white" {} - [Normal] _NormalMap("Normal", 2D) = "bump" {} + [NoScaleOffset]_FurMap("Fur", 2D) = "white" {} + [NoScaleOffset][Normal] _NormalMap("Normal", 2D) = "bump" {} _NormalScale("Normal Scale", Range(0.0, 2.0)) = 1.0 - [IntRange] _ShellAmount("Shell Amount", Range(1, 14)) = 14 - _ShellStep("Shell Step", Range(0.0, 0.01)) = 0.001 + [IntRange] _ShellAmount("Shell Amount", Range(1, 52)) = 13 + [Header(More Shell Amount)][Space] + [Toggle(_GEOM_INSTANCING)] _GeomInstancing("Enable", Float) = 0 + [HideInInspector] _ShellStep("Shell Step", Range(0.0, 0.02)) = 0.001 + [Space][Space] _TotalShellStep("Total Shell Step", Range(0.0, 0.25)) = 0.026 _AlphaCutout("Alpha Cutout", Range(0.0, 1.0)) = 0.2 _FurScale("Fur Scale", Range(0.0, 10.0)) = 1.0 _Occlusion("Occlusion", Range(0.0, 1.0)) = 0.5 + [NoScaleOffset] _FurLengthMap("Fur Length Map", 2D) = "white" {} + _FurLengthIntensity("Fur Length Intensity", Range(0.0, 5.0)) = 1.0 _BaseMove("Base Move", Vector) = (0.0, -0.0, 0.0, 3.0) _WindFreq("Wind Freq", Vector) = (0.5, 0.7, 0.9, 1.0) _WindMove("Wind Move", Vector) = (0.2, 0.3, 0.2, 1.0) - [Header(Lighting)][Space] + [Header(Lighting)] + [Header(Rim Light)][Space] _RimLightPower("Rim Light Power", Range(1.0, 20.0)) = 6.0 _RimLightIntensity("Rim Light Intensity", Range(0.0, 1.0)) = 0.5 + + [Header(Marschner Scatter)][Space] + [Toggle(_FUR_SPECULAR)] _FurSpecular("Enable", Float) = 0 + _Backlit("Backlit", Range(0.0, 1.0)) = 0.5 + _Area("Lit Area", Range(0.01, 1.0)) = 0.1 + _MedulaScatter("Fur Scatter", Range(0.01, 1.0)) = 1.0 + _MedulaAbsorb("Fur Absorb", Range(0.01, 1.0)) = 0.1 + _Kappa("Kappa", Range(0.0, 2.0)) = 1.0 + + [Header(Shadow)][Space] _ShadowExtraBias("Shadow Extra Bias", Range(-1.0, 1.0)) = 0.0 + [Toggle(_NO_FUR_SHADOW)] _NoFurShadow("Mesh Shadow Only", Float) = 0 + } SubShader @@ -42,6 +63,7 @@ SubShader LOD 100 ZWrite On + ZTest LEqual Cull Back Pass @@ -49,26 +71,43 @@ SubShader Name "ForwardLit" Tags { "LightMode" = "UniversalForward" } + ZWrite On + HLSLPROGRAM - // URP のキーワード + // URP 縺ョ繧ュ繝シ繝ッ繝シ繝 +#if (UNITY_VERSION >= 202111) + #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN + #pragma multi_compile_fragment _ _LIGHT_LAYERS +#else #pragma multi_compile _ _MAIN_LIGHT_SHADOWS #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE +#endif #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile _ _SHADOWS_SOFT #pragma multi_compile _ _MIXED_LIGHTING_SUBTRACTIVE + #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION + #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF + //#pragma shader_feature_local_fragment _ENVIRONMENTREFLECTIONS_OFF + + #pragma multi_compile_fragment _ _FUR_SPECULAR + #pragma multi_compile _ _GEOM_INSTANCING - // Unity のキーワード + // Unity 縺ョ繧ュ繝シ繝ッ繝シ繝 #pragma multi_compile _ DIRLIGHTMAP_COMBINED #pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile_fog + #pragma multi_compile_instancing + #pragma multi_compile _ DOTS_INSTANCING_ON #pragma prefer_hlslcc gles #pragma exclude_renderers d3d11_9x - #pragma target 2.0 + // if "_GEOM_INSTANCING", then Microsoft ShaderModel 4.1 (geometry shader instancing support) + // It is "target 4.6" in Unity. (Tested on OpenGL 4.1, instancing not supported on OpenGL 4.0) + #pragma target 4.6 _GEOM_INSTANCING #pragma vertex vert #pragma require geometry - #pragma geometry geom + #pragma geometry geom #pragma fragment frag #include "./Lit.hlsl" ENDHLSL @@ -83,15 +122,38 @@ SubShader ColorMask 0 HLSLPROGRAM - #pragma exclude_renderers gles gles3 glcore + #pragma multi_compile _ _GEOM_INSTANCING + + #pragma exclude_renderers gles #pragma vertex vert #pragma require geometry - #pragma geometry geom + #pragma geometry geom #pragma fragment frag + #pragma target 4.6 _GEOM_INSTANCING #include "./Depth.hlsl" ENDHLSL } + Pass + { + Name "DepthNormals" + Tags { "LightMode" = "DepthNormals" } + + ZWrite On + + HLSLPROGRAM + #pragma multi_compile _ _GEOM_INSTANCING + + #pragma exclude_renderers gles + #pragma vertex vert + #pragma require geometry + #pragma geometry geom + #pragma fragment frag + #pragma target 4.6 _GEOM_INSTANCING + #include "./DepthNormals.hlsl" + ENDHLSL + } + Pass { Name "ShadowCaster" @@ -102,14 +164,123 @@ SubShader ColorMask 0 HLSLPROGRAM - #pragma exclude_renderers gles gles3 glcore + #pragma multi_compile _ _GEOM_INSTANCING + #pragma multi_compile _ _NO_FUR_SHADOW + + #pragma exclude_renderers gles #pragma vertex vert #pragma require geometry - #pragma geometry geom + #pragma geometry geom #pragma fragment frag + #pragma target 4.6 _GEOM_INSTANCING #include "./Shadow.hlsl" ENDHLSL } -} +//---------------------------For Microsoft Shader Model < 4.1--------------------------------------------- +//-----------------------Geometry Shader Instancing not supported.---------------------------------------- + Pass + { + Name "ForwardLit" + Tags { "LightMode" = "UniversalForward" } + + ZWrite On + + HLSLPROGRAM + // URP 縺ョ繧ュ繝シ繝ッ繝シ繝 +#if (UNITY_VERSION >= 202111) + #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN + #pragma multi_compile_fragment _ _LIGHT_LAYERS +#else + #pragma multi_compile _ _MAIN_LIGHT_SHADOWS + #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE +#endif + #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS + #pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS + #pragma multi_compile _ _SHADOWS_SOFT + #pragma multi_compile _ _MIXED_LIGHTING_SUBTRACTIVE + #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION + #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF + //#pragma shader_feature_local_fragment _ENVIRONMENTREFLECTIONS_OFF + + #pragma multi_compile_fragment _ _FUR_SPECULAR + + // Unity 縺ョ繧ュ繝シ繝ッ繝シ繝 + #pragma multi_compile _ DIRLIGHTMAP_COMBINED + #pragma multi_compile _ LIGHTMAP_ON + #pragma multi_compile_fog + #pragma multi_compile_instancing + #pragma multi_compile _ DOTS_INSTANCING_ON + + #pragma prefer_hlslcc gles + #pragma exclude_renderers d3d11_9x + #pragma vertex vert + #pragma require geometry + #pragma geometry geom + #pragma fragment frag + #include "./Lit.hlsl" + ENDHLSL + } + + Pass + { + Name "DepthOnly" + Tags { "LightMode" = "DepthOnly" } + + ZWrite On + ColorMask 0 + + HLSLPROGRAM + #pragma multi_compile _ _GEOM_INSTANCING + + #pragma exclude_renderers gles + #pragma vertex vert + #pragma require geometry + #pragma geometry geom + #pragma fragment frag + #include "./Depth.hlsl" + ENDHLSL + } + + Pass + { + Name "DepthNormals" + Tags { "LightMode" = "DepthNormals" } + + ZWrite On + + HLSLPROGRAM + #pragma multi_compile _ _GEOM_INSTANCING + + #pragma exclude_renderers gles + #pragma vertex vert + #pragma require geometry + #pragma geometry geom + #pragma fragment frag + #include "./DepthNormals.hlsl" + ENDHLSL + } + + Pass + { + Name "ShadowCaster" + Tags { "LightMode" = "ShadowCaster" } + + ZWrite On + ZTest LEqual + ColorMask 0 + + HLSLPROGRAM + #pragma multi_compile _ _NO_FUR_SHADOW + + #pragma exclude_renderers gles + #pragma vertex vert + #pragma require geometry + #pragma geometry geom + #pragma fragment frag + #include "./Shadow.hlsl" + ENDHLSL + } +} + FallBack "Hidden/Universal Render Pipeline/FallbackError" } diff --git a/Assets/Fur/Shaders/Shell/Param.hlsl b/Assets/Fur/Shaders/Shell/Param.hlsl index 00284d1..d2c00ca 100644 --- a/Assets/Fur/Shaders/Shell/Param.hlsl +++ b/Assets/Fur/Shaders/Shell/Param.hlsl @@ -21,4 +21,15 @@ SAMPLER(sampler_NormalMap); float4 _NormalMap_ST; float _NormalScale; +TEXTURE2D(_FurLengthMap); +SAMPLER(sampler_FurLengthMap); +float _FurLengthIntensity; + +float _TotalShellStep; + +float _Backlit; +float _Area; +float _MedulaScatter; +float _MedulaAbsorb; +float _Kappa; #endif \ No newline at end of file diff --git a/Assets/Fur/Shaders/Shell/Shadow.hlsl b/Assets/Fur/Shaders/Shell/Shadow.hlsl index 91acc41..8182d56 100644 --- a/Assets/Fur/Shaders/Shell/Shadow.hlsl +++ b/Assets/Fur/Shaders/Shell/Shadow.hlsl @@ -3,8 +3,12 @@ #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl" +// Declare "_BaseMap_ST" for line 76. +#include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl" #include "./Param.hlsl" #include "../Common/Common.hlsl" +// For VR single pass instance compability: +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { @@ -12,39 +16,110 @@ struct Attributes float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID }; -struct Varyings +struct v2g +{ + float4 positionOS : POSITION; + float3 normalOS : NORMAL; + float4 tangentOS : TANGENT; + float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID +}; + +struct g2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float fogCoord : TEXCOORD1; float layer : TEXCOORD2; + UNITY_VERTEX_INPUT_INSTANCE_ID + UNITY_VERTEX_OUTPUT_STEREO }; -Attributes vert(Attributes input) +v2g vert(Attributes input) { - return input; + v2g output = (v2g)0; + // setup the instanced id + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the "v2g output" to 0.0 + // This is the URP version of UNITY_INITIALIZE_OUTPUT() + ZERO_INITIALIZE(v2g, output); + // copy instance id in the "Attributes input" to the "v2g output" + UNITY_TRANSFER_INSTANCE_ID(input, output); + + output.positionOS = input.positionOS; + output.normalOS = input.normalOS; + output.tangentOS = input.tangentOS; + output.uv = input.uv; + return output; } -void AppendShellVertex(inout TriangleStream stream, Attributes input, int index) +void AppendShellVertex(inout TriangleStream stream, v2g input, int index) { - Varyings output = (Varyings)0; + g2f output = (g2f)0; + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the g2f output to 0.0 + ZERO_INITIALIZE(g2f, output); + + UNITY_TRANSFER_INSTANCE_ID(input, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS); - float moveFactor = pow(abs((float)index / _ShellAmount), _BaseMove.w); + float clampedShellAmount = clamp(_ShellAmount, 1, 13); + _ShellStep = _TotalShellStep / clampedShellAmount; + + float moveFactor = pow(abs((float)index / clamp(_ShellAmount, 1, 13)), _BaseMove.w); float3 posOS = input.positionOS.xyz; float3 windAngle = _Time.w * _WindFreq.xyz; float3 windMove = moveFactor * _WindMove.xyz * sin(windAngle + posOS * _WindMove.w); float3 move = moveFactor * _BaseMove.xyz; float3 shellDir = normalize(normalInput.normalWS + move + windMove); - float3 posWS = vertexInput.positionWS + shellDir * (_ShellStep * index); + float FurLength = SAMPLE_TEXTURE2D_LOD(_FurLengthMap, sampler_FurLengthMap, input.uv / _BaseMap_ST.xy, 0).x; + float3 posWS = vertexInput.positionWS + shellDir * (_ShellStep * index * FurLength * _FurLengthIntensity); //float4 posCS = TransformWorldToHClip(posWS); float4 posCS = GetShadowPositionHClip(posWS, normalInput.normalWS); + output.vertex = posCS; + output.uv = TRANSFORM_TEX(input.uv, _FurMap); + output.fogCoord = ComputeFogFactor(posCS.z); + output.layer = (float)index / clamp(_ShellAmount, 1, 13); + + stream.Append(output); +} + +// For geometry shader instancing, no clamp on _ShellAmount. +void AppendShellVertexInstancing(inout TriangleStream stream, v2g input, int index) +{ + g2f output = (g2f)0; + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the g2f output to 0.0 + ZERO_INITIALIZE(g2f, output); + + UNITY_TRANSFER_INSTANCE_ID(input, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); + VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS); + + _ShellStep = _TotalShellStep / _ShellAmount; + + float moveFactor = pow(abs((float)index / _ShellAmount), _BaseMove.w); + float3 posOS = input.positionOS.xyz; + float3 windAngle = _Time.w * _WindFreq.xyz; + float3 windMove = moveFactor * _WindMove.xyz * sin(windAngle + posOS * _WindMove.w); + float3 move = moveFactor * _BaseMove.xyz; + + float3 shellDir = normalize(normalInput.normalWS + move + windMove); + float FurLength = SAMPLE_TEXTURE2D_LOD(_FurLengthMap, sampler_FurLengthMap, input.uv / _BaseMap_ST.xy, 0).x; + float3 posWS = vertexInput.positionWS + shellDir * (_ShellStep * index * FurLength * _FurLengthIntensity); + //float4 posCS = TransformWorldToHClip(posWS); + float4 posCS = GetShadowPositionHClip(posWS, normalInput.normalWS); + output.vertex = posCS; output.uv = TRANSFORM_TEX(input.uv, _FurMap); output.fogCoord = ComputeFogFactor(posCS.z); @@ -53,10 +128,43 @@ void AppendShellVertex(inout TriangleStream stream, Attributes input, stream.Append(output); } -[maxvertexcount(128)] -void geom(triangle Attributes input[3], inout TriangleStream stream) +//-----------------------------------(below) For Microsoft Shader Model > 4.1----------------------------------- +// See "Lit.hlsl" for more information. +#if defined(_GEOM_INSTANCING) +[instance(3)] +[maxvertexcount(39)] +void geom(triangle v2g input[3], inout TriangleStream stream, uint instanceID : SV_GSInstanceID) +{ +#if defined(_NO_FUR_SHADOW) + float loopTimes = 1; +#else + float loopTimes = _ShellAmount; +#endif + // 13 is calculated manually, because "maxvertexcount" is 39 in "Lit.hlsl", keep all passes to have the smallest (39 now). + // If not, DepthNormals will be incorrect and Depth Priming (DepthNormal Mode) won't work. + // "39 / 3 = 13", 3 means 3 vertices of a tirangle. + [loop] for (float i = 0 + (instanceID * 13); i < loopTimes; ++i) + { + [unroll] for (float j = 0; j < 3; ++j) + { + AppendShellVertexInstancing(stream, input[j], i); + } + stream.RestartStrip(); + } +} +//-----------------------------------(above) For Microsoft Shader Model > 4.1----------------------------------- + +//-----------------------------------(below) For Microsoft Shader Model < 4.1----------------------------------- +#else +[maxvertexcount(39)] +void geom(triangle v2g input[3], inout TriangleStream stream) { - [loop] for (float i = 0; i < _ShellAmount; ++i) +#if defined(_NO_FUR_SHADOW) + float loopTimes = 1; +#else + float loopTimes = clamp(_ShellAmount, 1, 13); +#endif + [loop] for (float i = 0; i < loopTimes; ++i) { [unroll] for (float j = 0; j < 3; ++j) { @@ -65,9 +173,11 @@ void geom(triangle Attributes input[3], inout TriangleStream stream) stream.RestartStrip(); } } +#endif +//-----------------------------------(above) For Microsoft Shader Model < 4.1----------------------------------- void frag( - Varyings input, + g2f input, out float4 outColor : SV_Target, out float outDepth : SV_Depth) { diff --git a/Assets/Fur/Shaders/Shell/Unlit.hlsl b/Assets/Fur/Shaders/Shell/Unlit.hlsl index b6cd108..aed5ce6 100644 --- a/Assets/Fur/Shaders/Shell/Unlit.hlsl +++ b/Assets/Fur/Shaders/Shell/Unlit.hlsl @@ -11,24 +11,55 @@ struct Attributes float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID }; -struct Varyings +struct v2g +{ + float4 positionOS : POSITION; + float3 normalOS : NORMAL; + float4 tangentOS : TANGENT; + float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID +}; + +struct g2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float fogCoord : TEXCOORD1; float layer : TEXCOORD2; + UNITY_VERTEX_INPUT_INSTANCE_ID + UNITY_VERTEX_OUTPUT_STEREO }; Attributes vert(Attributes input) { - return input; + v2g output = (v2g)0; + // setup the instanced id + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the "v2g output" to 0.0 + // This is the URP version of UNITY_INITIALIZE_OUTPUT() + ZERO_INITIALIZE(v2g, output); + // copy instance id in the "Attributes input" to the "v2g output" + UNITY_TRANSFER_INSTANCE_ID(input, output); + + output.positionOS = input.positionOS; + output.normalOS = input.normalOS; + output.tangentOS = input.tangentOS; + output.uv = input.uv; + return output; } -void AppendShellVertex(inout TriangleStream stream, Attributes input, int index) +void AppendShellVertex(inout TriangleStream stream, v2g input, int index) { - Varyings output = (Varyings)0; + g2f output = (g2f)0; + UNITY_SETUP_INSTANCE_ID(input); + // set all values in the g2f output to 0.0 + ZERO_INITIALIZE(g2f, output); + + UNITY_TRANSFER_INSTANCE_ID(input, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS); @@ -59,7 +90,7 @@ void AppendShellVertex(inout TriangleStream stream, Attributes input, } [maxvertexcount(128)] -void geom(triangle Attributes input[3], inout TriangleStream stream) +void geom(triangle v2g input[3], inout TriangleStream stream) { [loop] for (float i = 0; i < _ShellAmount; ++i) { @@ -71,7 +102,7 @@ void geom(triangle Attributes input[3], inout TriangleStream stream) } } -float4 frag(Varyings input) : SV_Target +float4 frag(g2f input) : SV_Target { float4 furColor = SAMPLE_TEXTURE2D(_FurMap, sampler_FurMap, input.uv * _FurScale); float alpha = furColor.r * (1.0 - input.layer);