Procedural Rocks [FREE CODE!]

EDIT: CHANGING FIRST POST, because this is done.

Here is a procedural rock generator. It takes any mesh, and randomizes the distance of its vertices from the center. Use this on a sphere, and you get a rock of random shape. This should work pretty decently, and is a good free alternative to some of the assets available in the store that do the same thing. All I ask is that you give credit to me, Ramaraunt (Daniel Valcour), as the creator.

Here is the code:

EDIT: Improved smoothness

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class rockRandomizer : MonoBehaviour {

    public int seed = 37823787;
    private Material mat;
    private List<Vector3> vertices = new List<Vector3>();
    private List<Vector3> doneVerts = new List<Vector3>();
    private Vector3 center;


    void Start()
    {
        Random.InitState(seed);
        float offset = Random.Range(0, 20);

        Mesh mesh = GetComponent<MeshFilter>().mesh;

        for (int s = 0; s < mesh.vertices.Length; s ++)
        {
            vertices.Add(mesh.vertices[s]);
        }

        center = GetComponent<Renderer>().bounds.center;

        for (int v = 0; v < vertices.Count; v++)
        {
            bool used = false;
            for (int k = 0; k < doneVerts.Count; k++)
            {
                if (doneVerts[k] == vertices[v])
                {
                    used = true;
                }
            }
            if (!used)
            {
                Vector3 curVector = vertices[v];
                doneVerts.Add(curVector);
                int smoothing = Random.Range(4, 6);
                Vector3 changedVector = (curVector + ((curVector - center) * (Mathf.PerlinNoise((float) v / offset, (float)v / offset)/smoothing)));
                for (int s = 0; s < vertices.Count; s ++)
                {
                    if (vertices[s] == curVector)
                    {
                        vertices[s] = changedVector;
                    }
                }
            }
        }

        mesh.SetVertices(vertices);
        mesh.RecalculateBounds();
        mesh.RecalculateNormals();

    }
}

Here is an example:

EDIT: To make the rock different each time, don’t forget to change the seed value of each rock!

EDIT: Moss shader! Use this for free, just give credit to me!

Shader "Custom/MossySnowShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _MossTex("Moss or Snow (RGBA)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 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 vertex:vert

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

        sampler2D _MainTex;
        sampler2D _MossTex;

        struct Input {
            float2 uv_MainTex;
            float2 uv_MossTex;
            float3 VertexDirection;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        void vert(inout appdata_full v, out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input, o);
            o.VertexDirection = v.normal;
        }

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            //Now grab the moss texture
            fixed4 m = tex2D(_MossTex, IN.uv_MossTex) * _Color;
            //now lets make the texture!
            o.Albedo =(m.rgb *m.a ) + (c.rgb * (1 - m.a));
       
       
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

The moss texture needs to have alpha blotches, which will be where you can see the rock underneath!

EDIT: Another Shader! This one only renders moss on top of the rock!

Taken from this forum post. I made some changes to it to allow transparency on the moss, so you still need to credit me! This also works nicely with snow.

Shader "Custom/BlendSamplersByDirection" {
    Properties{
        _MainTex("Base (RGB)", 2D) = "white" {}
    _MainBump("MainBump", 2D) = "bump" {}
    _LayerTex("Layer (RGB)", 2D) = "white" {}
    _LayerBump("LayerBump", 2D) = "bump" {}
    _LayerStrength("Layer Strength", Range(0, 1)) = 0
        _LayerDirection("Layer Direction", Vector) = (0, 1, 0)
        _LayerDepth("Layer Depth", Range(0, 0.005)) = 0.0005
    }

        SubShader{
        Tags{ "RenderType" = "Opaque" }
        LOD 200

        CGPROGRAM
#pragma target 3.0
#pragma surface surf Lambert vertex:vert

        sampler2D _MainTex;
    sampler2D _MainBump;
    sampler2D _LayerTex;
    sampler2D _LayerBump;
    float _LayerStrength;
    float3 _LayerDirection;
    float _LayerDepth;

    struct Input {
        float2 uv_MainTex;
        float2 uv_MainBump;
        float2 uv_LayerTex;
        float2 uv_LayerBump;
        float3 worldNormal;
        INTERNAL_DATA
    };

    void vert(inout appdata_full v) {
        // Convert the normal to world coordinates/world space
        float3 sn = mul((float3x3)_World2Object, _LayerDirection);

        if (dot(v.normal, sn.xyz) >= lerp(1, -1, (_LayerStrength * 2) / 3))
        {
            v.vertex.xyz += (sn.xyz + v.normal) * _LayerDepth * _LayerStrength;
        }
    }

    void surf(Input IN, inout SurfaceOutput o) {

        // Diffuse color of pixel
        half4 mainDiffuse = tex2D(_MainTex, IN.uv_MainTex);
        half4 layerDiffuse = tex2D(_LayerTex, IN.uv_LayerTex);

        // Normal vector of pixel
        o.Normal = UnpackNormal(tex2D(_MainBump, IN.uv_MainBump));
        half3 layerNormal = half3(0, 0, 0);

        // Snow mask
        half sm = dot(WorldNormalVector(IN, o.Normal), _LayerDirection);
        sm = pow(0.5 * sm + 0.5, 2.0);


        if (sm >= lerp(1, 0, _LayerStrength))
        {
            o.Albedo = (layerDiffuse.rgb *(layerDiffuse.a)) + (mainDiffuse.rgb * (1 - layerDiffuse.a));
            layerNormal = UnpackNormal(tex2D(_LayerBump, IN.uv_LayerBump));
            o.Normal = normalize(o.Normal + layerNormal);
        }
        else
        {
            o.Albedo = mainDiffuse.rgb;
        }

        o.Alpha = mainDiffuse.a;
    }
    ENDCG
    }
        FallBack "Diffuse"
}

