System Semantics and Function Bodies
System semantic are annotations that indicate how the GPU pipeline should process the marked data types.
When using a structure as a return value, system semantics should not be declared in the function header. Declaring semantics both out side and inside a structure creates ambiguity, so modern versions only permit internal declarations.
Incorrect Usage Example
Shader "Tutorial/Basic/ShaderSemantics" {
Properties {
_MainColor ("Base Color", Color) = (1.0,1.0,1.0,1.0)
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex ProcessVertex
#pragma fragment ProcessFragment
fixed4 _MainColor;
struct VertexInput {
float4 position : POSITION;
float3 normalVector : NORMAL;
float4 uvCoordinates : TEXCOORD0;
};
struct VertexToFragment {
float4 clipPosition : SV_POSITION;
fixed3 vertexColor : COLOR0;
};
VertexToFragment ProcessVertex(VertexInput inputData) : SV_POSITION { // Error: Cannot declare semantic outside struct
VertexToFragment outputData;
outputData.clipPosition = mul(UNITY_MATRIX_MVP, inputData.position);
outputData.vertexColor = inputData.normalVector * 0.5 + fixed3(0.5, 0.5, 0.5);
return outputData;
}
fixed4 ProcessFragment(VertexToFragment interpolatedData) : SV_Target {
fixed3 finalColor = interpolatedData.vertexColor;
finalColor *= _MainColor.rgb;
return fixed4(finalColor, 1.0);
}
ENDCG
}
}
}
Key issues with modern shader compilers:
- No longer tolerant of ambiguous or incorrect semantic usage
- Semantic annotations on entire structures are not permitted (e.g.,
struct VertexToFragment : SV_POSITION)
The
muloperation is deprecated in favor ofUnityObjectToClipPos
Corrected Implementation
Shader "Tutorial/Basic/ShaderSemantics" {
Properties {
_MainColor ("Base Color", Color) = (1.0,1.0,1.0,1.0)
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex ProcessVertex
#pragma fragment ProcessFragment
fixed4 _MainColor;
struct VertexInput {
float4 position : POSITION;
float3 normalVector : NORMAL;
float4 uvCoordinates : TEXCOORD0;
};
struct VertexToFragment {
float4 clipPosition : SV_POSITION;
fixed3 vertexColor : COLOR0;
};
VertexToFragment ProcessVertex(VertexInput inputData) {
VertexToFragment outputData;
outputData.clipPosition = UnityObjectToClipPos(inputData.position);
outputData.vertexColor = inputData.normalVector * 0.5 + fixed3(0.5, 0.5, 0.5);
return outputData;
}
fixed4 ProcessFragment(VertexToFragment interpolatedData) : SV_Target {
fixed3 finalColor = interpolatedData.vertexColor;
finalColor *= _MainColor.rgb;
return fixed4(finalColor, 1.0);
}
ENDCG
}
}
}
Input structure semantics are automatically captured and assigned by the GPU, while return structure semantics are automatically unpacked during pipeline processing.
Two Valid Function Signature Patterns
Pattern 1: Semantics declared within structure members, no semantic annotation in function header
struct VertexInput {
float4 position : POSITION;
float3 normalVector : NORMAL;
float4 uvCoordinates : TEXCOORD0;
};
struct VertexToFragment {
float4 clipPosition : SV_POSITION;
fixed3 vertexColor : COLOR0;
};
VertexToFragment ProcessVertex(VertexInput inputData) {
VertexToFragment outputData;
outputData.clipPosition = UnityObjectToClipPos(inputData.position);
outputData.vertexColor = inputData.normalVector * 0.5 + fixed3(0.5, 0.5, 0.5);
return outputData;
}
Pattern 2: Basic type with semantic annotation in function header
fixed4 ProcessFragment(VertexToFragment interpolatedData) : SV_Target {
fixed3 finalColor = interpolatedData.vertexColor;
finalColor *= _MainColor.rgb;
return fixed4(finalColor, 1.0);
}