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

移动端Unity着色器输出32位整数异常问题求助

问题

需要生成神经网络输入图像,每像素包含16个int8类型值。此前用原生OpenGL/ES实现时,可将16个int8打包为R32G32B32A32纹理的4个int32通道,桌面和移动端均正常。

但在Unity中实现相同逻辑时,桌面端正常,同一移动端设备上无法向32位通道的高16位写入数据:高16位为0时正常,填充数据后通道会变为0x0080FFFF或0xFF7F0000这类异常钳位值。尝试过纹理和着色器的有符号/无符号数据类型,仅桌面端有效。

寻求以下方向的解决方案:

  • 着色器或配置修改建议
  • 切换为计算着色器是否可行
  • 能否在不破坏Unity框架(无需额外操作即可渲染Camera的MeshRenderer/SkinnedMeshRenderer组件)的前提下调用底层OpenGL

原着色器代码:

Shader "Unlit/Neural Input"
{
    Properties
    {
        _NeuralTex ("NeuralTexture", 2DArray) = "" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        //LOD 100
     
        Pass {
            //Tags { "LightMode" = "Never" }
         
            Fog { Mode Off }
            ZWrite On
            ZTest LEqual
            Cull Back
            Lighting Off
 
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma    require    2darray integers
            #pragma fragmentoption ARB_precision_hint_nicest
            //#pragma target 5.0 // OPENGL ES of specific version
 
            // depends on target texture's internal type
            #pragma multi_compile SIGNED_INTEGERS UNSIGNED_INTEGERS
           
            #include "UnityCG.cginc"
 
            struct vertexInput
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
 
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
 
            v2f vert (vertexInput v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
 
            UNITY_DECLARE_TEX2DARRAY(_NeuralTex);
 
#if UNSIGNED_INTEGERS
            uint4 frag (v2f i) : SV_Target
#elif SIGNED_INTEGERS
            int4 frag (v2f i) : SV_Target
#endif
            {
                uint4 packingMultiplier = uint4(1U, 256U, 65536U, 16777216U);
                // 4 byte values packed into a single channel
                // but if 3rd or 4th byte is set to other than 0 it renders incorrectly
                uint4 latentFragment = uint4(
                    dot(uint4(10 , 20 , 0, 0), packingMultiplier), // ok
                    dot(uint4(50 , 60 , 0, 0), packingMultiplier), // ok
                    dot(uint4(90 , 100, 1, 0), packingMultiplier), // not ok
                    dot(uint4(130, 128, 0, 1), packingMultiplier) // not ok
                );
#if UNSIGNED_INTEGERS
                    return latentFragment;
#elif SIGNED_INTEGERS
                    return int4(latentFragment);
#endif
                }
            }
            ENDCG
        }
    }
}
解决方案建议

着色器与纹理配置优化

  • 明确渲染目标格式与精度
    确保目标纹理格式设为RenderTextureFormat.ARGB32Int(无符号)或RenderTextureFormat.ARGB32Signed(有符号),创建时禁用sRGB转换:renderTexture.sRGB = false;。在着色器中替换目标版本为#pragma target 3.0(对应移动端主流支持的OpenGL ES 3.0),并添加显式整数精度声明:precision highp uint;和precision highp int;,移除ARB_precision_hint_nicest避免精度冲突。
  • 修改打包逻辑
    原dot操作可能触发隐式类型转换,改用逐位拼接函数确保位操作准确:
    uint pack4Bytes(uint b0, uint b1, uint b2, uint b3) {
        return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
    }
    
    调用时直接传入单个byte值,避免精度损失。
  • 验证渲染管线设置
    在PlayerSettings中将移动端Graphics API设为OpenGLES3,确保Camera渲染目标绑定到32位整数纹理,且未开启颜色空间转换或后处理效果。

计算着色器方案(推荐)

计算着色器绕开渲染管线的颜色钳位逻辑,更适合数据打包任务:

  1. 创建计算着色器核心代码:
    #pragma kernel CSMain
    
    RWTexture2D<uint4> _OutputTexture;
    
    [numthreads(8,8,1)]
    void CSMain (uint3 id : SV_DispatchThreadID)
    {
        uint r = 10 | (20 << 8) | (0 << 16) | (0 << 24);
        uint g = 50 | (60 << 8) | (0 << 16) | (0 << 24);
        uint b = 90 | (100 << 8) | (1 << 16) | (0 << 24);
        uint a = 130 | (128 << 8) | (0 << 16) | (1 << 24);
    
        _OutputTexture[id.xy] = uint4(r, g, b, a);
    }
    
  2. C#调度代码:
    computeShader.SetTexture(0, "_OutputTexture", neuralInputTexture);
    int threadGroupsX = Mathf.CeilToInt(neuralInputTexture.width / 8f);
    int threadGroupsY = Mathf.CeilToInt(neuralInputTexture.height / 8f);
    computeShader.Dispatch(0, threadGroupsX, threadGroupsY, 1);
    

Unity框架内调用底层OpenGL的可行性

可以通过Rendering.CommandBuffer或GL类调用底层OpenGL命令,但需编写平台原生插件(C/C++)处理版本兼容性,且要与Unity渲染管线同步避免冲突。若仅生成神经网络输入,计算着色器方案已足够,无需调用底层API。

修改后的片段着色器参考
Shader "Unlit/Neural Input"
{
    Properties
    {
        _NeuralTex ("NeuralTexture", 2DArray) = "" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Pass {
            Fog { Mode Off }
            ZWrite On
            ZTest LEqual
            Cull Back
            Lighting Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma require 2darray integers
            #pragma target 3.0
            #pragma multi_compile SIGNED_INTEGERS UNSIGNED_INTEGERS

            #include "UnityCG.cginc"

            precision highp uint;
            precision highp int;

            struct vertexInput
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            v2f vert (vertexInput v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            UNITY_DECLARE_TEX2DARRAY(_NeuralTex);

            uint pack4Bytes(uint b0, uint b1, uint b2, uint b3) {
                return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
            }

#if UNSIGNED_INTEGERS
            uint4 frag (v2f i) : SV_Target
#elif SIGNED_INTEGERS
            int4 frag (v2f i) : SV_Target
#endif
            {
                uint4 latentFragment = uint4(
                    pack4Bytes(10 , 20 , 0, 0),
                    pack4Bytes(50 , 60 , 0, 0),
                    pack4Bytes(90 , 100, 1, 0),
                    pack4Bytes(130, 128, 0, 1)
                );
#if UNSIGNED_INTEGERS
                return latentFragment;
#elif SIGNED_INTEGERS
                return int4(latentFragment);
#endif
            }
            ENDCG
        }
    }
}

内容的提问来源于stack exchange,提问作者Alexey S. Larionov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 10:18:14