Skip to content

Instantly share code, notes, and snippets.

@kraj0t
Last active April 27, 2026 10:05
Show Gist options
  • Select an option

  • Save kraj0t/3275c44b1d200b9997e625d66f1be9f1 to your computer and use it in GitHub Desktop.

Select an option

Save kraj0t/3275c44b1d200b9997e625d66f1be9f1 to your computer and use it in GitHub Desktop.
How to include C# code in an HLSL file - useful for sharing code between CPU and shader without duplicating any files
// Share code between HLSL (shaders) and C#
//
// Limitations:
// - Cannot use #include
// - Must use defines to hide certain keywords, such as public, private, protected, or any other object-oriented programming keywords
// - Use defines to convert half or fixed to your desired C# equivalent
// - Must always write f after float literals
// - Use #if !MY_DEFINE instead of #ifndef MY_DEFINE
// - #define cannot be used with a value in C#, not even inside an '#if SHADER_TARGET' block. Therefore, you have two options for declaring valued constants:
// a. Declare each constant separately in HLSL (using 'static const float MyConstant = 1.0f') and in C# (using 'const float MyConstant = 1.0f'). C# does not support 'static const'.
// b. Declare the constants inside the methods in your shared code using 'const float MyConstant = 1.0f;'
// Include guard that works identically in both HLSL and C#
#if !MY_CODE_INCLUDED
#define MY_CODE_INCLUDED
// SHADER_TARGET is a preprocessor directive that is defined for shaders, but not for C# code. We can use it to know if we are being included from a shader.
#if (SHADER_TARGET || SHADER_STAGE_COMPUTE)
// HLSL exclusive header section: HLSL constants, macros, remove the C# keywords...
// These defines let your C# code use certain keywords normally, because they will get replaced with "" in HLSL. Feel free to add others.
#define public
#define private
#define internal
#else
// C# exclusive header section: references, class, namespace...
// The two lines below let you use HLSL syntax in C# such as float3, mul, lerp, etc.
using Unity.Mathematics;
using static Unity.Mathematics.math;
public static class UsingCsCodeInHLSL
{
#endif
//////// SHARED CODE START ////////
float3 TestFunctionWrittenInCs(float3 color)
{
float lum = dot(color, float3(0.2126f, 0.7152f, 0.0722f));
return float3(lum, lum, lum);
}
//////// SHARED CODE END ////////
// End the C# class
#if !(SHADER_TARGET || SHADER_STAGE_COMPUTE)
}
#endif
#endif // MY_CODE_INCLUDED
Shader "Unlit/UsingCsCodeInHLSL"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// Include the C# file
#include "UsingCsCodeInHLSL.cs"
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (float4 vertex : POSITION, float2 uv : TEXCOORD0)
{
v2f o;
o.vertex = UnityObjectToClipPos(vertex);
o.uv = uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// Use the C# code!
col.rgb = TestFunctionWrittenInCs(col.rgb);
return col;
}
ENDCG
}
}
}
@runevision

Copy link
Copy Markdown

Thanks for sharing this! Incredible that this is only mentioned in a non-official gist when the entire Unity.Mathematics library seems to be designed around making the same code work in C# and HLSL.

I wonder if there's a way to make it work for compute shaders too. The SHADER_TARGET doesn't seem to be defined in this case, and even if I define it myself piror to #include statements in the compute shader, it still seems to have no effect.

@aras-p

aras-p commented Apr 21, 2026

Copy link
Copy Markdown

SHADER_STAGE_COMPUTE preprocessor macro should be defined when compiling compute shaders

@runevision

Copy link
Copy Markdown

@aras-p Thanks! Yes, after replacing the conditionals with these below, it seems to compile and use the included shared code:

#if (SHADER_TARGET || SHADER_STAGE_COMPUTE)

#if !(SHADER_TARGET || SHADER_STAGE_COMPUTE)

@kraj0t

kraj0t commented Apr 22, 2026

Copy link
Copy Markdown
Author

Thank you both, I have updated the code to support compute shaders πŸ‘πŸ»

@runevision

runevision commented Apr 24, 2026

Copy link
Copy Markdown

By the way, an annoying limitation (at least this happened for me in Unity 2022.3.62) is that Unity won't detect changes in the shared code for the purposes of the shaders using it. So I had to restart Unity for every edit I made to the shared code.

Reimporting all involved files doesn't help. Deleting the temp file and then reimporting doesn't help either (the imported result must be stored in memory). Seemingly no way at all to invalidate the cache, apart from a restart of the editor.

I ended up making an AssetPostprocessor that copies the .cs files into .cginc files, and then reference those .cginc files in the shaders instead of the .cs files directly. This circumvents the issue and lets me use the changes to shared code immediately without an editor restart.

This AssetPostprocessor relies on a specific folder structure, but the principle can be tweaked to one's needs.
https://gist.github.com/runevision/85fa46093a894f351f8a4e0602910176

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment