u3d honey hex framework 代码解读记录(五)

// 接着讲上次剩下的一个函数chunk类中的GetForegroundData
/// <summary>
/// parses through controlled hexes and produces single list of the foreground data which fits within chunk 
/// Note that single hex may be shared up to even 4 chunks and so its foreground elements!
/// </summary>
/// <returns></returns>
public void GetForegroundData()
{
    if (height == null)
    {
        Debug.LogError("Missing height! Cant build foreground without this data!");
    }
                
    // hexes should have pre-generated foreground content. Chunks simply finds which elements belong to them, and then sort by z            
    foreach (KeyValuePair<Vector3i, Hex> pair in hexesCovered)
    {
        Hex h = pair.Value;
        foreach (ForegroundData d in h.foregroundData)
        {
            Vector2 uv = GetWorldToUV(d.position);

            // 由于hex可能会差超出chunk的边界,所以上面的树的位置也可能超出边界,此处就需要判断树的
            // 位置是否在当前chunk内,如果在的话才做渲染操作
            if (uv.x > 0.0f && uv.x <= 1.0f &&
                uv.y > 0.0f && uv.y <= 1.0f)
            {
                Vector2i textureUV = new Vector2i((int)((1 - uv.x) * height.width), (int)((1 - uv.y) * height.height));
                Color heightColor = height.GetPixel(textureUV.x, textureUV.y);
                Vector2i ShadowUV = new Vector2i((int)((1 - uv.x) * shadows.width), (int)((1 - uv.y) * shadows.height));
                Color shadowsCenter = shadows.GetPixel(ShadowUV.x, ShadowUV.y);
                // 高度在一定范围内才会绘制树
                if (heightColor.a > 0.495f && heightColor.a < 0.75f)
                {
                    // 树的位置的y坐标就是地形的y坐标,稍微矮一点
                    d.position.y = (heightColor.a - 0.5f) * 2f - 0.02f;

                    float LandSX = shadows.GetPixel(ShadowUV.x + 1, ShadowUV.y).r;
                    float LandSY = shadows.GetPixel(ShadowUV.x, ShadowUV.y + 1).r;
                    float LandSXX = shadows.GetPixel(ShadowUV.x - 1, ShadowUV.y).r;
                    float LandSYY = shadows.GetPixel(ShadowUV.x, ShadowUV.y - 1).r;
                   
                    //adds value form 5 points then uses 0.2 to scale it to 0-1 value
                    // then it subtracts 0.5 moving it into range from  0.5 to -0.5
                    // scales result (as its rarely reaches far from 0) and then adds 1 ensuring previous 0 is now 1, which is neutral for multiplication
                    float extraLightning = ((((shadowsCenter.r + LandSX + LandSY + LandSXX + LandSYY) * 0.2f + -0.5f) * 5.0f) + 1f);                            

                    //cut down over burnings and too deep shadows if any showed up
                    float lightAndShadow = Mathf.Min(1.5f, Mathf.Max(0.6f, extraLightning));

                    // 上面的计算作为微调颜色的参数
                    d.colorFinal = d.color.GetColor() * lightAndShadow;
                    foregroundData.Add(d);
                }
            }
        }
    }

    //sort foreground ensuring their order is proper for the camera (and so will be mesh)
    // 排序,z坐标越大,就越靠近摄像机
    foregroundData.Sort(
            delegate(ForegroundData a, ForegroundData b)
            {
                //return inverse of the order
                return -a.position.z.CompareTo(b.position.z);
            }
        );

    //if none exists, create foreground object
    // foregroundObject是当前chunk包含了所有树的贴图的GameObject
    if (foregroundObject == null)
    {
        foregroundObject = GameObject.Instantiate(World.GetInstance().foregroundBase) as GameObject;
        foregroundObject.transform.parent = chunkObject.transform;                
    }

    //build foreground mesh and set it to mesh filter
    // 获取相机的旋转角度,在后面的BuildForeground用于将树的mesh平面正对相机
    float angle = worldOwner.terrainCamera.transform.rotation.eulerAngles.x;
    angle = Mathf.Min(40.0f, angle);
    // 生成树的mesh
    Mesh m = ForegroundFactory.BuildForeground(this, World.GetInstance().foregroundAtlas, angle, World.GetInstance().transform.position);
    MeshFilter mf = foregroundObject.GetComponent<MeshFilter>();
    mf.mesh = m;

    // 生成好的mesh和贴图赋值给GameObject,最后渲染所有chunk的foregroundObject的时候,按照从左到右的顺序渲染(x从小到大)
    MeshRenderer mr = foregroundObject.GetComponent<MeshRenderer>();
    mr.material.mainTexture = World.GetInstance().foregroundAtlas.texture;
    mr.gameObject.GetComponent<Renderer>().sortingOrder = position.x;

}

