2
\$\begingroup\$

I'm attempting to make a shader for unity3d web project. I want to implement something like in the great answer by DMGregory in this question. in order to achieve a final look something like this.. enter image description here

Its metaballs with specular and shading.
The steps to make this shader are.
1. Convert the feathered blobs into a heightmap.
2. Generate a normalmap from the heightmap
3. Feed the normal map and height map into a standard unity shader, for instance transparent parallax specular.

I pretty much have all the pieces I need assembled but I am new to shaders and need help putting them together

I can generate a heightmap from the blobs using some fragment shader code I wrote (I'm just using the red channel here cus i dont know if you can access the brightness)

    half4 frag (v2f i) : COLOR{     
        half4 texcol,finalColor;
        texcol = tex2D (_MainTex, i.uv);        
        finalColor=_MyColor;
        if(texcol.r<_botmcut)
        {
            finalColor.r= 0;  
        }
        else if((texcol.r>_topcut))
        {
            finalColor.r= 0;
        }
        else
        {
            float r = _topcut-_botmcut; 
            float xpos = _topcut - texcol.r;

            finalColor.r= (_botmcut + sqrt((xpos*xpos)-(r*r)))/_constant;
        }           
        return finalColor;
    }

turns these blobs.. enter image description here

into this heightmap enter image description here

Also I've found some CG code that generates a normal map from a height map. The bit of code that makes the normal map from finite differences is here

    void surf (Input IN, inout SurfaceOutput o)
                {
                    o.Albedo = fixed3(0.5);

                    float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));

                    float me = tex2D(_HeightMap,IN.uv_MainTex).x;
                    float n = tex2D(_HeightMap,float2(IN.uv_MainTex.x,IN.uv_MainTex.y+1.0/_HeightmapDimY)).x;
                    float s = tex2D(_HeightMap,float2(IN.uv_MainTex.x,IN.uv_MainTex.y-1.0/_HeightmapDimY)).x;
                    float e = tex2D(_HeightMap,float2(IN.uv_MainTex.x-1.0/_HeightmapDimX,IN.uv_MainTex.y)).x;
                    float w = tex2D(_HeightMap,float2(IN.uv_MainTex.x+1.0/_HeightmapDimX,IN.uv_MainTex.y)).x;

                    float3 norm = normal;
                    float3 temp = norm; //a temporary vector that is not parallel to norm
                    if(norm.x==1)
                        temp.y+=0.5;
                    else
                        temp.x+=0.5;

                    //form a basis with norm being one of the axes:
                    float3 perp1 = normalize(cross(norm,temp));
                    float3 perp2 = normalize(cross(norm,perp1));

                    //use the basis to move the normal in its own space by the offset
                    float3 normalOffset = -_HeightmapStrength * ( ( (n-me) - (s-me) ) * perp1 + ( ( e - me ) - ( w - me ) ) * perp2 );
                    norm += normalOffset;
                    norm = normalize(norm);

                    o.Normal = norm;
                }

Also here is the built-in transparent parallax specular shader for unity.

    Shader "Transparent/Parallax Specular" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 0)
        _Shininess ("Shininess", Range (0.01, 1)) = 0.078125
        _Parallax ("Height", Range (0.005, 0.08)) = 0.02
        _MainTex ("Base (RGB) TransGloss (A)", 2D) = "white" {}
        _BumpMap ("Normalmap", 2D) = "bump" {}
        _ParallaxMap ("Heightmap (A)", 2D) = "black" {}
    }

    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        LOD 600

    CGPROGRAM
    #pragma surface surf BlinnPhong alpha
    #pragma exclude_renderers flash

    sampler2D _MainTex;
    sampler2D _BumpMap;
    sampler2D _ParallaxMap;
    fixed4 _Color;
    half _Shininess;
    float _Parallax;

    struct Input {
        float2 uv_MainTex;
        float2 uv_BumpMap;
        float3 viewDir;
    };

    void surf (Input IN, inout SurfaceOutput o) {
        half h = tex2D (_ParallaxMap, IN.uv_BumpMap).w;
        float2 offset = ParallaxOffset (h, _Parallax, IN.viewDir);
        IN.uv_MainTex += offset;
        IN.uv_BumpMap += offset;

        fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
        o.Albedo = tex.rgb * _Color.rgb;
        o.Gloss = tex.a;
        o.Alpha = tex.a * _Color.a;
        o.Specular = _Shininess;
        o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
    }
    ENDCG
    }

    FallBack "Transparent/Bumped Specular"
    }
