原文:
Basic Shader
Summary
在前面三个教程中,我介绍了着色器工作的基本原理、与结构。接下来我进一步介绍如何修改其中的内容。
在此之前,我并没有介绍着色器的执行代码部分。因为作为入门介绍,我们需要从结构框架入手,而不应该拘泥于细节。在大致了解着色器的实现流程后,接下来让我们来发现更有趣的细节。
What we have so far
下面的着色器脚本,如果你觉得有前三章没有阐述到位的地方,都可以告诉我。我必将事事有会响!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| Shader "Tutorial/001-004_Basic_Unlit"{ Properties{ _Color ("Tint", Color) = (0, 0, 0, 1) _MainTex ("Texture", 2D) = "white" {} }
SubShader{ Tags{ "RenderType"="Opaque" "Queue"="Geometry" }
Pass{ CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert #pragma fragment frag
sampler2D _MainTex; float4 _MainTex_ST;
fixed4 _Color;
struct appdata{ float4 vertex : POSITION; float2 uv : TEXCOORD0; };
struct v2f{ float4 position : SV_POSITION; float2 uv : TEXCOORD0; };
v2f vert(appdata v){ v2f o; o.position = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; }
fixed4 frag(v2f i) : SV_TARGET{ fixed4 col = tex2D(_MainTex, i.uv); col *= _Color; return col; }
ENDCG } } Fallback "VertexLit" }
|
Setting up the shader stages
之前提到的顶点着色器、片段着色器,在着色器脚本中实际上就是HLSL
函数。只不过,这些函数对应这渲染管线中的特定阶段。为了将这些函数关联到指定阶段,我们可以使用#pragma
关键字来说明。前面经常谈到的顶点着色器、和片段着色器是非常重要的两个阶段。因为顶点着色器负责将模型数据转换到裁剪空间,然后经由栅格化处理,进入到片段着色器,最终有片段着色器计算出像素颜色,并写入渲染对象。可以说这两个着色器代表了渲染的基本流程。其中关联函数的操作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #pragma vertex vert #pragma fragment frag
v2f vert(appdata v){ }
fixed4 frag(v2f i) : SV_TARGET{ }
|
Vertex stage
在实现顶点着色器函数之前,我满需要定义好插值数据类型,前面提到过,这类数据是在顶点着色器中计算好,然后传递给片段着色器的。
顶点着色器的主要功能就是执行空间变换,将顶点数据从模型坐标系转换到裁剪坐标系。空间转换可以借助矩阵乘法来实现。但是,很多时候我们并不需要知道乘法的具体实现,因为Unity为我们提供了很多矩阵变换相关的函数。我们可以使用宏命令来引入Unity预先编写好的工具函数。例如#include UnityCG.cginc
。其中UnityObjectToClipPos
便是实现模型坐标系到裁剪坐标系的工具函数。而UnityCG.cginc
文件中处了定义了很多常用的工具函数外,还定义了很多宏操作。宏操作的使用方法和函数的使用类似。例如,用于UV转换的宏TRANSFORM_TEX
,使用顶点UV,以及纹理变量为参数,最终得到转换后的UV坐标。
编写好的顶点着色器函数如下:
1 2 3 4 5 6 7 8 9
| v2f vert(appdata v){ v2f o; o.position = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; }
|
Fragment stage
在片段着色器中,我们使用由顶点着色器传来的插值数据、以及公共的纹理数据等,来进一步计算每个像素点的颜色。当然,我们可以直接返回白色,如return float4(1,1,1,1);
。但是更实际的情况是,我们结合前面提到的数据,通过一些简单、或复杂的计算,来得到比较自然的颜色值。
我们把使用纹理数据的过程叫做纹理采样。因为纹理数据是一整张包含无数像素点的图片,而我们的片段着色器计算的是一个单独像素的颜色。因此我们只需要纹理中的一个像素值。采样的方法也很简单,直接使用tex2D
函数就可以,当然还有其他一些复杂一点的采样函数。这里我们以tex2D
为例,需要两个参数,第一个是纹理变量,第二个是UV坐标。下面我们除了使用采样后的像素颜色,同时也叠加了公共变量_Color
的值。
1 2 3 4 5 6 7 8 9
| fixed4 frag(v2f i) : SV_TARGET{ fixed4 col = tex2D(_MainTex, i.uv); col *= _Color; return col; }
|
把上面的各个步骤组合起来,恭喜你,创建了属于你自己的着色器脚本。
Source
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| Shader "Tutorial/001-004_Basic_Unlit"{ Properties{ _Color ("Tint", Color) = (0, 0, 0, 1) _MainTex ("Texture", 2D) = "white" {} }
SubShader{ Tags{ "RenderType"="Opaque" "Queue"="Geometry" }
Pass{ CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert #pragma fragment frag
sampler2D _MainTex; float4 _MainTex_ST;
fixed4 _Color;
struct appdata{ float4 vertex : POSITION; float2 uv : TEXCOORD0; };
struct v2f{ float4 position : SV_POSITION; float2 uv : TEXCOORD0; };
v2f vert(appdata v){ v2f o; o.position = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; }
fixed4 frag(v2f i) : SV_TARGET{ fixed4 col = tex2D(_MainTex, i.uv); col *= _Color; return col; }
ENDCG } } Fallback "VertexLit" }
|
希望你能喜欢这个教程哦!如果你想支持我,可以关注我的推特,或者通过ko-fi、或patreon给两小钱。总之,各位大爷,走过路过不要错过,有钱的捧个钱场,没钱的捧个人场:-)!!!