在 Unity 中使用 Shells 技术实现 Fur

Implementing Fur with Shells technique in Unity

我正在尝试使用 Shells technique 在 Unity 中实现毛发。 Fins 技术被故意排除在外,因为我希望它在低端手机(主要是 Android 设备)上 运行 并且需要 OpenGL ES 3.0 及更高版本,而 Shell技术只需要OpenGL ES 2.0.

有一个基于 XNA 的 Shell 技术示例,我尝试将其移植到 Unity 中,但没有成功。 Here 是 XNA 项目的文章。

XNA 着色器:

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();
    }
}

控制着色器的XNAC#脚本:

/// <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);
        }
    }

}



我小心地将着色器和控制脚本逐行移植到Unity。

移植的 Unity 着色器:

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
    }
    }
}

移植的Unity控制着色器的C#脚本:

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);
    }
}

Unity

移植的 VertexPositionNormalTexture 结构
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));
    }
}

移植的Unity工作不正常。没有贝壳,输出图像是平的。

这是 预期的 结果XNA(工作正常):

但这是我在 Unity 中看到的(无壳):

最终图像应该如下图所示,但我无法继续进行移植工作,因为基本实现无法在 Unity 中正常工作。

我的脚本public变量设置:

为什么移植后的Unity结果是平的?我错过了什么吗?

编辑:

提到了可能的backface问题,因为Unity使用的是左手坐标系,而XNA使用的是右手坐标系。

我翻转了 UnitZ() 值并尝试反转网格顶点,但屏幕上没有任何内容。 这不太可能是问题所在。

Unity 正在对 material 进行批量优化。您可以在帧调试器中看到这一点。每个 DrawGeometry 调用都使用相同的 CurrentLayer 值。每次调用 DrawMesh 都需要使用属性块。 设置新的 material 会导致一些闪烁。

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);
    }
}
}

我还修改了着色器以可视化外壳。

// 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
    }
}

}

这是在修改参数并稍微移动相机后的样子。