#if _DECAL_MAX0 #define _DECALMAX 1 #elif _DECAL_MAX4 #define _DECALMAX 4 #elif _DECAL_MAX8 #define _DECALMAX 8 #elif _DECAL_MAX16 #define _DECALMAX 16 #elif _DECAL_MAX32 #define _DECALMAX 32 #elif _DECAL_MAX64 #define _DECALMAX 64 #elif _DECAL_MAX128 #define _DECALMAX 128 #elif _DECAL_MAX256 #define _DECALMAX 256 #else #define _DECALMAX 1 #endif #if _DECAL_STATICMAX0 #define _STATICDECALMAX 1 #elif _DECAL_STATICMAX64 #define _STATICDECALMAX 64 #elif _DECAL_STATICMAX128 #define _STATICDECALMAX 128 #elif _DECAL_STATICMAX256 #define _STATICDECALMAX 256 #elif _DECAL_STATICMAX512 #define _STATICDECALMAX 512 #elif _DECAL_STATICMAX1024 #define _STATICDECALMAX 1024 #elif _DECAL_STATICMAX2048 #define _STATICDECALMAX 2048 #else #define _STATICDECALMAX 1 #endif TEXTURE2D_ARRAY(_DecalAlbedo); TEXTURE2D_ARRAY(_DecalNormalSAO); #if _DECAL_EMISMETAL TEXTURE2D_ARRAY(_DecalEmisMetal); #endif #if _DECAL_SPLAT TEXTURE2D_ARRAY(_DecalSplats); #endif int _MSDecalCount; TEXTURE2D(_DecalCullData); float4 _DecalCullData_TexelSize; UNITY_DECLARE_TEX2D(_DecalControl); float4 _DecalControl_TexelSize; TEXTURE2D(_DecalStaticData); float4 _DecalStaticData_TexelSize; TEXTURE2D(_DecalDynamicData); float4 _DecalDynamicData_TexelSize; #define DECALDATA1 4 #define DECALDATA2 5 #define DECALSPLATS 6 #define DECALTINT 7 float4 GetDecalStaticData(float index, float y) { float2 uv = float2(index + 0.5, y + 0.5) * _DecalStaticData_TexelSize.xy; return SAMPLE_TEXTURE2D_LOD(_DecalStaticData, sampler_DecalControl, uv, 0); } float4x4 GetDecalStaticMtx(float index) { float4x4 mtx; float sz = _DecalStaticData_TexelSize.y; float u = index * _DecalStaticData_TexelSize.x; mtx._m00_m01_m02_m03 = SAMPLE_TEXTURE2D_LOD(_DecalStaticData, sampler_DecalControl, float2(u, 0), 0); mtx._m10_m11_m12_m13 = SAMPLE_TEXTURE2D_LOD(_DecalStaticData, sampler_DecalControl, float2(u, 1*sz), 0); mtx._m20_m21_m22_m23 = SAMPLE_TEXTURE2D_LOD(_DecalStaticData, sampler_DecalControl, float2(u, 2*sz), 0); mtx._m30_m31_m32_m33 = SAMPLE_TEXTURE2D_LOD(_DecalStaticData, sampler_DecalControl, float2(u, 3*sz), 0); return mtx; } float4 GetDecalDynamicData(float index, float y) { float2 uv = float2(index + 0.5, y + 0.5) * _DecalDynamicData_TexelSize.xy; return SAMPLE_TEXTURE2D_LOD(_DecalDynamicData, sampler_DecalControl, uv, 0); } float4x4 GetDecalDynamicMtx(float index) { float4x4 mtx; float sz = _DecalDynamicData_TexelSize.y; float u = index * _DecalDynamicData_TexelSize.x; mtx._m00_m01_m02_m03 = SAMPLE_TEXTURE2D_LOD(_DecalDynamicData, sampler_DecalControl, float2(u, 0), 0); mtx._m10_m11_m12_m13 = SAMPLE_TEXTURE2D_LOD(_DecalDynamicData, sampler_DecalControl, float2(u, 1*sz), 0); mtx._m20_m21_m22_m23 = SAMPLE_TEXTURE2D_LOD(_DecalDynamicData, sampler_DecalControl, float2(u, 2*sz), 0); mtx._m30_m31_m32_m33 = SAMPLE_TEXTURE2D_LOD(_DecalDynamicData, sampler_DecalControl, float2(u, 3*sz), 0); return mtx; } DecalLayer InitDecalLayer() { DecalLayer o = (DecalLayer)0; o.uv = float3(0,0,-1); o.dx = float2(0,0); o.dy = float2(0,0); o.dynamic = 0; o.decalIndex = 0; return o; } DecalOutput InitDecalOutput() { DecalOutput o = (DecalOutput)0; o.l0 = InitDecalLayer(); o.l1 = InitDecalLayer(); o.l2 = InitDecalLayer(); o.l3 = InitDecalLayer(); o.Weights = half4(0,0,0,0); o.Indexes = half4(0,1,2,3); o.fxLevels = half4(0,0,0,0); return o; } // we are drawn from highest to the lowest void DecalInsert(inout DecalOutput o, DecalLayer l) { if (o.l0.uv.z < 0) { o.l0 = l; } else if (o.l1.uv.z < 0) { o.l1 = o.l0; o.l0 = l; } else if (o.l2.uv.z < 0) { o.l2 = o.l1; o.l1 = o.l0; o.l0 = l; } else { o.l3 = o.l2; o.l2 = o.l1; o.l1 = o.l0; o.l0 = l; } } void DrawDecal(int decalIndex, inout DecalOutput o, float4 data, bool dynamic, float2 uv, float2 dx, float2 dy) { #if !_DECAL_NOTEXTURES || _DECAL_EMISMETAL int texIndex = data.x - floor(data.x * 0.01); DecalLayer l = InitDecalLayer(); l.uv = float3(uv, texIndex); l.dx = ddx(uv); //recalculate derivative to reduce artifacts l.dy = ddy(uv); l.dynamic = dynamic; l.decalIndex = decalIndex; DecalInsert(o, l); #endif #if _DECAL_SPLAT int splatTexIndex = floor(data.x * 0.01); half4 splats = SAMPLE_TEXTURE2D_GRAD(_DecalSplats, shared_linear_clamp_sampler, float3(uv.xy, splatTexIndex), dx, dy); float4 splatIndexes = 0; UNITY_BRANCH if (dynamic) { splatIndexes = GetDecalDynamicData(decalIndex, DECALSPLATS); } else { splatIndexes = GetDecalStaticData(decalIndex, DECALSPLATS); } float splatOpacity = abs(data.z) - 1; float splatMode = data.z > 0 ? 1 : 0; splats *= splatOpacity * 2; UNITY_BRANCH if (splatMode > 0.5) { // Another odity, splat index 0 won't register.. DoMergeDecalSplats(splats, splatIndexes, o.Weights, o.Indexes); } else { o.fxLevels = max(o.fxLevels, splats); } #endif } void CullDrawStaticDecal(int i, float3 worldPos, float3 localPos, inout DecalOutput o, float2 dx, float2 dy) { i = min(_STATICDECALMAX-1, i); float3 localProj = mul(GetDecalStaticMtx(i), float4(localPos, 1)).xyz; float2 uv = localProj.xz + 0.5; float4 decalData1 = GetDecalStaticData(i, DECALDATA1); float scaleY = decalData1.y; float clipPos = (localProj.y + 0.5) - 1.0/max(scaleY, 0.001); bool clipBounds = (uv.x == saturate(uv.x) && uv.y == saturate(uv.y) && clipPos == saturate(clipPos)); UNITY_BRANCH if (clipBounds) { DrawDecal(i, o, decalData1, false, uv, dx, dy); } } void CullDrawDynamicDecal(int i, float3 worldPos, float3 localPos, inout DecalOutput o) { float3 localProj = mul(GetDecalDynamicMtx(i), float4(localPos, 1)).xyz; float2 uv = localProj.xz + 0.5; float2 dx = ddx(uv); float2 dy = ddy(uv); float4 decalData1 = GetDecalDynamicData(i, DECALDATA1); float scaleY = decalData1.y; float clipPos = (localProj.y + 0.5) - 1.0/max(scaleY, 0.001); bool clipBounds = (uv.x == saturate(uv.x) && uv.y == saturate(uv.y) && clipPos == saturate(clipPos)); UNITY_BRANCH if (clipBounds) { DrawDecal(i, o, decalData1, true, uv, dx, dy); } } void CullDrawDynamicDecalTess(int i, float3 worldPos, float3 localPos, inout DecalOutput o) { float3 localProj = mul(GetDecalDynamicMtx(i), float4(localPos, 1)).xyz; float2 uv = localProj.xz + 0.5; float2 dx = 0; float2 dy = 0; float4 decalData1 = GetDecalDynamicData(i, DECALDATA1); float scaleY = decalData1.y; float clipPos = (localProj.y + 0.5) - 1.0/max(scaleY, 0.001); bool clipBounds = (uv.x == saturate(uv.x) && uv.y == saturate(uv.y) && clipPos == saturate(clipPos)); UNITY_BRANCH if (clipBounds) { DrawDecal(i, o, decalData1, true, uv, dx, dy); } } // Distance based culling void RoughCullDynamicDecal(int i, float3 worldPos, float3 localPos, inout DecalOutput o) { float4 cullData = SAMPLE_TEXTURE2D_LOD(_DecalCullData, sampler_DecalControl, float2((i+0.5) * _DecalCullData_TexelSize.x, 0.5), 0); float3 lv = worldPos - cullData.xyz; float dist = lv.x * lv.x + lv.y * lv.y + lv.z * lv.z; UNITY_BRANCH if (dist < cullData.w) { CullDrawDynamicDecal(i, worldPos, localPos, o); } } // Distance based culling void RoughCullDynamicDecalTess(int i, float3 worldPos, float3 localPos, inout DecalOutput o) { float4 cullData = SAMPLE_TEXTURE2D_LOD(_DecalCullData, sampler_DecalControl, float2((i+0.5) * _DecalCullData_TexelSize.x, 0.5), 0); float3 lv = worldPos - cullData.xyz; float dist = lv.x * lv.x + lv.y * lv.y + lv.z * lv.z; UNITY_BRANCH if (dist < cullData.w) { CullDrawDynamicDecalTess(i, worldPos, localPos, o); } } DecalOutput DoDecals(float2 uv, float3 worldPos, float camDist, float3 worldNormalVertex) { DecalOutput o = InitDecalOutput(); // Terrain matrix's lie, so in terrain mode, we just use worldPos float3 localPos = worldPos; #if !_DECAL_STATICMAX0 // Static float2 cuv = uv; half4 c0 = SAMPLE_TEXTURE2D_LOD(_DecalControl, sampler_DecalControl, uv, 0); c0 -= 1; // OK, I don't quite understand this, and expect it to break, but haven't so far. // The issue is that we get derivative lines when we have overlapping decals. This is // because the index map may report (2,1,0,0) on one pixel, and (1,0,0,0) on the next when // one decal ends. Thus, when mip map data is shared and the indexes change, you get derivative issues. // // For regular UV scale blending, I just average the derivatives of all texels being used. But // here we can't do that. So while testing, I just transformed the first decal in the list into // decal space and used it's derivatives for all decals. But this can't possibly work right!? Right? int initialIdx = max(c0.r, 0); float3 localProj = mul(GetDecalStaticMtx(initialIdx), float4(localPos, 1)).xyz; float2 xuv = localProj.xy + 0.5; float2 dx = ddx(xuv); float2 dy = ddy(xuv); UNITY_BRANCH if (c0.r >= 0) { CullDrawStaticDecal((int)c0.r, worldPos, localPos, o, dx, dy); UNITY_BRANCH if (c0.g >= 0) { CullDrawStaticDecal((int)c0.g, worldPos, localPos, o, dx, dy); UNITY_BRANCH if (c0.b >= 0) { CullDrawStaticDecal((int)c0.b, worldPos, localPos, o, dx, dy); UNITY_BRANCH if (c0.a >= 0) { CullDrawStaticDecal((int)c0.a, worldPos, localPos, o, dx, dy); } } } } #endif #if !_DECAL_MAX0 // dynamic int count = _MSDecalCount; if (count > _DECALMAX) count = _DECALMAX; [loop] for (int i = 0; i < count; i++) { RoughCullDynamicDecal(i, worldPos, localPos, o); } #endif //!_DECAL_MAX0 return o; } #if (_TESSDISTANCE || _TESSEDGE) && _DECAL_TESS DecalOutput DoDecalsTess(float2 uv, float3 worldPos, float camDist, float3 worldNormalVertex) { DecalOutput o = InitDecalOutput(); // Terrain matrix's lie, so in terrain mode, we just use worldPos float3 localPos = worldPos; #if !_DECAL_STATICMAX0 // Static // texture must be clamped, but we want to share samplers, so floor float2 cuv = uv; half4 c0 = SAMPLE_TEXTURE2D_LOD(_DecalControl, sampler_DecalControl, uv, 0); c0 -= 1; // OK, I don't quite understand this, and expect it to break, but haven't so far. // The issue is that we get derivative lines when we have overlapping decals. This is // because the index map may report (2,1,0,0) on one pixel, and (1,0,0,0) on the next when // one decal ends. Thus, when mip map data is shared and the indexes change, you get derivative issues. // // For regular UV scale blending, I just average the derivatives of all texels being used. But // here we can't do that. So while testing, I just transformed the first decal in the list into // decal space and used it's derivatives for all decals. But this can't possibly work right!? Right? UNITY_BRANCH if (c0.r >= 0) { CullDrawStaticDecal((int)c0.r, worldPos, localPos, o, 0, 0); UNITY_BRANCH if (c0.g >= 0) { CullDrawStaticDecal((int)c0.g, worldPos, localPos, o, 0, 0); UNITY_BRANCH if (c0.b >= 0) { CullDrawStaticDecal((int)c0.b, worldPos, localPos, o, 0, 0); UNITY_BRANCH if (c0.a >= 0) { CullDrawStaticDecal((int)c0.a, worldPos, localPos, o, 0, 0); } } } } #endif #if !_DECAL_MAX0 // dynamic int count = _MSDecalCount; if (count > _DECALMAX) count = _DECALMAX; [loop] for (int i = 0; i < count; i++) { RoughCullDynamicDecalTess(i, worldPos, localPos, o); } #endif //!_DECAL_MAX0 return o; } #endif // mode is encoded in sign float BlendDecalNormal(half2 src, inout half2 dest, float opacity) { if (opacity < 0) { dest = BlendNormal2(src, dest); } half alpha = abs(opacity)-1; return alpha; } void SampleDecalTexLayer(DecalLayer l, inout half4 albedo, inout half4 normalSAO, inout half3 surf, inout half4 emisMetal) { int texIndex = l.uv.z; int decalIndex = l.decalIndex; float4 data1; float4 data2; fixed3 tint = fixed3(1,1,1); UNITY_BRANCH if (l.dynamic) { data1 = GetDecalDynamicData(decalIndex, DECALDATA1); data2 = GetDecalDynamicData(decalIndex, DECALDATA2); #if _DECAL_TINT tint = GetDecalDynamicData(decalIndex, DECALTINT); #endif } else { data1 = GetDecalStaticData(decalIndex, DECALDATA1); data2 = GetDecalStaticData(decalIndex, DECALDATA2); #if _DECAL_TINT tint = GetDecalStaticData(decalIndex, DECALTINT); #endif } float albedoOpacity = abs(data2.x) - 1; float normalOpacity = data2.y; float smoothnessOpacity = data2.z; float heightBlend = data2.w; half4 dalbedo = SAMPLE_TEXTURE2D_GRAD(_DecalAlbedo, sampler_Diffuse, l.uv, l.dx, l.dy); COUNTSAMPLE half4 dnsao = SAMPLE_TEXTURE2D_GRAD(_DecalNormalSAO, sampler_NormalSAO, l.uv, l.dx, l.dy).agrb; COUNTSAMPLE dnsao.xy *= 2; dnsao.xy -= 1; half alpha = dnsao.a; #if _DECAL_TINT dalbedo.rgb *= tint; #endif // reconstruct ao dnsao.a = 1 - (dnsao.x * dnsao.y); half hb = lerp(alpha, HeightBlend(albedo.a, dalbedo.a, alpha, _Contrast), heightBlend); if (data2.x < 0) { dalbedo.rgb = BlendMult2X(albedo.rgb, dalbedo.rgb); } albedo = lerp(albedo, dalbedo, albedoOpacity * hb); #if _SURFACENORMALS half3 surfN = ConvertNormal2ToGradient(dnsao.xy); #endif float alpha0 = BlendDecalNormal(normalSAO.xy, dnsao.xy, normalOpacity); normalSAO.xy = lerp(normalSAO.xy, dnsao.xy, alpha0 * hb); normalSAO.zw = lerp(normalSAO.zw, dnsao.zw, smoothnessOpacity * hb); #if _SURFACENORMALS if (normalOpacity < 0) surf += surfN * alpha0 * hb; else surf = lerp(surf, surfN, alpha * hb); #endif #if _DECAL_EMISMETAL half4 demisMetal = SAMPLE_TEXTURE2D_GRAD(_DecalEmisMetal, sampler_Diffuse, l.uv, l.dx, l.dy); COUNTSAMPLE emisMetal.w = lerp(emisMetal.w, demisMetal.w, heightBlend); emisMetal.rgb += demisMetal.rgb; #endif } void DoDecalBlend(DecalOutput i, inout half4 albedo, inout half4 normalSAO, inout half3 surf, inout half4 emisMetal, float2 uv) { #if !_DECAL_NOTEXTURES UNITY_BRANCH if (i.l0.uv.z >= 0) { SampleDecalTexLayer(i.l0, albedo, normalSAO, surf, emisMetal); #if _DEBUG_DECAL_STATIC albedo.r += 0.5; #endif UNITY_BRANCH if (i.l1.uv.z >= 0) { #if _DEBUG_DECAL_STATIC albedo.g += 0.5; #endif SampleDecalTexLayer(i.l1, albedo, normalSAO, surf, emisMetal); UNITY_BRANCH if (i.l2.uv.z >= 0) { #if _DEBUG_DECAL_STATIC albedo.b += 0.5; #endif SampleDecalTexLayer(i.l2, albedo, normalSAO, surf, emisMetal); UNITY_BRANCH if (i.l3.uv.z >= 0) { SampleDecalTexLayer(i.l3, albedo, normalSAO, surf, emisMetal); } } } } float2 cuv = uv; #endif } #if (_TESSDISTANCE || _TESSEDGE) && _DECAL_TESS void SampleDecalTexLayerTess(DecalLayer l, inout half h0, inout half h1, inout half h2, inout half h3, float mipLevel) { int texIndex = l.uv.z; int decalIndex = l.decalIndex; float4 data1; float4 data2; if (l.dynamic) { data1 = GetDecalDynamicData(decalIndex, DECALDATA1); data2 = GetDecalDynamicData(decalIndex, DECALDATA2); } else { data1 = GetDecalStaticData(decalIndex, DECALDATA1); data2 = GetDecalStaticData(decalIndex, DECALDATA2); } float tessOpacity = data1.w; float heightBlend = data2.w; half4 dalbedo = SAMPLE_TEXTURE2D_ARRAY_LOD(_DecalAlbedo, sampler_Diffuse, l.uv.xy, l.uv.z, mipLevel); half4 dnsao = SAMPLE_TEXTURE2D_ARRAY_LOD(_DecalNormalSAO, sampler_Diffuse, l.uv.xy, l.uv.z, mipLevel).agrb; half alpha = dnsao.a; const float dec = 1.0 / 0.95; float alphaOp = frac(tessOpacity) * dec; float offset = floor(tessOpacity) / 256; half height = (dalbedo.a - 0.5 + offset); half blend = alpha * alphaOp; h0 = lerp(h0, HeightBlend(h0, height, heightBlend, _Contrast), blend); h1 = lerp(h1, HeightBlend(h1, height, heightBlend, _Contrast), blend); h2 = lerp(h2, HeightBlend(h2, height, heightBlend, _Contrast), blend); h3 = lerp(h3, HeightBlend(h3, height, heightBlend, _Contrast), blend); } void DoDecalBlendTess(DecalOutput i, inout half h0, inout half h1, inout half h2, inout half h3, float mipLevel) { #if !_DECAL_NOTEXTURES && _DECAL_TESS UNITY_BRANCH if (i.l0.uv.z >= 0) { SampleDecalTexLayerTess(i.l0, h0, h1, h2, h3, mipLevel); UNITY_BRANCH if (i.l1.uv.z >= 0) { SampleDecalTexLayerTess(i.l1, h0, h1, h2, h3, mipLevel); UNITY_BRANCH if (i.l2.uv.z >= 0) { SampleDecalTexLayerTess(i.l2, h0, h1, h2, h3, mipLevel); UNITY_BRANCH if (i.l3.uv.z >= 0) { SampleDecalTexLayerTess(i.l3, h0, h1, h2, h3, mipLevel); } } } } #endif } #endif