How to find average color of a texture efficiently using compute shader.

Hello,
Not sure if we have a thread related to the same, sorry if it is. So im trying to read a render texture’s colors and getting an average color using CPU and its quite expensive if we try implementing every frame. So i tried to do it with a compute shader and im new to shader programming.

This is my c# code without a proper shader implementation or dispatch (please ignore the shader code)

VideoPlayer videoPlayer;
RenderTexture _videoRenderTex;
Texture2D _tempTexture;
public RenderTexture _tempRend;
public ComputeShader _averageColorCS;

public int downScaleRatio = 8;
[SerializeField]
Color color;
[SerializeField]
private Material _projectorMat;
private void Awake()
{
videoPlayer = GetComponent();
_videoRenderTex = videoPlayer.targetTexture;
_tempRend = new RenderTexture(256/ downScaleRatio, 256 / downScaleRatio, 24);
AverageColor color = new AverageColor();
// InvokeRepeating(nameof(DebugColor), 0, 1);
//for (int i = 0; i < k; i++)
//{
// int index = r.Next(0, colors.Count);
// AddInitialCluster(colors[index]);
//}
}
public void DebugColor()
{
//_tempTexture = toTexture2D(_videoRenderTex);

_videoRenderTex.enableRandomWrite = true;
Graphics.Blit(_videoRenderTex, _tempRend);
int ColorSize = sizeof(float) * 4;
ComputeBuffer colorBuffer = new ComputeBuffer(1, ColorSize);
//colorBuffer.SetData(color);

_averageColorCS.SetTexture(0, “Result”, _tempRend);
_averageColorCS.SetFloat(“Resolution”, _tempRend.width);
_averageColorCS.Dispatch(0, _tempRend.width / 8, _tempRend.height / 8, 1);
Vector3 _color = GetSourceData();
color = new Color(_color.x,_color.y,_color.z);
SetColor();
}
Texture2D toTexture2D(RenderTexture rTex)
{

Texture2D tex = new Texture2D(videoRenderTex.width / downScaleRatio, videoRenderTex.height / downScaleRatio, TextureFormat.RGB24, false);
// ReadPixels looks at the active RenderTexture.
RenderTexture.active = rTex;
tex.ReadPixels(new Rect(0, 0, rTex.width, rTex.height), 0, 0);
tex.Apply();
return tex;
}
private Vector3 GetSourceData()
{
Color32[ ] texColors = tempTexture.GetPixels32();
int total = texColors.Length;
float r = 0;
float g = 0;
float b = 0;
for (int i = 0; i < total; i++)
{
r += texColors*.r;*
g += texColors*.g;*
b += texColors*.b;*
color = new Color32((byte)(r / total), (byte)(g / total), (byte)(b / total), 0);

}
return new Vector3(color.r, color.g, color.b);
}
private void SetColor()
{
_projectorMat.color = color;
}
private void Update()
{
DebugColor();
}
Can anyone help me setup this compute shader and im pretty sure i have the logic implemented but couldnt translate to shader. Any help is much appreciated._

So, this can be implemented on the GPU. But there’s an easier solution.

As long as your RenderTexture has mip maps enabled, you can either have them generate automatically, or manually. The smallest mip is a 1x1 image that’s the average color of the screen.

2 Likes

Thanks for your reply. I really also want to learn the usage of Compute shader here, havent really had a scenario to make use of them. Will check the implementation you suggested, if possible do share any resource to learn. Cheers and thanks :slight_smile:

How can i get the pixel value of the 1x1 mipmap? i cant figure it out, im fairly new unity so sorry if im just stupid

Nvm forget my question after alot of headache and searching the docs ive finally figured it out so for anyone wondering here you go

// Read pixel data from the 1x1 mipmap level
var asyncAction = AsyncGPUReadback.Request(Texture, Texture.mipmapCount - 1);
asyncAction.WaitForCompletion();

// Extract average color
Color32 Average = asyncAction.GetData<Color32>()[0];
2 Likes

Thanks a lot for sharing your solution!