u3d honey hex framework 代码解读记要(五)
u3d honey hex framework 代码解读记录(五)
附两张图,第一张是生成的地形的mesh,第二张是树的mesh,可以清楚得看到mesh的顶点构成。
// 接着讲上次剩下的一个函数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的顶点构成。