// file: WorldOven.cs /// <summary> /// If baking were requested this function will handle starting all required processes and default settings required for initial pass /// </summary> /// <returns></returns> void Update() { // 如果有dirtychunk,则开始烘焙;如果已经在烘焙了,就跳过,避免重复烘焙 // 此前world对象循环调用函数PrepareChunkData,将9个chunk加入到了dirtychunks中,所以此处dirtychunks有9个 if (dirtyChunks.Count > 0 && baking == false) { baking = true; // 设置烘焙摄像机 bakingCamera.orthographicSize = Chunk.ChunkSizeInWorld * 0.5f; bakingCamera.aspect = 1.0f; // quadBase是绘制hex的原型gameobject,他是worldoven的子节点,即quadBase.transform.parent = worldOven.transform quadBase.transform.localScale = new Vector3(Hex.hexTextureScale * 2.0f, Hex.hexTextureScale * 2.0f, Hex.hexTextureScale * 2.0f); // 绘制河流的处理,暂时跳过 GameObject root = GameObject.Find("RiverRoot"); foreach (Transform t in root.transform) { MeshRenderer mr = t.GetComponent<MeshRenderer>(); if (mr != null) { riverSections.Add(mr); } } root = GameObject.Find("RiverSmoothener"); foreach (Transform t in root.transform) { MeshRenderer mr = t.GetComponent<MeshRenderer>(); if (mr != null) { riverSmoothenerSections.Add(mr); } } // 开始烘焙 StartCoroutine("Baking"); } } /// <summary> /// Most complex functionality which ensures all stages of baking take place in order and that they allow world to update form time to time instead of freezing everything. /// </summary> /// <returns></returns> IEnumerator Baking() { // croutinhelper是一个协程帮助函数,主要用来判断协程执行时间是否过长,如果过长,则暂停执行,下一帧再继续执行 // 此函数很长,所以本次只看PreparationStage,BakingMixerStage和BakingHeightStage三个阶段 CoroutineHelper.StartTimer(); while (dirtyChunks.Count > 0) { //setup chunk scene in order required by blending PreparationStage(); if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); } // Mixer //This texture later defines how different hexes blend each other. It bakes highest domination value taking into account distance from hex center and mixer texture. //Uses "Blending Max" which produces result by comparing previous color value with new result from shader: //R = R1 > R2 ? R1 : R2; //G = G1 > G2 ? G1 : G2; //B = B1 > B2 ? B1 : B2; //A = A1 > A2 ? A1 : A2; BakingMixerStage(); if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); } // Height // defines how high terrain is. At this stage we blend using Mixer textures and previously prepared sum of mixers. By comparing both we know how strong our hex is within this point // Height is written to R, While writting strenght is based on A channel // 相机的orthographicSize是视图的高度的一半,所以此处乘以0.5 bakingCamera.orthographicSize = Chunk.ChunkSizeInWorld * 0.5f; BakingHeightStage(); BlurTexture(heightRT, 1, 1, 1); if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); } // 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 bakingCamera.transform.localPosition = lightSourceDirection.normalized * 0.15f; //small offset for shadow detail BakeTo(ref heightRTOffset1, 1); BlurTexture(heightRTOffset1, 1, 1, 1); if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); } bakingCamera.transform.localPosition = lightSourceDirection.normalized * 0.3f; //higher offset to get shadow body BakeTo(ref heightRTOffset2, 1); BlurTexture(heightRTOffset2, 1, 1, 1); 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 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) { if (mr.GetComponent<Renderer>().material != null) { GameObject.Destroy(mr.GetComponent<Renderer>().material); } mr.gameObject.SetActive(false); } foreach (MeshRenderer mr in hexOutlineCollection) { mr.gameObject.SetActive(false); } // Copy Height to Texture2D (ARGB) because we cant render directly to Alpha8. 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(); } 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); currentChunk.height = gScale; //source texture will not be used anymore GameObject.Destroy(texture); if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); } //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 if (currentChunk.shadows != null) currentChunk.texturesForCleanup.Add(currentChunk.shadows); currentChunk.shadows = texture; RenderTexture.active = null; shadowTarget.Release(); if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); } // Copy Diffuse to Texture2D 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); currentChunk.diffuse = texture; // SaveImage.SaveJPG(texture, currentChunk.position.ToString() + "d", randomIndex.ToString()); currentChunk.CompressDiffuse(); RenderTexture.active = null; if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); } bakingCamera.targetTexture = null; currentChunk.CreateGameObjectWithTextures(); dirtyChunks.RemoveAt(0); World.GetInstance().ReadyToPolishChunk(currentChunk); RenderTargetManager.ReleaseAll(); if (CoroutineHelper.CheckIfPassed(30)) { yield return null; CoroutineHelper.StartTimer(); } } 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; } } /// <summary> /// Preparation of the scene for baking process /// </summary> /// <returns></returns> void PreparationStage() { displayCollection = new Dictionary<Hex, MeshRenderer>(); // 取第一个dirtychunk,baking函数是一个循环,每次都处理第一个chunk,处理完之后, // 将第一个dirtychunk移除,这样来处理完所有的chunk currentChunk = dirtyChunks[0]; Chunk c = currentChunk; Rect r = c.GetRect(); Vector2 center = r.center; // hexesCovered是在函数PrepareChunkData中设置,表示当前chunk所包含的所有hex的字典 // 将字典转换为数组,并且按照hex的顺序排序,hex的顺序是在GetHexDefinition函数中随机生成的 List<Hex> hexes = c.hexesCovered.Values.ToList(); hexes = hexes.OrderBy(x => x.orderPosition).ToList(); foreach (KeyValuePair<Vector3i, Hex> pair in c.hexesCovered) { // 获取一个空闲的meshrender, 此处是从quadBase拷贝一个gameObject,然后返回它的meshrender MeshRenderer mr = GetFreeRenderer(); Hex h = pair.Value; // 因为meshrender是从quadBase拷贝来的,所以他的gameobject也是worldOven的子节点, // 所以localPostion需要设置为相对坐标 Vector2 pos = h.GetWorldPosition() - center; int index = hexes.IndexOf(h); // index就是顺序 // hexTexutreScale定义了texture覆盖hex的比例,因为它的定义是比例乘以半径,所以这里要乘以2 mr.transform.localScale = Vector3.one * Hex.hexTextureScale * 2f; mr.transform.localPosition = new Vector3(pos.x, pos.y, 20 + index * 5); // rotationAngle也是在函数GetHexDefinition中随机生成的 mr.transform.localRotation = Quaternion.Euler(0.0f, 0.0f, h.rotationAngle); mr.material.mainTexture = h.terrainType.diffuse; // 添加到显示字典中,这样meshrender和hex就通过位置(vector3i)对应起来了 displayCollection[h] = mr; // outline就是每个hex的暗灰色的边 //add hex outlines behind camera for later use MeshRenderer outlineMr = GetFreeOutlineRenderer(); // z轴是负数,渲染的planez轴为0,所以暂时看不到outline outlineMr.transform.localPosition = new Vector3(pos.x, pos.y, -5); } // 准备河流信息,暂时跳过 PrepareRivers(new Vector3(-center.x, -center.y, 10)); } /// <summary> /// Preparation and render of the global mixer texture containing summary of the territorial fights between hexes /// </summary> /// <returns></returns> void BakingMixerStage() { // 在函数PreparationStage中,displayCollection包含了当前chunk中所有的hex的meshrender //Hexes foreach (KeyValuePair<Hex, MeshRenderer> pair in displayCollection) { // 清除掉旧的material if (pair.Value.material != null) { GameObject.Destroy(pair.Value.material); } // mixerMaterial 是worldoven这个prefab里面带的一个材质,他包含了一个叫1MixerSharder的shader,后面再来解读它 // 此处应该是有set方法,所以并不是引用mixerMaterial,而是从mixerMaterial拷贝了一个新的material pair.Value.material = mixerMaterial; Material m = pair.Value.material; // 设置名字 m.name = "mixerMaterialAT" + currentChunk.position + "FOR" + pair.Key.position; // 设置主贴图 m.mainTexture = pair.Key.terrainType.mixer; } // 河流数据设置,暂时跳过 //river shape background foreach (MeshRenderer river in riverSmoothenerSections) { if (river.material != null) { GameObject.Destroy(river.material); } river.material = mixerMaterial; Material m = river.material; m.name = "mixerMaterialAT" + currentChunk.position + "FORriverSmooth" + riverSmoothenerSections.IndexOf(river); m.SetFloat("_Centralization", 0.0f); m.mainTexture = riverSmoothenerTexture; } // 河流数据设置,暂时跳过 //River TerrainDefinition riverDef = TerrainDefinition.definitions.Find(o => o.source.mode == MHTerrain.Mode.IsRiverType); Texture riverMixer = riverDef.mixer; foreach (MeshRenderer river in riverSections) { if (river.material != null) { GameObject.Destroy(river.material); } river.material = mixerMaterial; Material m = river.material; m.name = "mixerMaterialAT" + currentChunk.position + "FORriver" + riverSmoothenerSections.IndexOf(river); m.SetFloat("_Centralization", 0.0f); m.mainTexture = riverMixer; } // mixer render texture, 释放旧的渲染贴图 if (mixerRT != null) mixerRT.Release(); // 按照当前chunk的大小获取一个新的渲染贴图,作为bakingCamera的渲染目标 mixerRT = RenderTargetManager.GetNewTexture(Chunk.TextureSize, Chunk.TextureSize, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default, 1); bakingCamera.targetTexture = mixerRT; // 执行渲染 bakingCamera.Render(); RenderTexture.active = null; } /// <summary> /// Preparation and render of the height texture /// </summary> /// <returns></returns> void BakingHeightStage() { //Hexes foreach (KeyValuePair<Hex, MeshRenderer> pair in displayCollection) { // 将之前BakingMixerStage阶段的材质都释放掉 if (pair.Value.material != null) { GameObject.Destroy(pair.Value.material); } // 设置为heightMaterial,它包含了2HeightShader.shader,后面解读 pair.Value.material = heightMaterial; Material m = pair.Value.material; // 主贴图为地形定义中的height贴图 m.SetTexture("_MainTex", pair.Key.terrainType.height); // 上次作为主贴图的mixer赋值给了_Mixer m.SetTexture("_Mixer", pair.Key.terrainType.mixer); // 设置全局mixer为之前BakingMixerStage阶段生成的渲染贴图 m.SetTexture("_GlobalMixer", mixerRT); } // 河流数据设置,暂时跳过 //river TerrainDefinition riverDef = TerrainDefinition.definitions.Find(o => o.source.mode == MHTerrain.Mode.IsRiverType); Texture riverHeight = riverDef.height; Texture riverMixer = riverDef.mixer; foreach (MeshRenderer river in riverSections) { if (river.material != null) { GameObject.Destroy(river.material); } river.material = riverHeightMaterial; Material m = river.material; m.SetFloat("_Centralization", 0.0f); m.SetTexture("_MainTex", riverHeight); m.SetTexture("_Mixer", riverMixer); m.SetTexture("_GlobalMixer", mixerRT); } // 河流数据设置,暂时跳过 //Smoothener will neutralize a bit area where river would be located to avoid artefacts as much as possible foreach (MeshRenderer river in riverSmoothenerSections) { if (river.material != null) { GameObject.Destroy(river.material); } river.material = riverSmoothenerMaterial; Material m = river.material; m.SetTexture("_MainTex", riverSmoothenerTexture); } // 释放旧的高度渲染贴图 if (heightRT != null) heightRT.Release(); // 贴图大小是前一阶段的四分之一,采用拉伸模式渲染高度贴图 heightRT = RenderTargetManager.GetNewTexture(Chunk.TextureSize >> 1, Chunk.TextureSize >> 1, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default, 1); heightRT.wrapMode = TextureWrapMode.Clamp; bakingCamera.targetTexture = heightRT; bakingCamera.Render(); } /// <summary> /// Simple function which adds a bit of the blur to texture. used mostly by the height textures to avoid artifacts /// </summary> /// <param name="texture"></param> /// <param name="downSample"></param> /// <param name="size"></param> /// <param name="interations"></param> /// <returns></returns> // 给贴图加上blur效果,用在高度图上面,可以使高度变化趋势变得比较平滑,看上去更加自然, // 解读本函数需要分析mobileblurshader,后续解读时再仔细对比和解读这个函数。 // 目前简单看起来就是设置采样范围,使用水平和垂直模糊 void BlurTexture(RenderTexture texture, int downSample, int size, int interations) { float widthMod = 1.0f / (1.0f * (1 << downSample)); // 需要后面分析blurMaterial带的sharder Material material = new Material(blurMaterial); material.SetVector("_Parameter", new Vector4(size * widthMod, -size * widthMod, 0.0f, 0.0f)); texture.filterMode = FilterMode.Bilinear; int rtW = texture.width >> downSample; int rtH = texture.height >> downSample; // downsample RenderTexture rt = RenderTargetManager.GetNewTexture(rtW, rtH, 0, texture.format); rt.filterMode = FilterMode.Bilinear; Graphics.Blit(texture, rt, material, 0); for (int i = 0; i < interations; i++) { float iterationOffs = (i * 1.0f); material.SetVector("_Parameter", new Vector4(size * widthMod + iterationOffs, -size * widthMod - iterationOffs, 0.0f, 0.0f)); // vertical blur RenderTexture rt2 = RenderTargetManager.GetNewTexture(rtW, rtH, 0, texture.format); rt2.filterMode = FilterMode.Bilinear; Graphics.Blit(rt, rt2, material, 1); RenderTargetManager.ReleaseTexture(rt); rt = rt2; // horizontal blur rt2 = RenderTargetManager.GetNewTexture(rtW, rtH, 0, texture.format); rt2.filterMode = FilterMode.Bilinear; Graphics.Blit(rt, rt2, material, 2); RenderTargetManager.ReleaseTexture(rt); rt = rt2; } GameObject.Destroy(material); Graphics.Blit(rt, texture); RenderTargetManager.ReleaseTexture(rt); }
时间: 2024-10-06 03:44:00