/// <summary>
/// Request to build foreground for specified chunk
/// </summary>
/// <param name="chunk"> chunk requesting foregorund build </param>
/// <param name="atlas"> atlas which should be used for foreground creation </param>
/// <param name="viewAngle"> foreground is rotated to the angle of the camera. It works well for camera lookign from fixed angle </param>
/// <param name="worldPosition"> world position </param>
/// <returns> mesh containing block of the foreground covering single chunk </returns>
static public Mesh BuildForeground(Chunk chunk, UFTAtlasMetadata atlas, float viewAngle, Vector3 worldPosition)
{
    //build mesh using sorted chunk data
    MeshPreparationData mpd = new MeshPreparationData();
    Quaternion qAngle = Quaternion.Euler(viewAngle, 0.0f, 0.0f);

    // 根据每棵树的sprite生成mesh所需要的信息:顶点,uv,三角形,颜色
    for (int i = 0; i < chunk.foregroundData.Count; i++)
    {
        AddSingleSprite(mpd, chunk.foregroundData[i], qAngle, atlas, worldPosition);
    }

    //check if anything have been produced
    if (mpd.vertexList.Count == 0) return null;

    //fill data to mesh
    Mesh m = null;
    m = new Mesh();
    m.vertices = mpd.vertexList.ToArray();
    m.uv = mpd.uvList.ToArray();
    m.triangles = mpd.indexList.ToArray();
    m.colors = mpd.colorList.ToArray();

    return m;
}

/// <summary>
/// Adds single sprite (4 vertices) to the mesh data prepared with foreground instance.
/// </summary>
/// <param name="data"> data block of the mesh to which we add data for new sprite</param>
/// <param name="source"> foreground definition</param>
/// <param name="viewAngle"> rotation for the sprite to look at</param>
/// <param name="atlas"> atlas data to find sprite uvs in </param>
/// <param name="worldPosition"> world position </param>
/// <returns></returns>
static private void AddSingleSprite(MeshPreparationData data, ForegroundData source, Quaternion viewAngle, UFTAtlasMetadata atlas, Vector3 worldPosition)
{
    UFTAtlasEntryMetadata spriteData = atlas.GetByName(source.name);
    int startingIndex = data.vertexList.Count;
    /*
     Vertices
     */
    if (spriteData == null)
    {
        if (warnings == null) warnings = new List<string>();

        if (!warnings.Contains(source.name))
        {
            Debug.Log("Foreground texture not found for: " + source.name);
            warnings.Add(source.name);
        }
        return;
    }
    // 根据缩放调整高度和宽度
    float height = spriteData.pixelRect.height * source.scale * 2f;
    float width = spriteData.pixelRect.width * source.scale * 2f;

    // 获取锚点获取uv值。uv.x是锚点到右边界的距离的比例,uv.y是锚点到下边界的距离的比例
    Vector2 uv = new Vector2(spriteData._pivot.x / spriteData._pixelRect.width, spriteData._pivot.y / spriteData._pixelRect.height);
    uv.x = 1f - uv.x;

    // 以锚点为中心零点,topLeft是左上角点坐标,buttomLeft,buttomRight和topRight分别为坐下,右下和右上的点的坐标
    // 乘以角度就是让平面正对摄像机
    Vector3 topLeft     = viewAngle * new Vector3(-(1f - uv.x) * width  , uv.y * height         , 0f);
    Vector3 bottomLeft  = viewAngle * new Vector3(-(1f - uv.x) * width  , -(1f - uv.y) * height , 0f);
    Vector3 bottomRight = viewAngle * new Vector3(uv.x * width          , -(1f - uv.y) * height , 0f);
    Vector3 topRight    = viewAngle * new Vector3(uv.x * width          , uv.y * height         , 0f);

    // 添加顶点坐标的绝对值(世界坐标)
    data.vertexList.Add(topLeft + source.position + worldPosition);
    data.vertexList.Add(bottomLeft + source.position + worldPosition);
    data.vertexList.Add(bottomRight + source.position + worldPosition);
    data.vertexList.Add(topRight + source.position + worldPosition);

    /* 
     UVs
     */
    float topUV = spriteData.uvRect.yMin;
    float leftUV = spriteData.uvRect.xMin;
    float bottomUV = spriteData.uvRect.yMax;
    float rightUV = spriteData.uvRect.xMax;

    // 可以左右翻转
    float left = source.horizontalInverse ? rightUV : leftUV;
    float right = source.horizontalInverse ? leftUV : rightUV;

    //texture uv
    // 设置当前树在整个sprite图中的uv值
    data.uvList.Add(new Vector2(left, bottomUV));
    data.uvList.Add(new Vector2(left, topUV));
    data.uvList.Add(new Vector2(right, topUV));
    data.uvList.Add(new Vector2(right, bottomUV));

    /*
     Index
     */
     // 加上顶点索引,按照上面的添加顶点坐标的顺序,这里对应就是
     // 第一个三角形为右下到做下到左上,第二个三角形是左上到右上到右下,都是逆时针方向
    data.indexList.Add(startingIndex + 2);
    data.indexList.Add(startingIndex + 1);
    data.indexList.Add(startingIndex + 0);

    data.indexList.Add(startingIndex + 0);
    data.indexList.Add(startingIndex + 3);
    data.indexList.Add(startingIndex + 2);

    /*
     Color
     */
     // 设置顶点颜色, 左下是阴影,所以暗一点,右下亮一点,上方都正常
    float lightStrength = 0.8f;
    data.colorList.Add(source.colorFinal.GetColor());
    data.colorList.Add(source.colorFinal.GetColor() * (1 - lightStrength));//use extra shadow on side of the foreground        
    data.colorList.Add(source.colorFinal.GetColor() * (1 + lightStrength));//use extra light on side of the foreground
    data.colorList.Add(source.colorFinal.GetColor());
}

附两张图,第一张是生成的地形的mesh,第二张是树的mesh,可以清楚得看到mesh的顶点构成。





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值