TriangleShader.metal 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. #include <metal_stdlib>
  2. using namespace metal;
  3. #include "VertexConstants.h"
  4. constexpr sampler depthSampler(mag_filter::nearest, min_filter::nearest);
  5. struct Vertex
  6. {
  7. float3 vPos [[attribute(0)]];
  8. float3 vNorm [[attribute(1)]];
  9. float2 vTex [[attribute(2)]];
  10. uchar4 vCol [[attribute(3)]];
  11. float4 iModel0 [[attribute(4)]];
  12. float4 iModel1 [[attribute(5)]];
  13. float4 iModel2 [[attribute(6)]];
  14. float4 iModel3 [[attribute(7)]];
  15. float4 iModelInvTrans0 [[attribute(8)]];
  16. float4 iModelInvTrans1 [[attribute(9)]];
  17. float4 iModelInvTrans2 [[attribute(10)]];
  18. float4 iModelInvTrans3 [[attribute(11)]];
  19. uchar4 iCol [[attribute(12)]];
  20. };
  21. struct TriangleOut
  22. {
  23. float4 oPosition [[position]];
  24. float3 oNormal;
  25. float3 oWorldPos;
  26. float2 oTex;
  27. float4 oPositionL;
  28. float4 oColor;
  29. };
  30. vertex TriangleOut TriangleVertexShader(Vertex vert [[stage_in]], constant VertexShaderConstantBuffer *constants [[buffer(2)]])
  31. {
  32. TriangleOut out;
  33. // Convert input matrices
  34. float4x4 iModel(vert.iModel0, vert.iModel1, vert.iModel2, vert.iModel3);
  35. float4x4 iModelInvTrans(vert.iModelInvTrans0, vert.iModelInvTrans1, vert.iModelInvTrans2, vert.iModelInvTrans3);
  36. // Get world position
  37. float4 pos = float4(vert.vPos, 1.0f);
  38. float4 world_pos = iModel * pos;
  39. // Transform the position from world space to homogeneous projection space
  40. float4 proj_pos = constants->View * world_pos;
  41. proj_pos = constants->Projection * proj_pos;
  42. out.oPosition = proj_pos;
  43. // Transform the position from world space to projection space of the light
  44. float4 proj_lpos = constants->LightView * world_pos;
  45. proj_lpos = constants->LightProjection * proj_lpos;
  46. out.oPositionL = proj_lpos;
  47. // output normal
  48. float4 norm = float4(vert.vNorm, 0.0f);
  49. out.oNormal = normalize(iModelInvTrans * norm).xyz;
  50. // output world position of the vertex
  51. out.oWorldPos = world_pos.xyz;
  52. // output texture coordinates
  53. out.oTex = vert.vTex;
  54. // output color
  55. out.oColor = float4(vert.vCol) * float4(vert.iCol) / (255.0 * 255.0);
  56. return out;
  57. }
  58. fragment float4 TrianglePixelShader(TriangleOut vert [[stage_in]], constant PixelShaderConstantBuffer *constants, texture2d<float> depthTexture [[texture(0)]])
  59. {
  60. // Constants
  61. float AmbientFactor = 0.3;
  62. float3 DiffuseColor = float3(vert.oColor.r, vert.oColor.g, vert.oColor.b);
  63. float3 SpecularColor = float3(1, 1, 1);
  64. float SpecularPower = 100.0;
  65. float bias = 1.0e-7;
  66. // Homogenize position in light space
  67. float3 position_l = vert.oPositionL.xyz / vert.oPositionL.w;
  68. // Calculate dot product between direction to light and surface normal and clamp between [0, 1]
  69. float3 view_dir = normalize(constants->CameraPos - vert.oWorldPos);
  70. float3 world_to_light = constants->LightPos - vert.oWorldPos;
  71. float3 light_dir = normalize(world_to_light);
  72. float3 normal = normalize(vert.oNormal);
  73. if (dot(view_dir, normal) < 0) // If we're viewing the triangle from the back side, flip the normal to get the correct lighting
  74. normal = -normal;
  75. float normal_dot_light_dir = clamp(dot(normal, light_dir), 0.0, 1.0);
  76. // Calculate texture coordinates in light depth texture
  77. float2 tex_coord;
  78. tex_coord.x = position_l.x / 2.0 + 0.5;
  79. tex_coord.y = -position_l.y / 2.0 + 0.5;
  80. // Check that the texture coordinate is inside the depth texture, if not we don't know if it is lit or not so we assume lit
  81. float shadow_factor = 1.0;
  82. if (vert.oColor.a > 0 // Alpha = 0 means don't receive shadows
  83. && tex_coord.x == clamp(tex_coord.x, 0.0, 1.0) && tex_coord.y == clamp(tex_coord.y, 0.0, 1.0))
  84. {
  85. // Modify shadow bias according to the angle between the normal and the light dir
  86. float modified_bias = bias * tan(acos(normal_dot_light_dir));
  87. modified_bias = min(modified_bias, 10.0 * bias);
  88. // Get texture size
  89. float width = 1.0 / 4096;
  90. float height = 1.0 / 4096;
  91. // Samples to take
  92. uint num_samples = 16;
  93. float2 offsets[] = {
  94. float2(-1.5 * width, -1.5 * height),
  95. float2(-0.5 * width, -1.5 * height),
  96. float2(0.5 * width, -1.5 * height),
  97. float2(1.5 * width, -1.5 * height),
  98. float2(-1.5 * width, -0.5 * height),
  99. float2(-0.5 * width, -0.5 * height),
  100. float2(0.5 * width, -0.5 * height),
  101. float2(1.5 * width, -0.5 * height),
  102. float2(-1.5 * width, 0.5 * height),
  103. float2(-0.5 * width, 0.5 * height),
  104. float2(0.5 * width, 0.5 * height),
  105. float2(1.5 * width, 0.5 * height),
  106. float2(-1.5 * width, 1.5 * height),
  107. float2(-0.5 * width, 1.5 * height),
  108. float2(0.5 * width, 1.5 * height),
  109. float2(1.5 * width, 1.5 * height),
  110. };
  111. // Calculate depth of this pixel relative to the light
  112. float light_depth = position_l.z + modified_bias;
  113. // Sample shadow factor
  114. shadow_factor = 0.0;
  115. for (uint i = 0; i < num_samples; ++i)
  116. shadow_factor += depthTexture.sample(depthSampler, tex_coord + offsets[i]).x <= light_depth? 1.0 : 0.0;
  117. shadow_factor /= num_samples;
  118. }
  119. // Calculate diffuse and specular
  120. float diffuse = normal_dot_light_dir;
  121. float specular = diffuse > 0.0? pow(clamp(-dot(reflect(light_dir, normal), view_dir), 0.0, 1.0), SpecularPower) : 0.0;
  122. // Apply procedural pattern based on the uv coordinates
  123. bool2 less_half = (vert.oTex - floor(vert.oTex)) < float2(0.5, 0.5);
  124. float darken_factor = less_half.r ^ less_half.g? 0.5 : 1.0;
  125. // Fade out checkerboard pattern when it tiles too often
  126. float2 dx = dfdx(vert.oTex), dy = dfdy(vert.oTex);
  127. float texel_distance = sqrt(dot(dx, dx) + dot(dy, dy));
  128. darken_factor = mix(darken_factor, 0.75, clamp(5.0 * texel_distance - 1.5, 0.0, 1.0));
  129. // Calculate color
  130. return float4(clamp((AmbientFactor + diffuse * shadow_factor) * darken_factor * DiffuseColor + SpecularColor * specular * shadow_factor, 0, 1), 1);
  131. }
  132. struct DepthOut
  133. {
  134. float4 oPosition [[position]];
  135. };
  136. vertex DepthOut TriangleDepthVertexShader(Vertex vert [[stage_in]], constant VertexShaderConstantBuffer *constants [[buffer(2)]])
  137. {
  138. DepthOut out;
  139. // Check if the alpha = 0
  140. if (vert.vCol.a * vert.iCol.a == 0.0)
  141. {
  142. // Don't draw the triangle by moving it to an invalid location
  143. out.oPosition = float4(0, 0, 0, 0);
  144. }
  145. else
  146. {
  147. // Convert input matrix
  148. float4x4 iModel(vert.iModel0, vert.iModel1, vert.iModel2, vert.iModel3);
  149. // Transform the position from world space to homogeneous projection space for the light
  150. float4 pos = float4(vert.vPos, 1.0f);
  151. pos = iModel * pos;
  152. pos = constants->LightView * pos;
  153. pos = constants->LightProjection * pos;
  154. out.oPosition = pos;
  155. }
  156. return out;
  157. }
  158. fragment float4 TriangleDepthPixelShader(DepthOut in [[stage_in]])
  159. {
  160. // We only write depth, so this shader does nothing
  161. return float4(0.0, 0.0, 0.0, 1.0);
  162. }