I’m using this simple hue and saturation shift shader and I’d like to make is as optimal for mobile as possible (Android and iOS) - any hints?
The shader:
Shader "Custom/Room/EnvironmentUnlitHSV"
{
Properties
{
_MainTex ("Base (RGB) Trans. (Alpha)", 2D) = "white" { }
}
SubShader
{
Pass
{
Fog { Mode off }
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
//
// hue shift 0..360
//
float _RoomHueShift = 0;
//
// saturation -1..1
//
float _RoomSatShift = 0;
float3 hsv_shift(float3 RGB, float3 shift)
{
float3 RESULT = float3(RGB);
float shiftX314_d_180 = shift.x*3.14159265/180;
float VSU = shift.z*shift.y*cos(shiftX314_d_180);
float VSW = shift.z*shift.y*sin(shiftX314_d_180);
float shiftZ587 = .587*shift.z;
float shiftZ114 = .114*shift.z;
float shiftZ299 = .299*shift.z;
RESULT.x = (shiftZ299+.701*VSU+.168*VSW)*RGB.x
+ (shiftZ587-.587*VSU+.330*VSW)*RGB.y
+ (shiftZ114-.114*VSU-.497*VSW)*RGB.z;
RESULT.y = (shiftZ299-.299*VSU-.328*VSW)*RGB.x
+ (shiftZ587+.413*VSU+.035*VSW)*RGB.y
+ (shiftZ114-.114*VSU+.292*VSW)*RGB.z;
RESULT.z = (shiftZ299-.3*VSU+1.25*VSW)*RGB.x
+ (shiftZ587-.588*VSU-1.05*VSW)*RGB.y
+ (shiftZ114+.886*VSU-.203*VSW)*RGB.z;
return (RESULT);
}
float4 frag (v2f_img i) : COLOR
{
float4 tex = tex2D(_MainTex, i.uv);
float3 hsv;
hsv.x = _RoomHueShift;
hsv.y = 1 + _RoomSatShift;
hsv.z = 1;
tex.rgb = hsv_shift(tex.rgb, hsv);
return tex;
}
ENDCG
}
}
}
The simple optimisation I see is using the correct type size in the fragment func, it’s currently using float’s everywhere and I’m pretty sure I can get away with half or fixed here. I’m trying to test using different types here but I’m somewhat confused, because:
If I change the frag() and hsv_shift() functions to use fixed values like so:
fixed3 hsv_shift(fixed3 RGB, fixed3 shift)
{
fixed3 RESULT = fixed3(RGB);
fixed shiftX314_d_180 = shift.x*3.14159265/180;
fixed VSU = shift.z*shift.y*cos(shiftX314_d_180);
fixed VSW = shift.z*shift.y*sin(shiftX314_d_180);
fixed shiftZ587 = .587*shift.z;
fixed shiftZ114 = .114*shift.z;
fixed shiftZ299 = .299*shift.z;
RESULT.x = (shiftZ299+.701*VSU+.168*VSW)*RGB.x
+ (shiftZ587-.587*VSU+.330*VSW)*RGB.y
+ (shiftZ114-.114*VSU-.497*VSW)*RGB.z;
RESULT.y = (shiftZ299-.299*VSU-.328*VSW)*RGB.x
+ (shiftZ587+.413*VSU+.035*VSW)*RGB.y
+ (shiftZ114-.114*VSU+.292*VSW)*RGB.z;
RESULT.z = (shiftZ299-.3*VSU+1.25*VSW)*RGB.x
+ (shiftZ587-.588*VSU-1.05*VSW)*RGB.y
+ (shiftZ114+.886*VSU-.203*VSW)*RGB.z;
return (RESULT);
}
fixed4 frag (v2f_img i) : COLOR
{
fixed4 tex = tex2D(_MainTex, i.uv);
fixed4 hsv;
hsv.x = _RoomHueShift;
hsv.y = 1 + _RoomSatShift;
hsv.z = 1;
tex.rgb = hsv_shift(tex.rgb, hsv);
return tex;
}
This should fail for the hue shift since it’s specified in 0…360 (out side of the -2…2 range for a fixed) - what is going on here?
Also, what happens with the RHS arithmetic, what types are they during computation? I can’t seem to find a way to specify fixed literals - do I need to litter casts every where through the arithmetic there?
Why is this even working when the hue is outside of the type range?
I understand that it’s expensive to convert between types in a fragment func - but it’s not very obvious when this is happening…
Now, the type sizes aside, what other ways could this be optimised?
It would be awesome to be able to keep the result from the previous frame for the next frame - this shader is being applied to some static textures, ie, they only need to be recalculated when the hue or saturation is changed (very infrequently) - can this be saved and reused?
Lookup tables? Is this a feasible thing to do here? I’ve see some mention of using a texture3d lookup, anyone had any experience doing this in Unity?
Open to all ideas…