preface:
For a 1024 * 1024 plot, it is necessary to calculate which plots are within the camera range in real time. In order to improve the operation efficiency, ComputeShader is used here.
1. Preparatory work
First, right-click Unity to create a ComputeShader, then create a Canvas and put a RawImage (an image for debugging).
Then create a control class and add the following control components:
public class ComputeTest : MonoBehaviour { #region external assignment attribute /// <summary> ///Debugging pictures for display; /// </summary> public RawImage ShowImage; /// <summary> ///Current calculated Shader /// </summary> public ComputeShader shader; /// <summary> ///Current master camera /// </summary> public Camera mainCamera; #endregion }
2. Write ComputeShader Code:
The principle is that the client transmits the VP matrix of the camera, and then calculates whether the current grid is displayed in the Shader. Then write it into the target image.
(there are many specific introductions to ComputeShader on the Internet, so I won't repeat them here)
// Each #kernel tells which function to compile; you can have many kernels #pragma kernel WolrdMapVisable // Create a RenderTexture with enableRandomWrite flag and set it // with cs.SetTexture RWTexture2D<float4> Result; float4x4 WorldToCameraMatrix; //Calculate visibility using VP matrix bool IsVisableInCamera (float x,float z) { float4 clipPos =mul(WorldToCameraMatrix, float4(x,0,z,1)); float3 ndcPos = float3(clipPos.x / clipPos.w, clipPos.y / clipPos.w, clipPos.z / clipPos.w); float view_x = 0.5f + 0.5f * clipPos.x / clipPos.w; float view_y = 0.5f + 0.5f * clipPos.y / clipPos.w; return view_x>=0 && view_x<=1 && view_y>=0 && view_y<=1 && clipPos.w>0; } [numthreads(8,8,1)] void WolrdMapVisable (uint3 id : SV_DispatchThreadID) { float x=id.x;//0~1024 float z=id.y;//0~1024 bool IsVisialbe = IsVisableInCamera(x,z); float retValue = IsVisialbe?1:0; //White if visible, black if invisible half4 col=half4(retValue,retValue,retValue,1); Result[id.xy] =col; }
The picture here is transmitted from the outside and has a fixed size (1024 * 1024);
3. Writing Unity C# control code
private const int TextureSize = 1024;//Entire map size private const int ThreadGroup = 8; private const int ThreadGroupSize = TextureSize / ThreadGroup; //Various attribute names; private const string ComputeShaderName = "WolrdMapVisable"; private const string ComputeTextureName = "Result"; private const string ComputeMatrixName = "WorldToCameraMatrix"; private int kID; private int matixNameID; private RenderTexture texture; // Start is called before the first frame update void Start() { RunComputeShader(); } private void RunComputeShader() { //Set the map for ComputeShader; texture = new RenderTexture(TextureSize, TextureSize, 24); texture.enableRandomWrite = true; texture.filterMode = FilterMode.Point; texture.Create(); //Display the picture on the UI and try it out; ShowImage.texture = texture; //Get some attributes of Shader; kID = shader.FindKernel(ComputeShaderName); matixNameID = Shader.PropertyToID(ComputeMatrixName); //Start Shader: shader.SetTexture(kID, ComputeTextureName, texture); UpdateComputeShader(); } void Update() { UpdateComputeShader(); } /// <summary> ///Call once per frame to set the camera matrix /// </summary> private void UpdateComputeShader() { shader.SetMatrix(matixNameID, mainCamera.projectionMatrix * mainCamera.worldToCameraMatrix); shader.Dispatch(kID, ThreadGroupSize, ThreadGroupSize, 1); }
This C# code is relatively simple. Just take a look at it.
Then run it to see:
You can see that the visual range (white) changes with the camera.
4. Return the calculation result
However, the above result calculated by ComputeShader is only a picture, and specific values cannot be obtained in C# inside. In some cases (such as displaying a small map), this is OK. However, if we want to know which coordinate points are within the camera range, we need to use ComputeBuffer.
Make the following supplements in the C# Code:
...... private const string ComputeBufferName = "VisableCellBuffer"; private int ComputeBufferID; private ComputeBuffer AppendBuffer; ...... private void RunComputeShader() { ...... //Here, the theoretical maximum value must be passed in when initializing the AppendBuffer, otherwise subsequent reading will fail; AppendBuffer = new ComputeBuffer(TextureSize * TextureSize, sizeof(int), ComputeBufferType.Append); ComputeBufferID = Shader.PropertyToID(ComputeBufferName); ...... } private void UpdateComputeShader() { ...... SetAppedBuffer(); ...... } private void SetAppedBuffer() { AppendBuffer.SetCounterValue(0); shader.SetBuffer(kID, ComputeBufferID, AppendBuffer); }
The following supplements need to be made in ComputeShader:
...... AppendStructuredBuffer<int2> VisableCellBuffer; ...... [numthreads(8,8,1)] void WolrdMapVisable (uint3 id : SV_DispatchThreadID) { ...... if(IsVisialbe) { //Convert the visible grid into int value and store it in VisableCellBuffer. int2 index=x*10000+z; VisableCellBuffer.Append(index); } ...... }
In this way, the display grid can be converted into a coordinate ID and transmitted.
Then read as follows:
private int[] ArrRet = new int[ThreadGroupSize]; private void ReadAppendBuffer() { var countBuffer = new ComputeBuffer(1, sizeof(int), ComputeBufferType.IndirectArguments); ComputeBuffer.CopyCount(AppendBuffer, countBuffer, 0); //The first data obtained through this method is the number of appendbuffers int[] counter = new int[1] { 0 };//ToDo, you can continue to optimize here; countBuffer.GetData(counter); int leftCount = counter[0]; int startIndex = 0; while (leftCount > 0) { int leftSize = Mathf.Min(leftCount, ThreadGroupSize); AppendBuffer.GetData(ArrRet, 0, startIndex, leftSize);//Read it out several times; for (int i = 0; i < ThreadGroupSize; i++) { //Debug.Log($"[{startIndex}|{i}] {ArrRet[i]}"); } leftCount = leftCount - ThreadGroupSize; startIndex = startIndex + ThreadGroupSize; } }
In this way, you can read the calculation results of ComputeShader.
PS:
Reading through ComputeBuffer actually consumes some performance. If the data is too large, you need to consider whether it is cost-effective.