using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; namespace TriLibCore.Extras { /// /// Represents a class that renders point clouds and is compatible with every Unity platform. /// public class PointRenderer : MonoBehaviour { /// /// The size of each rendered point. /// public float PointSize = 0.01f; /// /// The texture containing the mesh colors. /// private Texture2D _colorTexture; /// /// The texture containing the mesh positions. /// private Texture2D _positionTexture; /// /// The material used to render the point cloud. /// public Material Material { get; private set; } /// /// The mesh created to draw the point cloud. /// This mesh is needed to overcome the lack of the `SV_VertexID` semantic in WebGL. /// public Mesh Mesh { get; private set; } /// /// Initializes the point renderer data with the given mesh data. /// /// The mesh to draw the points from. public void Initialize(Mesh mesh) { if (mesh == null) { throw new ArgumentNullException(nameof(mesh)); } var textureResolution = Mathf.CeilToInt(Mathf.Sqrt(mesh.vertexCount)); _colorTexture = new Texture2D(textureResolution, textureResolution, TextureFormat.RGBA32, false); var colorData = _colorTexture.GetRawTextureData(); _positionTexture = new Texture2D(textureResolution, textureResolution, TextureFormat.RGBAFloat, false); var positionData = _positionTexture.GetRawTextureData(); var vertices = mesh.vertices; var colors = mesh.colors32; if (colors.Length < vertices.Length) { colors = new Color32[vertices.Length]; for (var i = 0; i < colors.Length; i++) { colors[i] = new Color32(255, 255, 255, 255); } } for (var i = 0; i < mesh.vertexCount; i++) { colorData[i] = colors[i]; positionData[i] = vertices[i]; } _colorTexture.Apply(false, true); _positionTexture.Apply(false, true); Material = new Material(Shader.Find("Hidden/TriLib/PointRenderer")); Material.SetTexture("_ColorTex", _colorTexture); Material.SetTexture("_PositionTex", _positionTexture); Material.SetInt("_TextureResolution", textureResolution); Mesh = CreateCompatibleMesh(MeshTopology.Quads, mesh.vertexCount * 4); } /// /// Creates a new mesh to simulate DrawProceduralNow. /// /// The mesh topology. /// The final vertex count. /// The created mesh. private static Mesh CreateCompatibleMesh(MeshTopology meshTopology, int vertexCount) { var mesh = new Mesh(); mesh.subMeshCount = 1; mesh.indexFormat = vertexCount > ushort.MaxValue ? IndexFormat.UInt32 : IndexFormat.UInt16; mesh.vertices = new Vector3[vertexCount]; var uv = new Vector2[vertexCount]; for (var i = 0; i < vertexCount; i++) { uv[i] = new Vector2(i, 0); } mesh.uv = uv; var indices = new List(vertexCount); switch (meshTopology) { case MeshTopology.Triangles: for (var i = 0; i < vertexCount; i += 3) { indices.Add(i + 0); indices.Add(i + 1); indices.Add(i + 2); } break; case MeshTopology.Quads: for (var i = 0; i < vertexCount; i += 4) { indices.Add(i + 0); indices.Add(i + 1); indices.Add(i + 2); indices.Add(i + 3); } break; case MeshTopology.Lines: for (var i = 0; i < vertexCount; i += 2) { indices.Add(i + 0); indices.Add(i + 1); } break; case MeshTopology.LineStrip: case MeshTopology.Points: for (var i = 0; i < vertexCount; i++) { indices.Add(i); } break; default: throw new ArgumentOutOfRangeException(nameof(meshTopology), meshTopology, null); } mesh.SetIndices(indices, 0, indices.Count, meshTopology, 0); mesh.UploadMeshData(true); return mesh; } /// /// Renders the point cloud using the previously created mesh. /// private void OnRenderObject() { var aspectRatio = (float)Screen.width / Screen.height; Material.SetFloat("_AspectRatio", aspectRatio); Material.SetFloat("_PointSize", PointSize); Material.SetPass(0); Graphics.DrawMeshNow(Mesh, transform.localToWorldMatrix); } } }