目录索引
译文
为了分析着色器的结构,我们先创建一个名为“USB_simple_color”的无光照着色器(Unlit Shader)。正如我们在上一章节中所介绍的,无光照着色器是一种基本的色彩模型,代码中没有太多优化,这使我们能够深入分析其中的各种属性和功能。
着色器创建完后,为方便编译 Unity 会自动添加一些GPU可以理解的默认代码。现在,打开 USB_simple_color 着色器,内部的代码应如下所示:
Shader "Unlit/USB_simple_color"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags {"RenderType"="Opaque"}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler 2D _MainTex;
float4 _MainTex;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
我们可能并不完全了解我们刚刚创建的着色器中的不同代码块发生了什么,让我们先关注着色器的整体结构吧!
Shader "InspectorPath/shaderName"
{
Properties
{
// properties in this field
}
SubShader
{
// SubShader configuration in this field
Pass
{
CGPROGRAM
// programa Cg - HLSL in this field
ENDCG
}
}
Fallback "ExampleOtherShader"
}
上面的代码展示了着色器的主要结构。第一行所代表的是着色器在检查器中的路径(InspectorPath)与着色器的名字(shaderName)。接着是属性(properties)语义块,包括了纹理、向量、颜色等多种不同类型的属性。再下一个语义块是子着色器(SubShader),最后是可选的回退(Fallback)。
“检查器中的路径”指的是我们通过 Unity 检查器选择着色器并将其应用到材质的位置时选择的路径。
我们没有办法直接将着色器应用在模型上,而是需要通过创建材质来完成。刚才创建的无光照着色器“USB_simple_color”的默认检查其路径为“Unlit”,这意味着创建材质后,我们需要进入材质的检查器,搜索 Unlit 路径、应用“USB_simple_color”着色器到材质上,接着我们就可以将材质应用到模型上了。
在结构上,GPU 将从上到下线性地读取着色器程序,也就是说如果我们创建了一个自定义函数,但是错误地把它编写在要使用它的代码下方,GPU 将无法读取这个函数,从而造成错误。然而即使错误发生,回退也将分配一个不同的着色器,以便图形硬件可以继续编译着色器。
我们来做一个小小的练习来理解这个概念吧:
// 1 . 声明自定义函数
float4 ourFunction()
{
// your code here …
}
// 2. 在这里面使用自定义函数
fixed4 frag (v2f i) : SV_Target
{
// 在这里使用
float4 f = ourFunction();
return f;
}
上面这些函数的语法目前可能还没法被理解,不过没有关系,创建这些函数只是为了在概念上表示它们的位置。
在第 4.0.4 节中,我们将会详细讨论用户自定义函数的结构。现在让我们先看看上面给出的这个例子,它在结构上是正确的,因为自定义函数“ourFunction”写在了使用它的代码前面。GPU将会先读取“ourFunction”函数,接着再执行片元着色器“frag”。
让我们看看另一个例子:
// 2. 使用自定义函数
fixed4 frag (v2f i) : SV_Target
{
// we are using the function here
float4 f = ourFunction();
return f;
}
// 1 . 声明自定义函数
float4 ourFunction()
{
// your code here …
}
与第一个例子不同的是,这个例子会报错,因为自定义函数“OurFunction”声明在了调用它的代码之后。
原文对照
To analyze its structure, we will create an Unlit Shader and call it “USB_simple_color“. As we already know, this type of shader is a basic color model and does not have great optimization in its code, this will allow us to analyze in-depth its various properties and functions.
When we create a shader for the first time, Unity adds default code to facilitate its compilation process. Within the program, we can find blocks of code structured in such a way that the GPU can interpret them. If we open our USB_simple_color shader, its structure should look like this:
Shader "Unlit/USB_simple_color"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags {"RenderType"="Opaque"}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler 2D _MainTex;
float4 _MainTex;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
Likely, we do not fully understand what is happening in the different code blocks from the shader we just created. However, to begin our study, we will pay attention to its general structure.
Shader "InspectorPath/shaderName"
{
Properties
{
// properties in this field
}
SubShader
{
// SubShader configuration in this field
Pass
{
CGPROGRAM
// programa Cg - HLSL in this field
ENDCG
}
}
Fallback "ExampleOtherShader"
}
The previous example shows the main structure of a shader. The shader starts with a path in the inspector (InspectorPath) and a name (shaderName), then the properties (e.g. textures, vectors, colors, etc.) after that the SubShader and at the end of it all is the optional Fallback.
The “inspectorPath” refers to the place where we will select our shader to apply it to a material. This selection is made through the Unity Inspector.
We must remember that we cannot apply a shader directly to a polygonal object, instead, it will have to be done through a previously created material. Our USB_simple_color shader has the path “Unlit” by default, this means that: from Unity, we must select our material, go to the inspector, search the path Unlit and apply the material called USB_simple_color.
A structural factor that we must take into consideration is that the GPU will read the program from top to bottom linearly, therefore, if shortly we create a function and position it below the code block where it will be used, the GPU will not be able to read it, generating an error in the shader processing, therefore Fallback will assign a different shader so that graphics hardware can continue its process.
Let’s do the following exercise to understand this concept.
// 1 . declare our function
float4 ourFunction()
{
// your code here …
}
// 2. we use the function
fixed4 frag (v2f i) : SV_Target
{
// we are using the function here
float4 f = ourFunction();
return f;
}
The syntax of the above functions may not be fully understood. These have been created only to conceptualize the position of one function for another.
In section 4.0.4 we will talk in detail about the structure of a function. For now, the only important thing is that in the previous example its structure is correct because the function “ourFunction” has been written where the block of code is placed. The GPU will first read the function “ourFunction” and then it will continue to the fragment stage called “frag“.
Let’s look at a different case.
// 2. we use our function
fixed4 frag (v2f i) : SV_Target
{
// we are using the function here
float4 f = ourFunction();
return f;
}
// 1 . declare the function
float4 ourFunction()
{
// your code here …
}
On the contrary, this structure will generate an “error”, because the function called “OurFunction” has been written below the code block which it is using.
暂无评论内容