Unity中逐字节读取PCX文件转纹理显示异常问题排查
Unity中PCX文件转纹理显示混乱的排查与解决
问题描述
我给自己设定了一项挑战:在Unity C#中逐字节读取PCX文件并将其转换为纹理。目前能正常读取文件数据,但生成的纹理显示混乱,甚至无法填满整个物体。
原代码
public class Parser : MonoBehaviour { public MeshRenderer mat; public int bitsInPlane; public int minX; public int minY; public int maxX; public int maxY; public int horzDPI; public int vertDPI; public int numColorPlanes; public int bytesPerColorPlane; public int horzSourceScreen; public int vertSourceScreen; public int startingPoint; void Start() { Application.targetFrameRate = 24; List<byte> byteStream = new List<byte>(); byteStream = readImageData(); printBytes(byteStream); readBytesHeader(byteStream); drawTexture(byteStream); } void Update() { } List<byte> readImageData() { string pcxPath = Application.dataPath + "/resources/bagit_icon.pcx"; List<byte> readByteStream = new List<byte>(); using (var stream = File.Open(pcxPath, FileMode.Open)) { using (var bReader = new BinaryReader(stream, System.Text.Encoding.UTF8, false)) { while (stream.Position != stream.Length) { readByteStream.Add(bReader.ReadByte()); } } } return readByteStream; } void printBytes(List<byte> bytes) { int offset = 0; foreach (var b in bytes) { Debug.Log(offset.ToString("X4") + " [" + offset + "]: " + b); offset++; } } void readBytesHeader(List<byte> bytes) { bitsInPlane = bytes[3]; minX = bytes[4]; minY = bytes[6]; maxX = bytes[8]; maxY = bytes[10]; horzDPI = bytes[12]; vertDPI = bytes[14]; numColorPlanes = bytes[65]; bytesPerColorPlane = bytes[66]; horzSourceScreen = bytes[70]; vertSourceScreen = bytes[72]; startingPoint = 128; } void drawTexture(List<byte> bytes) { int currentPoint = startingPoint; Debug.Log("BYTES: " + bytes); Debug.Log("Size Calculation: " + ((bytes.Count - startingPoint) / 3) + " " + ((maxX / horzDPI) * (maxY / vertDPI))); Texture2D imgTexture = new Texture2D(maxX + 1, maxY + 1, TextureFormat.RGB24, false); for (int y = 0; y < maxY; y++) { for (int x = 0; x < maxX; x++) { if(currentPoint <= bytes.Count - 3) { Debug.Log("COLOR: " + bytes[currentPoint] + " " + bytes[currentPoint + 1] + " " + bytes[currentPoint + 2]); Color newColor = new Color(bytes[currentPoint], bytes[currentPoint + 1], bytes[currentPoint + 2]); imgTexture.SetPixel(x, y, newColor); currentPoint += 3; } } } imgTexture.Apply(); mat.materials[0].mainTexture = imgTexture; } }
问题分析
- 未处理PCX的RLE压缩:PCX图像数据采用游程编码(RLE)压缩,直接按原始字节读取会导致颜色序列完全错乱。
- 颜色值范围错误:Unity的
Color构造函数需要0-1的浮点数参数,直接传入0-255的byte值会导致颜色异常过亮。 - 图像尺寸读取错误:PCX头中的
minX、maxX、minY、maxY是16位整数,原代码仅读取了低8位,导致尺寸计算错误。 - 像素存储方向错误:PCX的Y轴像素是从下到上存储的,原代码从上到下绘制会导致图像上下颠倒。
- 颜色平面存储逻辑错误:24位PCX的颜色数据是按平面存储(先存所有R通道字节,再存G通道,最后存B通道),而非按像素RGB顺序存储。
修复后的代码
public class Parser : MonoBehaviour { public MeshRenderer mat; public int bitsInPlane; public int minX; public int minY; public int maxX; public int maxY; public int horzDPI; public int vertDPI; public int numColorPlanes; public int bytesPerColorPlane; public int horzSourceScreen; public int vertSourceScreen; public int startingPoint; void Start() { Application.targetFrameRate = 24; List<byte> byteStream = readImageData(); readBytesHeader(byteStream); drawTexture(byteStream); } List<byte> readImageData() { string pcxPath = Application.dataPath + "/resources/bagit_icon.pcx"; List<byte> readByteStream = new List<byte>(); using (var stream = File.Open(pcxPath, FileMode.Open)) using (var bReader = new BinaryReader(stream, System.Text.Encoding.UTF8, false)) { while (stream.Position != stream.Length) { readByteStream.Add(bReader.ReadByte()); } } return readByteStream; } void readBytesHeader(List<byte> bytes) { bitsInPlane = bytes[3]; // 读取16位整数(小端模式) minX = bytes[4] | (bytes[5] << 8); minY = bytes[6] | (bytes[7] << 8); maxX = bytes[8] | (bytes[9] << 8); maxY = bytes[10] | (bytes[11] << 8); horzDPI = bytes[12] | (bytes[13] << 8); vertDPI = bytes[14] | (bytes[15] << 8); numColorPlanes = bytes[65]; bytesPerColorPlane = bytes[66] | (bytes[67] << 8); horzSourceScreen = bytes[70] | (bytes[71] << 8); vertSourceScreen = bytes[72] | (bytes[73] << 8); startingPoint = 128; } // 解压缩PCX的RLE数据 List<byte> DecodeRLE(List<byte> compressedData) { List<byte> decompressed = new List<byte>(); int i = 0; while (i < compressedData.Count) { byte b = compressedData[i]; // 判断是否是RLE标记位(高2位为1) if ((b & 0xC0) == 0xC0) { int count = b & 0x3F; // 低6位是重复次数 byte value = compressedData[i + 1]; for (int j = 0; j < count; j++) { decompressed.Add(value); } i += 2; } else { decompressed.Add(b); i += 1; } } return decompressed; } void drawTexture(List<byte> bytes) { int width = maxX - minX + 1; int height = maxY - minY + 1; Texture2D imgTexture = new Texture2D(width, height, TextureFormat.RGB24, false); // 提取并解压缩图像数据 List<byte> compressedImageData = bytes.GetRange(startingPoint, bytes.Count - startingPoint); List<byte> decompressedData = DecodeRLE(compressedImageData); // 按平面拆分颜色数据(R、G、B三个平面) int planeSize = width * height; List<byte> rPlane = decompressedData.GetRange(0, planeSize); List<byte> gPlane = decompressedData.GetRange(planeSize, planeSize); List<byte> bPlane = decompressedData.GetRange(planeSize * 2, planeSize); // 填充纹理像素,注意Y轴反转 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // PCX的Y是从下到上,所以转换为Unity的从上到下 int srcIndex = x + (height - 1 - y) * width; float r = rPlane[srcIndex] / 255f; float g = gPlane[srcIndex] / 255f; float b = bPlane[srcIndex] / 255f; imgTexture.SetPixel(x, y, new Color(r, g, b)); } } imgTexture.Apply(); mat.materials[0].mainTexture = imgTexture; } }
修复说明
- 修复尺寸读取:按小端模式读取16位的尺寸参数,确保图像宽高正确。
- 添加RLE解压缩:实现PCX的游程编码解码逻辑,还原真实的颜色数据。
- 修正颜色值范围:将byte值转换为0-1的浮点数后传入Color构造函数。
- 处理颜色平面:按R、G、B平面拆分解压缩后的数据,正确映射每个像素的颜色。
- 反转Y轴:将PCX的从下到上存储转换为Unity纹理的从上到下存储,解决图像颠倒问题。
内容的提问来源于stack exchange,提问作者user1675108
相关产品推荐
相关产品推荐