\$\endgroup\$
2
  • 2
    \$\begingroup\$ Your title is a bit broad, could you make it more specific ? With a detailed title, people having the same problem would be able to find your question and maybe the answer! \$\endgroup\$
    – Heckel
    Jun 4 '14 at 17:43
  • 1
    \$\begingroup\$ Aw, thank you for the kind review. :) If your blobs are moving around at runtime, this will require the use of a RenderTexture (a Pro-only feature) to gather the blobs into a texture (possibly then blitted to multiple textures, if you want to convert to standard diffuse/normal maps for use in a stock shader) as input for the final display shader. Are you familiar with using multiple cameras/rendering/blitting passes in Unity, or is this an area you need more information about? \$\endgroup\$
    – DMGregory
    Jun 4 '14 at 18:22
1
\$\begingroup\$

One way to do this is with OnPostRender and Graphics.Blit.

First, create a camera that views the input blobs, set its depth so it renders before your other cameras, and attach to it a script something like this one:

public class TextureExtractor : MonoBehaviour
{
  public RenderTexture normalMap;
  public RenderTexture diffuseMap;

  public Material normalExtractionMaterial;
  public Material diffuseExtractionMaterial;

  private RenderTexture _heightfield;

  void Start()
  {  
     // Here I assume your blobs have only one channel of information, and that you want high precision/range (floating point format)   
     _heightfield = new RenderTexture(normalMap.width, normalMap.height, 0, RenderTextureFormat.RFloat);

     camera.targetTexture = _heightfield;
     camera.aspect = _heightfield.width/(float)_heightfield.height;

     // This allows floating point rendering outside the 0-1 range, reducing banding and plateaus in the heightmap.
     camera.hdr = true;
  }

  // This gets called each time the camera to which the script is attached finishes rendering its view.
  void OnPostRender()
  {
     // Uses the normalExtractionMaterial's shader to populate normalMap from _heightfield.
     Graphics.Blit(_heightfield, normalMap, normalExtractionMaterial);

     // Uses the diffuseExtractionMaterial's shader to populate diffuseMap from _heightfield.
     Graphics.Blit(_heightfield, diffuseMap, diffuseExtractionMaterial);
  }
}

Create two new RenderTextures in your Assets folder for the normal and diffuse. Then create materials with the shaders you want to use to generate the normals & diffuse out of the heightmap. Assign all of these assets to the TextureExtractor instance on your blob-viewing camera.

You can then re-use the normal & diffuse RenderTextures as texture inputs on other materials elsewhere in your scene.

What's happening under the hood is that Graphics.Blit sets the input texture (here, _heightfield) as the shader's _MainTex, and then renders a full-screen quad into the destination texture using this shader. (For example, if the shader is UnlitTexture, this has the effect of copying one texture's contents into the other, up/down-sampling as needed). The destination texture's contents are now updated for any other materials that are referencing it.

You can chain these blits as deep as you need, if you want to process the blobs into an intermediate texture (say, a blurred version to smooth it out), then transform that into a different intermediate (say, a scaled/thresholded version to get the contours you want), and then finally build your output textures out of the end of that chain.

For intermediate processes where the texture format & resolution doesn't change, OnRenderImage(src, dest) may also be useful - it's designed for doing one blit per call, sequentially, using the previous output as the next input. It gets called after rendering the scene is complete, but before OnPostRender, so it's the typical home for postprocess image effects.

\$\endgroup\$
3
  • \$\begingroup\$ Hey thanks for another great answer! I will try this out in a bit and get back with the results. I'm working on somethign else atm. I have one query though. Is it not possible to do all this work in one shader? and shouldnt that perform a lot better as it would avoid making lots of calls to the cpu like graphics.blit (which I've heard is fairly demanding in terms of performance) \$\endgroup\$ Jun 10 '14 at 15:49
  • \$\begingroup\$ Most of this can be done with one shader. I broke it into multiple above because you asked how to "Feed the normal map and height map into a standard unity shader" - which requires generating separate maps in custom shaders first. Even if you combine these, one step that does require a separate pass is gathering all of the blobs into a heightfield. The math after that point depends on the COMBINED height in the field, not just the height of a single blob. This is what allows blobs to merge together like water droplets. So you will typically need a separate heightfield gather & render pass. \$\endgroup\$
    – DMGregory
    Jun 10 '14 at 17:29
  • \$\begingroup\$ ...with one caveat: if you have a small collection of blobs, or blobs that follow some statistical pattern, you may be able to compute the sum of blobs procedurally within a single-pass shader. Doing this restricts your freedom to independently control the blobs from gameplay code, though. The shader will mostly steer them according to its own mathematical rules. \$\endgroup\$
    – DMGregory
    Jun 10 '14 at 17:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .