Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing Fur with Shells technique in Unity

I am trying to implement fur in Unity with the Shells technique. The Fins technique is purposely left out because I want this to run on low end mobiles (mostly Android devices) and that requires OpenGL ES 3.0 and above while Shells technique only requires OpenGL ES 2.0.

There is an example on the Shell technique based on XNA and I made an attempt to port that into Unity but it failed to work. Here is the article with the XNA project.

The XNA shader:

float4x4 World;
float4x4 View;
float4x4 Projection;

float CurrentLayer; //value between 0 and 1
float MaxHairLength; //maximum hair length

texture FurTexture;
sampler FurSampler = sampler_state
{
    Texture = (FurTexture);
    MinFilter = Point;
    MagFilter = Point;
    MipFilter = Point;
    AddressU = Wrap;
    AddressV = Wrap;
};


struct VertexShaderInput
{
    float3 Position : POSITION0;
    float3 Normal : NORMAL0;
    float2 TexCoord : TEXCOORD0;
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};

VertexShaderOutput FurVertexShader(VertexShaderInput input)
{
    VertexShaderOutput output;
    float3 pos;
    pos = input.Position + input.Normal * MaxHairLength * CurrentLayer;

    float4 worldPosition = mul(float4(pos,1), World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);

    output.TexCoord = input.TexCoord;
    return output;
}

float4 FurPixelShader(VertexShaderOutput input) : COLOR0
{
    return tex2D(FurSampler, input.TexCoord);
}

technique Fur
{
    pass Pass1
    {
        AlphaBlendEnable = true;
        SrcBlend = SRCALPHA;
        DestBlend = INVSRCALPHA;
        CullMode = None;

        VertexShader = compile vs_2_0 FurVertexShader();
        PixelShader = compile ps_2_0 FurPixelShader();
    }
}

The XNA C# script that controls the shader:

/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    //simple camera for use in the game
    Camera camera;
    //texture containing fur data
    Texture2D furTexture;
    //effect for fur shaders
    Effect furEffect;
    //number of layers of fur
    int nrOfLayers = 60;
    //total length of the hair
    float maxHairLength = 2.0f;
    //density of hair
    float density = 0.2f;
    Texture2D furColorTexture;

    //movement vectors
    Vector3 gravity = new Vector3(0, -1.0f, 0);
    Vector3 forceDirection = Vector3.Zero;
    //final displacement for hair
    Vector3 displacement;


    /// <summary>
    /// Allows the game to perform any initialization it needs to before starting to run.
    /// This is where it can query for any required services and load any non-graphic
    /// related content.  Calling base.Initialize will enumerate through any components
    /// and initialize them as well.
    /// </summary>
    protected override void Initialize()
    {
        // TODO: Add your initialization logic here
        camera = new Camera(this);
        Components.Add(camera);
        base.Initialize();
    }

    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
        //generate the geometry
        GenerateGeometry();
        //load the effect
        furEffect = Content.Load<Effect>("FurEffect");
        //create the texture
        furTexture = new Texture2D(GraphicsDevice,
                                                    256, 256, 1,
                                                    TextureUsage.None,
                                                    SurfaceFormat.Color);
        //fill the texture
        FillFurTexture(furTexture, density);
        furColorTexture = Content.Load<Texture2D>("bigtiger");
    }

    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// all content.
    /// </summary>
    protected override void UnloadContent()
    {
        // TODO: Unload any non ContentManager content here
    }

    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Update(GameTime gameTime)
    {
        // Allows the game to exit
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        // TODO: Add your update logic here

        base.Update(gameTime);
    }

    /// <summary>
    /// This is called when the game should draw itself.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Draw(GameTime gameTime)
    {
        forceDirection.X = (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds) * 0.5f;
        displacement = gravity + forceDirection;
        furEffect.Parameters["Displacement"].SetValue(displacement);

        graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

        furEffect.Parameters["World"].SetValue(Matrix.CreateTranslation(0, -10, 0));
        furEffect.Parameters["View"].SetValue(camera.View);
        furEffect.Parameters["Projection"].SetValue(camera.Projection);
        furEffect.Parameters["MaxHairLength"].SetValue(maxHairLength);
        furEffect.Parameters["FurTexture"].SetValue(furTexture);
        furEffect.Parameters["Texture"].SetValue(furColorTexture);

        furEffect.Begin();
        for (int i = 0; i < nrOfLayers; i++)
        {
            furEffect.Parameters["CurrentLayer"].SetValue((float)i / nrOfLayers);
            furEffect.CommitChanges();
            furEffect.CurrentTechnique.Passes[0].Begin();
            DrawGeometry();
            furEffect.CurrentTechnique.Passes[0].End();
        }
        furEffect.End();

        base.Draw(gameTime);
    }

    /// <summary>
    /// This functions prepares a texture to be used for fur rendering
    /// </summary>
    /// <param name="furTexture">This will contain the final texture</param>
    /// <param name="density">Hair density in [0..1] range </param>
    private void FillFurTexture(Texture2D furTexture, float density)
    {
        //read the width and height of the texture
        int width = furTexture.Width;
        int height = furTexture.Height;
        int totalPixels = width * height;

        //an array to hold our pixels
        Color[] colors;
        colors = new Color[totalPixels];

        //random number generator
        Random rand = new Random();

        //initialize all pixels to transparent black
        for (int i = 0; i < totalPixels; i++)
            colors[i] = Color.TransparentBlack;

        //compute the number of opaque pixels = nr of hair strands
        int nrStrands = (int)(density * totalPixels);

        //compute the number of strands that stop at each layer
        int strandsPerLayer = nrStrands / nrOfLayers;

        //fill texture with opaque pixels
        for (int i = 0; i < nrStrands; i++)
        {
            int x, y;
            //random position on the texture
            x = rand.Next(height);
            y = rand.Next(width);

            //compute max layer
            int max_layer = i / strandsPerLayer;
            //normalize into [0..1] range
            float max_layer_n = (float)max_layer / (float)nrOfLayers;

            //put color (which has an alpha value of 255, i.e. opaque)
            //max_layer_n needs to be multiplied by 255 to achieve a color in [0..255] range
            colors[x * width + y] = new Color((byte)(max_layer_n * 255), 0, 0, 255);
        }

        //set the pixels on the texture.
        furTexture.SetData<Color>(colors);
    }


    VertexPositionNormalTexture[] vertices;

    private void GenerateGeometry()
    {
        vertices = new VertexPositionNormalTexture[6];
        vertices[0] = new VertexPositionNormalTexture(
                                                                    new Vector3(-10, 0, 0),
                                                                    -Vector3.UnitZ,
                                                                    new Vector2(0, 0));
        vertices[1] = new VertexPositionNormalTexture(
                                                                    new Vector3(10, 20, 0),
                                                                    -Vector3.UnitZ,
                                                                    new Vector2(1, 1));
        vertices[2] = new VertexPositionNormalTexture(
                                                                    new Vector3(-10, 20, 0),
                                                                    -Vector3.UnitZ,
                                                                    new Vector2(0, 1));

        vertices[3] = vertices[0];
        vertices[4] = new VertexPositionNormalTexture(
                                                                    new Vector3(10, 0, 0),
                                                                    -Vector3.UnitZ,
                                                                    new Vector2(1, 0));
        vertices[5] = vertices[1];
    }

    private void DrawGeometry()
    {
        using (VertexDeclaration vdecl = new VertexDeclaration(
                                                                    GraphicsDevice,
                                                                    VertexPositionNormalTexture.VertexElements))
        {
            GraphicsDevice.VertexDeclaration = vdecl;
            GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList, vertices, 0, 2);
        }
    }

}



I carefully ported the both the shader and the control script line by line to Unity.

The Ported Unity shader:

Shader "Programmer/Fur Shader"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    //_TintColor("Tint Color", Color) = (1,1,1,1)
    }
        SubShader
    {
        Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
        LOD 100
        Blend SrcAlpha One
        Blend DstAlpha OneMinusSrcAlpha
        ZWrite Off
        Cull Off

        Pass
    {
        CGPROGRAM
#pragma vertex vert
#pragma fragment frag
        // make fog work
        //#pragma multi_compile_fog

#include "UnityCG.cginc"

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

    //Out
    struct v2f
    {
        float2 uv : TEXCOORD0;
        UNITY_FOG_COORDS(1)
            float4 vertex : SV_POSITION;
    };

    struct VertexShaderInput
    {
        float3 Position : POSITION0;
        float3 Normal : NORMAL0;
        float2 TexCoord : TEXCOORD0;
    };

    struct VertexShaderOutput
    {
        float4 Position : POSITION0;
        float2 TexCoord : TEXCOORD0;
    };

    sampler2D _MainTex;
    float4 _MainTex_ST;

    //Test variable/delete after
    float4 _TintColor;

    //The variables
    float4x4 World;
    float4x4 View;
    float4x4 Projection;

    float CurrentLayer; //value between 0 and 1
    float MaxHairLength; //maximum hair length

    VertexShaderOutput vert(VertexShaderInput input)
    {
        VertexShaderOutput output;
        float3 pos;
        pos = input.Position + input.Normal * MaxHairLength * CurrentLayer;

        float4 worldPosition = mul(float4(pos, 1), World);
        float4 viewPosition = mul(worldPosition, View);
        output.Position = mul(viewPosition, Projection);

        output.TexCoord = input.TexCoord;
        return output;
    }

    float4 frag(VertexShaderOutput  i) : COLOR0
    {
        return tex2D(_MainTex,  i.TexCoord);
    }
        ENDCG
    }
    }
}

The ported Unity C# script that controls the shader:

public class Game1 : MonoBehaviour
{
    public Material material;


    public Vector3 pos = new Vector3(0f, 0.98f, -9.54f);


    //simple camera for use in the game
    private new Camera camera;
    //texture containing fur data
    public Texture2D furTexture;
    //effect for fur shaders
    //Effect furEffect;
    //number of layers of fur
    public int nrOfLayers = 40;
    //total length of the hair
    public float maxHairLength = 2.0f;
    //density of hair
    public float density = 0.2f;

    //[Space(20)]
    //public Vector3 dirWorldVal = new Vector3(0, -10, 0);

    void Start()
    {
        Initialize();
        GenerateGeometry();
    }

    public void Update()
    {
        Draw();
    }


    void Initialize()
    {

        //Initialize the camera
        camera = Camera.main;

        //create the texture
        furTexture = new Texture2D(256, 256, TextureFormat.ARGB32, false);
        furTexture.wrapModeU = TextureWrapMode.Repeat;
        furTexture.wrapModeV = TextureWrapMode.Repeat;
        furTexture.filterMode = FilterMode.Point;

        //fill the texture
        FillFurTexture(furTexture, density);

        /*XNA's SurfaceFormat.Color is ARGB.
        //https://gamedev.stackexchange.com/a/6442/98839*/


        if (material.mainTexture != null)
        {
            material.mainTexture.wrapModeU = TextureWrapMode.Repeat;
            material.mainTexture.wrapModeV = TextureWrapMode.Repeat;
            material.mainTexture.filterMode = FilterMode.Point;
        }
    }

    bool firstDraw = true;

    protected void Draw()
    {
        camera.backgroundColor = CornflowerBlue();

        Matrix4x4 worldValue = Matrix4x4.Translate(pos);
        Matrix4x4 viewValue = camera.projectionMatrix;
        // viewValue = camera.worldToCameraMatrix;
        Matrix4x4 projectionValue = camera.projectionMatrix;

        material.SetMatrix("World", worldValue);
        material.SetMatrix("View", viewValue);
        material.SetMatrix("Projection", projectionValue); //Causes object to disappear

        material.SetFloat("MaxHairLength", maxHairLength);

        if (firstDraw)
            material.SetTexture("_MainTex", furTexture);

        //furEffect.Begin();
        for (int i = 0; i < nrOfLayers; i++)
        {
            material.SetFloat("CurrentLayer", (float)i / nrOfLayers);
            DrawGeometry();
        }

        if (firstDraw)
        {
            material.mainTexture.wrapModeU = TextureWrapMode.Repeat;
            material.mainTexture.wrapModeV = TextureWrapMode.Repeat;
            material.mainTexture.filterMode = FilterMode.Point;
        }

        if (firstDraw)
            firstDraw = false;
    }

    void DrawGeometry()
    {
        Quaternion rotation = Quaternion.Euler(0, 180, 0);
        Graphics.DrawMesh(verticesMesh, pos, rotation, material, 0, camera);
    }

    private VertexPositionNormalTexture[] verticesPText;
    public Mesh verticesMesh;

    private void GenerateGeometry()
    {
        verticesPText = new VertexPositionNormalTexture[6];
        verticesPText[0] = new VertexPositionNormalTexture(new Vector3(-10, 0, 0),
                                                      -UnitZ(),
                                                       new Vector2(0, 0));
        verticesPText[1] = new VertexPositionNormalTexture(new Vector3(10, 20, 0),
                                                       -UnitZ(),
                                                       new Vector2(1, 1));
        verticesPText[2] = new VertexPositionNormalTexture(new Vector3(-10, 20, 0),
                                                       -UnitZ(),
                                                       new Vector2(0, 1));

        verticesPText[3] = verticesPText[0];
        verticesPText[4] = new VertexPositionNormalTexture(new Vector3(10, 0, 0),
                                                       -UnitZ(),
                                                       new Vector2(1, 0));
        verticesPText[5] = verticesPText[1];

        verticesMesh = VertexPositionNormalTextureToUnityMesh(verticesPText);
    }

    Mesh VertexPositionNormalTextureToUnityMesh(VertexPositionNormalTexture[] vpnt)
    {
        Vector3[] vertices = new Vector3[vpnt.Length];
        Vector3[] normals = new Vector3[vpnt.Length];
        Vector2[] uvs = new Vector2[vpnt.Length];

        int[] triangles = new int[vpnt.Length];

        //Copy variables to create a mesh
        for (int i = 0; i < vpnt.Length; i++)
        {
            vertices[i] = vpnt[i].Position;
            normals[i] = vpnt[i].Normal;
            uvs[i] = vpnt[i].TextureCoordinate;

            triangles[i] = i;
        }

        Mesh mesh = new Mesh();
        mesh.vertices = vertices;
        mesh.normals = normals;
        mesh.uv = uvs;

        mesh.triangles = triangles;
        return mesh;
    }

    private void FillFurTexture(Texture2D furTexture, float density)
    {
        //read the width and height of the texture
        int width = furTexture.width;
        int height = furTexture.height;
        int totalPixels = width * height;

        //an array to hold our pixels
        Color32[] colors = new Color32[totalPixels];

        //random number generator
        System.Random rand = new System.Random();

        //initialize all pixels to transparent black
        for (int i = 0; i < totalPixels; i++)
            colors[i] = TransparentBlack();

        //compute the number of opaque pixels = nr of hair strands
        int nrStrands = (int)(density * totalPixels);

        //fill texture with opaque pixels
        for (int i = 0; i < nrStrands; i++)
        {
            int x, y;
            //random position on the texture
            x = rand.Next(height);
            y = rand.Next(width);
            //put color (which has an alpha value of 255, i.e. opaque)
            colors[x * width + y] = Gold();
        }

        //set the pixels on the texture.
        furTexture.SetPixels32(colors);
        // actually apply all SetPixels, don't recalculate mip levels
        furTexture.Apply();
    }

    Color32 TransparentBlack()
    {
        //http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_TransparentBlack
        Color32 color = new Color32(0, 0, 0, 0);
        return color;
    }

    Color32 Gold()
    {
        //http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_Gold
        Color32 color = new Color32(255, 215, 0, 255);
        return color;
    }

    Color32 CornflowerBlue()
    {
        //http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_CornflowerBlue
        Color32 color = new Color32(100, 149, 237, 255);
        return color;
    }

    public static Vector3 UnitZ()
    {
        return new Vector3(0f, 0f, 1f);
    }
}

The ported VertexPositionNormalTexture struct for Unity

public struct VertexPositionNormalTexture
{
    public Vector3 Position;
    public Vector3 Normal;
    public Vector2 TextureCoordinate;
    //public static readonly VertexDeclaration VertexDeclaration;
    public VertexPositionNormalTexture(Vector3 position, Vector3 normal, Vector2 textureCoordinate)
    {
        this.Position = position;
        this.Normal = normal;
        this.TextureCoordinate = textureCoordinate;
    }

    public override int GetHashCode()
    {
        // TODO: FIc gethashcode
        return 0;
    }

    public override string ToString()
    {
        return string.Format("{{Position:{0} Normal:{1} TextureCoordinate:{2}}}", new object[] { this.Position, this.Normal, this.TextureCoordinate });
    }

    public static bool operator ==(VertexPositionNormalTexture left, VertexPositionNormalTexture right)
    {
        return (((left.Position == right.Position) && (left.Normal == right.Normal)) && (left.TextureCoordinate == right.TextureCoordinate));
    }

    public static bool operator !=(VertexPositionNormalTexture left, VertexPositionNormalTexture right)
    {
        return !(left == right);
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }
        if (obj.GetType() != base.GetType())
        {
            return false;
        }
        return (this == ((VertexPositionNormalTexture)obj));
    }
}

The ported Unity work is not working properly. No shells and the output image is flat.

This is the expected result in XNA (Works fine):

enter image description here

But this is what I see in Unity (no shells):

enter image description here

The final image supposed to look like the image below but I can't go on with the porting work since the basic implementation is not working properly in Unity.

enter image description here

My script public variable settings:

enter image description here

Why is the the ported Unity result flat? Did I miss anything?

EDIT:

Leo mentioned about possible backface problem because Unity uses the left-handed coordinate system while XNA uses the right-handed coordinate system.

I flipped the UnitZ() value and also tried reversing the mesh vertices but there was nothing on the screen. This is not likely the issue.

like image 405
Programmer Avatar asked Oct 09 '18 13:10

Programmer


1 Answers

Unity is doing a batch optimization on the material. You can see this in the frame debugger. Each DrawGeometry call is using the same value for CurrentLayer. You need to use a propertyblock for each call to DrawMesh. Setting a new material causes some flickering.

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

namespace foo {
public class FurBehavior : MonoBehaviour
{
    public Material material;


    public Vector3 pos = new Vector3(0f, 0.98f, -9.54f);


    //simple camera for use in the game
    private new Camera camera;
    //texture containing fur data
    public Texture2D furTexture;
    //effect for fur shaders
    //Effect furEffect;
    //number of layers of fur
    public int nrOfLayers = 40;
    //total length of the hair
    public float maxHairLength = 2.0f;
    //density of hair
    public float density = 0.2f;

    //[Space(20)]
    //public Vector3 dirWorldVal = new Vector3(0, -10, 0);

    void Start()
    {
        this.transform.position = new Vector3(0f, 0.98f, -9.54f);
        this.transform.rotation = Quaternion.Euler(0, 180, 0);
        Initialize();
        GenerateGeometry();
    }

    public void Update()
    {
        Draw();

    }


    void Initialize()
    {

        //Initialize the camera
        camera = Camera.main;

        //create the texture
        furTexture = new Texture2D(256, 256, TextureFormat.ARGB32, false);
        furTexture.wrapModeU = TextureWrapMode.Repeat;
        furTexture.wrapModeV = TextureWrapMode.Repeat;
        //furTexture.filterMode = FilterMode.Point;

        //fill the texture
        FillFurTexture(furTexture, density);

        /*XNA's SurfaceFormat.Color is ARGB.
        //https://gamedev.stackexchange.com/a/6442/98839*/


        if (material.mainTexture != null)
        {
            material.mainTexture.wrapModeU = TextureWrapMode.Repeat;
            material.mainTexture.wrapModeV = TextureWrapMode.Repeat;
           // material.mainTexture.filterMode = FilterMode.Point;
        }
    }

    bool firstDraw = true;

    protected void Draw()
    {
        var pos = this.transform.position;

        camera.backgroundColor = CornflowerBlue();

        Matrix4x4 worldValue = Matrix4x4.Translate(pos);
        Matrix4x4 viewValue = camera.projectionMatrix;
        // viewValue = camera.worldToCameraMatrix;
        Matrix4x4 projectionValue = camera.projectionMatrix;

        material.SetMatrix("World", worldValue);
        material.SetMatrix("View", viewValue);
        material.SetMatrix("Projection", projectionValue); //Causes object to disappear

        material.SetFloat("MaxHairLength", maxHairLength);

        //if (firstDraw)
            material.SetTexture("_MainTex", furTexture);

        //furEffect.Begin();
        for (int i = 0; i < nrOfLayers; i++)
        {
            var propertyBlock = new MaterialPropertyBlock();

            var layer = (float)i / (float)nrOfLayers;
            propertyBlock.SetFloat("CurrentLayer", layer);
            propertyBlock.SetFloat("MaxHairLength", maxHairLength);
            propertyBlock.SetColor("_TintColor", new Color(layer, layer, layer, layer));
            DrawGeometry(propertyBlock);
        }

        if (firstDraw)
        {
            material.mainTexture.wrapModeU = TextureWrapMode.Repeat;
            material.mainTexture.wrapModeV = TextureWrapMode.Repeat;
            material.mainTexture.filterMode = FilterMode.Point;
        }

        if (firstDraw)
            firstDraw = false;
    }

    void DrawGeometry(MaterialPropertyBlock props)
    {
        var rot = Quaternion.Euler(0, 180, 0);
        Graphics.DrawMesh(verticesMesh, pos, rot, material, 0, camera, 0, props);
    }

    private VertexPositionNormalTexture[] verticesPText;
    public Mesh verticesMesh;

    private void GenerateGeometry()
    {
        var UnitZ = new Vector3(0, 0, 1);
        var verticesPText = new VertexPositionNormalTexture[6];
        verticesPText[5] = new VertexPositionNormalTexture(new Vector3(-10, 0, 0),
                                                    -UnitZ,
                                                    new Vector2(0, 0));
        verticesPText[4] = new VertexPositionNormalTexture(new Vector3(10, 20, 0),
                                                    -UnitZ,
                                                    new Vector2(1, 1));
        verticesPText[3] = new VertexPositionNormalTexture(new Vector3(-10, 20, 0),
                                                    -UnitZ,
                                                    new Vector2(0, 1));

        verticesPText[2] = verticesPText[5];
        verticesPText[1] = new VertexPositionNormalTexture(new Vector3(10, 0, 0),
                                                    -UnitZ,
                                                    new Vector2(1, 0));
        verticesPText[0] = verticesPText[4];

    }

    Mesh VertexPositionNormalTextureToUnityMesh(VertexPositionNormalTexture[] vpnt)
    {
        Vector3[] vertices = new Vector3[vpnt.Length];
        Vector3[] normals = new Vector3[vpnt.Length];
        Vector2[] uvs = new Vector2[vpnt.Length];

        int[] triangles = new int[vpnt.Length];

        //Copy variables to create a mesh
        for (int i = 0; i < vpnt.Length; i++)
        {
            vertices[i] = vpnt[i].Position;
            normals[i] = vpnt[i].Normal;
            uvs[i] = vpnt[i].TextureCoordinate;

            triangles[i] = i;
        }

        Mesh mesh = new Mesh();
        mesh.vertices = vertices;
        mesh.normals = normals;
        mesh.uv = uvs;

        mesh.MarkDynamic();


        mesh.triangles = triangles;
                    mesh.UploadMeshData(false);
        return mesh;
    }

    private void FillFurTexture(Texture2D furTexture, float density)
    {
        //read the width and height of the texture
        int width = furTexture.width;
        int height = furTexture.height;
        int totalPixels = width * height;

        //an array to hold our pixels
        Color32[] colors = new Color32[totalPixels];

        //random number generator
        System.Random rand = new System.Random();

        //initialize all pixels to transparent black
        for (int i = 0; i < totalPixels; i++)
            colors[i] = TransparentBlack();

        //compute the number of opaque pixels = nr of hair strands
        int nrStrands = (int)(density * totalPixels);

        //fill texture with opaque pixels
        for (int i = 0; i < nrStrands; i++)
        {
            int x, y;
            //random position on the texture
            x = rand.Next(height);
            y = rand.Next(width);
            //put color (which has an alpha value of 255, i.e. opaque)
           // colors[x * width + y] = new Color32((byte)255, (byte)x, (byte)y, (byte)255);
           colors[x * width + y] = Gold();
        }

        //set the pixels on the texture.
        furTexture.SetPixels32(colors);
        // actually apply all SetPixels, don't recalculate mip levels
        furTexture.Apply();
    }

    Color32 TransparentBlack()
    {
        //http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_TransparentBlack
        Color32 color = new Color32(0, 0, 0, 0);
        return color;
    }

    Color32 Gold()
    {
        //http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_Gold
        Color32 color = new Color32(255, 215, 0, 255);
        return color;
    }

    Color32 CornflowerBlue()
    {
        //http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_CornflowerBlue
        Color32 color = new Color32(100, 149, 237, 255);
        return color;
    }

    public static Vector3 UnitZ()
    {
        return new Vector3(0f, 0f, 1f);
    }
}
}

I also modified the shader to visualize the shells.

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 
'UnityObjectToClipPos(*)'

Shader "Programmer/Fur Shader"
{
Properties
{
    _MainTex("Texture", 2D) = "white" {}
_TintColor("Tint Color", Color) = (1,1,1,1)
}
SubShader
{
    Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
    LOD 100
    //Blend SrcAlpha One
    //Blend DstAlpha OneMinusSrcAlpha
    Blend SrcAlpha OneMinusSrcAlpha
    ZWrite Off
    Cull Off

    Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
                // make fog work
                //#pragma multi_compile_fog

        #include "UnityCG.cginc"

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

    //Out
        struct v2f
        {
            float2 uv : TEXCOORD0;
            UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
        };

        struct VertexShaderInput
        {
            float3 Position : POSITION0;
            float3 Normal : NORMAL0;
            float2 TexCoord : TEXCOORD0;
        };

        struct VertexShaderOutput
        {
            float4 Position : POSITION0;
            float2 TexCoord : TEXCOORD0;
            float4 Tint: COLOR1;
        };

        sampler2D _MainTex;
        float4 _MainTex_ST;

        //Test variable/delete after
        float4 _TintColor;

        //The variables
        float4x4 World;
        float4x4 View;
        float4x4 Projection;

        float CurrentLayer; //value between 0 and 1
        float MaxHairLength; //maximum hair length

        VertexShaderOutput vert(VertexShaderInput input)
        {
            VertexShaderOutput output;
            float3 pos;
            pos = input.Position + input.Normal * MaxHairLength * CurrentLayer;

            //float4 worldPosition = mul(float4(pos, 1), World);
            //float4 viewPosition = mul(worldPosition, View);
            output.Position = UnityObjectToClipPos(pos);

            output.TexCoord = input.TexCoord;
            output.Tint = float4(CurrentLayer, CurrentLayer, 0, 1);
            return output;
        }

        float4 frag(VertexShaderOutput  i) : COLOR0
        {
            float4 t = tex2D(_MainTex,  i.TexCoord) * i.Tint;
            return t;//float4(t, i.x, i.y, 1);
        }
        ENDCG
    }
}

}

Here's what it looks like after messing with the parameters and moving the camera a bit.

Looking at it from around {0, 0, -10}

like image 163
Leo Bartkus Avatar answered Nov 14 '22 09:11

Leo Bartkus