TEXTURE2D(_ProcTexCurves); TEXTURE2D(_ProcTexParams); TEXTURE2D(_ProcTexNoise); TEXTURE2D(_ProcTexBiomeMask); TEXTURE2D(_ProcTexBiomeMask2); TEXTURE2D(_CavityMap); // g = cavity, a = erosion float ComputeMipLevel(float2 dx, float2 dy, float4 textureSize) { float2 dx_vtc = dx * textureSize.zw; float2 dy_vtc = dy * textureSize.zw; float delta_max_sqr = max(dot(dx_vtc, dx_vtc), dot(dy_vtc, dy_vtc)); return 0.5 * log2(delta_max_sqr); } // ONLY use LOD samplers is here, compute lod from ddx half PCFilter(int index, float height, float slope, float cavity, float erosion, float3 worldPos, float2 uv, half4 bMask, half4 bMask2, out int texIndex, half3 pN, float2 dx, float2 dy, float3 wdx, float3 wdy) { // params0 are rgba (noise scale, min, max, offset) // params1 are rg (weight, index) float2 noiseUV = uv; float offset = 1.0 / 32.0; float halfOff = 1.0 / 64.0; float y = (index * offset) + halfOff; half h0 = SAMPLE_TEXTURE2D_LOD(_ProcTexCurves, shared_linear_clamp_sampler, float2(height, y), 0).r; half s0 = SAMPLE_TEXTURE2D_LOD(_ProcTexCurves, shared_linear_clamp_sampler, float2(slope, y), 0).g; half c0 = 1; half e0 = 1; #if _PCCAVITY || _PCUSECOMBINEDAO c0 = SAMPLE_TEXTURE2D_LOD(_ProcTexCurves, shared_linear_clamp_sampler, float2(cavity, y), 0).b; #endif #if _PCFLOW e0 = SAMPLE_TEXTURE2D_LOD(_ProcTexCurves, shared_linear_clamp_sampler, float2(erosion, y), 0).a; #endif // 4 texels, so 1/4 spacing + 1/2 spacing in X half4 params0 = SAMPLE_TEXTURE2D_LOD(_ProcTexParams, shared_linear_clamp_sampler, float2(0.125, y), 0); half4 params1 = SAMPLE_TEXTURE2D_LOD(_ProcTexParams, shared_linear_clamp_sampler, float2(0.375, y), 0); half4 params2 = half4(1,1,1,1); half4 params3 = half4(1,1,1,1); #if _PCBIOMEMASK || _PCBIOMEMASK16 params2 = SAMPLE_TEXTURE2D_LOD(_ProcTexParams, shared_linear_clamp_sampler, float2(0.625, y), 0); bMask = smoothstep(0.5 - _ProcBiomeCurveWeight, 0.5 + _ProcBiomeCurveWeight, bMask); #endif #if _PCBIOMEMASK2 params3 = SAMPLE_TEXTURE2D_LOD(_ProcTexParams, shared_linear_clamp_sampler, float2(0.875, y), 0); bMask2 = smoothstep(0.5 - _ProcBiomeCurveWeight, 0.5 + _ProcBiomeCurveWeight, bMask2); #endif half4 noise = 0; UNITY_BRANCH if (abs(params0.y - params0.z) > 0) { #if _PCNOISEPROCEDURAL noise = FBM3D(worldPos * 0.002 * params0.r + params0.a); #else // WOW, this is odd. So if we do triplanar or world space, the actual UVs which are set on the config.uv end up 0 // However, if we use those UVs to sample a texture and then multiply the saturate(result+1) (which is basically always 1) // into the noise it works around the bug- which must be something in the compiler. float lod = ComputeMipLevel(dx * params0.r, dy * params0.r, _ProcTexNoise_TexelSize); noise = SAMPLE_TEXTURE2D_LOD(_ProcTexNoise, sampler_Diffuse, float2(noiseUV * params0.r + params0.a), lod); #if _PCNOISETRIPLANAR float lod0 = ComputeMipLevel(wdx.zy * 0.002 * params0.r, wdy.zy * 0.002 * params0.r, _ProcTexNoise_TexelSize); float lod1 = ComputeMipLevel(wdx.xz * 0.002 * params0.r, wdy.xz * 0.002 * params0.r, _ProcTexNoise_TexelSize); float lod2 = ComputeMipLevel(wdx.xy * 0.002 * params0.r, wdy.xy * 0.002 * params0.r, _ProcTexNoise_TexelSize); half4 noise0 = SAMPLE_TEXTURE2D_LOD(_ProcTexNoise, sampler_Diffuse, float2(worldPos.zy * 0.002 * params0.r + params0.a), lod0); half4 noise1 = SAMPLE_TEXTURE2D_LOD(_ProcTexNoise, sampler_Diffuse, float2(worldPos.xz * 0.002 * params0.r + params0.a + 0.31), lod1); half4 noise2 = SAMPLE_TEXTURE2D_LOD(_ProcTexNoise, sampler_Diffuse, float2(worldPos.xy * 0.002 * params0.r + params0.a + 0.71), lod2); noise = saturate(noise + 1) * noise0 * pN.x + noise1 * pN.y + noise2 * pN.z; #elif !_PCNOISEUV float2 pcNoiseUV = float2(worldPos.xz * 0.002 * params0.r + params0.a); float lodX = ComputeMipLevel(wdx.xz * 0.002 * params0.r, wdy.xz * 0.002 * params0.r, _ProcTexNoise_TexelSize); noise = saturate(SAMPLE_TEXTURE2D_LOD(_ProcTexNoise, sampler_Diffuse, pcNoiseUV, lodX)); #endif noise = noise * 2 - 1; #endif // !_PCNOISEPROCEDURAL h0 *= 1.0 + lerp(params0.y, params0.z, noise.r); s0 *= 1.0 + lerp(params0.y, params0.z, noise.g); c0 *= 1.0 + lerp(params0.y, params0.z, noise.b); e0 *= 1.0 + lerp(params0.y, params0.z, noise.a); } half bWeight = 1; half bWeight2 = 0; #if _PCBIOMEMASK bMask *= params2; bWeight = max(max(max(bMask.x, bMask.y), bMask.z), bMask.w); #elif _PCBIOMEMASK16 bMask.x = 1 - abs(bMask.x - params2.x); bMask.y = 1 - abs(bMask.y - params2.y); bMask.z = 1 - abs(bMask.z - params2.z); bMask.w = 1 - abs(bMask.w - params2.w); bWeight = bMask.x * bMask.y * bMask.z * bMask.w; #endif #if _PCBIOMEMASK2 bMask2 *= params3; bWeight2 = max(max(max(bMask2.x, bMask2.y), bMask2.z), bMask2.w); #endif texIndex = params1.g; return saturate(h0 * s0 * c0 * e0 * params1.x * saturate(bWeight + bWeight2)); } void PCProcessLayer(inout half4 weights, inout int4 indexes, inout float totalWeight, int curIdx, float height, float slope, float cavity, float erosion, float3 worldPos, float2 uv, half4 biomeMask, half4 biomeMask2, half3 pN, float2 dx, float2 dy, float3 wdx, float3 wdy) { int texIndex = 0; half w = PCFilter(curIdx, height, slope, cavity, erosion, worldPos, uv, biomeMask, biomeMask2, texIndex, pN, dx, dy, wdx, wdy); w = min(totalWeight, w); totalWeight -= w; // sort if (w > weights.x) { weights.w = weights.z; weights.z = weights.y; weights.y = weights.x; indexes.w = indexes.z; indexes.z = indexes.y; indexes.y = indexes.x; weights.x = w; indexes.x = texIndex; } else if (w > weights.y) { weights.w = weights.z; weights.z = weights.y; indexes.w = indexes.z; indexes.z = indexes.y; weights.y = w; indexes.y = texIndex; } else if (w > weights.z) { weights.w = weights.z; indexes.w = indexes.z; weights.z = w; indexes.z = texIndex; } else if (w > weights.w) { weights.w = w; indexes.w = texIndex; } } void ProceduralSetup(Input i, float3 worldPos, float worldHeight, float3 worldNormal, float3 up, inout half4 weights, float2 uv, inout Config config, float2 dx, float2 dy, float3 wdx, float3 wdy, DecalOutput decalOutput) { #if _PROCEDURALBLENDSPLATS #if !_PCPRESERVEINDEXES // true = 1, so any index being used will work. float brch = (config.uv0.z == _PCShowProceduralIndex) + (config.uv1.z == _PCShowProceduralIndex) + (config.uv2.z == _PCShowProceduralIndex) + (config.uv3.z == _PCShowProceduralIndex); UNITY_BRANCH if (brch == 0) { return; // early exit on places where there is no procedural texturing } #endif Config origConfig = config; half4 origWeights = weights; half4 origIndexes = half4(config.uv0.z, config.uv1.z, config.uv2.z, config.uv3.z); #endif weights = 0; #if defined(SHADER_STAGE_VERTEX) && (_TESSDISTANCE || _TESSEDGE) #if _PCUSECOMBINEDHEIGHT || _PCUSECOMBINEDAO #if _MESHCOMBINEDPACKEDMAP half4 packedMap = SAMPLE_TEXTURE2D_LOD(_StandardPackedMap, sampler_StandardDiffuse, uv, 0); #else half4 packedMap = half4(0,0,0,0); packedMap.b = SAMPLE_TEXTURE2D_LOD(_StandardHeight, sampler_StandardDiffuse, uv, _TessData1.b).g; packedMap.a = SAMPLE_TEXTURE2D_LOD(_StandardOcclusion, sampler_StandardDiffuse, uv, _TessData1.b).g; #endif #endif // height #if _PCUSECOMBINEDHEIGHT worldHeight = packedMap.b; float height = worldHeight; #else float height = saturate((worldHeight - _WorldHeightRange.x) / max(0.1, (_WorldHeightRange.y - _WorldHeightRange.x))); #endif // slope #if _PCUSECOMBINEDNORMAL half3 tangentNormal = UnpackNormal(SAMPLE_TEXTURE2D_LOD(_StandardNormal, sampler_StandardDiffuse, uv, 0)); worldNormal = WorldNormalVector(i, tangentNormal); #endif float slope = 1.0 - saturate(dot(worldNormal, up) * 0.5 + 0.49); // cavity and erosion float cavity = 0.5; float erosion = 0.5; #if _PCFLOW || (_PCCAVITY && !_PCUSECOMBINEDAO) half4 cavityData = SAMPLE_TEXTURE2D_LOD(_CavityMap, sampler_Diffuse, uv, _TessData1.b); cavity = cavityData.g; erosion = cavityData.a; #endif half4 biomeMask = half4(1,1,1,1); half4 biomeMask2 = half4(1,1,1,1); #if _PCBIOMEMASK || _PCBIOMEMASK16 biomeMask = SAMPLE_TEXTURE2D_LOD(_ProcTexBiomeMask, shared_linear_clamp_sampler, uv, 0); #endif #if _PCBIOMEMASK2 biomeMask2 = SAMPLE_TEXTURE2D_LOD(_ProcTexBiomeMask2, shared_linear_clamp_sampler, uv, 0); #endif #else // not vertex #if _PCUSECOMBINEDHEIGHT || _PCUSECOMBINEDAO #if _MESHCOMBINEDPACKEDMAP half4 packedMap = SAMPLE_TEXTURE2D_GRAD(_StandardPackedMap, sampler_StandardDiffuse, uv, dx, dy); #else half4 packedMap = half4(0,0,0,0); packedMap.b = SAMPLE_TEXTURE2D_GRAD(_StandardHeight, sampler_StandardDiffuse, uv, dx, dy).g; packedMap.a = SAMPLE_TEXTURE2D_GRAD(_StandardOcclusion, sampler_StandardDiffuse, uv, dx, dy).g; #endif #endif // height #if _PCUSECOMBINEDHEIGHT worldHeight = packedMap.b; float height = worldHeight; #else float height = saturate((worldHeight - _WorldHeightRange.x) / max(0.1, (_WorldHeightRange.y - _WorldHeightRange.x))); #endif // slope #if _PCUSECOMBINEDNORMAL half3 tangentNormal = UnpackNormal(SAMPLE_TEXTURE2D_GRAD(_StandardNormal, sampler_StandardDiffuse, uv, dx, dy)); worldNormal = WorldNormalVector(i, tangentNormal); #endif float slope = 1.0 - saturate(dot(worldNormal, up) * 0.5 + 0.49); // cavity and erosion float cavity = 0.5; float erosion = 0.5; #if _PCFLOW || (_PCCAVITY && !_PCUSECOMBINEDAO) half4 cavityData = SAMPLE_TEXTURE2D_GRAD(_CavityMap, shared_linear_clamp_sampler, uv, dx, dy); cavity = cavityData.g; erosion = cavityData.a; #endif half4 biomeMask = half4(1,1,1,1); half4 biomeMask2 = half4(1,1,1,1); #if _PCBIOMEMASK || _PCBIOMEMASK16 biomeMask = SAMPLE_TEXTURE2D_GRAD(_ProcTexBiomeMask, shared_linear_clamp_sampler, uv, dx, dy); #endif #if _PCBIOMEMASK2 biomeMask2 = SAMPLE_TEXTURE2D_GRAD(_ProcTexBiomeMask2, shared_linear_clamp_sampler, uv, dx, dy); #endif #endif #if _PCUSECOMBINEDAO cavity = packedMap.a * packedMap.a * packedMap.a; #endif // find 4 highest weights and indexes int4 indexes = int4(0, 1, 2, 3); float totalWeight = 1.0; half3 pN = 0; #if _PCNOISETRIPLANAR pN = pow(abs(worldNormal), 4); pN = pN / (pN.x + pN.y + pN.z); #endif int j = 0; for (j = 0; j < _PCLayerCount; ++j) { COUNTPROCLAYER PCProcessLayer(weights, indexes, totalWeight, j, height, slope, cavity, erosion, worldPos, uv, biomeMask, biomeMask2, pN, dx, dy, wdx, wdy); UNITY_BRANCH if (totalWeight <= 0) { break; } } #if _MAX2LAYER weights.zw = 0; weights.xy *= (1.0 / (weights.x + weights.y)); #elif _MAX3LAYER weights.w = 0; weights.xyz *= (1.0 / (weights.x + weights.y + weights.z)); #else weights *= (1.0 / (weights.x + weights.y + weights.z + weights.w)); #endif // when blending is enabled, we need to resort the weights/indexes between a list of 8 #if _PROCEDURALBLENDSPLATS #if _PCPRESERVEINDEXES if (origIndexes.x == _PCPreserveIndexes.x || origIndexes.x == _PCPreserveIndexes.y || origIndexes.x == _PCPreserveIndexes.z || origIndexes.x == _PCPreserveIndexes.w) { weights *= 1.0 - origWeights.x; } else { origWeights.x = 0; } if (origIndexes.y == _PCPreserveIndexes.x || origIndexes.y == _PCPreserveIndexes.y || origIndexes.y == _PCPreserveIndexes.z || origIndexes.y == _PCPreserveIndexes.w) { weights *= 1.0 - origWeights.y; } else { origWeights.y = 0; } if (origIndexes.z == _PCPreserveIndexes.x || origIndexes.z == _PCPreserveIndexes.y || origIndexes.z == _PCPreserveIndexes.z || origIndexes.z == _PCPreserveIndexes.w) { weights *= 1.0 - origWeights.z; } else { origWeights.z = 0; } if (origIndexes.w == _PCPreserveIndexes.x || origIndexes.w == _PCPreserveIndexes.y || origIndexes.w == _PCPreserveIndexes.z || origIndexes.w == _PCPreserveIndexes.w) { weights *= 1.0 - origWeights.w; } else { origWeights.w = 0; } #else // first, adjust procedural weights by splat weights if (origIndexes.x == _PCShowProceduralIndex) { weights *= origWeights.x; origWeights *= 1 - origWeights.x; origWeights.x = 0; } else if (origIndexes.y == _PCShowProceduralIndex) { weights *= origWeights.y; origWeights *= 1 - origWeights.y; origWeights.y = 0; } else if (origIndexes.z == _PCShowProceduralIndex) { weights *= origWeights.z; origWeights *= 1 - origWeights.z; origWeights.z = 0; } else if (origIndexes.w == _PCShowProceduralIndex) { weights *= origWeights.w; origWeights *= 1 - origWeights.w; origWeights.w = 0; } #endif // now merge and resort fixed splats[8]; fixed totalIndexes[8]; splats[0] = origWeights.x; splats[1] = origWeights.y; splats[2] = origWeights.z; splats[3] = origWeights.w; splats[4] = weights.x; splats[5] = weights.y; splats[6] = weights.z; splats[7] = weights.w; totalIndexes[0] = origIndexes.x; totalIndexes[1] = origIndexes.y; totalIndexes[2] = origIndexes.z; totalIndexes[3] = origIndexes.w; totalIndexes[4] = indexes.x; totalIndexes[5] = indexes.y; totalIndexes[6] = indexes.z; totalIndexes[7] = indexes.w; half4 mergeIndexes = 0; half4 mergeWeights = 0; j = 0; for (j = 0; j < 8; ++j) { fixed w = splats[j]; if (w >= mergeWeights[0]) { mergeWeights[3] = mergeWeights[2]; mergeIndexes[3] = mergeIndexes[2]; mergeWeights[2] = mergeWeights[1]; mergeIndexes[2] = mergeIndexes[1]; mergeWeights[1] = mergeWeights[0]; mergeIndexes[1] = mergeIndexes[0]; mergeWeights[0] = w; mergeIndexes[0] = totalIndexes[j]; } else if (w >= mergeWeights[1]) { mergeWeights[3] = mergeWeights[2]; mergeIndexes[3] = mergeIndexes[2]; mergeWeights[2] = mergeWeights[1]; mergeIndexes[2] = mergeIndexes[1]; mergeWeights[1] = w; mergeIndexes[1] = totalIndexes[j]; } else if (w >= mergeWeights[2]) { mergeWeights[3] = mergeWeights[2]; mergeIndexes[3] = mergeIndexes[2]; mergeWeights[2] = w; mergeIndexes[2] = totalIndexes[j]; } else if (w >= mergeWeights[3]) { mergeWeights[3] = w; mergeIndexes[3] = totalIndexes[j]; } } // clamp and renormalize #if _MAX2LAYER mergeWeights.zw = 0; mergeWeights.xy = TotalOne(mergeWeights.xy); #elif _MAX3LAYER mergeWeights.w = 0; mergeWeights.xyz = TotalOne(mergeWeights.xyz); #else mergeWeights = TotalOne(mergeWeights); #endif weights = mergeWeights; indexes = mergeIndexes; #endif #if _DECAL_SPLAT DoMergeDecalSplats(decalOutput.Weights, decalOutput.Indexes, weights, indexes); #endif #if !_PROCEDURALBLENDSPLATS #if _WORLDUV uv = worldPos.xz; #endif float2 scaledUV = uv * _UVScale.xy + _UVScale.zw; config.uv0 = float3(scaledUV, indexes.x); config.uv1 = float3(scaledUV, indexes.y); config.uv2 = float3(scaledUV, indexes.z); config.uv3 = float3(scaledUV, indexes.w); #else config.uv0.z = indexes.x; config.uv1.z = indexes.y; config.uv2.z = indexes.z; config.uv3.z = indexes.w; #endif } void PCUnpackWeight(int index, int splatIndex, half weight, inout half4 o) { if (index == splatIndex*4) { o.x += weight; } else if (index == splatIndex*4+1) { o.y += weight; } else if (index == splatIndex*4+2) { o.z += weight; } else if (index == splatIndex*4+3) { o.w += weight; } } void DebugPCSplatOutput(inout MicroSplatLayer l, half4 weights, int4 indexes, int splatIndex) { l.Normal = half3(0,0,1); l.Occlusion = 1; l.Smoothness = 0; l.Emission = 0; // indexes (2, 1, 0, 3) // weights (0.8, 0.2, 0, 0) // result (0, .2, 0.8, 0) half4 o = 0; PCUnpackWeight(indexes.x, splatIndex, weights.x, o); PCUnpackWeight(indexes.y, splatIndex, weights.y, o); PCUnpackWeight(indexes.z, splatIndex, weights.z, o); PCUnpackWeight(indexes.w, splatIndex, weights.w, o); l.Albedo = o.xyz; l.Alpha = o.w; // this has to be in here or the windows compiler will throw errors // it will strip the texture, but not the sampler for _Diffuse, and not compile. // On OSX in OpenGL or metal this is all fine. l.Albedo *= saturate(SAMPLE_TEXTURE2D_ARRAY_LOD(_Diffuse, sampler_Diffuse, float2(0,0), 0, 11).r + 2); l.Alpha *= saturate(SAMPLE_TEXTURE2D_ARRAY_LOD(_Diffuse, sampler_Diffuse, float2(0,0), 0, 11).r + 2); l.Albedo *= saturate(SAMPLE_TEXTURE2D_ARRAY_LOD(_NormalSAO, sampler_NormalSAO, float2(0,0), 0, 11).r + 2); l.Alpha *= saturate(SAMPLE_TEXTURE2D_ARRAY_LOD(_NormalSAO, sampler_NormalSAO, float2(0,0), 0, 11).r + 2); } void ProceduralTextureDebugOutput(inout MicroSplatLayer l, half4 weights, Config config) { int4 outIndexes = int4(config.uv0.z, config.uv1.z, config.uv2.z, config.uv3.z); #if _DEBUG_OUTPUT_SPLAT0 DebugPCSplatOutput(l, weights, outIndexes, 0); #elif _DEBUG_OUTPUT_SPLAT1 DebugPCSplatOutput(l, weights, outIndexes, 1); #elif _DEBUG_OUTPUT_SPLAT2 DebugPCSplatOutput(l, weights, outIndexes, 2); #elif _DEBUG_OUTPUT_SPLAT3 DebugPCSplatOutput(l, weights, outIndexes, 3); #elif _DEBUG_OUTPUT_SPLAT4 DebugPCSplatOutput(l, weights, outIndexes, 4); #elif _DEBUG_OUTPUT_SPLAT5 DebugPCSplatOutput(l, weights, outIndexes, 5); #elif _DEBUG_OUTPUT_SPLAT6 DebugPCSplatOutput(l, weights, outIndexes, 6); #elif _DEBUG_OUTPUT_SPLAT7 DebugPCSplatOutput(l, weights, outIndexes, 7); #elif _DEBUG_OUTPUT_SPLAT0A DebugPCSplatOutput(l, weights, outIndexes, 0); l.Albedo = l.Alpha; #elif _DEBUG_OUTPUT_SPLAT1A DebugPCSplatOutput(l, weights, outIndexes, 1); l.Albedo = l.Alpha; #elif _DEBUG_OUTPUT_SPLAT2A DebugPCSplatOutput(l, weights, outIndexes, 2); l.Albedo = l.Alpha; #elif _DEBUG_OUTPUT_SPLAT3A DebugPCSplatOutput(l, weights, outIndexes, 3); l.Albedo = l.Alpha; #elif _DEBUG_OUTPUT_SPLAT4A DebugPCSplatOutput(l, weights, outIndexes, 4); l.Albedo = l.Alpha; #elif _DEBUG_OUTPUT_SPLAT5A DebugPCSplatOutput(l, weights, outIndexes, 5); l.Albedo = l.Alpha; #elif _DEBUG_OUTPUT_SPLAT6A DebugPCSplatOutput(l, weights, outIndexes, 6); l.Albedo = l.Alpha; #elif _DEBUG_OUTPUT_SPLAT7A DebugPCSplatOutput(l, weights, outIndexes, 7); l.Albedo = l.Alpha; #endif }