Table of contents
Table of contents
Different types of conditionals
Deduplication of shader variants
Check how many shader variants you have
Get the number of variants used under editor
Get the number of variants at build time
Definition type: “multi compile” or “shader feature”
Create a shader variant for disabled keywords
Using shader keywords with C# scripts
How Unity loads and uses shaders
Marker for shader loading in Profiler:
Shader compilation
1. Compilation is divided into two types: Editor and Runtime
Editor:
- Pre-compiled, the compiled version will be cached under Library/ShaderCache. The next time you use this variant, you can directly retrieve it from the cache, delete the Library folder, and recompile.
- The compilation is asynchronous under the Editor, that is, it is compiled in the background, and the object is cyan before it is completed;
- You can see the progress of compilation in the lower right corner of the Unity editor. You can turn on/off asynchronous compilation under preference ->Editor. Generally, one core of the CPU corresponds to one compilation job.
Runtime:
- When building, if you use shader_feature to define variants, unused variants will be eliminated, and then all used variants will be compiled and placed in the package body.
2. Different platforms, Shader compiler will be different
3. A shader contained in multiple materials and put into an assetbundle will result in multiple copies of the shader and also interrupt the batching. You can use the Asset Bundle Browser to check the dependencies of the bundle.
Conditionals in shaders
- Sometimes, you want the same shader to do different things in different situations.
- For example, configure different settings for different materails, define capabilities for different hardware, or dynamically change the behavior of shaders at runtime. It may also be desirable to avoid executing computationally expensive code such as texture fetches, vertex input, interpolators, or loops when not needed.
- Use conditions to define behaviors that the GPU will only perform under certain conditions.
Different types of conditionals
There are three ways for shaders to use conditions:
- Static branching: the shader compiler evaluates conditional code at compile time.Use preprocesser constants and macros.
- Dynamic branching: the GPU evaluates conditional code at runtime.
- Shader variants
: Unity uses static branching to compile the shader source code into multiple shader programs. Unity then uses the shader program that matches the conditions at runtime.
Using shader variants, unused variants will be eliminated during build . Therefore, in this case, you should avoid enable/disable keywords in C#
shader_feature
, because it is possible that the enabled variants are not included in the package. If the changes are body does not exist, a default will be selected usingIf you need to enable/disable keywords in c#
shader_feature
, you should include all shader variants in the package body, using the following two methods:
- Create a shader variant collection resource, include all variants, and add it
- Include a Material in your build for every combination of
shader_feature
keywords you want to use.
Switch code branch at runtime
There are two ways to switch shader branches in c# code:
- Declare the multi_compile keyword in the shader. The disadvantage is that there are many compiled variants and occupy memory.
- The disadvantage of using dynamic branch is that it consumes GPU
#pragma shader_feature REFLECTION_TYPE1 REFLECTION_TYPE2 REFLECTION_TYPE3
Shader directive | Branching type | Shader variants Unity creates |
---|---|---|
shader_feature |
Static branching | Variants for keyword combinations you enable at build time (just enable the combination of shader_feature) |
multi_compile |
Static branching | Variants for every possible combination of keywords |
dynamic_branch |
Dynamic branching | No variants |
Branching in shaders
According to the way the shader executes branch, it is divided into two types:
Static branching
It has been compiled before running and is called static branch.
- Advantages: Since unused branches will be eliminated during construction, the built package is small, takes a short time, and has no impact on performance.
- Disadvantages: Some branches cannot be used in runtime due to elimination
How to use static branching
three methods:
- Handwritten shaders:
- Use #if, #elif, #else, and #endif preprocessor directives, or #ifdef and #ifndef preprocessor directives to create static branches.
- Use an if statement that evaluates a compile-time constant value. Although
if
statements can also be used for dynamic branches, the compiler detects the compile-time constant value and creates a static branch instead. - Unity provides built-in macros for some compile-time constants that you can use with static branches.
Note: Static branching is available only in hand-coded shaders. You cannot create static branches in Shader Graph.
Dynamic branching
It is divided into two types:
- Based on uniform variables
- based on any other runtime value
Based on uniform variables is usually more efficient, because uniform variables are constant for the entire drawcall
- Advantages: Different strategies can be adopted in real time according to dynamic values
- Disadvantages: GPU consumption, because the GPU execution process is parallelized unit by unit, non-uniform variables will interrupt this parallelism, because some units process another branch, and some process the opposite branch. If one is processed, The other one will wait for the other one. The value of the uniform variables means that all units will process one of the branches, and the performance will be relatively good.
Note: All branches will be compiled into programs and written into the shader. This will increase the file size, but compared to multiple variants, it is still small.
How to use dynamic branching
You can use dynamic branching in your shaders in the following ways:
- In hand-coded shaders, use an if statement that evaluates runtime state. You can use attributes to force the GPU to execute both branches, or to execute only one branch.
- In Shader Graph, use a Branch Node. This always executes both branches.
Shader variants
Use shader keywords . to configure variants. When building, each variant is a piece of shader code.
- Advantages: consumes less GPU performance than dynamic branch
- Disadvantages: If there are too many, it will increase memory and loading time.
Deduplication of shader variants
- After compilation, Unity automatically recognizes identical variants within the same channel and ensures that these identical variants point to the same bytecode. This is called deduplication .
- Deduplication prevents the same variants in the same pass from increasing the file size, but it also increases the compilation time and requires stripping unneeded variants .
Check how many shader variants you have
Get the number of variants used under editor
Select Save to asset… to create a shader variant collection asset.
Get the number of variants at build time
Open Editor.log
the file ( menu: Window > General > Console and select Open Editor Log from the Console window menu.) and search Compiling shader
Shader keywords
Through the method of shader keywords, conditional statements can be used in the shader.
A set of keywords is called Keyword, and a single keyword in it is called state.
Definition type: “multi compile” or “shader feature”
There are two types of keywords declared in shader:
1.multi compile: Any keyword declared here will be compiled into a variant
2.shader feature: Only enable keywords will be compiled into variants and put into the package, and the others will be cut out.
Note: If you add the shader to projectsetting->graphics-> Always Included Shaders , unity will include all the keywords in it in the package body, even if the type is shader_feature.
Local or global scope
The keywords declared by default are all global, but when declaring the keyword type, you can add the suffix _local to indicate that it is local, and after adding it, it means that it cannot be overridden, that is, it cannot be copied with the same name
Stage-specific keywords
By default, Unity declares the same variant for each stage. For example, if the shader contains a vertex stage and a fragment stage, Unity will declare the same variant for them. If keywords are only used in one stage, it will result in a variant It is repeated in another stage, Unity will automatically recognize and crop them, so it will not increase the package size, but they still result in wasted compilation time, increased shader loading times, and increased runtime memory usage.
In order to avoid this problem, when declaring the keywords type, add a specific suffix to indicate which stage it is used for.
for example:
_vertex
_fragment
_hull
_domain
_geometry
_raytracing
#pragma shader_feature_fragment RED GREEN BLUE--indicates that it is used in the fragment stage
Create a shader variant for disabled keywords
If you shader_feature
create a single keyword, Unity will automatically create a second variant when the keyword is disabled. This helps reduce the number of keywords you need to enable and disable. For example, the following code creates 2 variants:
#pragma shader_feature EXAMPLE_ON
If multi_compile/
shader_feature
created using two or more keywords, use _
creates a shader variant when all keywords in the set are disabled
#pragma multi_compile _ EXAMPLE_ON
#pragma shader_feature _ RED GREEN BLUE WHITE
Using shader keywords with C# scripts
- The concept of shader keywords in c# is divided into two types: Local Shader Keywords and Global Shader Keywords, which are concepts in c#. In shader, the difference between local and global is that local needs to be suffixed with _local after type to declare its It is local, otherwise it is global.
- The keywords of the shader in c# are stored in the LocalKeywordSpace structure, accessed through Shader.keywordSpace and ComputeShader-keywordSpace in the compute shader
- Local and global are indicated by the attribute isOverridable . If true, it means global, if false, it means local. Global does not need to be declared actively. It is either local or global.
Local Shader Keywords:
- It cannot be overridden, that is, the same name will not affect its value.
- Only affects a single shader, compute shader
- Check on/off via Material.IsKeywordEnabled or Material.EnableKeyword . For a compute shader, use ComputeShader.IsKeywordEnabled
- 通过 Material.SetKeyword, Material.EnableKeyword, or Material.DisableKeyword. For a compute shader, use ComputeShader.SetKeyword, ComputeShader.EnableKeyword, or ComputeShader.DisableKeyword. 开启/关闭
Global Shader Keywords:
- Can be overridden
- Affected shader, compute shader
- Check on/off via Shader.IsKeywordEnabled or Shader.EnableKeyword or ComputeShader.enabledKeywords
- Turn on/off via Shader.SetKeyword , ComputeShader.EnableKeyword , or ComputeShader.DisableKeyword
At runtime, switching between different variants may easily affect performance. If the variant has not been loaded before and is a complex variant, it may cause the screen to freeze. If you use global keywords and modify multiple shaders at the same time, it is easy to cause the same problem.
How Unity loads and uses shaders
- Unity loads compiled variants within the application
- When loading a scene or assetbundle (resource in resource) , all variants used by the scene or resource will be loaded into the CPU, and then decompressed to another memory of the CPU. The size of the memory occupied can be found in Player Change within settings ->Other Settings > Shader Variant Loading
- When a variant is used for the first time, the CPU will send the resource data to the GPU, instead of putting all variants into the GPU at once. The GPU will generate a GPU-specific version of the corresponding variant. shader variant, cache it and won’t resend it next time you use it.
- If the variant does not have any references in the scene, Unity will remove it from the CPU and GPU
Prewarming shader variants
In order to avoid loading stagnation when using it, you can use preloading:
- Prewarm a Shader object or shader variant collection using the Experimental.Rendering.ShaderWarmup API.
- Prewarm a shader variant collection by using the ShaderVariantCollection.WarmUp API.
- Prewarm all variants of all Shader objects currently in memory using the Shader.WarmupAllShaders API.
也可以在projectsetting->graphics 里面配置Preloaded shaders section of the Graphics Settings window. Unity uses the ShaderVariantCollection.WarmUp
API to load and prewarm the shader variant collections when your built application starts.
Marker for shader loading in Profiler:
Shader.ParseThreaded、
Shader.ParseMainThread
for Unity loading the shader object from serialized data.Shader.CreateGPUProgram
for Unity creating a GPU-specific version of a shader variant