// 接着讲上次剩下的一个函数chunk类中的GetForegroundData
///
/// 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!
///
///
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 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();
mf.mesh = m;
// 生成好的mesh和贴图赋值给GameObject,最后渲染所有chunk的foregroundObject的时候,按照从左到右的顺序渲染(x从小到大)
MeshRenderer mr = foregroundObject.GetComponent();
mr.material.mainTexture = World.GetInstance().foregroundAtlas.texture;
mr.gameObject.GetComponent().sortingOrder = position.x;
}
///
/// Request to build foreground for specified chunk
///
/// chunk requesting foregorund build
/// atlas which should be used for foreground creation
/// foreground is rotated to the angle of the camera. It works well for camera lookign from fixed angle
/// world position
/// mesh containing block of the foreground covering single chunk
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;
}
///
/// Adds single sprite (4 vertices) to the mesh data prepared with foreground instance.
///
/// data block of the mesh to which we add data for new sprite
/// foreground definition
/// rotation for the sprite to look at
/// atlas data to find sprite uvs in
/// world position
///
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();
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的顶点构成。