GPU Dissolve(GPU 消散)学习GPU Instancing

一:摘要

        通过制作一个模型GPU消散效果来学习GPU Instancing 也就是实例化。

目标效果是杨超大佬文章《GPU shatter》里面的消散效果如图:

 Tags:模型顶点分裂(Mesh Vertex Splitting), 实例化绘制(GPU Instancing Drawing),顶点运动(Vertex Anim)。

二:实现原理简述

1:构建获取数据:(instancing数据及模型信息)

        instancing数据需要的M矩阵,及自己想要传递的信息。

        鹿模型mesh的顶点信息(mesh.vertices)和索引信息(mesh.triangles)以及面数(N)等信息,通过computerBuffer传递给材质。

2:构建instancing用的Triangle mesh(uv and vertices)
3:Render

        正常render鹿模型。

        通过instancing绘制三角面,数量位置等信息已通过鹿模型获取并传递,M矩阵也构建,隐藏可以得到另一个鹿模型。

4:构建动画(compute shader anim  or vertex anim)

        最简单的就是使用vertex anim顶点动画。方便易懂。

        compute shader动画复杂一点但是性能应该会更好。

5:调参

        把效果跳的稍微能看一点

三:实现

1:获取模型数据
  第一步:构建instaning数据(M矩阵构建)
    //创建对应结构体
    private struct MeshProperties
    {
        public Matrix4x4 drawMeshInsM;
    }
    //在初始化时构建M矩阵
    void OnEnable()
    {
        //num为面数
        for (int i = 0; i < num; i++)
        {
            Vector3 pos = commonDrawGO.transform.position;
            pos.x = -pos.x;
            Quaternion rotation = commonDrawGO.transform.rotation;
            Vector3 scale = commonDrawGO.transform.localScale;
            //通过Transform信息构建对应模型
            tmpProperties.drawMeshInsM = Matrix4x4.TRS(pos, rotation, scale);

            properties[i] = tmpProperties;
        }
        // 通过computeBuffer传参给Material
        //(使用computeBuffer是因为之前写的用到了CS)
        meshPropertiesBuffer = new ComputeBuffer(num, meshPropertiesSize);
        meshPropertiesBuffer.SetData (properties);
        GPUDrawMat.SetBuffer("_Properties", meshPropertiesBuffer);
    }
第二步:构建mesh数据(顶点等)

需要获取mesh数据如下

索引数量args[0](除3就是面数)

起始索引值args[3]等

(后面我没用到,因为实验用的mesh索引从0开始不需要这些数据)

        //mesh起始索引等信息
        uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
        args[0] = (uint) mesh.GetIndexCount(0);
        args[1] = (uint) num;
        args[2] = (uint) mesh.GetIndexStart(0);
        args[3] = (uint) mesh.GetBaseVertex(0);

        //vertices
        GPUDrawMat.SetBuffer("_Properties", meshPropertiesBuffer);
        meshVerticesBuffer =
            new ComputeBuffer(TargetMesh.vertexCount, sizeof(float) * 3);
        meshVerticesBuffer.SetData(TargetMesh.vertices);
        GPUDrawMat.SetBuffer("_Vertices", meshVerticesBuffer);


        //triangles 
        meshindicesBuffer =
            new ComputeBuffer(TargetMesh.triangles.Length, sizeof(int));
        meshindicesBuffer.SetData(TargetMesh.triangles);
        GPUDrawMat.SetBuffer("_Indices", meshindicesBuffer);
       2:构建Triangle

构建triangle时为了实现边线亮中间暗淡效果,同时为了解决边界锯齿以及边界线不等宽问题对uv进行了设计。看采样贴图及很好理解。

构建等边三角形以及渐变贴图解决(图片是求美术大佬用sp生成的)

(贴图信息演示及原图)

uv信息其实和顶点位置是一样的,但是顶点位置原点在三角形中心,顶点uv在左下角。

