using UnityEngine; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; public class MeshTextureInfo { public float[] vertices; // [x,y,z, x,y,z, ...] public int[] indices; // 三角形索引 public bool revertSeam; // 是否需要翻转缝隙 public Vector2 position; // 最终绘制位置 public Vector2 localTransform; public float rotation; // 旋转角度 public Vector2 scale; // 缩放 public Texture2D sourceTexture; // 源贴图 public Vector2 tileSize; // 瓷砖大小 } public abstract class CombineTileByCPU : MonoBehaviour { // WebGL 平台的外部 JavaScript 调用 #if UNITY_WEBGL && !UNITY_EDITOR [DllImport("__Internal")] private static extern void UnityCallback(string callbackName, string data); #else // 非 WebGL 或编辑器模式下的模拟实现 private static void UnityCallback(string callbackName, string data) { // Debug.Log($"UnityCallback called with: {callbackName} {data}"); } #endif public abstract Material GetCurrentMaterial(); public abstract Shader GetCurrentShader(); public abstract JsonConfigParser.TileConfig GetConfig(); public abstract void SetTileConfig(string jsonContent); public Texture2D CreateTextureByAllConfigs() { JsonConfigParser.TileConfig config = GetConfig(); Texture2D[] textures = new Texture2D[config.base64Textures.Length]; for (int i = 0; i < config.base64Textures.Length; i++) { textures[i] = Base64ToTexture.ConvertBase64ToTexture(config.base64Textures[i]); } int maxWidth = config.size[0]; int maxHeight = config.size[1]; List meshInfos = new(); for (int i = 0; i < config.instances.Count; i++) { JsonConfigParser.InstanceData instance = config.instances[i]; JsonConfigParser.TileUniformData uniforms = instance.uniforms; JsonConfigParser.MeshData meshData = config.mesh[instance.meshidx]; MeshTextureInfo meshInfo = new(); int textureIndex = instance.texture; JsonConfigParser.TileSizeData tileSize = config.tileSize[textureIndex]; meshInfo.vertices = meshData.vertices; meshInfo.indices = meshData.indices; meshInfo.revertSeam = meshData.revertSeam != 0; meshInfo.position = new Vector2(instance.uniforms.vTransform[0] * tileSize.size[0], instance.uniforms.vTransform[1] * tileSize.size[1]); meshInfo.scale = new Vector2(instance.uniforms.vTextureSize[0], instance.uniforms.vTextureSize[1]); meshInfo.rotation = instance.uniforms.vTransform[2]; meshInfo.sourceTexture = textures[instance.texture]; meshInfo.localTransform = new Vector2(instance.uniforms.vLocalTransform[0], instance.uniforms.vLocalTransform[1]); meshInfo.tileSize = new Vector2(tileSize.size[0], tileSize.size[1]); Debug.Log($"tileSize => x: {meshInfo.tileSize.x}, y: {meshInfo.tileSize.y}"); meshInfos.Add(meshInfo); } Texture2D combinedTexture = CombineTexturesWithMesh(meshInfos, maxWidth, maxHeight); // 销毁不再需要的源贴图以释放内存 foreach (var tex in textures) { Destroy(tex); } return combinedTexture; } public void UpdateMaterial(string jsonContent) { // 销毁旧的纹理资源 Texture oldTexture = GetCurrentMaterial().GetTexture("_BaseMap"); if (oldTexture != null) { Destroy(oldTexture); } SetTileConfig(jsonContent); JsonConfigParser.TileConfig config = GetConfig(); Texture2D mainTex = CreateTextureByAllConfigs(); GetCurrentMaterial().SetTexture("_BaseMap", mainTex); GetCurrentMaterial().SetVector("_Tiling", new Vector2(config.tiles[0], config.tiles[1])); GetCurrentMaterial().mainTextureScale = new Vector2(config.tiles[0], config.tiles[1]); } public void GetCombinedTexture(string jsonContent) { SetTileConfig(jsonContent); string callbackName = JsonCallbackParser.ParseJsonConfig(jsonContent).callbackName; Texture2D mainTex = CreateTextureByAllConfigs(); UnityCallback(callbackName, System.Convert.ToBase64String(mainTex.EncodeToPNG())); Destroy(mainTex); } // 旋转材质 public void SetTextureRotate(float deg) { Material material = GetCurrentMaterial(); material.SetFloat("_Rotation", deg); material.SetPass(0); } // 根据 mesh 信息裁切并合成贴图 public Texture2D CombineTexturesWithMesh(List meshInfos, int width, int height) { RenderTexture renderTexture = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.ARGB32); RenderTexture previousRT = RenderTexture.active; RenderTexture.active = renderTexture; GL.Clear(true, true, Color.clear); // 使用一个共享的材质实例 Material mat = new(GetCurrentShader()) { hideFlags = HideFlags.HideAndDontSave, // 防止在场景中留下多余的材质实例 enableInstancing = false, renderQueue = 3000 // 确保在透明队列中渲染 }; mat.SetFloat("_Cutoff", 0f); // mat.SetInt("_ZWrite", 0); // 禁用深度写入 // mat.SetInt("_ZTest", (int)UnityEngine.Rendering.CompareFunction.LessEqual); // mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); // mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); // 开始绘制 GL.PushMatrix(); GL.LoadPixelMatrix(0, width, height, 0); // var i = 0; foreach (var meshInfo in meshInfos) { // 裁切源贴图 Texture2D clippedTexture = ClipTextureByMesh(meshInfo); // SaveTexture.SaveTextureAsPNG(clippedTexture, $"clippedTexture{i++}.png"); // 创建变换矩阵 Matrix4x4 matrix = Matrix4x4.TRS( new Vector3(meshInfo.position.x, meshInfo.position.y, 0), Quaternion.Euler(0, 0, 0), new Vector3(meshInfo.scale.x, meshInfo.scale.y, 1) ); Vector2 offset = meshInfo.localTransform; Vector2 tileSize = meshInfo.tileSize; Debug.Log($"offset: {offset} tileSize: {tileSize}"); // 计算旋转后的四个角的位置 Vector3[] corners = new Vector3[] { matrix.MultiplyPoint3x4(offset), matrix.MultiplyPoint3x4(new Vector3(tileSize.x + offset.x, offset.y, 0)), matrix.MultiplyPoint3x4(new Vector3(offset.x, tileSize.y + offset.y, 0)), matrix.MultiplyPoint3x4(new Vector3(tileSize.x + offset.x, tileSize.y + offset.y, 0)) }; // 计算包围矩形 float minX = corners.Min(c => c.x); float minY = corners.Min(c => c.y); float maxX = corners.Max(c => c.x); float maxY = corners.Max(c => c.y); Debug.Log($"minX: {minX} minY: {minY} maxX: {maxX} maxY: {maxY}"); Rect drawRect = new( minX, minY, maxX - minX, maxY - minY ); // Debug.Log($"Rect: {drawRect}"); // 设置主贴图 mat.SetTexture("_BaseMap", clippedTexture); mat.SetFloat("_Rotation", meshInfo.rotation); // 使用材质绘制 mat.SetPass(0); // 设置渲染通道 // 绘制贴图,注意透明部分不要覆盖上次绘制 Graphics.DrawTexture(drawRect, clippedTexture, mat); // 销毁临时贴图 Destroy(clippedTexture); } // 结束绘制 GL.PopMatrix(); // 确保所有绘制操作完成 GL.Flush(); // 读取渲染结果 Texture2D resultTexture = new(width, height, TextureFormat.RGBA32, false); resultTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0); resultTexture.Apply(); // 还原 RenderTexture RenderTexture.active = previousRT; RenderTexture.ReleaseTemporary(renderTexture); // 替换透明区域的颜色 ReplaceTransparentColor(resultTexture, new Color(0.6f, 0.6f, 0.6f, 1)); // 清理临时材质 Destroy(mat); // SaveTexture.SaveTextureAsPNG(resultTexture, "resultTexture.png"); return resultTexture; } private void ReplaceTransparentColor(Texture2D texture, Color replacementColor) { Color32[] pixels = texture.GetPixels32(); for (int i = 0; i < pixels.Length; i++) { if (pixels[i].a == 0) { pixels[i] = replacementColor; } } texture.SetPixels32(pixels); texture.Apply(); } // 根据 mesh 信息裁切贴图 private Texture2D ClipTextureByMesh(MeshTextureInfo meshInfo) { Texture2D sourceTexture = meshInfo.sourceTexture; // 创建一个新的贴图,初始化为透明 Texture2D clippedTexture = new( sourceTexture.width, sourceTexture.height, TextureFormat.RGBA32, false ); // 初始化像素为透明色 Color32[] clearColors = new Color32[sourceTexture.width * sourceTexture.height]; for (int i = 0; i < clearColors.Length; i++) clearColors[i] = new Color32(0, 0, 0, 0); clippedTexture.SetPixels32(clearColors); // 获取源贴图的像素 Color[] sourcePixels = sourceTexture.GetPixels(); // 处理每个三角形 for (int i = 0; i < meshInfo.indices.Length; i += 3) { int index1 = meshInfo.indices[i]; int index2 = meshInfo.indices[i + 1]; int index3 = meshInfo.indices[i + 2]; // 获取三角形顶点 Vector2 v1 = new( meshInfo.vertices[index1 * 3], meshInfo.vertices[index1 * 3 + 1] ); Vector2 v2 = new( meshInfo.vertices[index2 * 3], meshInfo.vertices[index2 * 3 + 1] ); Vector2 v3 = new( meshInfo.vertices[index3 * 3], meshInfo.vertices[index3 * 3 + 1] ); // 计算三角形的包围盒 float minX = Mathf.Min(v1.x, Mathf.Min(v2.x, v3.x)); float maxX = Mathf.Max(v1.x, Mathf.Max(v2.x, v3.x)); float minY = Mathf.Min(v1.y, Mathf.Min(v2.y, v3.y)); float maxY = Mathf.Max(v1.y, Mathf.Max(v2.y, v3.y)); // 转换到像素坐标 int startX = Mathf.Clamp(Mathf.FloorToInt(minX * sourceTexture.width), 0, sourceTexture.width - 1); int endX = Mathf.Clamp(Mathf.CeilToInt(maxX * sourceTexture.width), 0, sourceTexture.width - 1); int startY = Mathf.Clamp(Mathf.FloorToInt(minY * sourceTexture.height), 0, sourceTexture.height - 1); int endY = Mathf.Clamp(Mathf.CeilToInt(maxY * sourceTexture.height), 0, sourceTexture.height - 1); // 遍历包围盒中的像素 for (int y = startY; y <= endY; y++) { for (int x = startX; x <= endX; x++) { // 转换到 UV 空间 Vector2 pixelUV = new Vector2( (float)x / sourceTexture.width, (float)y / sourceTexture.height ); // 检查点是否在三角形内 if (IsPointInTriangle(pixelUV, v1, v2, v3)) { int pixelIndex = y * sourceTexture.width + x; clippedTexture.SetPixel(x, y, sourcePixels[pixelIndex]); } } } } clippedTexture.Apply(); return clippedTexture; } // 判断点是否在三角形内 private bool IsPointInTriangle(Vector2 p, Vector2 a, Vector2 b, Vector2 c) { // 使用重心坐标法判断点是否在三角形内 float denominator = ((b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y)); float alpha = ((b.y - c.y) * (p.x - c.x) + (c.x - b.x) * (p.y - c.y)) / denominator; float beta = ((c.y - a.y) * (p.x - c.x) + (a.x - c.x) * (p.y - c.y)) / denominator; float gamma = 1.0f - alpha - beta; return alpha >= 0 && beta >= 0 && gamma >= 0; } }