You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
    }
}

修复说明

  1. 修复尺寸读取:按小端模式读取16位的尺寸参数,确保图像宽高正确。
  2. 添加RLE解压缩:实现PCX的游程编码解码逻辑,还原真实的颜色数据。
  3. 修正颜色值范围:将byte值转换为0-1的浮点数后传入Color构造函数。
  4. 处理颜色平面:按R、G、B平面拆分解压缩后的数据,正确映射每个像素的颜色。
  5. 反转Y轴:将PCX的从下到上存储转换为Unity纹理的从上到下存储,解决图像颠倒问题。

内容的提问来源于stack exchange,提问作者user1675108

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 20:04:57