This article shares the specific code for Unity to display the spray paint effect for your reference. The specific content is as follows
Painting function
**Application scenarios: **Such as slogan decals on the wall, spray paint on the car, etc.
Choose a plan:
1. Of course, the implementation method is different, the easiest and the most "unsightly" method is to directly give a dough sheet, then obtain the spray paint position, and add an offset in the normal direction of the surface to be spray painted as the final dough sheet placement position. Of course, don't forget to set the direction of the dough sheet. Although this method is simple, the effect is not ideal. It will often intersperse with other objects. If there are too many curved surfaces in the game, then this solution is basically unavailable.
2. For individual special needs, for example, the tattoo on the character can be fully implemented in a shader. This method is limited to one decal corresponding to an object. If it is a one-to-many situation, please look at the two.
3. There is a simple method to use Projector. This method is relatively simple to implement, so I won’t say much.
4. Next, let’s talk about a dynamic grid generation scheme, which is also more commonly used. Next, let’s talk about this scheme in detail.
Implementation ideas:
The spray-painted mesh is dynamically generated based on the mesh of the objects at the spray position in the scene. When spraying, the objects within the specified range are obtained, and then a cube (or spheres can be used) to intercept the mesh of these objects, thereby constructing a new mesh and rendering the spray-paint on this mesh is OK.
Code implementation:
First, we need a function to get the MeshRenderer within the specified scope:
public GameObject[] GetAffectedObjects(Bounds bounds, LayerMask affectedLayers) { MeshRenderer[] renderers = FindObjectsOfType<MeshRenderer>(); List<GameObject> objects = new List<GameObject>(); foreach (Renderer r in renderers) { if (!) continue; if ((1 << & ) == 0) continue; if (<Decal>() != null) continue; if (()) { (); } } return (); }
Then take these GameObjects to do the cropping function:
public void BuildDecal(GameObject affectedObject, bool isLast) { Mesh affectedMesh = <MeshFilter>().sharedMesh; if (affectedMesh == null) return; //The vertices and triangles of the retrieved objects are pre-stored here, reducing unnecessary GC Vector3[] vertices = GetVertexList(affectedObject); int[] triangles = GetTriangleList(affectedObject); //The target vertex is converted to the current object's model space Matrix4x4 matrix = *; //Move the main calculation into asynchronous (() => { for (int i = 0; i < ; i += 3) { int i1 = triangles[i]; int i2 = triangles[i + 1]; int i3 = triangles[i + 2]; Vector3 v1 = (vertices[i1]); Vector3 v2 = (vertices[i2]); Vector3 v3 = (vertices[i3]); Vector3 side1 = v2 - v1; Vector3 side2 = v3 - v1; Vector3 normal = (side1, side2).normalized; if ((-, normal) >= maxAngle) continue; DecalPolygon poly = new DecalPolygon(v1, v2, v3); //Crop in cube poly = (poly, right); if (poly == null) continue; poly = (poly, left); if (poly == null) continue; poly = (poly, top); if (poly == null) continue; poly = (poly, bottom); if (poly == null) continue; poly = (poly, front); if (poly == null) continue; poly = (poly, back); if (poly == null) continue; AddPolygon(poly, normal); } if (isLast) { RenderDecal(); } }); }
DecalPolygon constructs a new triangle (here we pay attention to the spatial transformation of vertices), and then uses each face of the cube to be cut and converted into a mathematical algorithm. In fact, it is to judge the relationship between faces and faces, and implement it in detail:
/// <summary> /// Two sides intersect and cut/// </summary> public static DecalPolygon ClipPolygon(DecalPolygon polygon, Plane plane) { //Intersection is True bool[] positive = new bool[9]; int positiveCount = 0; for (int i = 0; i < ; i++) { positive[i] = !([i]); //Not on the front of the cutting side, it means there is an intersection if (positive[i]) positiveCount++; } if (positiveCount == 0) return null; //They are all cutting the front and the sides are not intersecting if (positiveCount == ) return polygon; //It's all on the back of the cutting side, completely intersecting DecalPolygon tempPolygon = new DecalPolygon(); for (int i = 0; i < ; i++) { int next = i + 1; next %= ; if (positive[i]) { ([i]); } if (positive[i] != positive[next]) { Vector3 v1 = [next]; Vector3 v2 = [i]; Vector3 v = LineCast(plane, v1, v2); (v); } } return tempPolygon; }
OK, all the data has been prepared for the new Mesh at this point, and then move the calculated data to the main thread for rendering:
public void RenderDecal() { //Main thread rendering (() => { if (sprite == null || Renderer == null||filter==null) { return; } //Generate UV information GenerateTexCoords(0, sprite); //Distance offset Push(pushDistance); Mesh mesh = CreateMesh(); if (mesh != null) { = "DecalMesh"; = mesh; = material; = true; } }); }
In this way, a painting function is done well. There are a few points to note:
Control
Example: Vector3[] vertices = ;
Note that this is not a simple memory reference, but will apply for new memory. Therefore, such temporary variables will cause GC. When the object's vertex is more than ten or even dozens of K, such GC cannot bear it! In order to avoid such a situation as much as possible, a pre-store process can be performed to save the vertex and triangle data of the object that has not been detected, and then directly retrieve it when used next time to replace it;
2. The problem of calculation quantity
It is still for performance reasons. When there are too many Mesh vertices cropped with it, it will be stuck in the main thread for loop for dozens of K times, and if nothing unexpected happens, the PC side will also be stuck, so asynchronous is a better choice. The complex cropping calculation is handed over to other threads, and the main thread calculates the data and renders it directly;
3. Effectiveness issues
Since the newly generated spray paint mesh is obtained by cutting the mesh of the original object, and the two Mesh positions overlap each other, and if the other dependent variables are the same, the computer will not know which one to render first, so the z-fighting problem occurs. So add a Push() method to extrude a little distance from the vertex of the new Mesh along the normal direction of the current vertex, thus realizing a paint spray function.
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.