テッセレーションで草
環境
Unity2021.2.18f1
概要
metalがジオメトリシェーダに対応していないらしいので使わずに草を生やしてみます。
テッセレーションでは点から三角(もしくは四角)形を生成できるのでやってみました。
1チャンネルのテクスチャからMeshTopology.Pointsのメッシュを作成して、テッセレーションシェーダで草にしています。
距離で分割数を変えているので手前は割と分割されていますが、少し後ろに行くとただの四角形になります。
Shader "Custom/TessellationGrass" { Properties { _Color("Color", color) = (12, 173, 0, 1) _UnderColor("UnderColor", color) = (0, 0, 0, 1) _FlowTex("Flow Texture", 2D) = "white" {} _MinDist("Min Distance", Range(0.1, 100)) = 0.1 _MaxDist("Max Distance", Range(0.1, 100)) = 2 _TessFactor("Tessellation", Range(1, 5000)) = 10 //分割レベル _PositionScale("Position Scale", Range(0.1, 100)) = 10 _Width("Width", Range(0.01, 0.5)) = 0.05 _Height("Height", Range(0.1, 1)) = 0.5 _FlowPower("FlowPower", Range(0.1, 1)) = 0.1 _FlowSpeed("_FlowSpeed", Range(0.1, 1)) = 0.1 [KeywordEnum(integer, fractional_even, fractional_odd, pow2)] _Partitioning("Partitioning", int) = 2 } SubShader { Tags { "RenderType" = "Opaque" "LightMode" = "ForwardBase" } Cull Off Pass { CGPROGRAM #include "Tessellation.cginc" #include "UnityCG.cginc" #include "Lighting.cginc" #pragma shader_feature _PARTITIONING_INTEGER _PARTITIONING_FRACTIONAL_EVEN _PARTITIONING_FRACTIONAL_ODD _PARTITIONING_POW2 #pragma vertex vertex_shader #pragma hull hull_shader #pragma domain domain_shader #pragma fragment pixel_shader fixed4 _Color; fixed4 _UnderColor; sampler2D _FlowTex; float4 _FlowTex_ST; float _TessFactor; float _MinDist; float _MaxDist; float _PositionScale; float _Width; float _Height; float _FlowPower; float _FlowSpeed; struct appdata { float4 position : POSITION; }; struct VS_OUTPUT { float4 position : POSITION; float2 flow : TEXCOORD0; }; struct HS_CONSTANT_DATA_OUTPUT { float Edges[4] : SV_TessFactor; float Inside[2] : SV_InsideTessFactor; }; struct HS_OUTPUT { float4 position : POS; float2 flow : TEXCOORD0; float2 basePosXZ : TEXCOORD1; }; struct DS_OUTPUT { float4 position : SV_Position; float4 color : Color; }; VS_OUTPUT vertex_shader(appdata i) { VS_OUTPUT o; o.position = i.position; o.position.xyz *= _PositionScale; o.position.w = i.position.w / 255.0; float2 uv = frac(i.position.xz + 0.5/*/ _TexelSize.xy*/ + _Time.y * _FlowSpeed); o.flow = (tex2Dlod(_FlowTex, float4(uv, 0, 0)).xy * 2 - 1) * _FlowPower; return o; } #define INPUT_PATCH_SIZE 1 #define OUTPUT_PATCH_SIZE 4 HS_CONSTANT_DATA_OUTPUT constantsHS(InputPatch<VS_OUTPUT, INPUT_PATCH_SIZE> i) { float tessFactor = UnityCalcDistanceTessFactor(i[0].position, _MinDist, _MaxDist, _TessFactor); HS_CONSTANT_DATA_OUTPUT o; o.Edges[0] = 1; o.Edges[1] = tessFactor; o.Edges[2] = 1; o.Edges[3] = tessFactor; o.Inside[0] = tessFactor; o.Inside[1] = 1; return o; } [domain("quad")] #if _PARTITIONING_INTEGER [partitioning("integer")] #elif _PARTITIONING_FRACTIONAL_EVEN [partitioning("fractional_even")] #elif _PARTITIONING_FRACTIONAL_ODD [partitioning("fractional_odd")] #else//elif _PARTITIONING_POW2 [partitioning("pow2")] #endif [outputtopology("triangle_cw")] [outputcontrolpoints(OUTPUT_PATCH_SIZE)] [patchconstantfunc("constantsHS")] HS_OUTPUT hull_shader(InputPatch<VS_OUTPUT, INPUT_PATCH_SIZE>i, uint id: SV_OutputControlPointID, uint patchID: SV_PrimitiveID) { HS_OUTPUT o; uint loopIndex = (id / INPUT_PATCH_SIZE); uint inIndex = id % INPUT_PATCH_SIZE; uint index = loopIndex * INPUT_PATCH_SIZE + inIndex; float widthH = _Width * 0.5; float3 offsets[] = {float3(-widthH,0,0), float3(-widthH,_Height,0), float3(widthH,0,0), float3(widthH, _Height, 0)}; o.position = float4(i[inIndex].position.xyz + offsets[index], i[inIndex].position.w); o.position.y *= i[inIndex].position.w; o.basePosXZ = i[inIndex].position.xz; o.flow = i[inIndex].flow; return o; } [domain("quad")] DS_OUTPUT domain_shader(HS_CONSTANT_DATA_OUTPUT iConst, const OutputPatch<HS_OUTPUT, OUTPUT_PATCH_SIZE> i, float2 bary: SV_DomainLocation) { DS_OUTPUT o; float3 pa = lerp(i[0].position.xyz, i[1].position.xyz, bary.x); float3 pb = lerp(i[2].position.xyz, i[3].position.xyz, bary.x); float3 pos = lerp(pa, pb, bary.y); float height = _Height * i[0].position.w; float minH = height * 0.3; float maxH = height; float r = saturate((pos.y - minH) / (maxH - minH)); float fr = sin(saturate(pos.y / height) * UNITY_HALF_PI); pos.xz = i[0].basePosXZ + (pos.xz - i[0].basePosXZ) * (1.0 - r) + i[0].flow * fr; o.position = UnityObjectToClipPos(float4(pos, 1.0)); float cminH = height * 0; float cmaxH = height * 1; float cr = saturate((pos.y - cminH) / (cmaxH - cminH)); o.color = lerp(_UnderColor, _Color, cr); return o; } fixed4 pixel_shader(DS_OUTPUT i) : SV_TARGET { return i.color; } ENDCG } } }
using System; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; using UnityEngine.Rendering; public class Texture2DToPointCloudedMesh : MonoBehaviour { [SerializeField] private Texture2D _texture = null; [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] struct Vertex { public Vector4 Position; } private unsafe void OnEnable() { NativeArray<byte> bytes = _texture.GetPixelData<byte>(0); var pBytes = (byte*)NativeArrayUnsafeUtility.GetUnsafePtr(bytes); var mesh = new Mesh(); var layout = new[] { new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 4), }; var vertexCount = _texture.width * _texture.height; mesh.SetVertexBufferParams(vertexCount, layout); var verts = new NativeArray<Vertex>(vertexCount, Allocator.Temp); var pVerts = (Vertex*)NativeArrayUnsafeUtility.GetUnsafePtr(verts); var indices = new NativeArray<UInt16>(vertexCount, Allocator.Temp); var pIndices = (UInt16*)NativeArrayUnsafeUtility.GetUnsafePtr(indices); for(int i = 0; i < vertexCount; i++) { var x = -0.5f + (float)(i % _texture.width) / (float)_texture.width; var z = -0.5f + (float)(i / _texture.width) / (float)_texture.height; pVerts[i].Position = new Vector4(x, 0, z, pBytes[i]); pIndices[i] = (UInt16)i; } mesh.SetVertexBufferData(verts, 0, 0, vertexCount); mesh.SetIndexBufferParams(vertexCount, IndexFormat.UInt16); mesh.SetIndices<UInt16>(indices, MeshTopology.Points, 0); mesh.bounds = new Bounds(Vector3.zero, new Vector3(1000, 1000, 1000)); var meshFilter = gameObject.GetComponent<MeshFilter>(); meshFilter.sharedMesh = mesh; } }
感想
・QuadじゃなくてTriangleで良かったです。
・今回はMeshにしましたが、頂点データが無駄です。