Hello!
Let me preface this with the fact that I’ve done a bunch of research already and haven’t been able to find enough information to piece this together entirely by myself.
I am using Graphics.DrawProcedural and a vert/geom/frag shader combo to render a bunch of triangles that were previously generated on the CPU using a marching cubes algorithm, and then pushed to a buffer on the GPU.
What is currently functional:
- The triangles are rendered properly in the correct location using a ForwardBase pass and a CommandBuffer that fires in each camera’s BeforeForwardOpaque.
- The generated triangles properly cast shadows on other objects in the scene using a ShadowCaster pass and a CommandBuffer that fires in each camera’s BeforeDepthTexture and the main light’s BeforeShadowMapPass.
- The triangles now properly write depth and normals to the DepthNormals texture using another shader pass.
Currently not functional:
- The object needs to receive shadows (I assume I have to do something with a ShadowCollector pass, but can’t find enough information about it).
The object needs to write normals and depth to the DepthNormals texture (this likely has something to do with a replacement shader that I do not know how to edit).
Things I’ve tried:
- Digging around in all of the Unity include files (AutoLight, etc.) and attempting to manually wire shadow-related stuff into the geometry shader (since I can’t use the vertex shader macros). This just leads to a bunch of undeclared identifier errors when using things like unity_WorldToLight, etc.
- Retrieving the light’s depth buffer (to no avail) along with the light’s worldToLocal matrix and sending those in to the shader as a sampler2D and float4x4 every frame.
- Hours of googling only to find people wanting help casting shadows (which I’ve solved) or responses effectively saying “use Unity’s macros”, which do not function for geometry shaders and/or DrawProcedural.
Relevant code:
This is where the actual draw commands are added:
public void LateUpdate()
{
m_shadowCasterBuffer.Clear();
m_forwardBaseBuffer.Clear();
m_shadowCasterBuffer.DrawProcedural(transform.localToWorldMatrix, p_material, 1, MeshTopology.Points, 1000000);
m_forwardBaseBuffer.DrawProcedural(transform.localToWorldMatrix, p_material, 0, MeshTopology.Points, 1000000);
foreach (Camera camera in Camera.allCameras)
{
camera.RemoveAllCommandBuffers();
camera.AddCommandBuffer(CameraEvent.BeforeDepthTexture, m_shadowCasterBuffer);
camera.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, m_forwardBaseBuffer);
}
sun.RemoveAllCommandBuffers();
sun.AddCommandBuffer(LightEvent.BeforeShadowMapPass, m_shadowCasterBuffer);
}
Here’s the shader (non-relevant stuff has been removed):
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
Pass
{
Name "ForwardBase"
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma target 5.0
#pragma vertex VertMain
#pragma geometry GeomMain
#pragma fragment FragMain
#pragma multi_compile_fwdbase
// NOTE: Removed includes (UnityCG, AutoLight, etc.) to be concise.
struct GeomOutput
{
float4 m_clipPos : SV_POSITION;
float4 m_objectPos : TEXCOORD1;
float4 m_worldPos : TEXCOORD2;
float3 m_normal : NORMAL;
half4 m_colour : COLOR;
};
struct FragOutput
{
half4 m_colour : SV_Target;
float m_depth : SV_Depth;
};
// NOTE: Removed non-relevant data types and vert shader to be concise since they're just feeding data into the geometry shader
[maxvertexcount(3)]
void GeomMain(point VertOutput a_point[1], inout TriangleStream<GeomOutput> a_triangleStream)
{
// NOTE: The geometry shader as-is currently places triangles in the correct location, so all the code here is functional.
// I removed a check for if a given triangle exists in the data buffer or not because that's not relevant to the current issue.
// u_triDataBuffer is the ComputeBuffer containing vertex positions, normals, and colours. ptr is a pointer into said buffer.
Triangle tri = u_triDataBuffer[a_point[0].ptr];
GeomOutput output;
output.m_objectPos = float4(tri.m_v1.m_objectPos, 1);
output.m_worldPos = mul(unity_ObjectToWorld, output.m_objectPos);
output.m_clipPos = UnityObjectToClipPos(output.m_objectPos);
output.m_normal = mul(unity_ObjectToWorld, float4(tri.m_v1.m_objectNormal, 0)).xyz;
output.m_colour = half4(tri.m_v1.m_colour);
a_triangleStream.Append(output);
output.m_objectPos = float4(tri.m_v2.m_objectPos, 1);
output.m_worldPos = mul(unity_ObjectToWorld, output.m_objectPos);
output.m_clipPos = UnityObjectToClipPos(output.m_objectPos);
output.m_normal = mul(unity_ObjectToWorld, float4(tri.m_v2.m_objectNormal, 0)).xyz;
output.m_colour = half4(tri.m_v2.m_colour);
a_triangleStream.Append(output);
output.m_objectPos = float4(tri.m_v3.m_objectPos, 1);
output.m_worldPos = mul(unity_ObjectToWorld, output.m_objectPos);
output.m_clipPos = UnityObjectToClipPos(output.m_objectPos);
output.m_normal = mul(unity_ObjectToWorld, float4(tri.m_v3.m_objectNormal, 0)).xyz;
output.m_colour = half4(tri.m_v3.m_colour);
a_triangleStream.Append(output);
}
FragOutput FragMain (GeomOutput a_geomOutput)
{
FragOutput output;
half4 litColour = /* NOTE: Here is where I do custom lighting. It's technically proprietary so I had to remove it. */;
// TODO: How do you receive shadows/sample the main directional light's shadow map? That needs to be done here.
float lightAttenuation = /* ??? */;
output.m_colour = litColour * lightAttenuation;
float4 clipPos = mul(UNITY_MATRIX_VP, a_geomOutput.m_worldPos);
output.m_depth = clipPos.z / clipPos.w;
return output;
}
ENDCG
}
Pass
{
// NOTE: As in the other pass, I edited out a bunch of non-relevant stuff to be concise. See comments above.
Name "ShadowCaster"
Tags { "LightMode"="ShadowCaster" }
CGPROGRAM
#pragma target 5.0
#pragma vertex VertMain
#pragma geometry GeomMain
#pragma fragment FragMain
#pragma multi_compile_shadowcaster
struct GeomOutput
{
float4 m_clipPos : SV_POSITION;
float4 m_worldPos : TEXCOORD1;
};
struct FragOutput
{
float4 m_colour : SV_Target;
float m_depth : SV_Depth;
};
[maxvertexcount(3)]
void GeomMain(point VertOutput a_point[1], inout TriangleStream<GeomOutput> a_triangleStream)
{
Triangle tri = u_triDataBuffer[triInfo.ptr];
GeomOutput output;
float4 objectPos = float4(tri.m_v1.m_objectPos, 1);
output.m_worldPos = mul(unity_ObjectToWorld, objectPos);
output.m_clipPos = UnityObjectToClipPos(objectPos);
a_triangleStream.Append(output);
objectPos = float4(tri.m_v2.m_objectPos, 1);
output.m_worldPos = mul(unity_ObjectToWorld, objectPos);
output.m_clipPos = UnityObjectToClipPos(objectPos);
a_triangleStream.Append(output);
objectPos = float4(tri.m_v3.m_objectPos, 1);
output.m_worldPos = mul(unity_ObjectToWorld, objectPos);
output.m_clipPos = UnityObjectToClipPos(objectPos);
a_triangleStream.Append(output);
}
FragOutput FragMain (GeomOutput a_geomOutput)
{
FragOutput output;
float4 clipPos = mul(UNITY_MATRIX_VP, a_geomOutput.m_worldPos);
output.m_depth = clipPos.z / clipPos.w;
output.m_colour = EncodeFloatRGBA(output.m_depth);
return output;
}
ENDCG
}
}
Would anybody be able to give me an explanation (or point me to one!) about how to receive shadows in a situation like this, and how I would go about implementing a custom depthnormals writing pass? If more info is needed, let me know.
Thank you for the help! ![]()