EDIT: The final product! Use this at will, just give credit to me!

Shader "Custom/BlendSamplersByDirection" {
    Properties{
        _MainTex("Base (RGB)", 2D) = "white" {}
    _MainBump("MainBump", 2D) = "bump" {}
    _LayerTex("Layer (RGB)", 2D) = "white" {}
    _LayerBump("LayerBump", 2D) = "bump" {}
    _LayerStrength("Layer Strength", Range(0, 1)) = 0
        _LayerDirection("Layer Direction", Vector) = (0, 1, 0)
        _LayerDepth("Layer Depth", Range(0, 0.005)) = 0.0005
    }

        SubShader{
        Tags{ "RenderType" = "Opaque" }
        LOD 200

        CGPROGRAM
#pragma target 3.0
#pragma surface surf Lambert vertex:vert

        sampler2D _MainTex;
    sampler2D _MainBump;
    sampler2D _LayerTex;
    sampler2D _LayerBump;
    float _LayerStrength;
    float3 _LayerDirection;
    float _LayerDepth;

    struct Input {
        float2 uv_MainTex;
        float2 uv_MainBump;
        float2 uv_LayerTex;
        float2 uv_LayerBump;
        float3 worldNormal;
        INTERNAL_DATA
    };

    void vert(inout appdata_full v) {
        // Convert the normal to world coordinates/world space
        float3 sn = mul((float3x3)_World2Object, _LayerDirection);

        if (dot(v.normal, sn.xyz) >= lerp(1, -1, (_LayerStrength * 2) / 3))
        {
            v.vertex.xyz += (sn.xyz + v.normal) * _LayerDepth * _LayerStrength;
        }
    }

    void surf(Input IN, inout SurfaceOutput o) {

        // Diffuse color of pixel
        half4 mainDiffuse = tex2D(_MainTex, IN.uv_MainTex);
        half4 layerDiffuse = tex2D(_LayerTex, IN.uv_LayerTex);

        // Normal vector of pixel
        o.Normal = UnpackNormal(tex2D(_MainBump, IN.uv_MainBump));
        half3 layerNormal = half3(0, 0, 0);

        // Snow mask
        half sm = dot(WorldNormalVector(IN, o.Normal), _LayerDirection);
        sm = pow(0.5 * sm + 0.5, 2.0);

        half difference = 1 - pow((clamp(lerp(1, 0, _LayerStrength),0,1) / sm),3);

        o.Albedo = (layerDiffuse.rgb *clamp(layerDiffuse.a * difference,0,1)) + (mainDiffuse.rgb * (clamp(1 - layerDiffuse.a * difference,0,1)));
        layerNormal = UnpackNormal(tex2D(_LayerBump, IN.uv_LayerBump));
        o.Normal = normalize(o.Normal + layerNormal);


        o.Alpha = mainDiffuse.a;
    }
    ENDCG
    }
        FallBack "Diffuse"
}

you create a vector from the center to the original point, then multiply or add the value to this, for example
Vector3 finalPoint = (currentPointOnSphere - sphereCenter) * Mathf.PerlinNoise(…);

THANK YOU. I’ve been so tired of looking for ways to do this, and all I find are people saying “BUY MY ASSET PACK!”

Well I’m running into more issues. It seems to only cause the sphere to grow, and there is no noise in its growth.

for (int v = 0; v < vertices.Count; v ++)
{
Debug.Log(vertices[v]);
vertices[v] = (vertices[v] - center) + ((vertices[v] - center) * Mathf.PerlinNoise((float)offset + (v * 30), (float)offset + (v * 30)));
Debug.Log(vertices[v]);
}

rather than multiplying c, divide it after you cast it to float

Try this out in Blender3D to understand what you want to do ultimately in code:

new blender file, delete starter cube

make an icosphere
-enter edit mode
-select all
-unwrap smart UVs
-return to object mode

goto texture tab, create new texture
-set texture type to clouds
-set texture cloud details to color (this decorrelates R,G,B)

add modifier to icosphere: displace
-select colored cloud texture created above
-select Direction: RGB to XYZ
-select Texture Coordinates: UV
-adjust strength to keep your verts from “folding over” on each other

Attached is the actual Blender3D file that makes the above so you can see what I mean.

THEN… you can replicate this in code by adding X,Y,Z offsets as separate channels from different areas of the perlin noise source, simulating the R,G,B colored cloud noise in Blender.

