Center UV co-ordinates, GLSL > CG/HLSL

Hi,

Been messing around with shaders for a little while now, trying to understand the language and getting used to different errors.

I have successfully ported a few simple shaderToy shaders into Unity, but I have a problem in centering the UV co-ordinates, the results I get are usually a quarter of the shader. 2928799--216505--Screen Shot 2017-01-20 at 21.11.55.png

float time = _Time.y * 1.;                                                // adjust time
            //float2 fragCoord =
            //float2 p = (-_ScreenParams.xy + 2.0*fragCoord)/_ScreenParams.x;        // center coordinates
            i.uv.y = 1-i.uv.y;
            float2 p = i.uv;

I think it’s because I’m just feeding i.uv, and not calculating the double and center bit.

Here’s the whole code to a 1/4 of a shader, it’s all good if you’ve got four quads, but not the best solution!

If anyone could point me in the right direction to help center this shader, I would be very appreciative!

Shader "Paintings/Base"{
    Properties{
    _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader{

        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0
            #include "UnityCG.cginc"

            struct appdata{
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f{
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 screenCoord : TEXCOORD1;
            };

            v2f vert (appdata v){
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                o.screenCoord.xy = ComputeScreenPos(o.vertex);
                return o;
            }

            //sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target
            {
            float time = _Time.y * 1.;                                                // adjust time
            //float2 fragCoord =
            //float2 p = (-_ScreenParams.xy + 2.0*fragCoord)/_ScreenParams.x;        // center coordinates
            i.uv.y = 1-i.uv.y;
            float2 p = i.uv;
            ////////////////////////////////////
            // deform technique from iq: https://www.shadertoy.com/view/Xdf3Rn
            float r2 = dot(p,p);
               float r = sqrt(r2);
               float2 uv = p/r2;   
            // animate   
            uv += 10.0 * cos( float2(0.6,0.3) + float2(0.1,0.13) * 2. * sin(time) );
            // uv = p; // switch back to normal coords to test drawing
            ////////////////////////////////////
   
            // custom drawing
            uv += float2(0., 2. * cos(uv.y * 8.));                                    // warp coordinates a little more
            uv = abs(sin(uv * 0.3));                                                // draw horizontal stripes
            float color = smoothstep(0.2, 0.8, abs(sin(time + uv.y * 3.)));
            color = min(color, smoothstep(0.1, 0.95, abs(sin(time + uv.y * 4.))) );
            color += 0.75;                                                            // brighten everything
            float3 col = float3(                                                    // oscillate color components
                0.6 + 0.1 * cos(time + color * 1.),
                0.5 * color,
                0.9 + 0.2 * sin(time + color * 1.1)
            );
            // reverse vignette
            col *= r*1.5 * color;
               col += pow(length(p)/2., 2.);
            return float4( col, 1.0 );
   
            }
            ENDCG
        }
    }
}

and the original shader:

1 Like
float2 p = (2 * i.uv) - 1 / 1;

Don’t understand it, but seems to work. Took out reference to screen positions, doubled and divided, by a number that’s the same to get the middle.

This shader still doesn’t like me with that trick, by the time the kaliedoscope rotates around, the picture goes out of sync, looks like the shader is manipulating the wrong parts of the picture.

Shader "Paintings/FreeBase2"{
    //Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    Properties
    {
    //_MainTex ("Texture", 2D) = "white" {}
    _Color ("Color", Color) = (1,1,1,1)
    _Blend ("Blend", Range (0, 1) ) = 0
    //_MainTex ("Texture 1", 2D) = ""
    _MainTex ("Color (RGB) Alpha (A)", 2D) = "white"
    _Texture2 ("Color (RGB) Alpha (A)", 2D) = "white"
    //_Texture2 ("Texture 2", 2D) = ""
    _SrcBlend ("_SrcBlend", Float) = 1
//    _DstBlend ("_DstBlend", Float) = 0
    }



    SubShader{
        Tags {"RenderType"="Transparent"  "Queue"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        Pass{

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature _ _RENDERING_CUTOUT _RENDERING_FADE
//            Tags {
//                "LightMode" = "ForwardBase"
//            }
            //Blend [_SrcBlend] [_DstBlend]


           
            #include "UnityCG.cginc"


            struct appdata
            {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float4 screenCoord : TEXCOORD1;
            };

//            struct Input {
//             float2 uv_MainTex;
//             };
             uniform float4 _Color;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.color = v.color;
                //o.Alpha = _Color.a;
                o.uv = v.uv;
                //o.screenCoord.xy = ComputeScreenPos(o.vertex);
                return o;
            }

            sampler2D _MainTex;
            sampler2D _Texture2;
            float _Blend;

//              void surf (Input IN)
//              {
//             fixed4 mainCol = tex2D(_MainTex, IN.uv_MainTex);
//              fixed4 texTwoCol = tex2D(_Texture2, IN.uv_MainTex);
//              fixed4 output = lerp(mainCol, texTwoCol, _Blend);
//              o.Albedo = output.rgb;
//              o.Alpha = output.a;
//             }

           
            fixed4 frag (v2f i) : SV_Target
            {
                const float PI = 3.141592658;
                //const float TAU = 2.0 * PI;
                const float TAU = 2.0 * PI;
                const float sections = 8.0;
                //{
                  //float2 pos = float2(i.screenCoord.xy - 0.5 * _ScreenParams.xy) / _ScreenParams.y;
                  //float2 pos = i.uv;
                  //float2 pos = (2 * i.uv) - 1 / 1;
                  //float2 pos = 2 * i.uv - 1 / float2(1,1);
                  //float2 pos = (2 * i.uv) - 4 / 4;
                  float2 pos = float2(i.uv.xy / 1 -.5)*2.;
                  float rad = length(pos);
                  float angle = atan2(pos.x, pos.y);

                  float ma = fmod(angle, TAU/sections);
                  ma = abs(ma - PI/sections);
 
                  float x = cos(ma) * rad;
                  float y = sin(ma) * rad;
   
                  float time = _Time.y/10.0;
                  fixed4 oricol = tex2D (_MainTex,float2(x+time, y-time)).a;
                   fixed4 col = tex2D (_Texture2,float2(x+time, y-time)).a;
                   float comp = smoothstep( 0.1, 0.9, sin(0.5) );
                   col = lerp(col,oricol, _Blend);
                   //return float4(col);
                  return float4(col) * _Color;
                  //tex2D(_MainTex, float2(x+time, y-time));

            }


            ENDCG
        }
    }
}

2928879--216510--Screen Shot 2017-01-20 at 22.23.25.png

2928912--216514--Screen Shot 2017-01-20 at 22.47.14.png

Different code same skewed effect, that’s still not quite right, is there something wrong with part of the maths?

Shader "ShaderToy/NewShader3"{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader{

        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
           
            #include "UnityCG.cginc"

            struct appdata{
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f{
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 screenCoord : TEXCOORD1;
            };

            v2f vert (appdata v){
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                o.screenCoord.xy = ComputeScreenPos(o.vertex);
                return o;
            }

            sampler2D _MainTex;

       
           
            fixed4 frag (v2f i) : SV_Target
            {

            const float PI = 3.141592658;
            const float TAU = 2.0 * PI;
            const float sections = 10.0;

              float2 pos = (i.uv*2)-2 / 2;

              float rad = length(pos);
              float angle = atan2(pos.y, pos.x);

              float ma = fmod(angle, TAU/sections);
              ma = abs(ma - PI/sections);
 
              float x = cos(ma) * rad;
              float y = sin(ma) * rad;
   
              float time = _Time.y/10.0;
 
              return tex2D(_MainTex, float2(x-time, y-time));
            }
               
            ENDCG
        }
    }
}

The commented out line you have from the original shadertoy shader:
float2 p = (-_ScreenParams.xy + 2.0*fragCoord)/_ScreenParams.x;

In shadertoy the fragCoord is the screen pixel coordinate, so that code converts the pixel coordinate to a -1 to 1 range along the x with a square aspect.

The UVs for a basic quad are from 0.0 to 1.0 in both x and y, so you just need to do this:
float2 p = i.uv * 2.0 - 1.0;

That results in the same -1 to 1 range as the original shader is expecting.

2 Likes

So screenParams is a constant value, fragCoord is the screen pixel coordinate, equivalent to the uv on a plane/quad, I have another bit from a shadertoy, that states screenCoord, would you treat it differently to uv, I tried:

float2 pos = float2(i.screenCoord.xy - 0.5 * _ScreenParams.xy) / _ScreenParams.y;
// becomes
float2 pos = float2(i.uv.xy - 0.5 * 1) / 1;

I’m not sure if it’s that part messing up the kaildoscope effect, I’ve attached the shader, instead of just the code…
2929604--216595--Screen Shot 2017-01-21 at 16.09.10.png

2929604–216598–FreeBase2.shader (2.29 KB)

Note all of these lines are mathematically exactly same:
(2 * uv) - 1 / 1
(uv * 2) - 2 / 2
(uv - 0.5 / 1) * 2

They all result in:
uv * 2 - 1

If the uv range is 0.0 to 1.0 all of those bits of math will change it to -1.0 to 1.0

However this line is different
(uv - 0.5 * 1) / 1

The multiply and divide by one both do nothing, that line is exactly the same as:
uv - 0.5

Which means that 0.0 to 1.0 range is now a -0.5 to 0.5 range. The visual results are fairly similar since 0.0 is now centered, but the visual result is slightly “zoomed in” compared to the above options. This is all kind of basic PEMDAS. If you’re having trouble here I would suggest spending some time refreshing yourself by going through a few Khan Academy courses on basic math, arithmetic, and algebra, because ultimately shaders are about lots and lots of math.

So, again, the original ShaderToy shaders are all getting pixel positions as an input to the function. That means if the area on screen being rendered is 128x80 pixels the fragCoord range will be from 0 to 127 and 0 to 79, and iResolution is vec2(128, 80). In Unity the basic quad’s UVs are from 0.0 to 1.0 for both x and y from one side of the quad to the other. The screen position you get from ComputeScreenPos() is also from 0.0 to 1.0, just from one edge of the area being rendered to the other, which is probably why when you were trying to use _ScreenParams.xy (which you figured out correctly is the equivalent of ShaderToy’s iResolution.xy) you weren’t getting the results you were expecting.

Now as for what’s happening with your kalidescope, I believe this is a subtle HLSL (what Unity uses) vs GLSL (what ShaderToy uses) difference. The fmod and mod functions are not the same! They’re both “modulo” functions, and with positive numbers they produce identical results. The problem is with negative numbers HLSL’s fmod will output a positive number where GLSL’s mod will output a different negative number.

Both are correct, just different. However the GLSL implementation is usually what people actually want. There’s two solutions. One is to implement your own GLSL style “mod” function in your shader, which if you want you can search for online. The other is, in this case at least, add tau to the angle variable before using fmod. The output of atan2 is a -pie to pie range, so adding tau should get the same results out of the sin and cos functions.

1 Like

Yeah I apologise for the scribbles, my maniacal re-stating the exact same thing over and over! I’m in no way an academic, but love the some of the videos about Maths like khan academies on youtube, specifically the Hart’s Vi/George have inspired me in unreasonably large quantities!

Ahh thank you so much, you don’t know how much of a legend you are! That’s quite a subtle difference, I’ve even come across someone stating this exact same thing when searching around, about negative numbers in mod functions giving un-expected results. Thank you so much. I have no idea how you managed to work out that just add Tau bit, I think I may need to get on more of those Khan videos!!

I’m sorry to bother, but I’ve tried writing the GLSL function of mod and am getting unexpected results…

Is this what it is supposed to be:

    float myMod(float x, float y)
        {
              return x - y * floor(x/y);
        }

I’ve tried on a shader, and get this:
2930682--216711--Screen Shot 2017-01-22 at 17.39.40.png

Compared to with fmod:
2930682--216710--Screen Shot 2017-01-22 at 17.40.07.png
Fmod is clearly doing half correct, and then getting messed up with repeating when numbers go in the negative range. But seems myMod is messing it up completely!?

I’m not sure if the end result could be right, but the image is incorrect because I’ve commented out the rotation part, (because it didn’t work) so I might have to change to individual functions, it didn’t seem like such a contributing factor. Anyways here’s the shader:

Shader "Paintings/Base"{
    Properties{
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader{

        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
           
            #include "UnityCG.cginc"

            struct appdata{
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f{
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 screenCoord : TEXCOORD1;
            };

            v2f vert (appdata v){
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                o.screenCoord.xy = ComputeScreenPos(o.vertex);
                return o;
            }

            sampler2D _MainTex;

            float sdBox( float3 p, float3 b )
            {
              float3 d = abs(p) - b;
              return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
            }

        float sdCross(in float3 p)
        {
              float da = sdBox(p.xyz,float3(4.0,0.0,1.0));
              float db = sdBox(p.yzx,float3(1.0,2.0,2.0));
              float dc = sdBox(p.zxy,float3(0.5,1.0,sin(_Time.y*4.0)+2.5));
              return min(da,min(db,dc));
        }

        float realMod(float x, float y) { return x - y*float(int(x)/int(y)); }

        float myMod(float x, float y)
        {
              return x - y * floor(x/y);
        }   

        float opRep(float3 p, float3 c) {
            float3 q = fmod(p,c)-0.5*c;
            return sdCross(q);
        }
//
//            float opRep(float3 p, float3 c) {
//            float3 q = fmod(p,c)-0.5*c;
//            return sdCross(q);
//        }

        float trace(float3 o, float3 r) {
            float t = 0.0;
            for (int i = 0; i < 32; i++) {
                float3 p = o + r * t;
                float d = opRep(p, float3(6.0, 6.0, 6.0));
                t += d * 1.0;
            }
            return t;
        }

        float2x2 rotation(float theta)
        {
            return float2x2(cos(theta), -sin(theta), sin(theta), cos(theta));
        }
           
            fixed4 frag (v2f i) : SV_Target
            {

                float2 uv = i.uv;
                   uv = uv * 2.0 - 1.0;
                    //uv.x *= 1 / 1;
                float3 r = normalize(float3(uv, 1.0));
                //r.xz *= rotation(sin(_Time.y) * 0.2);
                //r.xy *= rotation(cos(_Time.y) * 0.2);
                float3 o = float3(0.0, _Time.y, _Time.y * 10.0);
                float t = trace(o, r);
                float fog = 1.0 / (1.0 + t * t * 0.001); 
                float3 fc = float3(1.0-fog,1-fog,1-fog);
                return float4(fc ,1.0);
            }
            ENDCG
        }
    }
}

Don’t worry sorry I figured it out…

    float3 myMod(float3 x, float3 y)
        {
              return x - y * floor(x/y);
        }

C is weird.

You can also do this:
#define myMod(x, y) (x - y * floor(x / y))

That’s a macro definition rather than a function. That’ll work equally with two floats or two float4 or anything in between. It’ll even properly handle x being a vector and y being a float! No need to write multiple versions of the function to handle all cases!

And yes, the lack of ; at the end of that define line is intentional. It means when you use it in code you just use it like you would a normal function either mid line or with a semi-colon afterward, otherwise it has to be used by itself and without a semi-colon after it.

Note: the inverse of x as a float and y as a vector might also “work”, as well as mixed vector sizes (like a float2 and float4), but it’s not recommended as it’ll be down to the shader compiler to decide how and if it works. If you wrote out multiple versions of the function for the different cases the same rules would apply.

Excellent, thank you very much, that has been working like a charm, that’s a really handy tip to know!

Just out of interest do you know if you can pass a value from the surf to the vert, or frag/vert? I’ve tried setting (I guess global) variables but can’t seem to access them in any meaningful way…2932683--216899--Screen Shot 2017-01-24 at 07.18.18.png
That’s what I’ve got to so far, and where i’m stuck at the moment, I have noise being made in the shader, and i’d like to pass an element from the color (_Col) value, like one of the r,g,b components to offset the verts on the y axis.

Here’s what I got:

Shader "Custom/2 - Lit Vertex Displacement/Normals" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0

        _Speed("Speed",Range(0.1,4)) = 1
        _Amount("Amount", Range(0.1,10)) = 3
        _Displacement("Displacement", Range( 0, 20 )) = 9.3
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
       
        CGPROGRAM

        #pragma surface surf Standard fullforwardshadows vertex:vert addshadow

        #pragma target 3.0

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        uniform float3 _Col;
        uniform float _Extract;
        half _Speed;
        half _Amount;
        half _Displacement;
                const float2 m = float2( 0.80,  0.60 );

        float hash( float2 p )
        {
            float h = dot(p,float2(127.1,311.7));
            return -1.0 + 2.0*frac(sin(h)*43758.5453123);
        }

        float noise( in float2 p )
        {
            float2 i = floor( p );
            float2 f = frac( p );
           
            float2 u = f*f*(3.0-2.0*f);

            return lerp( lerp( hash( i + float2(0.0,0.0) ),
                             hash( i + float2(1.0,0.0) ), u.x),
                        lerp( hash( i + float2(0.0,1.0) ),
                             hash( i + float2(1.0,1.0) ), u.x), u.y);
        }

        float fbm( float2 p )
        {
            float f = 0.0;
            f += 0.5000*noise( p ); p = m*p*2.02;
            f += 0.2500*noise( p ); p = m*p*2.03;
            f += 0.1250*noise( p ); p = m*p*2.01;
            f += 0.0625*noise( p );
            return f/0.9375;
        }

        float2 fbm2( in float2 p )
        {
            return float2( fbm(p.xy), fbm(p.yx) );
        }

        float3 map( float2 p )
        {  
            p *= 2.9;

            float f = dot( fbm2( 1.0*(0.5*_Time.y + p + fbm2(-0.5*_Time.y+1.0*(p + fbm2(4.0*p)))) ), float2(1.0,-1.0) );

            float bl = smoothstep( -0.8, 0.8, f );

            float ti = smoothstep( -1.0, 1.0, fbm(p) );

            return lerp( lerp( float3(0.50,0.00,0.00),
                             float3(1.00,0.5,0.35), ti ),
                             float3(0.00,0.00,0.02), bl );
        }
        float3 makeMeNoise(float2 uv)
        {
                  float2 p =  4 * uv;
                float e = 0.0016;

                float3 colc = map( p               ); float gc = dot(colc,float3(0.333, 0.333, 0.333));
                float3 cola = map( p + float2(e,0.0) ); float ga = dot(cola,float3(0.333, 0.333, 0.333));
                float3 colb = map( p + float2(0.0,e) ); float gb = dot(colb,float3(0.333, 0.333, 0.333));
   
                float3 nor = normalize( float3(ga-gc, e, gb-gc ) );

                _Col = colc;
                _Col += float3(0.1,0.1,8.3)*1.0*abs(3.0*gc-ga-gb);
                _Col *= 0.5+0.2*nor.y*nor.y;
                    _Col += 0.05*nor.y*nor.y*nor.y;
   
   
                float2 q = uv;
                _Col *= pow(16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.1);
                //_Extract = _Col.r;// damnit??
                return _Col;
        }

//        float4 getNewVertPosition( float4 p )
//        {
//            p.x += sin( _Time.y * _Speed + p.y * _Amount ) * _Distance;
//            return p;
//        }

//        void disp (inout appdata v)
//            {
//                float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
//                v.vertex.xyz += v.normal * d;
//            }

        float4 getNewVertPosition( float4 p )
        {
            //float d = makeMeNoise(p.xy).y; // How do I pass the functions from MakeMeNoise at same time as making color,
//            // I'm calling makeMeNoise twice and above probably passing wrong values.
            //p += _Col.g*_Displacement;
            return p;
        }

            // Does it matter the order of the surf/vert elements? Can I get the color value to depthmap it
        // instead of passing through the same fuctions and calling MakeMeNoise, can I have a public ColorValue??

        void vert( inout appdata_full v )
        {
            //float d = _Col.y * _Displacement; // How to do I access wht Col is from here?
            //d+=_Col.y * _Displacement;
            float d = makeMeNoise(v.vertex.xy).z * _Displacement;
            v.vertex.xyz += v.normal * d;
            //v.vertex.z += _Displacement ;
            float4 vertPosition = getNewVertPosition( v.vertex );

            // calculate the bitangent (sometimes called binormal) from the cross product of the normal and the tangent
            float4 bitangent = float4( cross( v.normal, v.tangent ), 0 );

            // how far we want to offset our vert position to calculate the new normal
            float vertOffset = 0.01;

            float4 v1 = getNewVertPosition( v.vertex + v.tangent * vertOffset );
            float4 v2 = getNewVertPosition( v.vertex + bitangent * vertOffset );

            // now we can create new tangents and bitangents based on the deformed positions
            float4 newTangent = v1 - vertPosition;
            float4 newBitangent = v2 - vertPosition;

            // recalculate the normal based on the new tangent & bitangent
            v.normal = cross( newTangent, newBitangent );

            v.vertex = vertPosition;
        }
            void surf (Input IN, inout SurfaceOutputStandard o)

        {
            // Albedo comes from a texture tinted by color
                //return float4( col, 1.0 );
                _Col = makeMeNoise(IN.uv_MainTex);

                fixed4 c = _Color;
                c.rgb = _Col;
                o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
                o.Metallic = _Metallic;
                o.Smoothness = _Glossiness;
                o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

No.

Unity’s ShaderLab has the vertex shader and fragment shader defined in a single file for convenience, but on the GPU they’re completely separate things that have no knowledge of each other. Data can only be passed from the vertex shader to the fragment shader via the vertex output semantics one vertex at a time. The fragment shader can only output a color (and optionally depth, color is optional too actually) for a single pixel at a time to the current render target. Any other “global” value you set in the shader only exists for the computation of each individual vertex or pixel, and then thrown away.

You may want to read this, all 5 parts.
http://www.alanzucconi.com/2015/06/10/a-gentle-introduction-to-shaders-in-unity3d/

Ahh thank you for clearing that up for me, that’s so annoying they’re in the same file but can’t see each other, I would have thought I could have accessed a variable as I thought it was still living somewhere in memory. Although I did read something yesterday about their being no stack and heap in GLSL (http://gamedev.stackexchange.com/questions/61257/glsl-declaring-global-variables-outside-of-the-main-function-scope) and the variables just live in registers, so I thought it may be the same in HLSL.

I’ve just read the intro to shaders, it’s really well written, and a couple of tricks, I take it if I change all my floats to halfs I might get a boost in performance from not using a high precision 32bit float?

Still got some going to solve my problem, and might need to think outside of the box to solve this one.

The way I’ve seen it done is with a heightmap usually, and use the texture look up table to feed into 2dTexLOD and displace vertex.z by one of the rgba values, simple. To solve with what I have i’d have to render out the length of animation, write a script to move through the height texture folder at the same time as the colour animation in the shader, or just do it all through exported textures, manually rendering out from the shader. Could work but not ideal on size, kinda defeats the point.

The other option I was thinking about and (I don’t know if it would work now you say Vert and Surf are uncommunicative) is to re-arrange the order of events, still only call makemeNoise from one place, but pass the whole lot on out of it with a custom data struct maybe. Does the vertex modifier have an idea of UV coords? I assume it would.
2933426--216958--Screen Shot 2017-01-24 at 18.25.33.png

Possibly. Depends heavily on the platform and hardware. If you’re on desktop using Nvidia, AMD, or Intel GPUs, probably not. If you’re on mobile using Mali, Adreno, or PowerVR it’s absolutely help … as long as you don’t mix floats and halfs together.
A float is a 32 bit floating point number, a half is a 16 bit floating point number, and a fixed is roughly a 12 bit floating point number (though it’s implementation is actually only defined as needing a precision of “at least” 1/256 across the range of -2.0 to 2.0). Some GPUs, usually mobile, have unique hardware paths to handle numbers of lower precision that use less power and / or less time to calculate. Desktop GPUs generally haven’t had this extra hardware, and just compute everything using the same hardware path … effectively on desktop all number types are 32 bit once in the shader so the float, half, fixed denotations aren’t as relevant.

Another issue with understanding shaders is that they’re both more and less linear than you might think. Like I said before, and Alan’s tutorial illustrates, the order of the processing is linear. However there is more than one vertex, and more than one pixel being processed at any one time, often in parallel, and not necessarily in an order that makes immediate sense.

Lets take a basic example of a single quad being rendered to the screen. It has two triangles, a vertex shader, and a fragment shader. Before it can start rendering anything it needs to process the three vertices from one triangle. Then it takes the output of those vertex shaders and finds the pixels it covers. Then it starts rendering the pixels with the fragment shader using data interpolated from the vertices. The order it processes the vertices might be one after another, or all three at once, or in larger batches of many vertices that include other triangles, that’s up to the GPU and the drivers. Similarly the pixels aren’t rendered just top to bottom, left to right, but at least in batches of 4 (2x2) in parallel together, but also possibly in larger batches, or seemingly randomly distributed 2x2 blocks across the screen. It might also be processing the vertices for the next triangle at the same time as rendering the pixels out, or even a completely different mesh and / or shader!

When you look at the specs for something like an Nvidia or AMD GPU you’ll see it has something like “512 shader cores”. That effectively means it could be processing fragment shader for 512 pixels at the same time, or maybe 256 vertices and 256 pixels, or any other combination. And there’s no guarantee that all of those pixels are using the same fragment shader, or that they all start and finish at the same time, so they could be out of phase (think row row row your boat). So, a “global” value that you set in your vertex shader that you want to be used by your fragment shader … from which vertex do you want that data from? It’s impossible to know which last set it, or if it might change while the fragment shader is running so it’s a different value depending on if you access it at the start of the shader or the end. It could even change while the fragment shader is reading the value meaning you get something completely random!

Now in reality this kind of collision never happens, because GPUs (usually) don’t allow it. However those are the same issues that other massively parallel computing systems have to deal with. This is why we don’t all have CPUs with 100 cores in them by now, because just getting 4 cores to work together efficiently with general purpose code is a hassle. GPUs and shader code were designed with the expected limitations of massive parallelism in place.

I recommend you get to the fourth page of Alan’s tutorial. The vertex modifier in a surface shader is basically just a function called at the start of the surface shader’s vertex shader to let you modify data that came from the mesh, including the UVs. If the vertex shader doesn’t know about the UVs, the fragment shader can’t either because all it knows is what the vertex shader told it (and any “uniform”, ie unchanging, values from the material). It’s not that the vertex modifier and surf functions are “uncommunicative”, it’s that the data only goes one direction from the vertex shader (where the vertex modifier function is called) to the fragment shader (where the surf function is called) via those explicit output semantics.

1 Like

Ahh ok thanks that clears that up, maybe eventually halfs will be relevant for VR, at the moment I’m not sure I have to implement limitations as my own machines gpu is a nvidia gtx 760 or something, so it’s seems it’ll all be put into 32bits, so shouldn’t worry much now. I have read somewhere about putting float/fixed/half and the compiler didn’t spit up anything, so it’s starting to kind of make sense.

I remember seeing a graph some time ago, about how the less parallelizable a problem the less effective a massive parallel computing array becomes, there are certain problems you can parallelize and in those cases you can chuck loads of cores and it will perform better, but in cases where the problem isn’t parallelizable and has to be iterated, I guess that would be serial computing then chucking a load of cores doesn’t help at all. Most of computing on CPU’s is like step by step instruction based, execute in this order, so it’s pointless chucking 100 cores in a cpu, as it wouldn’t make you solve the problem any faster. (I maybe badly remembering all that, apologies)

So is there a way I can embed or pull out the data from the stream that I’m passing from vert to surf/frag? Maybe call the noise function in vert, pass out two sets of UV/tex coords with the colour embedded inside.

I could re-write to test to see if I can just make the function work just as a distortion on a plane, but I’m wondering if i’ve stumbled down the wrong path writing a surface shader, as I did start doing this as a frag/vert shader to begin with and then for some reason assumed you couldn’t do vector displacement.

You can pass all kinds of arbitrary data from the vertices to the fragment shader, and nearly any function you can run in the fragment shader you can run in the vertex shader too. So yes, try just running the noise function in your vertex modifier function or vertex shader. There is literally nothing a surface shader can do that a vertex fragment shader can’t since, as I mentioned before, surface shaders are vertex fragment shaders.

There’s actually a ton you can do with vertex fragment shaders you can’t do with surface shaders though… but you’re probably not at that point yet.

If you want to stick with surface shaders check out this page:

Scroll down to the “Custom data computed per-vertex” example.

Ahh that’s ace to hear, was starting to think I may have started going down the wrong path with surface shaders, good to know they they are essentially the same thing.

Not at the point yet, would that include stuff like raymarching? In all fairness I just want to make demos eventually, just ridiculous generative graphics, I’ve been messing around with games, but more of an musician/artist so naturally got drawn into finding out about demoscene, now I know how to tunnel and deform planes, make a bit of noise, just need to deform a plane with the noise and I’ve got some goopy awesomeness! I really would like to find out how far you can push shaders!! It’s just GLSL resources are abundant, hlsl are a bit scattered.

That was exactly the page I was studying last night, and saw the per vertex example, so do you reckon the important thing here is UNITY_INITIALIZE_OUTPUT in the vert and specifying out, that looks like how it’s passing data out of it. In theory could I have a custom Input struct that returns the noise colour values for the frag shader, pass it out as o or something.

I think in theory I’ve got it there, let me mess around for a while and see if I can get something working, I’ve initialised output and, made it go black, so it’s step in the right direction!!

Surface shaders exists to simplify the use Unity’s built in lighting systems and shading models. If you don’t plan on using Unity’s lighting there’s no reason to use surface shaders. You could do raymarching in a surface shader, and some people do for things like parallax occlusion mapping or relief mapping techniques (both of which are minor variations of raymarching implementations).

It’s more subtle stuff like alpha to coverage rendering or mixing tessellation and custom per vertex data. Just limitations in the Surface Shader generation code that they didn’t add support to handle.

Nope. That’s just a helping macro for setting all values to zero so the shader compiler doesn’t complain if you forget to set something in the output struct. I generally don’t use it in my own shaders since it can hide errors.

I really suggest you finish all 5 parts of that tutorial I linked to earlier. A lot of this is on page 4.

I know I should just start over from a frag/vert shader with the structs aI would but I just love hacking my way to an epiphany, learning through doing, or in my case failing until success!

Come on I think I’m only one epiphany away now!

Shader "Custom/NewGoop" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Displacement("Displacement", Range( 0, 200 )) = 9.3
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
       
        CGPROGRAM

        #pragma surface surf Standard fullforwardshadows vertex:vert addshadow

        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
            float3 noiseFunc;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float3 _Col;
        half _Displacement;
        const float2 m = float2( 0.80,  0.60 );

        float hash( float2 p )
        {
            float h = dot(p,float2(127.1,311.7));
            return -1.0 + 2.0*frac(sin(h)*43758.5453123);
        }

        float noise( in float2 p )
        {
            float2 i = floor( p );
            float2 f = frac( p );
           
            float2 u = f*f*(3.0-2.0*f);

            return lerp( lerp( hash( i + float2(0.0,0.0) ),
                             hash( i + float2(1.0,0.0) ), u.x),
                        lerp( hash( i + float2(0.0,1.0) ),
                             hash( i + float2(1.0,1.0) ), u.x), u.y);
        }

        float fbm( float2 p )
        {
            float f = 0.0;
            f += 0.5000*noise( p ); p = m*p*2.02;
            f += 0.2500*noise( p ); p = m*p*2.03;
            f += 0.1250*noise( p ); p = m*p*2.01;
            f += 0.0625*noise( p );
            return f/0.9375;
        }

        float2 fbm2( in float2 p )
        {
            return float2( fbm(p.xy), fbm(p.yx) );
        }

        float3 map( float2 p )
        {  
            p *= 2.9;

            float f = dot( fbm2( 1.0*(0.5*_Time.y + p + fbm2(-0.5*_Time.y+1.0*(p + fbm2(4.0*p)))) ), float2(1.0,-1.0) );

            float bl = smoothstep( -0.8, 0.8, f );

            float ti = smoothstep( -1.0, 1.0, fbm(p) );

            return lerp( lerp( float3(0.50,0.00,0.00),
                             float3(1.00,0.5,0.35), ti ),
                             float3(0.00,0.00,0.02), bl );
        }
        float3 makeMeNoise(float2 uv)
        {
                  float2 p =  4 * uv;
                float e = 0.0016;
                float3 colc = map( p               ); float gc = dot(colc,float3(0.333, 0.333, 0.333));
                float3 cola = map( p + float2(e,0.0) ); float ga = dot(cola,float3(0.333, 0.333, 0.333));
                float3 colb = map( p + float2(0.0,e) ); float gb = dot(colb,float3(0.333, 0.333, 0.333));
                float3 nor = normalize( float3(ga-gc, e, gb-gc ) );

                _Col = colc;
                _Col += float3(0.1,0.1,8.3)*1.0*abs(3.0*gc-ga-gb);
                _Col *= 0.5+0.2*nor.y*nor.y;
                    _Col += 0.05*nor.y*nor.y*nor.y;
   
                float2 q = uv;
                _Col *= pow(16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.1);
                //_Extract = _Col.r;// damnit??
                return _Col;
        }

        float4 getNewVertPosition( float4 p )
        {
            return p;
        }

        void vert( inout appdata_full v, out Input o)
        {
            UNITY_INITIALIZE_OUTPUT(Input,o); // Init output
            o.noiseFunc = makeMeNoise(v.vertex); // make noiseFunc
            float3 nois = o.noiseFunc;
            float d = nois.z * _Displacement;
            v.vertex.xyz += v.normal * d;
            float4 vertPosition = getNewVertPosition( v.vertex );
            // calculate the bitangent (sometimes called binormal) from the cross product of the normal and the tangent
            float4 bitangent = float4( cross( v.normal, v.tangent ), 0 );
            // how far we want to offset our vert position to calculate the new normal
            float vertOffset = 0.01;
            float4 v1 = getNewVertPosition( v.vertex + v.tangent * vertOffset );
            float4 v2 = getNewVertPosition( v.vertex + bitangent * vertOffset );
            // now we can create new tangents and bitangents based on the deformed positions
            float4 newTangent = v1 - vertPosition;
            float4 newBitangent = v2 - vertPosition;
            // recalculate the normal based on the new tangent & bitangent
            v.normal = cross( newTangent, newBitangent );
            v.vertex = vertPosition;
        }
            void surf (Input IN, inout SurfaceOutputStandard o)

        {
            // Albedo comes from a texture tinted by color
                //return float4( col, 1.0 );
                _Col *= IN.noiseFunc;
                fixed4 c = _Color;
                c.rgb = _Col;
                o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
                o.Metallic = _Metallic;
                o.Smoothness = _Glossiness;
                o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

I’ve set it up like the custom vertex data example, o.noiseFunc in mine is equivalent to customColour on this page: https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

I get down to surf and looks like I’m passing the value in the same way as the example but I’m not getting the colour come through. I suspect it’s 114, as I’m passing v.vertex instead of a uv value.

I’ve just had a crack at the frag/vert one:

 Shader"Paintings/Base"{
Properties{
 _MainTex ("Texture", 2D) = "white" {}
 }
SubShader{

Pass{
CGPROGRAM
#pragmavertexvert
#pragmafragmentfrag

 #include "UnityCG.cginc"

struct appdata{
float4vertex : POSITION;
float2 uv : TEXCOORD0;
float4 noiseFunc : TEXCOORD2; //isthisright??
 };

struct v2f
 {
float2 uv : TEXCOORD0;
float4vertex : SV_POSITION;
float4 screenCoord : TEXCOORD1;
float4 noiseFunc : TEXCOORD2; //isthisright??
 };

constfloat2 m = float2( 0.80, 0.60 );
float3 _Col;
half _Displacement;

float hash( float2 p )
 {
float h = dot(p,float2(127.1,311.7));
return -1.0 + 2.0*frac(sin(h)*43758.5453123);
 }

float noise( infloat2 p )
 {
float2 i = floor( p );
float2 f = frac( p );

float2 u = f*f*(3.0-2.0*f);

returnlerp( lerp( hash( i + float2(0.0,0.0) ), 
 hash( i + float2(1.0,0.0) ), u.x),
lerp( hash( i + float2(0.0,1.0) ), 
 hash( i + float2(1.0,1.0) ), u.x), u.y);
 }

float fbm( float2 p )
 {
float f = 0.0;
 f += 0.5000*noise( p ); p = m*p*2.02;
 f += 0.2500*noise( p ); p = m*p*2.03;
 f += 0.1250*noise( p ); p = m*p*2.01;
 f += 0.0625*noise( p );
return f/0.9375;
 }

float2 fbm2( infloat2 p )
 {
returnfloat2( fbm(p.xy), fbm(p.yx) );
 }

float3 map( float2 p )
 { 
 p *= 2.9;

float f = dot( fbm2( 1.0*(0.5*_Time.y + p + fbm2(-0.5*_Time.y+1.0*(p + fbm2(4.0*p)))) ), float2(1.0,-1.0) );

float bl = smoothstep( -0.8, 0.8, f );

float ti = smoothstep( -1.0, 1.0, fbm(p) );

returnlerp( lerp( float3(0.50,0.00,0.00), 
float3(1.00,0.5,0.35), ti ), 
float3(0.00,0.00,0.02), bl );
 }
float3 makeMeNoise(float2 uv)
 {
float2 p = 4 * uv;
float e = 0.0016;

float3 colc = map( p ); float gc = dot(colc,float3(0.333, 0.333, 0.333));
float3 cola = map( p + float2(e,0.0) ); float ga = dot(cola,float3(0.333, 0.333, 0.333));
float3 colb = map( p + float2(0.0,e) ); float gb = dot(colb,float3(0.333, 0.333, 0.333));

float3 nor = normalize( float3(ga-gc, e, gb-gc ) );

 _Col = colc;
 _Col += float3(0.1,0.1,8.3)*1.0*abs(3.0*gc-ga-gb);
 _Col *= 0.5+0.2*nor.y*nor.y;
 _Col += 0.05*nor.y*nor.y*nor.y;


float2 q = uv;
 _Col *= pow(16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.1);
return _Col;
 }

float4 getNewVertPosition( float4 p )
 {
return p;
 }

 v2f vert (appdata v)
 {
 v2f o;
 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
 o.uv = v.uv;

 o.noiseFunc.xyz = makeMeNoise(o.uv);
float3 nois = o.noiseFunc.xyz;
float d = nois.x;
//v.vertex.xyz+=v.normal*d;
 v.vertex.xyz += d;
//v.vertex.z+=_Displacement;
float4 vertPosition = getNewVertPosition( v.vertex );

////calculatethebitangent(sometimescalledbinormal)fromthecrossproductofthenormalandthe tangent
//float4bitangent=float4(cross(v.normal,v.tangent),0);
//
////howfarwewanttooffsetourvertpositiontocalculatethenew normal
//floatvertOffset=0.01;
//
//float4v1=getNewVertPosition(v.vertex+v.tangent*vertOffset);
//float4v2=getNewVertPosition(v.vertex+bitangent*vertOffset);
//
////nowwecancreatenewtangentsandbitangentsbasedonthedeformed positions
//float4newTangent=v1-vertPosition;
//float4newBitangent=v2-vertPosition;
//
////recalculatethenormalbasedonthenewtangent& bitangent
//v.normal=cross(newTangent,newBitangent);

 v.vertex = vertPosition;
 o.screenCoord.xy = ComputeScreenPos(o.vertex); 
return o;
 }

sampler2D _MainTex;

fixed4 frag (v2f i) : SV_Target
 {
float2 uv = i.uv;
float3 col = i.noiseFunc; 
//float3col=(1,1,1);
returnfloat4(col,1);

 }
ENDCG
 }
 }
}

It compiles, haven’t tested it yet though!