u3d honey hex framework 代碼解讀記錄(四)

// 接着上次baking函數中的循環開始

// 生成陰影貼圖,源和高度貼圖源是一樣的,目標貼圖小了很多,邊長是2的4次方分之1
//Render shadow and light to scaled down texture and copy it to Texture2D
int scaleDownPower = 4; //scaling it down 4 powers will resize e.g.: from 2048 to 128 making it marginally small
RenderTexture shadowTarget = RenderTargetManager.GetNewTexture(Chunk.TextureSize >> scaleDownPower, Chunk.TextureSize >> scaleDownPower);
Graphics.Blit(shadowsAndHeightRT, shadowTarget);
RenderTexture.active = shadowTarget;
texture = new Texture2D(Chunk.TextureSize >> scaleDownPower, Chunk.TextureSize >> scaleDownPower, TextureFormat.RGB24, false);
texture.wrapMode = TextureWrapMode.Clamp;
texture.ReadPixels(new Rect(0, 0, Chunk.TextureSize >> scaleDownPower, Chunk.TextureSize >> scaleDownPower), 0, 0);
texture.Apply();

//  SaveImage.SaveJPG(texture, currentChunk.position.ToString() + "h", randomIndex.ToString());

texture.Compress(false); //rgb24 will compress to 4 bits per pixel.
texture.Apply();

//if this is chunk refresh we need to destroy old texture soon
// 後面爲chunk生成mesh的時候會將無用的texture釋放掉
if (currentChunk.shadows != null) currentChunk.texturesForCleanup.Add(currentChunk.shadows);
// chunk的陰影貼圖就有了
currentChunk.shadows = texture;
RenderTexture.active = null;
shadowTarget.Release();

if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); }

// Copy Diffuse to Texture2D  
// 將之前生成的diffuse render texture轉換成texture          
RenderTexture.active = diffuseRT;
texture = new Texture2D(Chunk.TextureSize, Chunk.TextureSize, TextureFormat.RGB24, false);
texture.wrapMode = TextureWrapMode.Clamp;
texture.ReadPixels(new Rect(0, 0, Chunk.TextureSize, Chunk.TextureSize), 0, 0);

texture.name = "Diffuse" + currentChunk.position;
//if this is chunk refresh we need to destroy old texture soon
if (currentChunk.diffuse != null) currentChunk.texturesForCleanup.Add(currentChunk.diffuse);
// chunk的diffuse貼圖就有了
currentChunk.diffuse = texture;
//   SaveImage.SaveJPG(texture, currentChunk.position.ToString() + "d", randomIndex.ToString());
// 壓縮diffuse貼圖
currentChunk.CompressDiffuse();

RenderTexture.active = null;

if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); }

bakingCamera.targetTexture = null; 
// 目前chunk已經有了高度貼圖,陰影貼圖和diffuse貼圖,用這些貼圖就可以生成一個mesh了               
currentChunk.CreateGameObjectWithTextures();
// 從dirtychunks中移除當前chunk,準備處理下一個chunk
dirtyChunks.RemoveAt(0);

// 將當前chunk添加到world對象的chunksToPolish列表中,準備後續處理
World.GetInstance().ReadyToPolishChunk(currentChunk);
RenderTargetManager.ReleaseAll();

if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); }

// baking 函數中的循環結束 --------------------------------------------------------------------

/// <summary>
/// Produces chunk assets, and ensures their height is continues with neighbours
/// </summary>
/// <returns></returns>
// chunk 類中的函數,用來生成chunk的GameObject
public void CreateGameObjectWithTextures()
{
	// 對齊相鄰chunk的高度
    WeldChunk();

    // DX11模式,由於我的電腦是mac,所以不能用
    if (MHGameSettings.GetDx11Mode())
    {
        if (chunkObject == null)
        {
            if (MHGameSettings.GetMarkersMode())
            {
                chunkObject = GameObject.Instantiate(worldOwner.chunkBaseDx11WithMarkers) as GameObject;
            }
            else
            {
                chunkObject = GameObject.Instantiate(worldOwner.chunkBaseDx11) as GameObject;
            }
        }
    }
    else
    {
        if (chunkObject == null)
        {
        	// 判斷是否使用marker模式,marker和非marker使用的材質和shader不一樣
        	// marker模式下地圖生成後,地圖上點擊鼠標,可以繪製路徑,具體用途現在還不是很明白
            if (MHGameSettings.GetMarkersMode())
            {
                chunkObject = GameObject.Instantiate(worldOwner.chunkBaseWithMarkers) as GameObject;
            }
            else
            {
                chunkObject = GameObject.Instantiate(worldOwner.chunkBase) as GameObject;
            }
        }

        MeshFilter m = chunkObject.GetComponent<MeshFilter>();
        if (m.sharedMesh != null)  { GameObject.Destroy(m.sharedMesh); }
        // 產生地形的mesh
        m.sharedMesh = ProduceTerrainMesh(this);
    }

    // 設置當前chunk的GameObject的位置
    chunkObject.transform.parent = worldOwner.transform;
    chunkObject.name = "Chunk" + position;

    Vector2 center = GetRect().center;
    // 使用y作爲高度了
    chunkObject.transform.localPosition = new Vector3(center.x, 0, center.y);
    float scale = ChunkSizeInWorld / 10.0f;
    chunkObject.transform.localScale = new Vector3(scale, scale, scale);

    // 爲mesh設置材質和貼圖
    MeshRenderer mr = chunkObject.GetComponent<MeshRenderer>();
    mr.material.SetTexture("_MainTex", diffuse);
    mr.material.SetTexture("_HeightTex", height);

    chunkMaterial = mr.material;

    // 如果是marker模式,給chunkMaterial添加marker的幾個屬性
    SetMarkerMaterials();            

    MeshFilter mf = chunkObject.GetComponent<MeshFilter>();
    Bounds b = mf.mesh.bounds;

    if (MHGameSettings.GetDx11Mode() && b.size.y < 1.0f)
    {
        //extending bounds so that chunk is loaded before it enters screen, flat (plane) bounding box is not enough for our needs, and our world works as plane just before rendering                
        b.Expand(new Vector3(0, 2, 0));
        mf.mesh.bounds = b;                
    }

    ChunkBehaviour cb = chunkObject.GetComponent<ChunkBehaviour>();
    if (cb == null)
    {
        cb = chunkObject.AddComponent<ChunkBehaviour>();
    }
    cb.owner = this;

    // 清除掉不用的texture資源
    if (texturesForCleanup.Count > 0)
    {
        foreach (Texture2D t in texturesForCleanup)
        {
            GameObject.Destroy(t);
        }
        texturesForCleanup.Clear();
    }
}

/// <summary>
/// this function will build mesh used by chunks when not using dx11 tessellation.
/// </summary>
/// <returns></returns>
static public Mesh ProduceTerrainMesh(Chunk source)
{
	// meshDensity是頂點密度
	// 舉個簡單點的例子,Chunk.ChunkSizeInWorld爲10,如果此處meshDensity爲3,
	// 表示邊被切割成3段,則一共需要16個頂點:
	// *---*---*---*
	// |   |   |   |  點之間的距離是3.3
	// *---*---*---*
	// |   |   |   |
	// *---*---*---*
	// |   |   |   |
	// *---*---*---*
	// 此處meshDensity=75,也就是說需要 76 * 76 個頂點
    int meshDensity = 75;
    MeshPreparationData data = new MeshPreparationData();
    
    float quadScale = Chunk.ChunkSizeInWorld / meshDensity;
    // 設置偏移量,用於將最後生成的座標範圍(x:0~10,z:0~10)轉換到(x:-5~+5,z:-5~+5)
    Vector3 offset = new Vector3(-Chunk.ChunkSizeInWorld / 2f, 0, -Chunk.ChunkSizeInWorld / 2f);

    // 邊上的頂點個數
    int vertexRowCount = meshDensity +1;
    
    //build vertex map
    // 下面依次生成每個頂點的座標和對應的貼圖的uv值
    for (int y = 0; y < vertexRowCount; y++)
    {
        for (int x = 0; x < vertexRowCount; x++)
        {
            float u = 1 - x / (float)meshDensity;
            float v = 1 - y / (float)meshDensity;
            // 高度貼圖裏面的alpha8的值代表高度,範圍是0~1,這裏轉換爲-0.5~0.5,再擴大1.6倍                    
            float h =(source.height.GetPixelBilinear(u, v).a - 0.5f) * 1.6f;

            data.vertexList.Add(new Vector3(x * quadScale, h, y * quadScale) + offset);

            data.uvList.Add(new Vector2(u, v));
        }
    }

    //build normal map
    // 生成法線信息
    for (int y = 0; y < vertexRowCount; y++)
    {
        for (int x = 0; x < vertexRowCount; x++)
        {
            //we take value of the vertex height in center and 4 neighbour vertices clamped to mesh size
            // 取出當前頂點和上下左右四個頂點的索引值
            int center = y * vertexRowCount + x;
            int top = y > 0 ? (y - 1) * vertexRowCount + x : center;
            int bottom = y < meshDensity ? (y + 1) * vertexRowCount + x : center;
            int left = x > 0 ? y * vertexRowCount + x - 1 : center;
             int right     = x < meshDensity   ? y * vertexRowCount + x +1     : center;

            //now define normal direction based on (top vs bottom) and (left vs right)
            // 根據周圍四個點的高度決定法線向量的值
            Vector3 normal = Vector3.up * 0.1f 
                + Vector3.left * (data.vertexList[left].y - data.vertexList[right].y) 
                + Vector3.forward * (data.vertexList[top].y - data.vertexList[bottom].y);
            normal.Normalize();

            data.normalsList.Add(normal);
        }
    }

    //index vertices to build triangles. 
    //Note that we do not iterate here over extended size (we use meshDensity instead of vertexRowCount, 
    //to ensure we have one vertex more on right size to take for the last quads)
    // 使用頂點來構成三角形,他的註釋也比較清楚了,如下圖就由兩個三角形構成(v1,v3,v4)和(v1,v4,v2)。保證都是逆時針方向。
    for (int y = 0; y < meshDensity; y++)
    {
        for (int x = 0; x < meshDensity; x++)
        {
            int row1 = y * vertexRowCount + x;
            int row2 = (y + 1) * vertexRowCount + x;

            // v1      v2
            // X--------X
            // |        |
            // |        |
            // |        |
            // X--------X
            // v3      v4

            int v1 = row1;
            int v2 = row1 + 1;
            int v3 = row2;
            int v4 = row2 + 1;

            data.indexList.Add(v1);
            data.indexList.Add(v3);
            data.indexList.Add(v4);

            data.indexList.Add(v1);
            data.indexList.Add(v4);
            data.indexList.Add(v2);

        }
    }
    
    // 返回生成好的mesh
    Mesh m = new Mesh();
    m.name = "ChunkMesh_" + meshDensity + "x" + meshDensity;
    m.vertices = data.vertexList.ToArray();
    m.uv = data.uvList.ToArray();
    m.triangles = data.indexList.ToArray();
    m.normals = data.normalsList.ToArray();

    return m;
}

// 在baking函數中的循環結束之後的最後一段代碼:
// 此時所有的chunk都已經處理完成,加入到了world對象的chunksToPolish列表中
if (World.GetInstance().StartPolishingWorld())
{

    MeshRenderer[] rArray = GetComponentsInChildren<MeshRenderer>(true) as MeshRenderer[];
    foreach (MeshRenderer r in rArray)
    {
        if (r.material != null)
        {
            GameObject.Destroy(r.material);
        }
    }

    RenderTargetManager.DestroyAllUnusedTextures();
    Cleanup();
    DestroyObject(gameObject);
    instance = null;
}

public bool StartPolishingWorld()
{
    if (chunks.Count > 0)
    {
        // 新的協程調用函數FinishingWorld
        StartCoroutine("FinishingWorld");
        return true;
    }
    return false;
}

/// <summary>
/// Coroutine which ensures height compression check (which knows itself if is required or not) 
/// It as well do processing foreground data and generation of the mesh for it
/// </summary>
/// <returns></returns>
IEnumerator FinishingWorld()
{
    status = Status.Finishing;

    //we will try to compress chunks. They will ignore this command if they are already compressed

    CoroutineHelper.StartTimer();
    foreach (Chunk c in chunksToPolish)
    { 
        // 這個函數實際上沒有做任何事,代碼都被註釋掉了            
        c.CompressHeight();
        if (CoroutineHelper.CheckIfPassed(20)) { yield return null; CoroutineHelper.StartTimer(); }
    }

    //now start preparation for trees
    status = Status.Foreground;

    foreach (Hex h in hexesToPolish )
    {
        // 爲每一個hex生成樹的數據
        h.GenerateForegroundData();
        if (CoroutineHelper.CheckIfPassed(20)) { yield return null; CoroutineHelper.StartTimer(); }
    }
    hexesToPolish.Clear();

    foreach (Chunk c in chunksToPolish)
    {
        c.CleanupForeground(false);
        // 渲染樹,下次再解讀
        c.GetForegroundData();
        if (CoroutineHelper.CheckIfPassed(20)) { yield return null; CoroutineHelper.StartTimer(); }
    }
    chunksToPolish.Clear();

    status = Status.Ready;
} 

/// <summary>
/// Generates foreground data of this single hex, later chunks will take control of foreground which have covered them but production occurs here.
/// </summary>
/// <returns></returns>
public void GenerateForegroundData()
{
    // 清空樹的信息
    foregroundData.Clear();

    // 獲取到當前hex的位置
    Vector2 flatHexPosition = GetWorldPosition();
    Vector3 hexPosition = new Vector3(flatHexPosition.x, 0.0f, flatHexPosition.y);

    // fgTypes就是當前地形類型上樹的定義
    foreach (MHSimpleCounter type in terrainType.source.fgTypes)
    {
        // type.count就是樹的個數
        for (int i = 0; i < type.count; i++)
        {
            ForegroundData data = new ForegroundData();

            //produces value focused around center of the range
            // 隨機設置樹離中心點的距離,foregroundRadius爲1.1,所以rad的期望值爲0.825
            float rad = (UnityEngine.Random.Range(0, foregroundRadius) + UnityEngine.Random.Range(0, foregroundRadius)) * 0.5f;
            // 隨機選擇角度
            Quaternion q = Quaternion.Euler(0.0f, UnityEngine.Random.Range(0.0f, 360.0f), 0.0f);
            // 樹的位置就爲距離中心點半徑爲rad,角度爲q的點上面
            data.position = q * new Vector3(rad, 0.0f, 0.0f) + hexPosition;

            // 根據基礎顏色,隨機一些偏差作爲樹的顏色
            data.color = TerrainDefinition.GetRandomizedColor(terrainType.source.foregroundColor, 30);
            data.colorFinal = data.color;
            data.name = type.name;
            // 一定範圍隨機樹的大小
            data.scale = UnityEngine.Random.Range(0.002f * Hex.hexRadius, 0.0026f * Hex.hexRadius);
            //data.horizontalInverse = Random.Range(0, 2) < 1 ? false : true;

            foregroundData.Add(data);
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章