2903366–213787–noiserock.zip (72.6 KB)

Ahh thanks! I think I got it, just one more issue sadly. I didn’t realize the perlin noise offsets have to be between 0 and 1, documentation on it sucks.

Anyways, the issue is: The vertices of the mesh are splitting.

You need to take the positions of the vertices into consideration somehow, I would suggest creating some kind of container, container, preferably a dictionary, and then every time you want to set a height, you need to check if this container contains the vertex already. If yes, take the height data from the container, if no, put the data with the vertex into the container

ahh alright. I’ll do it thanks.

Seems the splitting has something to do with how I UV unwrap the sphere. I can’t find a way to unwrap it where it is all one mesh though.

No, the solution I posted above is why you have that problem
If you have flat shading on a mesh, that means, that every triangle has it’s own vertices, there is no sharing
this means. that at a single point there can be multiple vertices, and if you don’t take their location into consideration, then you will set each of those vertices to a different height

1 Like

Ah, so what is going on here is this: in Unity each vertex can only have precisely one UV coordinate (as well as one normal coordinate).

Therefore, if you are unwrapping with the technique I mentioned above, Unity sees that and then duplicates certain vertices that belong to two different split-apart faces in order for their UVs to map correctly to the two faces unique location in UV space.

This means that when you drive the perlin noise source from the UV coordinates only, you will displace one cloned vert in one way, and its clone (originally at precisely the right location) will displace differently because it is drawing from another part of the noise, creating the seam.

There are ways of mitigating against this: you need to “declone” the vertices and recognize that there may be more than one vertex at a given world space, and then choose which of its clones’ UV space you’re going to use for sourcing the perlin noise.

When you declone a vertex, remember you cannot reliably compare Vector3 structures as they are floating point based. Therefore you want to subtract one from the other (to end up with a new difference Vector3) and decide if its .magnitude property is “small enough” that they are indeed identical vertices.

And when I mean “declone,” I don’t mean actually CHANGE the underlying vertices, because then you would need to retopologize the mesh, which would be painful. What you want to do is recognize that a set of vertices are all clones of each other by the “close enough” method above, and then when you have completed clustering them into what we could call “uniquely located vertices,” then ALL those verts at a given spot would be offset by the same amount of noise, preserving the seamlessness.

Whew! :slight_smile:

Its alright, I made a block of code that compensates. Looks and sees if you already had a vertex at each point, and doesn’t change it again if you did.

This is the complete script:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class rockRandomizer : MonoBehaviour {

    public int seed = 37823787;
    private Material mat;
    private List<Vector3> vertices = new List<Vector3>();
    private List<Vector3> doneVerts = new List<Vector3>();
    private Vector3 center;


    void Start()
    {
        Random.InitState(seed);
        float offset = Random.Range(0, 20);

        Mesh mesh = GetComponent<MeshFilter>().mesh;

        for (int s = 0; s < mesh.vertices.Length; s ++)
        {
            vertices.Add(mesh.vertices[s]);
        }

        center = GetComponent<Renderer>().bounds.center;

        for (int v = 0; v < vertices.Count; v++)
        {
            bool used = false;
            for (int k = 0; k < doneVerts.Count; k++)
            {
                if (doneVerts[k] == vertices[v])
                {
                    used = true;
                }
            }
            if (!used)
            {
                Vector3 curVector = vertices[v];
                doneVerts.Add(curVector);
                Vector3 changedVector = (curVector + ((curVector - center) * Mathf.PerlinNoise((float)v / offset, (float)v / offset)));
                for (int s = 0; s < vertices.Count; s ++)
                {
                    if (vertices[s] == curVector)
                    {
                        vertices[s] = changedVector;
                    }
                }
            }
        }

        mesh.SetVertices(vertices);
        mesh.RecalculateBounds();
        mesh.RecalculateNormals();

    }
}

It works great! However, the rock that results from this is far too jagged. How should I sample the perlin, so it looks smoother?

BTW, I’ve decided I’m releasing this as free for everyone to use, as long as you give credit to me.

You should divide the perlinnoise value

I’m not sure what you mean. I tried changing * to / and it does this :stuck_out_tongue:

Alright I’m super confused about perlin and how it works. Seems I can only input to arguments, and it says in documentation they are coordinates? But I’ve tried, and it seems only values between 0 and 1 work. So that’s not many coordinates to work with, I’m guessing its something else.

If I use anything larger than 1 in the arguments, it does this:

EDIT: I figured it out. you divide the perlin noise by a smoothing value. I’ll change the code now. Editing the first post.

And I’m adding a moss shader. Its not done yet, I can’t figure out how to make it so that moss only grows on faces where the normals are facing upwards. But I got moss to grow on it at least for now.

I figured out how to make is to the moss only grows on top. I found an old forum post with how to do it.

1 Like

You should have the alpha be the similarity of the face normal to an upward vector, so if it’s slightly slanted, it should have an alpha value of 0.5, so the hard egdes disappear

ahh alright, ill get to work. Good idea.

Got it!

1 Like