您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
441 行
14 KiB
441 行
14 KiB
// Shader targeted for LowEnd mobile devices. Single Pass Forward Rendering. Shader Model 2
|
|
Shader "LowEndMobilePipeline/Specular"
|
|
{
|
|
// Keep properties of StandardSpecular shader for upgrade reasons.
|
|
Properties
|
|
{
|
|
_Color("Color", Color) = (1,1,1,1)
|
|
_MainTex("Base (RGB) Glossiness / Alpha (A)", 2D) = "white" {}
|
|
|
|
_Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5
|
|
|
|
_Glossiness("Shininess", Range(0.0, 1.0)) = 0.5
|
|
_SpecularStrength("Specular Strength", Range(0.0, 255.0)) = 200
|
|
_GlossMapScale("Smoothness Factor", Range(0.0, 1.0)) = 1.0
|
|
[Enum(Specular Alpha,0,Albedo Alpha,1)] _SmoothnessTextureChannel("Smoothness texture channel", Float) = 0
|
|
|
|
[HideInInspector] _SpecSource("Specular Color Source", Float) = 0.0
|
|
_SpecColor("Specular", Color) = (1.0, 1.0, 1.0)
|
|
_SpecGlossMap("Specular", 2D) = "white" {}
|
|
[HideInInspector] _GlossinessSource("Glossiness Source", Float) = 0.0
|
|
[ToggleOff] _SpecularHighlights("Specular Highlights", Float) = 1.0
|
|
[ToggleOff] _GlossyReflections("Glossy Reflections", Float) = 1.0
|
|
|
|
[HideInInspector] _BumpScale("Scale", Float) = 1.0
|
|
[NoScaleOffset] _BumpMap("Normal Map", 2D) = "bump" {}
|
|
|
|
_Parallax("Height Scale", Range(0.005, 0.08)) = 0.02
|
|
_ParallaxMap("Height Map", 2D) = "black" {}
|
|
|
|
_OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0
|
|
_OcclusionMap("Occlusion", 2D) = "white" {}
|
|
|
|
_EmissionColor("Emission Color", Color) = (0,0,0)
|
|
_EmissionMap("Emission", 2D) = "white" {}
|
|
|
|
_DetailMask("Detail Mask", 2D) = "white" {}
|
|
|
|
_DetailAlbedoMap("Detail Albedo x2", 2D) = "grey" {}
|
|
_DetailNormalMapScale("Scale", Float) = 1.0
|
|
_DetailNormalMap("Normal Map", 2D) = "bump" {}
|
|
|
|
[Enum(UV0,0,UV1,1)] _UVSec("UV Set for secondary textures", Float) = 0
|
|
|
|
// Blending state
|
|
[HideInInspector] _Mode("__mode", Float) = 0.0
|
|
[HideInInspector] _SrcBlend("__src", Float) = 1.0
|
|
[HideInInspector] _DstBlend("__dst", Float) = 0.0
|
|
[HideInInspector] _ZWrite("__zw", Float) = 1.0
|
|
}
|
|
|
|
SubShader
|
|
{
|
|
Tags { "RenderType" = "Opaque" "RenderPipeline" = "LowEndMobilePipeline" }
|
|
LOD 300
|
|
|
|
Pass
|
|
{
|
|
Name "LD_SINGLE_PASS_FORWARD"
|
|
Tags { "LightMode" = "LowEndMobileForward" }
|
|
|
|
// Use same blending / depth states as Standard shader
|
|
Blend[_SrcBlend][_DstBlend]
|
|
ZWrite[_ZWrite]
|
|
|
|
CGPROGRAM
|
|
#pragma target 2.0
|
|
#pragma vertex vert
|
|
#pragma fragment frag
|
|
#pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON
|
|
#pragma shader_feature _ _SHARED_SPECULAR_DIFFUSE _SPECGLOSSMAP _SPECULAR_COLOR
|
|
#pragma shader_feature _GLOSSINESS_FROM_BASE_ALPHA
|
|
#pragma shader_feature _NORMALMAP
|
|
|
|
#pragma multi_compile _ LIGHTMAP_ON
|
|
#pragma multi_compile _ HARD_SHADOWS SOFT_SHADOWS
|
|
#pragma multi_compile_fog
|
|
#pragma only_renderers d3d9 d3d11 d3d11_9x glcore gles gles3 metal
|
|
#pragma enable_d3d11_debug_symbols
|
|
|
|
#include "UnityCG.cginc"
|
|
#include "UnityStandardBRDF.cginc"
|
|
#include "UnityStandardInput.cginc"
|
|
#include "UnityStandardUtils.cginc"
|
|
|
|
#define DEBUG_CASCADES 0
|
|
#define MAX_SHADOW_CASCADES 4
|
|
#define MAX_LIGHTS 8
|
|
|
|
#define INITIALIZE_LIGHT(light, lightIndex) \
|
|
light.pos = globalLightPos[lightIndex]; \
|
|
light.color = globalLightColor[lightIndex]; \
|
|
light.atten = globalLightAtten[lightIndex]; \
|
|
light.spotDir = globalLightSpotDir[lightIndex]
|
|
|
|
// The variables are very similar to built-in unity_LightColor, unity_LightPosition,
|
|
// unity_LightAtten, unity_SpotDirection as used by the VertexLit shaders, except here
|
|
// we use world space positions instead of view space.
|
|
half4 globalLightColor[MAX_LIGHTS];
|
|
float4 globalLightPos[MAX_LIGHTS];
|
|
half4 globalLightSpotDir[MAX_LIGHTS];
|
|
half4 globalLightAtten[MAX_LIGHTS];
|
|
int4 globalLightCount; // x: pixelLightCount, y = totalLightCount (pixel + vert)
|
|
|
|
sampler2D_float _ShadowMap;
|
|
float _PCFKernel[8];
|
|
|
|
half4x4 _WorldToShadow[MAX_SHADOW_CASCADES];
|
|
half4 _PSSMDistancesAndShadowResolution; // xyz: PSSM Distance for 4 cascades, w: 1 / shadowmap resolution. Used for filtering
|
|
half _SpecularStrength;
|
|
|
|
struct LowendVertexInput
|
|
{
|
|
float4 vertex : POSITION;
|
|
float3 normal : NORMAL;
|
|
float4 tangent : TANGENT;
|
|
float3 texcoord : TEXCOORD0;
|
|
float2 lightmapUV : TEXCOORD1;
|
|
};
|
|
|
|
struct v2f
|
|
{
|
|
float4 uv01 : TEXCOORD0; // uv01.xy: uv0, uv01.zw: uv1
|
|
float4 posWS : TEXCOORD1; // xyz: posWorld, w: eyeZ
|
|
#if _NORMALMAP
|
|
half3 tangentToWorld[3] : TEXCOORD2; // tangentToWorld matrix
|
|
#else
|
|
half3 normal : TEXCOORD2;
|
|
#endif
|
|
half4 viewDir : TEXCOORD5; // xyz: viewDir
|
|
UNITY_FOG_COORDS_PACKED(6, half4) // x: fogCoord, yzw: vertexColor
|
|
float4 hpos : SV_POSITION;
|
|
};
|
|
|
|
struct LightInput
|
|
{
|
|
half4 pos;
|
|
half4 color;
|
|
half4 atten;
|
|
half4 spotDir;
|
|
};
|
|
|
|
inline half ComputeCascadeIndex(half eyeZ)
|
|
{
|
|
// PSSMDistance is set to infinity for non active cascades. This way the comparison for unavailable cascades will always be zero.
|
|
half3 cascadeCompare = step(_PSSMDistancesAndShadowResolution.xyz, half3(eyeZ, eyeZ, eyeZ));
|
|
return dot(cascadeCompare, cascadeCompare);
|
|
}
|
|
|
|
inline half ShadowAttenuation(half2 shadowCoord, half shadowCoordDepth)
|
|
{
|
|
half depth = tex2D(_ShadowMap, shadowCoord).r;
|
|
#if defined(UNITY_REVERSED_Z)
|
|
return step(depth, shadowCoordDepth);
|
|
#else
|
|
return step(shadowCoordDepth, depth);
|
|
#endif
|
|
}
|
|
|
|
inline half ShadowPCF(half4 shadowCoord)
|
|
{
|
|
// TODO: simulate textureGatherOffset not available, simulate it
|
|
half2 offset = half2(0, 0);
|
|
half attenuation = ShadowAttenuation(shadowCoord.xy + half2(_PCFKernel[0], _PCFKernel[1]) + offset, shadowCoord.z) +
|
|
ShadowAttenuation(shadowCoord.xy + half2(_PCFKernel[2], _PCFKernel[3]) + offset, shadowCoord.z) +
|
|
ShadowAttenuation(shadowCoord.xy + half2(_PCFKernel[4], _PCFKernel[5]) + offset, shadowCoord.z) +
|
|
ShadowAttenuation(shadowCoord.xy + half2(_PCFKernel[6], _PCFKernel[7]) + offset, shadowCoord.z);
|
|
return attenuation * 0.25;
|
|
}
|
|
|
|
inline half3 EvaluateOneLight(LightInput lightInput, half3 diffuseColor, half4 specularGloss, half3 normal, float3 posWorld, half3 viewDir)
|
|
{
|
|
float3 posToLight = lightInput.pos.xyz;
|
|
posToLight -= posWorld * lightInput.pos.w;
|
|
|
|
float distanceSqr = max(dot(posToLight, posToLight), 0.001);
|
|
float lightAtten = 1.0 / (1.0 + distanceSqr * lightInput.atten.z);
|
|
|
|
float3 lightDir = posToLight * rsqrt(distanceSqr);
|
|
float SdotL = saturate(dot(lightInput.spotDir.xyz, lightDir));
|
|
lightAtten *= saturate((SdotL - lightInput.atten.x) / lightInput.atten.y);
|
|
|
|
float cutoff = step(distanceSqr, lightInput.atten.w);
|
|
lightAtten *= cutoff;
|
|
|
|
float NdotL = saturate(dot(normal, lightDir));
|
|
|
|
half3 halfVec = normalize(lightDir + viewDir);
|
|
half NdotH = saturate(dot(normal, halfVec));
|
|
|
|
half3 lightColor = lightInput.color.rgb * lightAtten;
|
|
half3 diffuse = diffuseColor * lightColor * NdotL;
|
|
|
|
#if defined(_SHARED_SPECULAR_DIFFUSE) || defined(_SPECGLOSSMAP) || defined(_SPECULAR_COLOR)
|
|
half3 specular = specularGloss.rgb * lightColor * pow(NdotH, 256.0 - _SpecularStrength) * specularGloss.a;
|
|
return diffuse + specular;
|
|
#else
|
|
return diffuse;
|
|
#endif
|
|
}
|
|
|
|
inline half3 EvaluateMainLight(LightInput lightInput, half3 diffuseColor, half4 specularGloss, half3 normal, float4 posWorld, half3 viewDir)
|
|
{
|
|
half3 color = EvaluateOneLight(lightInput, diffuseColor, specularGloss, normal, posWorld, viewDir);
|
|
|
|
#if defined(HARD_SHADOWS) || defined(SOFT_SHADOWS)
|
|
int cascadeIndex = ComputeCascadeIndex(posWorld.w);
|
|
float4 shadowCoord = mul(_WorldToShadow[cascadeIndex], float4(posWorld.xyz, 1.0));
|
|
shadowCoord.z = saturate(shadowCoord.z);
|
|
|
|
#ifdef SOFT_SHADOWS
|
|
half shadowAttenuation = ShadowPCF(shadowCoord);
|
|
#else
|
|
half shadowAttenuation = ShadowAttenuation(shadowCoord.xy, shadowCoord.z);
|
|
#endif
|
|
|
|
#if DEBUG_CASCADES
|
|
half3 cascadeColors[MAX_SHADOW_CASCADES] = { half3(1.0, 0.0, 0.0), half3(0.0, 1.0, 0.0), half3(0.0, 0.0, 1.0), half3(1.0, 0.0, 1.0) };
|
|
return cascadeColors[cascadeIndex] * diffuseColor * max(shadowAttenuation, 0.5);
|
|
#endif
|
|
|
|
return color * shadowAttenuation;
|
|
#else
|
|
return color;
|
|
#endif
|
|
}
|
|
|
|
v2f vert(LowendVertexInput v)
|
|
{
|
|
v2f o;
|
|
UNITY_INITIALIZE_OUTPUT(v2f, o);
|
|
|
|
o.uv01.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
|
|
o.uv01.zw = v.lightmapUV * unity_LightmapST.xy + unity_LightmapST.zw;
|
|
o.hpos = UnityObjectToClipPos(v.vertex);
|
|
|
|
o.posWS.xyz = mul(unity_ObjectToWorld, v.vertex).xyz;
|
|
o.posWS.w = -UnityObjectToViewPos(v.vertex).z;
|
|
|
|
o.viewDir.xyz = normalize(_WorldSpaceCameraPos - o.posWS.xyz);
|
|
half3 normal = normalize(UnityObjectToWorldNormal(v.normal));
|
|
|
|
#if _NORMALMAP
|
|
half sign = v.tangent.w * unity_WorldTransformParams.w;
|
|
half3 tangent = normalize(UnityObjectToWorldDir(v.tangent));
|
|
half3 binormal = cross(normal, tangent) * v.tangent.w;
|
|
|
|
// Initialize tangetToWorld in column-major to benefit from better glsl matrix multiplication code
|
|
o.tangentToWorld[0] = half3(tangent.x, binormal.x, normal.x);
|
|
o.tangentToWorld[1] = half3(tangent.y, binormal.y, normal.y);
|
|
o.tangentToWorld[2] = half3(tangent.z, binormal.z, normal.z);
|
|
#else
|
|
o.normal = normal;
|
|
#endif
|
|
|
|
half4 diffuseAndSpecular = half4(1.0, 1.0, 1.0, 1.0);
|
|
for (int lightIndex = globalLightCount.x; lightIndex < globalLightCount.y; ++lightIndex)
|
|
{
|
|
LightInput lightInput;
|
|
INITIALIZE_LIGHT(lightInput, lightIndex);
|
|
o.fogCoord.yzw += EvaluateOneLight(lightInput, diffuseAndSpecular.rgb, diffuseAndSpecular, normal, o.posWS.xyz, o.viewDir.xyz);
|
|
}
|
|
|
|
#ifndef LIGHTMAP_ON
|
|
o.fogCoord.yzw += max(half3(0, 0, 0), ShadeSH9(half4(normal, 1)));
|
|
#endif
|
|
|
|
UNITY_TRANSFER_FOG(o, o.hpos);
|
|
return o;
|
|
}
|
|
|
|
half4 frag(v2f i) : SV_Target
|
|
{
|
|
half4 diffuseAlpha = tex2D(_MainTex, i.uv01.xy);
|
|
half3 diffuse = diffuseAlpha.rgb * _Color.rgb;
|
|
half alpha = diffuseAlpha.a * _Color.a;
|
|
|
|
#ifdef _ALPHATEST_ON
|
|
clip(alpha - _Cutoff);
|
|
#endif
|
|
|
|
#if _NORMALMAP
|
|
half3 normalmap = UnpackNormal(tex2D(_BumpMap, i.uv01.xy));
|
|
|
|
// glsl compiler will generate underperforming code by using a row-major pre multiplication matrix: mul(normalmap, i.tangentToWorld)
|
|
// i.tangetToWorld was initialized as column-major in vs and here dot'ing individual for better performance.
|
|
// The code below is similar to post multiply: mul(i.tangentToWorld, normalmap)
|
|
half3 normal = half3(dot(normalmap, i.tangentToWorld[0]), dot(normalmap, i.tangentToWorld[1]), dot(normalmap, i.tangentToWorld[2]));
|
|
#else
|
|
half3 normal = normalize(i.normal);
|
|
#endif
|
|
|
|
half4 specularGloss;
|
|
#ifdef _SHARED_SPECULAR_DIFFUSE
|
|
specularGloss.rgb = diffuse;
|
|
specularGloss.a = alpha;
|
|
#elif defined(_SHARED_SPECULAR_DIFFUSE)
|
|
#if _GLOSSINESS_FROM_BASE_ALPHA
|
|
specularGloss.rgb = tex2D(_SpecGlossMap, i.uv01.xy) * _SpecColor;
|
|
specularGloss.a = alpha;
|
|
#else
|
|
specularGloss = tex2D(_SpecGlossMap, i.uv01.xy) * _SpecColor;
|
|
#endif
|
|
#else
|
|
#if _GLOSSINESS_FROM_BASE_ALPHA
|
|
specularGloss.rgb = _SpecColor;
|
|
specularGloss.a = alpha;
|
|
#else
|
|
specularGloss = _SpecColor;
|
|
#endif
|
|
#endif
|
|
|
|
float3 posWorld = i.posWS.xyz;
|
|
half3 viewDir = i.viewDir.xyz;
|
|
|
|
// Indirect Light Contribution
|
|
half3 indirectDiffuse;
|
|
#ifdef LIGHTMAP_ON
|
|
indirectDiffuse = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv01.zw)) * diffuse;
|
|
#else
|
|
indirectDiffuse = i.fogCoord.yzw * diffuse;
|
|
#endif
|
|
// Compute direct contribution from main directional light.
|
|
// Only a single directional shadow caster is supported.
|
|
LightInput mainLight;
|
|
INITIALIZE_LIGHT(mainLight, 0);
|
|
|
|
#if DEBUG_CASCADES
|
|
return half4(EvaluateMainLight(mainLight, diffuse, specularGloss, normal, i.posWS, viewDir), 1.0);
|
|
#endif
|
|
half3 directColor = EvaluateMainLight(mainLight, diffuse, specularGloss, normal, i.posWS, viewDir);
|
|
|
|
// Compute direct contribution from additional lights.
|
|
for (int lightIndex = 1; lightIndex < globalLightCount.x; ++lightIndex)
|
|
{
|
|
LightInput additionalLight;
|
|
INITIALIZE_LIGHT(additionalLight, lightIndex);
|
|
directColor += EvaluateOneLight(additionalLight, diffuse, specularGloss, normal, posWorld, viewDir);
|
|
}
|
|
|
|
half3 color = directColor + indirectDiffuse + _EmissionColor;
|
|
UNITY_APPLY_FOG(i.fogCoord, color);
|
|
|
|
#ifdef _ALPHABLEND_ON
|
|
return half4(color, alpha);
|
|
#else
|
|
return half4(color, 1);
|
|
#endif
|
|
};
|
|
ENDCG
|
|
}
|
|
|
|
Pass
|
|
{
|
|
Name "LD_SHADOW_CASTER"
|
|
Tags { "Lightmode" = "ShadowCaster" }
|
|
|
|
ZWrite On ZTest LEqual
|
|
|
|
CGPROGRAM
|
|
#pragma target 2.0
|
|
#pragma vertex vert
|
|
#pragma fragment frag
|
|
|
|
float4 _WorldLightDirAndBias;
|
|
|
|
#include "UnityCG.cginc"
|
|
|
|
struct VertexInput
|
|
{
|
|
float4 pos : POSITION;
|
|
float3 normal : NORMAL;
|
|
};
|
|
|
|
// Similar to UnityClipSpaceShadowCasterPos but using LDPipeline lightdir and bias and applying near plane clamp
|
|
float4 ClipSpaceShadowCasterPos(float4 vertex, float3 normal)
|
|
{
|
|
float4 wPos = mul(unity_ObjectToWorld, vertex);
|
|
|
|
if (_WorldLightDirAndBias.w > 0.0)
|
|
{
|
|
float3 wNormal = UnityObjectToWorldNormal(normal);
|
|
|
|
// apply normal offset bias (inset position along the normal)
|
|
// bias needs to be scaled by sine between normal and light direction
|
|
// (http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/)
|
|
//
|
|
// _WorldLightDirAndBias.w shadow bias defined in LRRenderPipeline asset
|
|
|
|
float shadowCos = dot(wNormal, _WorldLightDirAndBias.xyz);
|
|
float shadowSine = sqrt(1 - shadowCos*shadowCos);
|
|
float normalBias = _WorldLightDirAndBias.w * shadowSine;
|
|
|
|
wPos.xyz -= wNormal * normalBias;
|
|
}
|
|
|
|
float4 clipPos = mul(UNITY_MATRIX_VP, wPos);
|
|
#if defined(UNITY_REVERSED_Z)
|
|
clipPos.z = min(clipPos.z, UNITY_NEAR_CLIP_VALUE);
|
|
#else
|
|
clipPos.z = max(clipPos.z, UNITY_NEAR_CLIP_VALUE);
|
|
#endif
|
|
return clipPos;
|
|
}
|
|
|
|
float4 vert(VertexInput i) : SV_POSITION
|
|
{
|
|
return ClipSpaceShadowCasterPos(i.pos, i.normal);
|
|
}
|
|
|
|
half4 frag() : SV_TARGET
|
|
{
|
|
return 0;
|
|
}
|
|
ENDCG
|
|
}
|
|
|
|
// This pass it not used during regular rendering, only for lightmap baking.
|
|
Pass
|
|
{
|
|
Name "LD_META"
|
|
Tags{ "LightMode" = "Meta" }
|
|
|
|
Cull Off
|
|
|
|
CGPROGRAM
|
|
#pragma vertex vert_meta
|
|
#pragma fragment frag_meta
|
|
|
|
#pragma shader_feature _EMISSION
|
|
#pragma shader_feature _METALLICGLOSSMAP
|
|
#pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
|
|
#pragma shader_feature ___ _DETAIL_MULX2
|
|
#pragma shader_feature EDITOR_VISUALIZATION
|
|
|
|
#include "UnityStandardMeta.cginc"
|
|
ENDCG
|
|
}
|
|
}
|
|
Fallback "Standard (Specular setup)"
|
|
CustomEditor "LowendMobilePipelineMaterialEditor"
|
|
}
|