edgeToStroke.shader
1 Shader "Custom/edgeToStroke" 2 { 3 Properties 4 { 5 [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1) 6 [MainTexture] _BaseMap("Base Map", 2D) = "white" 7 _PyramidHeight("Pyramid Height", Float) = 1 8 } 9 SubShader 10 { 11 Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" } 12 13 Pass 14 { 15 Name "ForwardLit" 16 Tags { "LightMode" = "UniversalForward" } 17 Cull Off 18 19 HLSLPROGRAM 20 21 #pragma prefer_hlslcc gles 22 #pragma exclude_renderers d3d11_9x 23 #pragma target 2.0 24 #pragma require geometry 25 26 #pragma vertex vert 27 #pragma fragment frag 28 29 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" 30 #include "Assets/Resources/GreasePencil/common_shader_util.hlsl" 31 // Define a struct to match the one in your C# script 32 struct VertexData 33 { 34 float3 position; 35 float3 normal; 36 }; 37 38 // Declare the GraphicsBuffers that we will set from the C# script 39 StructuredBuffer<int> _MeshIndices; 40 // adjacency not needed here; we read adj from _inEdges 41 42 struct StrokeData 43 { 44 float3 pos[2]; 45 int adj[2]; // Adjacent face index for each endpoint's edge (-1 none, -2 invalid) 46 float3 faceNormal; 47 uint minPoint[2]; 48 }; 49 StructuredBuffer<StrokeData> _inEdges; 50 51 struct v2f 52 { 53 float3 normalWS : TEXCOORD0; // World space normal 54 float4 vertex : SV_POSITION; 55 }; 56 57 TEXTURE2D(_BaseMap); 58 SAMPLER(sampler_BaseMap); 59 60 CBUFFER_START(UnityPerMaterial) 61 half4 _BaseColor; 62 float4 _BaseMap_ST; 63 CBUFFER_END 64 65 float2 project_to_screenspace(float4 v) 66 { 67 return ((v.xy / v.w) * 0.5f + 0.5f) * _ScreenParams.xy; 68 } 69 float stroke_radius_modulate(float radius) 70 { 71 float3x3 obj3x3 = (float3x3)unity_ObjectToWorld; 72 float3 scaled = mul(obj3x3, float3(radius * 0.57735, radius * 0.57735, radius * 0.57735)); 73 radius = length(scaled); 74 75 float screen_radius = radius * -(UNITY_MATRIX_P[1][1]) * _ScreenParams.y; 76 77 return screen_radius; 78 } 79 80 // Vertex Shader 81 // We use SV_VertexID to get the index of the vertex being processed 82 v2f vert (uint vertexID : SV_VertexID) 83 { 84 v2f o; 85 86 // 1. Use the vertexID to find the correct index from the index buffer 87 uint faceIdx = vertexID / 6; 88 89 90 float3 p1 = _inEdges[faceIdx].pos[0]; 91 float3 p2 = _inEdges[faceIdx].pos[1]; 92 float3 faceNormal = _inEdges[faceIdx].faceNormal; // fetch face normal if needed 93 94 if (_inEdges[faceIdx].adj[0] < 0 && _inEdges[faceIdx].adj[1] < 0) 95 { 96 o.vertex = float4(0.0f, 0.0f, -3e36f, 0.0f); 97 o.normalWS = float3(0,0,0); 98 return o; 99 } 100 101 bool isSecond = (vertexID%6 > 2); 102 int vI = vertexID%6; 103 104 float x; 105 float y; 106 107 if (!isSecond) 108 { 109 x = float(vI & 1) * 2.0f - 1.0f; /* [-1..1] */ 110 y = float(vI & 2) - 1.0f; /* [-1..1] */ 111 } 112 else 113 { 114 x = -(float(vI+1 & 1) * 2.0f - 1.0f); /* [-1..1] */ 115 y = -(float(vI+1 & 2) - 1.0f); /* [-1..1] */ 116 } 117 118 bool is_on_p1 = (x == -1.0f); 119 120 int adj; 121 if (is_on_p1) 122 { 123 adj = _inEdges[faceIdx].adj[0]; 124 } 125 else 126 { 127 adj = _inEdges[faceIdx].adj[1]; 128 } 129 130 if (adj < 0) 131 { 132 o.vertex = float4(0.0f, 0.0f, -3e36f, 0.0f); 133 o.normalWS = float3(0,0,0); 134 return o; 135 } 136 137 float3 pos_adj = _inEdges[adj].pos[0]; 138 if (is_on_p1) 139 { 140 if (length(pos_adj-p1) < 0.001) 141 { 142 pos_adj = _inEdges[adj].pos[1]; 143 } 144 } 145 else 146 { 147 if (length(pos_adj-p2) < 0.001) 148 { 149 pos_adj = _inEdges[adj].pos[1]; 150 } 151 } 152 153 float3 wpos_adj = TransformObjectToWorld(pos_adj); 154 float3 wpos1 = TransformObjectToWorld(p1); 155 float3 wpos2 = TransformObjectToWorld(p2); 156 157 float4 ndc_adj = TransformWorldToHClip(wpos_adj); 158 float4 ndc1 = TransformWorldToHClip(wpos1.xyz); 159 float4 ndc2 = TransformWorldToHClip(wpos2.xyz); 160 161 o.vertex = (is_on_p1) ? ndc1 : ndc2; 162 163 float2 ss_adj = project_to_screenspace(ndc_adj); 164 float2 ss1 = project_to_screenspace(ndc1); 165 float2 ss2 = project_to_screenspace(ndc2); 166 167 float edge_len; 168 float2 edge_dir = safe_normalize_and_get_length(ss2 - ss1, edge_len); 169 float2 edge_adj_dir = safe_normalize((is_on_p1) ? (ss1 - ss_adj) : (ss_adj - ss2)); 170 171 float radius = 0.05; 172 radius = stroke_radius_modulate(radius); 173 float clamped_radius = max(0.0f, radius); 174 175 // OUT.uv = float2(x, y) * 0.5f + 0.5f; 176 177 //TODO dot 178 // bool is_stroke_start = (p0.mat == -1 && x == -1); 179 // bool is_stroke_end = (p3.mat == -1 && x == 1); 180 181 bool is_stroke_endpoint = (adj == -1); 182 183 /* Mitter tangent vector. */ 184 float2 miter_tan = safe_normalize(edge_adj_dir + edge_dir); 185 float miter_dot = dot(miter_tan, edge_adj_dir); 186 /* Break corners after a certain angle to avoid really thick corners. */ 187 const float miter_limit = 0.5f; /* cos(60 degrees) */ 188 bool miter_break = (miter_dot < miter_limit); 189 miter_tan = (miter_break || is_stroke_endpoint) ? edge_dir : (miter_tan / miter_dot); 190 191 /* Rotate 90 degrees counter-clockwise. */ 192 float2 miter = float2(-miter_tan.y, miter_tan.x); 193 194 float3 ss_faceNormal = TransformWorldToHClipDir(faceNormal); 195 196 if (dot(ss_faceNormal.xy, miter) < 0.0f) 197 { 198 miter = -miter; 199 } 200 201 float2 screen_ofs = miter * (y+1); 202 203 // /* Reminder: we packed the cap flag into the sign of strength and thickness sign. */ 204 // if ((is_stroke_start && p1.opacity > 0.0f) || (is_stroke_end && p1.radius > 0.0f) || 205 // (miter_break && !is_stroke_start && !is_stroke_end)) 206 // { 207 // screen_ofs += edge_dir * x; 208 // } 209 210 211 float2 clip_space_per_pixel = float2(1.0 / _ScreenParams.x, 1.0 / _ScreenParams.y); 212 o.vertex.xy += screen_ofs * clip_space_per_pixel * clamped_radius; 213 // if (isSecond) 214 // { 215 // p0+=n0; 216 // p1+=n1; 217 // p2+=n2; 218 // } 219 // // 3. Transform the vertex position from object space to clip space 220 // if (vI==0) 221 // { 222 // o.vertex = TransformObjectToHClip(p0); 223 // o.normalWS = TransformObjectToWorldDir(n0); 224 // } 225 // else if (vI==1) 226 // { 227 // o.vertex = TransformObjectToHClip(p1); 228 // o.normalWS = TransformObjectToWorldDir(n1); 229 // } 230 // else if (vI==2) 231 // { 232 // o.vertex = TransformObjectToHClip(p2); 233 // o.normalWS = TransformObjectToWorldDir(n2); 234 // } 235 236 // else // (vI==3) 237 // { 238 // o.vertex = TransformObjectToHClip(p2); 239 // o.normalWS = TransformObjectToWorldDir(n2); 240 // } 241 //get normals 242 //find zero points 243 244 return o; 245 } 246 247 // Fragment Shader 248 // This is a simple implementation that uses the normal for basic lighting 249 half4 frag (v2f i) : SV_Target 250 { 251 // Normalize the incoming normal vector 252 half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, float2(0,0)) * _BaseColor; 253 return color; 254 // // Get the main light direction from Unity's built-in variables 255 // float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); 256 // 257 // // Calculate basic Lambertian lighting 258 // float lightIntensity = saturate(dot(normal, lightDir)); 259 // 260 // // Define a base color (e.g., grey) 261 // fixed3 albedo = fixed3(0.8, 0.8, 0.8); 262 // 263 // // Combine lighting and color 264 // fixed3 finalColor = albedo * lightIntensity; 265 // 266 // return fixed4(finalColor, 1.0); 267 } 268 ENDHLSL 269 } 270 } 271 }