移动端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操作可能触发隐式类型转换,改用逐位拼接函数确保位操作准确:
调用时直接传入单个byte值,避免精度损失。uint pack4Bytes(uint b0, uint b1, uint b2, uint b3) { return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); } - 验证渲染管线设置
在PlayerSettings中将移动端Graphics API设为OpenGLES3,确保Camera渲染目标绑定到32位整数纹理,且未开启颜色空间转换或后处理效果。
计算着色器方案(推荐)
计算着色器绕开渲染管线的颜色钳位逻辑,更适合数据打包任务:
- 创建计算着色器核心代码:
#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); } - 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
相关产品推荐
相关产品推荐