private Mesh CreateTriMesh()
    {
        Mesh ans = new Mesh();
        //等边三角形三点位置
        Vector3[] vertices = new Vector3[3];
        vertices[0] = new Vector3(0, 0.134f, 0) - Vector3.one * 0.5f;
        vertices[1] = new Vector3(1, 0.134f, 0) - Vector3.one * 0.5f;
        vertices[2] = new Vector3(0.5f, 1, 0) - Vector3.one * 0.5f;
        //等边三角形三点UV
        Vector2[] uvs = new Vector2[3];
        uvs[0] = new Vector2(0, 0.134f);
        uvs[1] = new Vector2(1, 0.134f);
        uvs[2] = new Vector2(0.5f, 1);
        int[] indices = new int[3];
        indices[0] = 0;
        indices[1] = 1;
        indices[2] = 2;
        ans.vertices = vertices;
        ans.uv = uvs;
        ans.triangles = indices;

        return ans;
    }
3:Render
第一步:C++++端
//instancing绘制
Graphics.DrawMeshInstancedIndirect(mesh, 0, GPUDrawMat, bounds, argsBuffer);
//另外一个走默认渲染就行
第二步:shader端(Vert And Frag)

主要是通过instanceID和顶点id构建索引,然后去顶点信息数组获取位置信息

struct MeshProperties{
                float4x4 drawMeshInsM;
            };
StructuredBuffer<MeshProperties> _Properties;
            StructuredBuffer<float3> _Vertices;
            StructuredBuffer<int> _Indices;


                v2f vert(appdata_t i, uint instanceID: SV_InstanceID,uint vertexID : SV_VertexID) {

                //通过vertexID(0,1,2)和instanceID去_Vertices获取真实的顶点信息
                //然后再乘上对应的M矩阵。

                float4 pos = mul(_Properties[instanceID].drawMeshInsM,float4(_Vertices[_Indices[vertexID + instanceID * 3.0]]  - center,1));

                //
                }
4:构建动画及着色

这里直接以顶点动画为例,其实也写了computershader的但是写的有瑕疵

第一步:构建旋转函数(Rotate)

前面有提到原点再三角中心,所以先构建一个旋转函数

经典的构建旋转矩阵,先把点移动到原点,然后再乘以旋转函数,再移动回自己的位置

            void Rotate(inout float4 vertex, float3 center, float3 around, float angle)
            {
                float4x4 translation = float4x4(
                1, 0, 0, -center.x,
                0, 1, 0, -center.y,
                0, 0, 1, -center.z,
                0, 0, 0, 1);
                float4x4 translationT = float4x4(
                1, 0, 0, center.x,
                0, 1, 0, center.y,
                0, 0, 1, center.z,
                0, 0, 0, 1);

                around.x = -around.x;
                around = normalize(around);
                float s = sin(angle);
                float c = cos(angle);
                float ic = 1.0 - c;

                float4x4 rotation = float4x4(
                ic * around.x * around.x + c           , ic * around.x * around.y - s * around.z, ic * around.z * around.x + s * around.y, 0.0,
                ic * around.x * around.y + s * around.z, ic * around.y * around.y + c           , ic * around.y * around.z - s * around.x, 0.0,
                ic * around.z * around.x - s * around.y, ic * around.y * around.z + s * around.x, ic * around.z * around.z + c           , 0.0,
                0.0                                    , 0.0                                    , 0.0                                    , 1.0);

                vertex = mul(translationT, mul(rotation, mul(translation, vertex)));

                if((instanceID  + 1.0) % _BatchCount < _BatchCount * _Range)
                {
                    o.insID = 1;
                }
                else
                {
                    o.insID = 0;
                }
            }
