zoukankan      html  css  js  c++  java
  • 糍粑大叔的独游之旅-u3d中2D轮廓的生成(中)

    创建RTCamera

    先创建RTCamera,设置一个非常远的地方(99999,99999),在这里开展渲染到纹理的工作。当然,也可以通过culling mask来通过layer来保障只有target被渲染,但要单独设置layer,会比较麻烦,所有在非常远的地方,不会有其他对象,可以起到相同目的。可以根据实际情况灵活选择。
    这里写图片描述
    这里必须要先设置一个target texture,否则,u3d会将他当一般的摄像机处理。
    (这里吐槽一下,个人觉得,u3d在代码动态控制这一方面用的很别扭,基于场景和节点的方式,有时候很不灵活)

    创建rt

    需要提供rt的长和宽,因为轮廓纹理一定大于原始图像,建议对原始图像放大1.5-2倍作为rt的长和宽,我的游戏中,单位是很多sprite组合而成,所以,算的公共的bounds。

     // 根据输入的x和y,创建一个rt
     RenderTexture rt = new RenderTexture((int)x, (int)y, 16);
     rt.Create();
     GetComponent<Camera>().targetTexture = rt;
    
     // 调整cemara的范围以适应rt
     float cameraSize = y / 2;
     GetComponent<Camera>().orthographicSize = cameraSize;

    设置位置或layer,保证RTCamera只“看”目标对象

    保存原始位置和旋转。
    如果使用layer,这里用设置target的layer

    // 移动目标到摄像机处,保证只有目标在摄像机的范围内
    // 由于此用一个极远的位置,此处只有目标,所有可以不用设置layer
    Vector3 pos = transform.position;
    pos.z = 0;
    target.transform.position = pos;
    target.transform.eulerAngles = Vector3.zero;
    //target.layer = RTGenLayer;

    进行Solid渲染

    使用SolidShader进行渲染,SolidShader是一个无光照、无纹理计算,尽绘制白色的shader。在u3d中,使用ShaderLab的固定管线即可实现,但由于我的资源图像不那么规范,所以只能自己做一些特殊处理。
    除了以下设置和fragment外,没有什么特殊的:

    Cull Off
    Lighting Off
    ZWrite Off
    Blend Off

    fragment shader:

    fixed4 frag(v2f IN) : SV_Target
    {
        fixed4 c = tex2D (_MainTex,IN.texcoord);
        if( c.a > 0.2 )
            return fixed4(1,1,1,1);
        else
            return fixed4(0,0,0,0);
    }

    用SolidShader渲染

    m_SolidMat = new Material(Shader.Find("Outterline/Solid"));
    GetComponent<Camera>().RenderWithShader(m_SolidMat.shader, "");

    RenderWithShader可以外出一个独立的渲染,这里没有用到第二个参数(替换某些场景中的shader)
    渲染完成后,可以将target的位置和旋转还原

    对rt进行扩大

    这部分对于不熟悉render to texture的人来说比较复杂,standard assets和网上有很多例子,这里简单讲一下原理。

    RenderTexture buffer = RenderTexture.GetTemporary(rt.width, rt.height, 0);
    RenderTexture buffer2 = RenderTexture.GetTemporary(rt.width, rt.height, 0);
    Graphics.Blit(rt, buffer);
    
    bool oddEven = true;
    for (int i = 0; i < iterations; i++)
    {
        if (oddEven)
            _FourTapCone(buffer, buffer2, i, spread);
        else
            _FourTapCone(buffer2, buffer, i, spread);
        oddEven = !oddEven;
    }
    if (oddEven)
    {
        Graphics.Blit(rt, buffer, m_CutoffMaterial);
        Graphics.Blit(buffer, rt, m_CompositeMaterial);
    }
    else
    {
        Graphics.Blit(rt, buffer2, m_CutoffMaterial);
        Graphics.Blit(buffer2, rt, m_CompositeMaterial);
    }       
    
    RenderTexture.ReleaseTemporary(buffer);
    RenderTexture.ReleaseTemporary(buffer2);

    其中

    void _FourTapCone(RenderTexture source, RenderTexture dest, int iteration, float spread)
    {
          float off = 0.5f + iteration * spread;
          Graphics.BlitMultiTap(source, dest, m_BlurMaterial,
              new Vector2(off, off),
              new Vector2(-off, off),
              new Vector2(off, -off),
              new Vector2(-off, -off)
          );
    }

    过程为:
    1、创建2个等大小的临时rt:buffer和buffer2
    2、把rt给blit到buffer,blit可以认为是u3d里的rt到rt的像素复制
    3、buffer和buffer2交互做BlitMultiTap,我认为,对于BlitMultiTap不用太深究,它就是u3d做image effect的方法,设置了shader里的相关的参数。这个步骤的目的就是通过周围(由offset指定)的4个点来,来实现模糊。offset越大,采样距离越大,模糊扩大程度越大。
    4、根据复制的次数,判断从buffer或buffer2作为源,生成rt,这里有两个步骤:a、从rt进行blit到buffer或buffer2,使用cutoffbodyshder,即消除rt里本身的那部分;b、再从buffer或buffer2复制回rt,得到最终rt。
    几个关键的shader:

    m_BlurMaterial = new Material(Shader.Find("Outterline/ShapeBlur"));
    m_CutoffMaterial = new Material(Shader.Find("Outterline/CutoffBody"));
    m_CompositeMaterial = new Material(Shader.Find("Outterline/Composer"));
    

    ShapeBlue的关键点:

    v2f vert (appdata_img v)
    {
        v2f o;
        float offX = _MainTex_TexelSize.x * _BlurOffsets.x;
        float offY = _MainTex_TexelSize.y * _BlurOffsets.y;
    
        o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
        float2 uv = MultiplyUV (UNITY_MATRIX_TEXTURE0, v.texcoord.xy-float2(offX, offY));
    
        o.uv[0].xy = uv + float2( offX, offY);
        o.uv[0].zw = uv + float2(-offX, offY);
        o.uv[1].xy = uv + float2( offX,-offY);
        o.uv[1].zw = uv + float2(-offX,-offY);
        return o;
    }
    
    sampler2D _MainTex;
    fixed4 frag( v2f i ) : COLOR
    {
        fixed4 c;
        c  = tex2D( _MainTex, i.uv[0].xy );
        c += tex2D( _MainTex, i.uv[0].zw );
        c += tex2D( _MainTex, i.uv[1].xy );
        c += tex2D( _MainTex, i.uv[1].zw );
        return c /2 ;
    }

    用uv存储offset,栅格化后,用offset对每个像素周围采样

    CutoffBody的关键点:

    Blend One One
    BlendOp RevSub,Add 
    ZTest Always 
    Cull Off 
    ZWrite Off 
    Fog { Mode Off }
    ...
    ...
    half4 frag(v2f i) : COLOR
    {
        fixed4 c =  tex2D(_MainTex, i.uv);
        if( c.r > 0 )
            return fixed4(1,1,1,1);
        else
            return fixed4(0,0,0,0);
    }

    使用RevSub将rt里像素减去

    创建轮廓绘制对象

    至此,rt已经保存了白色的轮廓图像。现在需要把他在target上绘制出来。
    这个步骤本来很简单,用sprite就行。
    但对rendertexture好像不能用sprite绘制。。。(可能是我没找到办法)
    所以,必须自己做一个支持rt的sprite,即创建一个四边形mesh,设置material和rt。

    Mesh mesh; 
    
    MeshRenderer renderer = gameObject.GetComponent<MeshRenderer>(); 
    mesh = GetComponent<MeshFilter>().mesh;
    
    /* 
     * 2 3
     * 0 1
     */
    
    int sectionCount = 1;
    int[] triangles = new int[ sectionCount * 6];  
    Vector3[] vertices = new Vector3[ sectionCount * 4];
    Color[] colors = new Color[ sectionCount * 4];
    Vector2[] uv = new Vector2[ sectionCount * 4];
    for (int i = 0; i < sectionCount; i++)
    {  
        vertices[4 * i] = new Vector2(-x / 2, -y / 2);
        vertices[4 * i + 1] = new Vector2(x / 2, -y / 2);
        vertices[4 * i + 2] = new Vector2(-x / 2, y / 2);
        vertices[4 * i + 3] = new Vector2(x / 2, y / 2);
        colors[4 * i] = Color.white;
        colors[4 * i + 1] = Color.white;
        colors[4 * i + 2] = Color.white;
        colors[4 * i + 3] = Color.white;
        uv[4 * i] = new Vector2(0, 0);
        uv[4 * i + 1] = new Vector2(1, 0);
        uv[4 * i + 2] = new Vector2(0, 1);
        uv[4 * i + 3] = new Vector2(1, 1);
    }  
    
    for (int i = 0; i < sectionCount; i++)
    {  
        triangles[6 * i] = (i * 4) + 0;  
        triangles[6 * i + 1] = (i * 4) + 3;  
        triangles[6 * i + 2] = (i * 4) + 1; 
        triangles[6 * i + 3] = (i * 4) + 0;  
        triangles[6 * i + 4] = (i * 4) + 2;  
        triangles[6 * i + 5] = (i * 4) + 3; 
    }
    
    mesh.vertices = vertices;  
    mesh.triangles = triangles;
    mesh.colors = colors;
    mesh.uv = uv;
    
    m_RT = rt;
    m_Material = new Material(Shader.Find("Outterline/Render"));
    m_Material.hideFlags = HideFlags.HideAndDontSave;
    renderer.material = m_Material;
    renderer.material.color = color;
    renderer.material.mainTexture = rt;

    代码里创建4个顶点,两个三角形索引,需要注重索引的顺序,u3d的Z方向采用左手准则。
    m_RT为生成的轮廓纹理,m_Material使用Outterline/Render作为shader,
    通过设置color,控制轮廓的颜色。
    Outterline/Render没有什么特殊的,使用tex2D (_MainTex,IN.texcoord) * _Color作为像素输出。

    总结

    生成rt后,rt作为纹理一直使用,可以不用实时生成,通过enable、disable控制轮廓是否显示,设置材质的color属性,改变颜色。这样大幅提升了效率,避免了反复的像素处理。
    一次动态生成,静态使用。缺点是,如果有动画或target子节点的transform变化,轮廓就失效。但这些问题也不难解决。

    本文说明了u3d中2D轮廓生成的技术实现细节,下篇中,将对这些过程进行提炼和封装,形成易用的结构

  • 相关阅读:
    Visual C++ 2010 SP1 x86&x64
    MVC拦截
    自定义HTTP消息拦截
    转mysql半主从同步
    mysql主从搭建之诡异事件
    snapshot相关
    分布式系统唯一ID生成方案汇总
    mysql监控利器mysqlmtop部署安装
    mysql日常运维
    MySQL索引背后的数据结构及算法原理
  • 原文地址:https://www.cnblogs.com/uncleciba/p/5721353.html
Copyright © 2011-2022 走看看