Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Full procedural sphere with Vertex Shader (source code)

Discussion in 'Shaders' started by Przemyslaw_Zaworski, Jul 30, 2019.

  1. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    327
    I think this small piece of code can be useful, so I share. It draws sphere with center at point (0,0,0)
    and has per-face lighting (normals reconstructed from screen space derivatives).

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class DrawSphere : MonoBehaviour
    4. {
    5.     public Shader shader;
    6.     protected Material material;
    7.    
    8.     void Start()
    9.     {
    10.         material = new Material(shader);
    11.     }
    12.  
    13.     void OnRenderObject()
    14.     {
    15.         material.SetPass(0);
    16.         Graphics.DrawProcedural(MeshTopology.Triangles, 12288, 1);
    17.     }
    18. }
    19.  
    Code (CSharp):
    1. // Draws full procedural sphere with per-face lighting
    2. Shader "Draw Sphere"
    3. {
    4.     Subshader
    5.     {  
    6.         Pass
    7.         {
    8.             Cull Off
    9.             CGPROGRAM
    10.             #pragma vertex VSMain
    11.             #pragma fragment PSMain
    12.             #pragma target 5.0
    13.            
    14.             float4 VSMain (uint id:SV_VertexID, out float3 p:TEXCOORD0) : SV_POSITION
    15.             {
    16.                 float f = id;
    17.                 float v = f - 6.0 * floor(f/6.0);
    18.                 f = (f - v) / 6.;
    19.                 float a = f - 64.0 * floor(f/64.0);
    20.                 f = (f-a) / 64.;
    21.                 float b = f-16.;
    22.                 a += (v - 2.0 * floor(v/2.0));
    23.                 b += v==2. || v>=4. ? 1.0 : 0.0;
    24.                 a = a/64.*6.28318;
    25.                 b = b/64.*6.28318;
    26.                 p = float3(cos(a)*cos(b), sin(b), sin(a)*cos(b));
    27.                 return UnityObjectToClipPos(float4(p, 1.0));
    28.             }
    29.  
    30.             float4 PSMain (float4 s:SV_POSITION, float3 p:TEXCOORD0) : SV_Target
    31.             {
    32.                 float3 dx = ddx_fine( p );
    33.                 float3 dy = ddy_fine( p );
    34.                 float3 light1 = normalize(float3(5,0,100));
    35.                 float3 light2 = normalize(float3(-100,0,-102));
    36.                 float3 light3 = normalize(float3(100,0,-100));  
    37.                 float3 normal = normalize(cross(dx,dy));
    38.                 float3 diffuse1 = max(dot(light1,normal),0.0) * float3(0.9,0.0,0.0);
    39.                 float3 diffuse2 = max(dot(light2,normal),0.0) * float3(0.0,0.9,0.0);
    40.                 float3 diffuse3 = max(dot(light3,normal),0.0) * float3(0.0,0.0,1.0);
    41.                 return float4(diffuse1+diffuse2+diffuse3,1.0);
    42.             }
    43.             ENDCG
    44.         }
    45.        
    46.     }
    47. }

    upload_2019-7-30_21-3-35.png
     
    Crawdad, Namey5, bgolus and 1 other person like this.
  2. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    327
    OK. Now version with "smooth" normals, sphere with center at point(50, 0, 0).

    Code (CSharp):
    1. Shader "Draw Sphere"
    2. {
    3.     Subshader
    4.     {  
    5.         Pass
    6.         {
    7.             Cull Off
    8.             CGPROGRAM
    9.             #pragma vertex VSMain
    10.             #pragma fragment PSMain
    11.             #pragma target 5.0
    12.            
    13.             float4 VSMain (uint id:SV_VertexID, out float3 p:TEXCOORD0, out float3 n:TEXCOORD1) : SV_POSITION
    14.             {
    15.                 float f = id;
    16.                 float v = f - 6.0 * floor(f/6.0);
    17.                 f = (f - v) / 6.;
    18.                 float a = f - 64.0 * floor(f/64.0);
    19.                 f = (f-a) / 64.;
    20.                 float b = f-16.;
    21.                 a += (v - 2.0 * floor(v/2.0));
    22.                 b += v==2. || v>=4. ? 1.0 : 0.0;
    23.                 a = a/64.*6.28318;
    24.                 b = b/64.*6.28318;
    25.                 p = float3(cos(a)*cos(b), sin(b), sin(a)*cos(b));
    26.                 n = normalize(p);
    27.                 p += float3(50.0, 0.0, 0.0);
    28.                 return UnityObjectToClipPos(float4(p, 1.0));
    29.             }
    30.  
    31.             float4 PSMain (float4 s:SV_POSITION, float3 p:TEXCOORD0, float3 n:TEXCOORD1) : SV_Target
    32.             {  
    33.                 return float4(max(dot(normalize(_WorldSpaceLightPos0).xyz, n),0.0).xxx + UNITY_LIGHTMODEL_AMBIENT, 1.0);
    34.             }
    35.             ENDCG
    36.         }
    37.        
    38.     }
    39. }

    Procedural sphere vs Unity built-in sphere with legacy diffuse shader and grayscale ambient lighting.
    Can you guess, which one is which one ?

    upload_2019-7-31_15-47-51.png
     
    Crawdad and vildauget like this.
  3. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    327
    Better example, with proper culling (it doesn't require Cull Off like previous examples):

    Code (CSharp):
    1. using UnityEngine;
    2. public class ProceduralSphereTessellation : MonoBehaviour
    3. {
    4.     public Shader ProceduralSphereTessellationShader;
    5.     [Range(0f, 10f)] public float Radius = 2.0f;
    6.     [Range(1, 256)] public int TessellationFactor = 64;
    7.     private Material _Material;
    8.  
    9.     void Start()
    10.     {
    11.         _Material = new Material(ProceduralSphereTessellationShader);
    12.     }
    13.  
    14.     void OnRenderObject()
    15.     {
    16.         _Material.SetPass(0);
    17.         _Material.SetFloat("_Radius", Radius);
    18.         _Material.SetFloat("_TessellationFactor", TessellationFactor);
    19.         Graphics.DrawProceduralNow(MeshTopology.Triangles, TessellationFactor * TessellationFactor * 6, 1);
    20.     }
    21.  
    22.     void OnDestroy()
    23.     {
    24.         Destroy(_Material);
    25.     }
    26. }
    Code (CSharp):
    1. Shader "ProceduralSphereTessellation"
    2. {
    3.     Subshader
    4.     {
    5.         Pass
    6.         {
    7.             CGPROGRAM
    8.             #pragma vertex VSMain
    9.             #pragma fragment PSMain
    10.             #pragma target 5.0
    11.  
    12.             float _Radius;
    13.             uint _TessellationFactor;
    14.  
    15.             float Mod (float x, float y)
    16.             {
    17.                 return x - y * floor(x/y);
    18.             }
    19.  
    20.             void GenerateSphere (uint id : SV_VertexID, float radius, uint tess, out float3 position, out float3 normal, out float2 texcoord)
    21.             {
    22.                 int instance = int(floor(id / 6.0));
    23.                 float x = sign(Mod(20.0, Mod(float(id), 6.0) + 2.0));
    24.                 float y = sign(Mod(18.0, Mod(float(id), 6.0) + 2.0));
    25.                 float u = (float(instance / tess) + x) / float(tess);
    26.                 float v = (Mod(float(instance), float(tess)) + y) / float(tess);
    27.                 float pi = 3.14159265359;
    28.                 float a = sin(pi * u) * cos(2.0 * pi * v);
    29.                 float b = sin(pi * u) * sin(2.0 * pi * v);
    30.                 float c = cos(pi * u);
    31.                 position = float3(a, b, c) * radius;
    32.                 normal = normalize(position);
    33.                 texcoord = float2(u, v);
    34.             }
    35.  
    36.             float4 VSMain (uint vertexId : SV_VertexID, out float3 normal : NORMAL, out float2 texcoord : TEXCOORD0) : SV_POSITION
    37.             {
    38.                 float3 position = 0;
    39.                 GenerateSphere (vertexId, _Radius, _TessellationFactor, position, normal, texcoord);
    40.                 return UnityObjectToClipPos(float4(position, 1.0));
    41.             }
    42.  
    43.             float4 PSMain (float4 vertex : SV_POSITION, float3 normal : NORMAL, float2 texcoord : TEXCOORD0) : SV_Target
    44.             {
    45.                 return float4(max(dot(normalize(_WorldSpaceLightPos0).xyz, normal),0.0).xxx + UNITY_LIGHTMODEL_AMBIENT, 1.0);
    46.             }
    47.             ENDCG
    48.         }
    49.     }
    50. }
     
    MrPapayaMan likes this.