第二步:构建位移动画(Pos And Scale)
                //构建中心点
                float3 center = _Vertices[_Indices[ instanceID * 3.0]] +_Vertices[_Indices[ instanceID * 3.0 + 1]] + _Vertices[_Indices[ instanceID * 3.0 + 2]];
                center /=3;
                //构建位置
                float4 pos = mul(_Properties[instanceID].drawMeshInsM,float4(_Vertices[_Indices[vertexID + instanceID * 3.0]]  - center,1));
                float4  pos1 = float4(_Vertices[_Indices[vertexID + instanceID * 3.0]],1);
                float3 around = normalize(GetRandomF3(pos.xyz));//float3(0.0,1.0,0.0);
                //动画时间数据
                float statyTime = 0.4;
                float offsetIntensity = saturate((_BatchCount * _Range - (instanceID + 1.0)%_BatchCount)/10 -statyTime);
                float offsetIntensity1 = max(-statyTime,((_BatchCount * _Range - (instanceID + 1.0)%_BatchCount)/10 - statyTime)) + statyTime;
                offsetIntensity1 = min(offsetIntensity1 * 3 ,1.0);
                o.alphaLerp = offsetIntensity1;
                pos1.xyz = (1 - offsetIntensity) * pos1.xyz + offsetIntensity * center;
                float angle = _Speed * offsetIntensity;
                
                float3 positionWS = pos1;
                float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - positionWS);
                //around = viewDir;
                Rotate(pos1,center,around,angle );
                pos1 = mul(_Properties[instanceID].drawMeshInsM,pos1);
                pos1.y += offsetIntensity * _FlowSpeed * 0.1;

第四步:着色

没有技巧全是smoothstep出来(按理不该这么做,性能很差)

                //frag  
                //使用insID来表示当前Tri是否还需要显示是否消失
                half4 frag(v2f i, uint instanceID: SV_InstanceID) : SV_Target {
                float insID = i.insID;

                if(insID > 0.9)
                {
                
                    fixed4 col = tex2D(_MainTex, i.uv);
                    float uuu1 = smoothstep(_Pos - _Width * 0.5 - _SmoothRange,_Pos - _Width * 0.5,col.r);
                    float uuu2 = 1 - smoothstep(_Pos + _Width * 0.5 ,_Pos + _Width * 0.5+ _SmoothRange,col.r);
                    float lines = uuu1 * uuu2;
                    float tris = saturate((uuu2 - uuu1) * uuu2);
                    return (lines * _LineColor + tris * _TriColor) * i.alphaLerp;
                }
                return 0;
                
            }
5:调参

        略

四:总结

        通过对模型进行拆分使用instancing进行重绘制,对模型数据结构以及instancing做了简单了解,还有用到的顶点动画较为简单,以及有很多可以优化的地方,比如M矩阵其实都是一样的,有些位置数据是没用的可以省略等等等。

后续会补上源代码链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!关于Unity的Shader消散效果,我可以给你一些指导。 在Unity中,你可以使用自定义的Shader来实现消散效果。消散效果通常基于一个纹理,并且通过渐变将纹理逐渐消除。 首先,你需要创建一个自定义的Shader。在Shader的片段着色器中,你可以使用插值的纹理坐标来获取纹理颜色。然后,可以根据需要添加一些控制消散效果的参数。 接下来,你可以使用一个控制器(比如时间或距离)来改变纹理坐标,从而实现纹理的渐变效果。通过改变纹理坐标,你可以将纹理从中心点向四周逐渐消失。 以下是一个简单的示例代码,用于实现基本的消散效果: ```csharp Shader "Custom/DissolveShader" { Properties { _MainTex ("Texture", 2D) = "white" {} _DissolveAmount ("Dissolve Amount", Range(0, 1)) = 0 } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; float _DissolveAmount; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag(v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); col.a *= smoothstep(_DissolveAmount - 0.1, _DissolveAmount + 0.1, i.uv.x); return col; } ENDCG } } } ``` 在这个示例代码中,_DissolveAmount 参数控制了消散的程度。你可以根据需要调整这个值来实现不同的效果。 希望这些信息对你有所帮助!如果你有任何其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值