// 接着上次的baking函数段 -------------------------------------------------------------------------
// 因为各个烘焙阶段都是使用shader来工作的,所以目前看烘焙相关的代码会觉得不是很清楚
// 等到将这个大的流程粗略过完之后,我会回头将每个shader和对应的函数解读一下
// 大概需要等到下下篇文章了
// Offset heights are baked using the same settings
//small offset allows with comparison find shadow borders ensuring its sharpness
//big offset ensures shadow body and coverage in irregular terrain and data artifacts
// 此时场景中meshrender的设置保持了BakingHeightStage中的设置:
// 材质中的参数:"_MainTex" = terrainType.height;"_Mixer" = terrainType.mixer;"_GlobalMixer" = mixerRT
// 第一次相机偏移少一点,烘焙出来的影子颜色是比较深的,第二次偏移多一点,影子颜色较浅,最后重叠起来后看上去比较自然
bakingCamera.transform.localPosition = lightSourceDirection.normalized * 0.15f; //small offset for shadow detail
// 烘焙,还是使用Chunk.TextureSize的1/2作为边长,这样rendertexture的尺寸和函数BakingHeightStage中的高度贴图一样大
BakeTo(ref heightRTOffset1, 1);
// 同样做blur操作,平滑变化趋势
BlurTexture(heightRTOffset1, 1, 1, 1);
if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); }
// 第2次阴影贴图烘焙
bakingCamera.transform.localPosition = lightSourceDirection.normalized * 0.3f; //higher offset to get shadow body
BakeTo(ref heightRTOffset2, 1);
BlurTexture(heightRTOffset2, 1, 1, 1);
// 恢复baking相机位置,localPosition为0,表示和worldoven坐标一样了
bakingCamera.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f);
if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); }
// 使用三次烘焙的贴图,生成最后阴影和高度贴图
shadowsAndHeightRT = ProduceShadowsAndHeightTexture(heightRT, heightRTOffset1, heightRTOffset2);
if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); }
// Bake Diffuse
// 将六边形的边的meshrender位置调整到摄像机能看得到的地方,这样在下面BakingDiffuseStage的时候就能渲染hex的边了
foreach (MeshRenderer mr in hexOutlineCollection)
{
Vector3 pos = mr.transform.localPosition;
pos.z = 5;
mr.transform.localPosition = pos;
}
ReorderHexesPlacingWaterOnTop();
if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); }
BakingDiffuseStage();
if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); }
//turn off everything from camera view
foreach (MeshRenderer mr in quadCollection)
{
// 释放材质,将GameObject的active设置为false,意味着后面可以重复使用
if (mr.GetComponent().material != null) { GameObject.Destroy(mr.GetComponent().material); }
mr.gameObject.SetActive(false);
}
foreach (MeshRenderer mr in hexOutlineCollection)
{
// 在上面BakingDiffuseStage的时候hex的边已经烘焙完成,所以这里可以不需要了
mr.gameObject.SetActive(false);
}
// Copy Height to Texture2D (ARGB) because we cant render directly to Alpha8.
// 高度贴图复制到texture2d上面去
Texture2D texture;
RenderTexture.active = shadowsAndHeightRT;
texture = new Texture2D(Chunk.TextureSize >> 1, Chunk.TextureSize >> 1, TextureFormat.ARGB32, false);
texture.wrapMode = TextureWrapMode.Clamp;
texture.ReadPixels(new Rect(0, 0, Chunk.TextureSize >> 1, Chunk.TextureSize >> 1), 0, 0);
texture.Apply();
//Convert height to Alpha8, its reasonably cheap and good even uncompressed format. Not compressed format saves us form artifacts near shaped water borders and mountain tops
if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); }
// 新的高度贴图,使用alpha8的模式
Texture2D gScale = new Texture2D(Chunk.TextureSize >> 1, Chunk.TextureSize >> 1, TextureFormat.Alpha8, false);
gScale.wrapMode = TextureWrapMode.Clamp;
Color32[] data = texture.GetPixels32();
gScale.SetPixels32(data);
gScale.Apply();
gScale.name = "Height" + currentChunk.position;
//if this is chunk refresh we need to destroy old texture soon
if (currentChunk.height != null) currentChunk.texturesForCleanup.Add(currentChunk.height);
// 将此高度贴图赋值给当前chunk。后面获取当前坐标的高度时,实际是将坐标映射到这张图的uv值,然后通过alpha8的值来决定高度
currentChunk.height = gScale;
//source texture will not be used anymore
GameObject.Destroy(texture);
if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); }
// baking 函数段结束 -------------------------------------------------------------------------
///
/// Shortcut to do another render with the same settings (camera settings my be different, but scene is the same)
///
///
///
///
// 函数很简单,和之前的函数BakingHeightStage最后几条语句是一样的
void BakeTo(ref RenderTexture target, int downscale)
{
if (target != null) target.Release();
target = RenderTargetManager.GetNewTexture(Chunk.TextureSize >> downscale, Chunk.TextureSize >> downscale, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default, 1);
target.wrapMode = TextureWrapMode.Clamp;
bakingCamera.targetTexture = target;
bakingCamera.Render();
}
///
/// Setting up and rendering using set of the heights textures producing shadows/light values and moving height to alpha channel for further conversion to Alpha8
///
///
///
///
///
// 通过sharder来将3个贴图处理成最终的一张贴图,需要结合shader来分析
RenderTexture ProduceShadowsAndHeightTexture(RenderTexture terrainHeight,
RenderTexture terrainHeightOff1,
RenderTexture terrainHeightOff2)
{
// worldoven对象的material,包含了shader:ShadowsAndHeightShader
shadowsAndHeightMaterial.SetTexture("_Height", terrainHeight);
shadowsAndHeightMaterial.SetTexture("_Height1", terrainHeightOff1);
shadowsAndHeightMaterial.SetTexture("_Height2", terrainHeightOff2);
// 获取一个新的rendertexture
RenderTexture rt = RenderTargetManager.GetNewTexture(terrainHeight.width, terrainHeight.height, 0, RenderTextureFormat.ARGB32);
//simpler baking for older GPUs, in case they have problem with bilinear filtering in render targets
terrainHeight.filterMode = FilterMode.Bilinear;
terrainHeightOff1.filterMode = FilterMode.Bilinear;
terrainHeightOff1.filterMode = FilterMode.Bilinear;
rt.filterMode = FilterMode.Bilinear;
// 通过shader生成最终贴图rt
Graphics.Blit(null, rt, shadowsAndHeightMaterial, 0);
// 清空参数
shadowsAndHeightMaterial.SetTexture("_Height", null);
shadowsAndHeightMaterial.SetTexture("_Height1", null);
shadowsAndHeightMaterial.SetTexture("_Height2", null);
return rt;
}
///
/// Functionality which places sea hexes to the top and scales them up. This way they can produce beaches easier
///
///
void ReorderHexesPlacingWaterOnTop()
{
// quadCollection包含了当前chunk中所有hex的meshrender
foreach (MeshRenderer mr in quadCollection)
{
// 将GameObject active设为false,这样下面调用GetFreeRenderer的就又把这些GameObject取出来了
mr.gameObject.SetActive(false);
}
Chunk c = currentChunk;
Rect r = c.GetRect();
Vector2 center = r.center;
//we want now sea hexes to be on top of all other hexes. No changes otherwise.
//for this purpose we will split hexes into two lists. sort them separately and then merge
// seaType进sea list,其他进 none sea list
List hexes = c.hexesCovered.Values.ToList();
List seaHexes = hexes.FindAll(o => o.terrainType.source.seaType);
List nonSeaHexes = hexes.FindAll(o => !o.terrainType.source.seaType);
seaHexes = seaHexes.OrderBy(x => x.orderPosition).ToList();
nonSeaHexes = nonSeaHexes.OrderBy(x => x.orderPosition).ToList();
// 先插入sea list,再插入non sea list,这样seatype的顺序就在前面了
hexes.Clear();
hexes.AddRange(seaHexes);
hexes.AddRange(nonSeaHexes);
foreach (KeyValuePair pair in c.hexesCovered)
{
// 由于上面将所有GameObject的active设为false,所有又再次取了出来
MeshRenderer mr = GetFreeRenderer();
Hex h = pair.Value;
Vector2 pos = h.GetWorldPosition() - center;
// 得到排序
int index = hexes.IndexOf(h);
if (h.terrainType.source.seaType)
{
//expand area covered by water hex so that it can draw better beaches
// 如果是sea type的GameObject,放大1.25倍
mr.transform.localScale = Vector3.one * Hex.hexTextureScale * 2f * 1.25f;
}
// 顺序(index)作为z轴的参考,越靠后,高度越高,所以陆地都在海洋上面了
mr.transform.localPosition = new Vector3(pos.x, pos.y, 20 + index * 5);
// 随机角度
mr.transform.localRotation = Quaternion.Euler(0.0f, 0.0f, h.rotationAngle);
// 重新设置hex和meshrender的对应关系
displayCollection[h] = mr;
}
}
///
/// Preparation and render of the diffuse texture
///
///
// 需要结合shader来看,除了shader之外,大体和之前的几个bakingstage没有什么区别
void BakingDiffuseStage()
{
//Hexes
foreach (KeyValuePair pair in displayCollection)
{
if (pair.Value.material != null) { GameObject.Destroy(pair.Value.material); }
pair.Value.material = diffuseMaterial;
Material m = pair.Value.material;
m.SetTexture("_MainTex", pair.Key.terrainType.diffuse);
m.SetTexture("_Mixer", pair.Key.terrainType.mixer);
m.SetTexture("_Height", pair.Key.terrainType.height);
m.SetTexture("_GlobalMixer", mixerRT);
m.SetTexture("_ShadowsAndHeight", shadowsAndHeightRT);
m.SetFloat("_Sea", pair.Key.terrainType.source.seaType ? 1f : 0f);
m.SetFloat("_Centralization", 1.0f);
}
//move river smoothener behind camera. It doesn't have diffuse to draw
GameObject root = GameObject.Find("RiverSmoothener");
root.transform.localPosition = root.transform.localPosition - Vector3.forward * 20;
//River
TerrainDefinition riverDef = TerrainDefinition.definitions.Find(o => o.source.mode == MHTerrain.Mode.IsRiverType);
Texture riverDiffuse = riverDef.diffuse;
Texture riverMixer = riverDef.mixer;
Texture riverHeight = riverDef.height;
foreach (MeshRenderer river in riverSections)
{
if (river.material != null) { GameObject.Destroy(river.material); }
river.material = diffuseMaterial;
Material m = river.material;
m.SetTexture("_MainTex", riverDiffuse);
m.SetTexture("_Mixer", riverMixer);
m.SetTexture("_Height", riverHeight);
m.SetTexture("_GlobalMixer", mixerRT);
m.SetTexture("_ShadowsAndHeight", shadowsAndHeightRT);
m.SetFloat("_Sea", 0f);
m.SetFloat("_Centralization", 0.0f);
}
if (diffuseRT != null) diffuseRT.Release();
diffuseRT = RenderTargetManager.GetNewTexture(Chunk.TextureSize, Chunk.TextureSize, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default, 1);
diffuseRT.wrapMode = TextureWrapMode.Clamp;
bakingCamera.targetTexture = diffuseRT;
bakingCamera.Render();
}