Very weird baffling bug (?)

Hello!

I have been refactoring code that use custom cubemap stored as octohedron map in an atlas, it use to work, but now I have weird behavior I don’t if that’s normal or not.

Basically I hash the position of the mesh data to select the appropriate cubemap, simple in theory, and it use to work flawlessly.

Now in the refactoring, instead of using a prefab in the scene, I instance the mesh by code, position it, apply the material. The shader itself now render to a texture, which is then applied to the mesh.

But after troubleshooting, nothing make sense at all. It seems that the world position, which is code untouched, doesn’t behave properly and break everything down.

in the old code rendering position gave:

define as

           struct d
            {
                float4 vertex    : POSITION;
                float2 uv        : TEXCOORD1;
                fixed4 color    : COLOR;
                fixed3 normal   : NORMAL;
            };
            struct v2f
            {
                float4 vertex    : POSITION;
                float4 wpos     : TEXCOORD1;
                fixed4 color    : COLOR;
                fixed3 wnormals : NORMAL;
            };

in both code

old vertex code do

v2f vert (d v)
            {
                v2f o;
          
                //vertex world position
                o.wpos = mul(unity_ObjectToWorld, v.vertex);
                //vertex screen position
                o.vertex = UnityObjectToClipPos(v.vertex);
                //normal to world normal
                o.wnormals =UnityObjectToWorldNormal(v.normal);
                //o.vertex = UnWrapToScreenSpace(float2 v.uv, float4 v.vertex);
                o.color = float4(v.uv, 0,1);// v.color;
                return o;
            }

New code vertex shader do

 rasterData vertexProgram (meshData input)
            {
                rasterData output;
               
                output.wpos = mul(unity_ObjectToWorld, input.vertex);    //world position
                // output.wpos     = mul ( _PosMat, input.vertex );//world position

                output.vertex = unwrap(input.uv, input.vertex.w);     //screen position
                //output.vertex = UnityObjectToClipPos(input.vertex);      //screen position
                output.wnormals =UnityObjectToWorldNormal(input.normal); //normal to world normal

                output.color = float4(input.uv, 0,1);// v.color;
                return output;
            }

The main difference is that I ouput to rendertexture in the new code, as such vertex position is using UV space, debug show it work flawlessly.

Fragment is virtually unchanged, but it’s where everything seems to break:

fixed4 fragmentProcessing (rasterData input) : COLOR
            {
                //set size
                const float size    = 4;
                const float2 cuberange = float2(16,16);

                float  epsilon      = 0.000001;
                float3 origin       = _Origin.xyz;
                float3 worldnorm    = normalize(input.wnormals) + epsilon;
                float3 pos          = input.wpos.xyz - origin;// + 0.0001;
              
                //hash position to read the right cubemap in the atlas
                // float3 hashpos      = floor(pos / size);
                float3 hashpos      = floor(input.wpos.xyz / size);
                float3 hash_offset  = hashpos * size;
                float2 hash_id      = max(float2(0,0), min(hashpos.xz, cuberange));

                //box projection
                float3 cubecenter   = hash_offset + (size / 2);
                float3 mincube      = hash_offset + 0;
                float3 maxcube      = hash_offset + size;
                float3 projected    = BoxProjectVector(pos, worldnorm, cubecenter, mincube, maxcube);
               
                //sampling the atlas
                float2 octnormal    = (PackNormalToOct(projected) + 1) / 2;
                float2 samplepos    = (octnormal + hash_id) / cuberange;

                //cubemap result
                float4 cubesample   = tex2D     ( _Atlas, samplepos );
                cubesample   = tex2D     ( _Atlas, input.color.xy );


                //light
                float3 light        = normalize(_MainLight);
                float  ndotl        = saturate(dot(light.xyz, worldnorm));
                // float  skyocclusion = saturate(dot(float3(0,1,0), worldnorm));//should be wrap lighting
                float  skyocclusion = wrappedTo1(worldnorm,float3(0,1,0));
                //skyocclusion *= skyocclusion;

                //shadow sampling, box projected and direct
                float3 lightproj    = BoxProjectVector(pos, light, cubecenter, mincube, maxcube);
                float2 lightbox     = (PackNormalToOct(lightproj) + 1) / 2;
                float2 shadowbox    = (lightbox + hash_id) / cuberange;
               
                float2 lightdirect  = (PackNormalToOct(light) + 1) / 2;
                float2 shadowdirect = (lightdirect + hash_id) / cuberange;
               


                float4 boxshadow    = tex2Dlod( _Atlas, float4(shadowbox,0,7));//tex2D(_MainTex, shadowtest);
                float4 boxshadow2   = tex2Dlod( _Atlas, float4(shadowbox,0,0));//tex2D(_MainTex, shadowtest);
                float4 directlight  = tex2Dlod( _Atlas, float4(shadowdirect,0,4));
                float4 occlufactor  = tex2Dlod( _Atlas, float4(shadowdirect,0,7));
                float4 occlusion    = occlufactor.b * (skyocclusion + 1.0);


                // return fixed4(1,0,0,1);
                // return fixed4(skyocclusion,0,0,1);
                // return fixed4(ndotl,0,0,1);
                // return fixed4(lightproj,1);
                // return fixed4(hashpos*64,1);
                //return fixed4(frac(pos/4),1);
                return float4(pos,1);
                //return fixed4(lightproj,1);
                //return fixed4(shadowdirect*64,0,1);
                //return fixed4(lightdirect,0,1);
                //return fixed4(lightbox,0,1);
                //return fixed4(floor(pos*8.0)/32,1);
                //return fixed4(origin*64,1);
                //return fixed4(hash_offset,1);
                //return fixed4(hash_id*64,0,1);
                //return fixed4(light,1);
                //return (input.color);
                //float4 nt = 0.8;return frac(nt);
                // return cubesample;
                // return boxshadow2;
                //return directlight;
                //return occlufactor;
                // return occlusion;
            }

