代码之家  ›  专栏  ›  技术社区  ›  Programmer

统一实现带壳毛皮技术

  •  9
  • Programmer  · 技术社区  · 6 年前

    我正在努力实现毛皮与 Shells technique . Fins技术被有意地忽略了,因为我希望它能在低端手机(主要是Android设备)上运行,这需要 更重要的是 炮弹技术 只需要 OpenGL ES 2.0 .

    有一个基于XNA的Shell技术的例子,我尝试将它移植到Unity中,但是失败了。 Here 是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();
        }
    }
    

    XNA公司 控制着色器的C脚本:

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



    门是开着的 着色器:

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

    门是开着的 团结

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

    门是开着的 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));
        }
    }
    

    预期 XNA公司 (工作正常):

    enter image description here

    但这就是我看到的 团结

    enter image description here

    最后的图片应该看起来像下面的图片,但我不能继续移植工作,因为基本的实现

    enter image description here

    我的脚本公共变量设置:

    enter image description here

    编辑:

    Leo 背面

    我翻了一下桌子 UnitZ() 值并尝试反转网格顶点,但屏幕上没有任何内容。

    1 回复  |  直到 6 年前
        1
  •  13
  •   Leo Bartkus    6 年前

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

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

    }

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