binding 3D world position noise to object position?

I’ve been tinkering with a procedural noise shader but I’m not sure how I’d go about “binding” the 3D noise to the object position?

8876460--1212447--Unity_ATYlnLFAK9.gif

I’ve tried various approaches, like passing in a position from a script and using various vertex parameters, but when it comes to most of this stuff I feel like that dog in that meme:

8876460--1212450--no-idea.jpg

Shader "Custom/NoiseShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Offset ("Offset", Vector) = (0,0,0,0)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        struct Input
        {
            float2 uv_MainTex;
            float3 worldPos;
            float3 objPos;
            float3 vertex;
            float3 uv;
        };

        void vert (inout appdata_full v, out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input,o);
            o.objPos = mul(unity_WorldToObject, v.vertex);
            o.vertex = v.vertex;
            o.uv = v.texcoord;
        }

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float3 _Offset;

        float hash13(float3 p3)
        {
            p3  = frac(p3 * .1031);
            p3 += dot(p3, p3.zyx + 31.32);
            return frac((p3.x + p3.y) * p3.z);
        }

        float noise( in float3 x )
        {
            float3 i = floor(x);
            float3 f = frac(x);
            f = f*f*(3.0-2.0*f);
           
            return lerp(lerp(lerp( hash13(i+float3(0,0,0)),
            hash13(i+float3(1,0,0)),f.x),
            lerp( hash13(i+float3(0,1,0)),
            hash13(i+float3(1,1,0)),f.x),f.y),
            lerp(lerp( hash13(i+float3(0,0,1)),
            hash13(i+float3(1,0,1)),f.x),
            lerp( hash13(i+float3(0,1,1)),
            hash13(i+float3(1,1,1)),f.x),f.y),f.z);
        }

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
        // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = _Color;

            float sc = 1.0;
            float3 p = floor(sc * (IN.worldPos - IN.objPos) * 0.99999);
            float val = noise(p);

            c += val * 0.5;

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

any help would be appreciated.

The v.vertex is already the object space position of the vertices. Multiplying that by unity_WorldToObject means you’re taking an object space position, and treating it as a world space position that you’re converting into object space. Basically it’s in “double object space”, which is a phrase that doesn’t make any sense, and neither do the position values you get from it.

So, if you change that line to:

o.objPos = v.vertex;

And then change:

float3 p = floor(sc * (IN.worldPos - IN.objPos) * 0.99999);

to:

float3 p = floor(sc * IN.objPos * 0.99999);

You should get what you’re trying to go for.

1 Like

ah, thanks for the clarification. I did try using “v.vertex” in some of my tests but it doesn’t seem to display the noise at all, and I did try to change the scale of the noise and position but no luck.

I did however continue to tinker with it after I posted the question and I think I managed to get it to work by changing

float3 p = floor(sc * (IN.worldPos - IN.objPos) * 0.99999);

to

float3 p = floor(sc * (IN.worldPos - _Offset) * 0.99999);

and passing in _Offset through a scripts Update() function. I’m not sure if that’s the best approach though? will that kill performance if I have a bunch of objects with that script doing this every frame?

_renderer.material.SetVector(“_Offset”, _tf.position);

I’m guessing you’re scaling the object up then.

You can also get that _Offset value using.

float3 worldSpacePivotPosition = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz;
1 Like

Yeah I am scaling the object, is there a way to get the object’s scale in the shader and factoring it into the vertex position somehow?

also thanks for the worldSpacePivotPosition variable, that did indeed produce the same result without using a script!

EDIT: although I noticed that this approach with the offset makes the noise repeat and look the same for each object, wonder if I could do something about that…

Yep.

float3 objectScale = float3(
  length(mul((float3x3)unity_ObjectToWorld, float3(1,0,0)),
  length(mul((float3x3)unity_ObjectToWorld, float3(0,1,0)),
  length(mul((float3x3)unity_ObjectToWorld, float3(0,0,1))
  );

My suggestion would be to do this in the vertex shader:

o.objectPos = objectScale * v.vertex;

And that should handle scaling and moving without any issues.

You’d have to supply a random offset value to each object’s material to randomize it. Otherwise the usual way to randomize values is using the world position. But then it’ll change when you move it which is what you were trying to avoid?

1 Like

So I tried your scaled objectPos solution (after adding closing braces to the length functions, cause they were missing)

But no luck with it, it just doesn’t work with this 3D noise function I’m using I guess? I’m not even sure if I should be using it over worldPos with an offset?

So I kept using worldPos and tinkering around with it and tried doing what you said; supplying a random offset through a script and now it seems to be working pretty well.

I’m just doing this on Awake in a script:
_renderer.material.SetVector(“_Offset”, _tf.position);

And here’s the shader code:

Shader "Custom/NoiseShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Offset ("Offset", Vector) = (0,0,0,0)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        struct Input
        {
            float3 worldPos;
            float3 objectPos;
        };

        void vert (inout appdata_full v, out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input,o);
            float3 objectScale = float3(
                length(mul((float3x3)unity_ObjectToWorld, float3(1,0,0))),
                length(mul((float3x3)unity_ObjectToWorld, float3(0,1,0))),
                length(mul((float3x3)unity_ObjectToWorld, float3(0,0,1)))
            );
            o.objectPos = objectScale * v.vertex;
        }

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float3 _Offset;

        float hash13(float3 p3)
        {
            p3  = frac(p3 * .1031);
            p3 += dot(p3, p3.zyx + 31.32);
            return frac((p3.x + p3.y) * p3.z);
        }

        float noise( in float3 x )
        {
            float3 i = floor(x);
            float3 f = frac(x);
            f = f*f*(3.0-2.0*f);
          
            return lerp(lerp(lerp( hash13(i+float3(0,0,0)),
            hash13(i+float3(1,0,0)),f.x),
            lerp( hash13(i+float3(0,1,0)),
            hash13(i+float3(1,1,0)),f.x),f.y),
            lerp(lerp( hash13(i+float3(0,0,1)),
            hash13(i+float3(1,0,1)),f.x),
            lerp( hash13(i+float3(0,1,1)),
            hash13(i+float3(1,1,1)),f.x),f.y),f.z);
        }

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
        // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = _Color;
          
            float3 worldSpacePivotPosition = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz;
            float3 offset = worldSpacePivotPosition;
            offset -= _Offset * 0.99999;
            float3 p = floor(1.0 * (IN.worldPos - offset) * 0.99999);
            //p = floor(1.0 * (IN.objectPos - offset) * 0.99999);
            float val = noise(p);

            c += val * 0.5;

            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’m not sure if there’s any way to get around using a script or if there are any benefits to using objectPos over worldPos?

The issue with using the object position isn’t anything to do with it working or not. It has everything to do with you’re missing this:

#pragma surface surf Standard fullforwardshadows vertex:vert

The vert function was never being called, so the surf function was getting an empty value.

Once you do that, you just need:
float3 p = floor((IN.objectPos - _Offset) * 0.99999);

1 Like

Yep, that did it alright! so that eliminates the need to use the worldSpacePivotPosition variable.

I don’t think there’s any alternative ways of supplying the _Offset uniquely for each object except through a script right?

So I think that solves everything, thanks for the help!

Unfortunately, that is the only option.