Outputing the pos no longer give me the same result

Which doesn’t make any sense.
Trying to output visual values also show that after floor() it return only zeroes (black texture even after boosting), the haspos variable being bogus and contaminating everything down.

If I had a single modification (like just a space character) while play in running, it reload the shader and show the expected data.

BUT it lose the binding to the atlas texture

Hypothesis: Maybe unity only initialize unity_ObjectToWorld just before rendering, the texture generation code is called within start.

    void Start(){
    // void Startup(){
        foreach (var scene in scenes){
           
            scene.root = Instantiate(DebugRoot,
                new Vector3(32,0,32),
                //Vector3.zero,
                //Quaternion.Euler(0,0,0)
                Quaternion.Euler(-90,0,0)
                //Quaternion.identity
            );
           
            scene.init(setLight(), shaders);
       }
    }
// int s = 0;
    void Update(){
        // if (s != 2){Startup();s+=1;}
        // Debug.Log(s);


        foreach (var scene in scenes){
            updateLight();
            scene.updateLight();//TODO: when light change, refresh
            scene.updateGI();
        }
    }

BUT if I rename Start into startup and delay calling it inside update, it never shift to the expected data.

Another idea was to bypass unity_ObjectToWorld and pass my own matrix by code:

        //debug world matrix in shader
        positionMatrix.SetTRS(
            new Vector3(0.5f,.0f,0),        //position
            Quaternion.Euler(
                0,
                0,
                0
            ),                        //rotation
            new Vector3(4,4,4)        //scale
        );
        directPass.SetMatrix("_PosMat", positionMatrix);
        //end debug

(code above is the last test, original was passing basic matrix)

I get the same result with bogus wpos, and tweaking the matrix never give me the same data than the expected one …

I thought it was issue with : TEXCOORD1 instead of : POSITION, but original code was already passing texcoord and worked correctly.

I’m kinda at loss as to what’s happening, analyse of the code data flow show that problem arise before the rendering to texture and unrelated to the the unwrapping and the rendertexture.

But I’m a loss as to what’s happening.

This is urgent matter, help!

That’s a lot of text:

Basically

floor(pos/size) return black for every input

with:

  • pos ranging from 0 to 64
  • size being set to 4
    basically smooth data should appear as discrete with step of 4 … it doesnt,

With unity 2020.3.23f1personal on built in render

smells like a bug, trying to port the project to an earlier version to see.

Hypothesis:
The compiler might have replace the duplication of wpos data in the original case since the wpos and the actual vertex position are the same, but diverge now that it unwrap to a texture

Oh wait no! I’m telling bullcrap, let me clean my mess. Both output wpos as a separate variable and the world position is lost to the fragment because it’s a clip space transformation, so there is no duplicate, for all intent and purpose it should be the same result, but it’s not …

HOWEVER some observation:

First, the wpos color output is black by one quarter, that would suggest it’s going into negative territory, which is clamped later, that’s already losing one quarter of data.

Second, it seems that the position data are below 1 as value, which is why we can see gradient, else it would have saturated to red, green and yellow. So the fact you floor it mean it would go to zero no matter what. It tells me the data probably not to scale to the scene, for whatever reason.

In the scene view we can see the mesh is rather big and roughly 100 times the size of the data, and it’s starting at 0, not being centered on 0.

That tells me you have two problems, scale and centering, and thus all the computations are wrong on the basis of faulty expectation.

After fixing the centering and the scale issues, the result are still bad even though everything down the code should have correct data, in fact end result is the same no matter what the positional data and hash data is. I’m going crazy. Still doesn’t make sense. I consulted Freya Holmer’s discord and they are as scared as me, what’s going on?