Skip to content

Commit e139982

Browse files
Pospow and SG triplanar fix (#144)
Co-authored-by: slunity <[email protected]>
1 parent 93c2813 commit e139982

File tree

4 files changed

+59
-3
lines changed

4 files changed

+59
-3
lines changed

com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,51 @@ uint FastLog2(uint x)
499499
// Note: https://msdn.microsoft.com/en-us/library/windows/desktop/bb509636(v=vs.85).aspx pow(0, >0) == 0
500500
TEMPLATE_2_REAL(PositivePow, base, power, return pow(abs(base), power))
501501

502+
// SafePositivePow: Same as pow(x,y) but considers x always positive and never exactly 0 such that
503+
// SafePositivePow(0,y) will numerically converge to 1 as y -> 0, including SafePositivePow(0,0) returning 1.
504+
//
505+
// First, like PositivePow, SafePositivePow removes this warning for when you know the x value is positive or 0 and you know
506+
// you avoid a NaN:
507+
// ie you know that x == 0 and y > 0, such that pow(x,y) == pow(0, >0) == 0
508+
// SafePositivePow(0, y) will however return close to 1 as y -> 0, see below.
509+
//
510+
// Also, pow(x,y) is most probably approximated as exp2(log2(x) * y), so pow(0,0) will give exp2(-inf * 0) == exp2(NaN) == NaN.
511+
//
512+
// SafePositivePow avoids NaN in allowing SafePositivePow(x,y) where (x,y) == (0,y) for any y including 0 by clamping x to a
513+
// minimum of FLT_EPS. The consequences are:
514+
//
515+
// -As a replacement for pow(0,y) where y >= 1, the result of SafePositivePow(x,y) should be close enough to 0.
516+
// -For cases where we substitute for pow(0,y) where 0 < y < 1, SafePositivePow(x,y) will quickly reach 1 as y -> 0, while
517+
// normally pow(0,y) would give 0 instead of 1 for all 0 < y.
518+
// eg: if we #define FLT_EPS 5.960464478e-8 (for fp32),
519+
// SafePositivePow(0, 0.1) = 0.1894646
520+
// SafePositivePow(0, 0.01) = 0.8467453
521+
// SafePositivePow(0, 0.001) = 0.9835021
522+
//
523+
// Depending on the intended usage of pow(), this difference in behavior might be a moot point since:
524+
// 1) by leaving "y" free to get to 0, we get a NaNs
525+
// 2) the behavior of SafePositivePow() has more continuity when both x and y get closer together to 0, since
526+
// when x is assured to be positive non-zero, pow(x,x) -> 1 as x -> 0.
527+
//
528+
// TL;DR: SafePositivePow(x,y) avoids NaN and is safe for positive (x,y) including (x,y) == (0,0),
529+
// but SafePositivePow(0, y) will return close to 1 as y -> 0, instead of 0, so watch out
530+
// for behavior depending on pow(0, y) giving always 0, especially for 0 < y < 1.
531+
//
532+
// Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/bb509636(v=vs.85).aspx
533+
TEMPLATE_2_REAL(SafePositivePow, base, power, return pow(max(abs(base), real(REAL_EPS)), power))
534+
535+
// Helpers for making shadergraph functions consider precision spec through the same $precision token used for variable types
536+
TEMPLATE_2_FLT(SafePositivePow_float, base, power, return pow(max(abs(base), float(FLT_EPS)), power))
537+
TEMPLATE_2_HALF(SafePositivePow_half, base, power, return pow(max(abs(base), half(HALF_EPS)), power))
538+
539+
float Eps_float() { return FLT_EPS; }
540+
float Min_float() { return FLT_MIN; }
541+
float Max_float() { return FLT_MAX; }
542+
half Eps_half() { return HALF_EPS; }
543+
half Min_half() { return HALF_MIN; }
544+
half Max_half() { return HALF_MAX; }
545+
546+
502547
// Composes a floating point value with the magnitude of 'x' and the sign of 's'.
503548
// See the comment about FastSign() below.
504549
float CopySign(float x, float s, bool ignoreNegZero = true)

com.unity.shadergraph/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2020
- Optimized loading a large Shader Graph. [1209047](https://issuetracker.unity3d.com/issues/shader-graph-unresponsive-editor-when-using-large-graphs)
2121
- New deleted asset dialogue fixes a bug where deleted assets would throw a missing file exception in the console. [1232246](https://issuetracker.unity3d.com/product/unity/issues/guid/1232246/)
2222
- Fixed a bug where `Scene Depth` nodes would stop working after adding a keyword on the blackboard. [1203333](https://issuetracker.unity3d.com/product/unity/issues/guid/1203333/)
23+
- Fixed NaN issue in triplanar SG node when blend goes to 0.
2324

2425
## [8.0.1] - 2020-05-25
2526

com.unity.shadergraph/Documentation~/Triplanar-Node.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Description
44

5-
Triplanar is a method of generating UVs and sampling a texture by projecting in world space. The input **Texture** is sampled 3 times, once in each of the world x, y and z axises, and the resulting information is planar projected onto the model, blended by the normal, or surface angle. The generated UVs can be scaled with the input **Tile** and the final blending strength can be controlled with the input **Blend**. The projection can be modified by overriding the inputs **Position** and **Normal**. This is commonly used to texture large models such as terrain, where hand authoring UV coordinates would be problematic or not performant.
5+
Triplanar is a method of generating UVs and sampling a texture by projecting in world space. The input **Texture** is sampled 3 times, once in each of the world x, y and z axes, and the resulting information is planar projected onto the model, blended by the normal, or surface angle. The generated UVs can be scaled with the input **Tile** and the final blending strength can be controlled with the input **Blend**. **Blend** controls the way the normal affects the blending of each plane sample and should be greater or equal to 0. The larger **blend** is, the more contribution will be given to the sample from the plane towards which the normal is most oriented. (The maximum blend exponent is between 17 and 158 depending on platform and the precision of the node.) A blend of 0 makes each plane get equal weight regardless of normal orientation. The projection can be modified by overriding the inputs **Position** and **Normal**. This is commonly used to texture large models such as terrain, where hand authoring UV coordinates would be problematic or not performant.
66

77
The expected type of the input **Texture** can be switched with the dropdown **Type**. If set to **Normal** the normals will be converted into world space so new tangents can be constructed then converted back to tangent space before output.
88

com.unity.shadergraph/Editor/Data/Nodes/UV/TriplanarNode.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ public virtual void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode gene
9292
// Whiteout blend method
9393
// https://medium.com/@bgolus/normal-mapping-for-a-triplanar-shader-10bf39dca05a
9494
case TextureType.Normal:
95-
sb.AppendLine("$precision3 {0}_Blend = max(pow(abs({1}), {2}), 0);"
95+
// See comment for default case.
96+
sb.AppendLine("$precision3 {0}_Blend = SafePositivePow_$precision({1}, min({2}, floor(log2(Min_$precision())/log2(1/sqrt(3)))) );"
9697
, GetVariableNameForNode()
9798
, GetSlotValue(NormalInputId, generationMode)
9899
, GetSlotValue(BlendInputId, generationMode));
@@ -134,7 +135,16 @@ public virtual void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode gene
134135
, GetVariableNameForNode());
135136
break;
136137
default:
137-
sb.AppendLine("$precision3 {0}_Blend = pow(abs({1}), {2});"
138+
// We want the sum of the 3 blend weights (by which we normalize them) to be > 0.
139+
// Max safe exponent is log2(REAL_MIN)/log2(1/sqrt(3)):
140+
// Take the set of all possible normalized vectors, make a set from selecting the maximum component of each 3-vectors from the previous set,
141+
// the minimum (:= min_of_max) of that new set is 1/sqrt(3) (by the fact vectors are normalized).
142+
// We then want a maximum exponent such that
143+
// precision_min < min_of_max^exponent_max
144+
// where exponent_max is blend,
145+
// log(precision_min) < log(min_of_max) * exponent_max
146+
// log(precision_min) / log(min_of_max) > exponent_max
147+
sb.AppendLine("$precision3 {0}_Blend = SafePositivePow_$precision({1}, min({2}, floor(log2(Min_$precision())/log2(1/sqrt(3)))) );"
138148
, GetVariableNameForNode()
139149
, GetSlotValue(NormalInputId, generationMode)
140150
, GetSlotValue(BlendInputId, generationMode));

0 commit comments

Comments
 (0)