《CG Programming in Unity》笔记1-基础知识
2015-03-21 01:38
537 查看
本文主要参考《CG Programming in Unity》一书。作为读书笔记,省略了原书中关于Unity的基本操作,在翻译原文主要内容的同时会加上一点个人理解或拓展。关于原书,你可以参考这里,原书PDF可以参考这里。
1.基础知识
1.1 一个最简单的Shader(原文参考)
这段教程包含了在 Unity 中创建一个最简单的 Shader 的基本步骤。
代码如下:
将 Shader 赋予 Material,再将 Material 赋予物体,会看到物体呈现出不透明的红色,如图:
我们可以修改 frag 函数的代码,以使物体呈现其他的颜色,比如:
保存代码,返回 Unity 中查看,如图:
我们也可以修改 vert 函数,比如:
保存代码,返回 Unity 中查看,如图:
可以看到,通过在 Y坐标上乘以0.1,使模型看起来被压扁了(这是控制向量分量的结果;更多关于向量和矩阵的信息可以参考在 附录A.3”向量与矩阵操作“中的讨论)。
假如这个 Shader 编译不通过,Unity 会在控制台给出错误提示。你也可以在 Inspector 面板选中 Shader,以查看所有错误和警告。
关于术语的一个提示
有一点要澄清的是,在一些APIs中,一个“shader”要么是一个 vertex shader 要么是一个 fragment shader,两者组合在一起叫做一个“program”。在另一些APIs中刚好相反,一个“program”要么是一个 vertex program 要么是一个 fragment program,两者组合在一起的叫做一个“shader”。很遗憾,Unity 的文档中混淆了两种惯例。简单起见,我们在这里避免使用“program”,而用“shader”表示 vertex shader 和 fragment shader 的组合。
本节摘要
• 如何创建一个 Shader。
• 如何在 Unity 中定义一个CG vertex and fragment shader。
• 如何创建一个材质并将一个 Shader 赋予这个材质。
• 如何在 fragment shader 中操作一个带有 COLOR 语义的片段输出参数。
• 如何在 vertex shader 中变换一个带有 POSITION 语义的顶点输入参数。
• 如何创建一个游戏对象并赋予其材质。
扩展阅读
如果你还想知道更多
• 关于通常的 vertex and fragment shaders ,你可以阅读 附录A.1“可编程图形管线”。
• 关于顶点变换,诸如包含 model-view matrix 和 projection matrix 乘积的 UNITY_MATRIX_MVP,你可以阅读 附录A.2“顶点变换”。
• 关于在CG中操作向量(比如float4类型)和矩阵,你可以阅读 附录A.3“向量与矩阵操作”。
• 关于如何应用诸如 UNITY_MATRIX_MVP 的顶点变换,你可以阅读 附录A.4“应用矩阵转换”。
• 关于 Unity 的ShaderLab语言,你可以阅读 Unity’s ShaderLab 参考手册。
1.2 RGB立方体(原文参考)
这段教程讨论了顶点输出参数与片段输入参数。它基于 1.1“一个最简单的Shader”。
在这段教程中,我们将完成一个如下图所示的效果。每个立方体顶点的颜色将由它的坐标决定。也就是说,处在(x,y,z)坐标的顶点的颜色为(red,green,blue)=(x,y,z)。比如:顶点(x,y,z)=(0,0,1),映射到颜色(red,green,blue)=(0,0,1),也就是蓝色。
代码如下:
顶点着色器与片段着色器的通信
上面的Shader的主要目的是将片段着色器中的输出颜色的值设置为在顶点着色器中的顶点位置的值。当然实际上这么说不太准确:带有POSITION语义的顶点输入参数的坐标在 Unity 的默认立方体中处于-0.5到0.5之间,而我们想要的颜色组件的值处于0到1之间;因此,我们需要为立方体的坐标加上0.5。
然而我们的主要问题是:片段着色器如何从顶点着色器中得到所需的值?唯一的办法是利用带有相同语义的成对的顶点输出参数和片段输入参数(在本例中就是TEXCOORD0)。实际上,语义只是用来判断哪个顶点输出参数相当于哪个片段输入参数。我们也可以用其他的语义来代替TEXCOORD0,比如COLOR,在本例中用哪个语义并不重要,除了被限定在0到1之间的那个带有COLOR语义的参数。为各类参数使用诸如TEXCOORD0, TEXCOORD1, TEXCOORD2等等语义是很常见的。
下一个问题是指定多个顶点输出参数。由于 return 指令只能返回一个值,所以为所有需要输出的顶点参数定义一个结构体是很常见的。本例中的结构体名为 vertexOutput:
利用结构体作为片段着色器的参数,我们要确保语义匹配。
out 修饰符
另一个不使用输出结构的方案是,使用带有 out 修饰符参数的顶点着色器函数,代码如下:
然而,在实际应用中使用输出结构更为普遍,它能确保顶点输出参数和片段输入参数的语义匹配。
这个着色器的变种
这个RGB立方体呈现出整个色域。因此,它也能用来展示颜色变化的效果。比如,灰度转换将计算红、绿、蓝三个值的平均值,也就是 (red + green + blue) /3,然后将这个平均值再赋给片段颜色中的红、绿、蓝,以得到相同饱和度的灰色。除了使用平均值,也可以使用相对的亮度,公式是 0.21 red + 0.72 green + 0.07 blue(扩展阅读1,扩展阅读2,扩展阅读3)。当然,颜色的其他任何变换(改变饱和度、对比度、色调等)也都适用。代码如下:
效果如图:
效果如图:
可以看到,两种方法的效果还是有些不同的。
这个Shader的另一个变种,可以计算一个CMY(青色,品红,黄色)立方体:根据位置信息(x,y,z),你可以从纯白色中减去 x(红色分量)来产生青色,减去y(绿色分量)来产生品红色,减去z(蓝色分量)来产生黄色。代码如下:
效果如图:
如果还想弄点有趣的,你可以计算一个HSV(色调,饱和度,亮度)圆柱体。因为 x 与 z 的坐标在 -0.5 到 0.5 之间,在 Cg 中,你可以通过公式 180.0 + degrees(atan2(z, x)) 来得到一个 0 到 360 度的夹角 H,可以通过公式 2.0 * sqrt(x * x + z * z) 在 y 轴上得到一个在 0 到 1 之间的距离 S。Unity 内置的圆柱体的 y 坐标在 -1 到 1 之间,可以通过公式 (y+1.0)/2.0 将其值转换为 0 到 1 之间的值 V。从 HSV 坐标来计算 RGB 颜色可以查看 article on HSV in Wikipedia。
1.3 调试Shader
1.4 在世界坐标空间里着色
1.基础知识
1.1 一个最简单的Shader(原文参考)
这段教程包含了在 Unity 中创建一个最简单的 Shader 的基本步骤。
代码如下:
Shader "Cg basic shader" { SubShader { // Unity 会为 GPU 选择最优的 SubShader Pass { // 一些 Shader 可能需要多个 Pass CGPROGRAM // 这里开始是在 Unity 中的CG代码部分 #pragma vertex vert // 这里指定一个名为 vert 的函数作为 vertex shader #pragma fragment frag // 这里指定一个名为 frag 的函数作为 fragment shader float4 vert(float4 vertexPos :POSITION) :SV_POSITION // 顶点着色器 { return mul(UNITY_MATRIX_MVP, vertexPos); // 这一行用 Unity 内建的矩阵 UNITY_MATRIX_MVP 变换输入的顶点参数 // 并将其结果作为一个未命名的顶点输出参数返回 } float4 frag(void) :COLOR // 片段着色器 { return float4(1.0, 0.0, 0.0, 1.0); // 这个片段着色器返回一个未命名的片段 // 输出参数是一个带有 COLOR 语义的被设为不透明的红色 } ENDCG // 这里结束CG部分 } } }
将 Shader 赋予 Material,再将 Material 赋予物体,会看到物体呈现出不透明的红色,如图:
我们可以修改 frag 函数的代码,以使物体呈现其他的颜色,比如:
float4 frag(void) :COLOR { return float4(0.6, 1.0, 0.0, 1.0); // (red = 0.6, green = 1.0, blue = 0.0, alpha = 1.0) }
保存代码,返回 Unity 中查看,如图:
我们也可以修改 vert 函数,比如:
float4 vert(float4 vertexPos :POSITION) :SV_POSITION { return mul(UNITY_MATRIX_MVP, float4(1.0, 0.1, 1.0, 1.0) *vertexPos); }
保存代码,返回 Unity 中查看,如图:
可以看到,通过在 Y坐标上乘以0.1,使模型看起来被压扁了(这是控制向量分量的结果;更多关于向量和矩阵的信息可以参考在 附录A.3”向量与矩阵操作“中的讨论)。
假如这个 Shader 编译不通过,Unity 会在控制台给出错误提示。你也可以在 Inspector 面板选中 Shader,以查看所有错误和警告。
关于术语的一个提示
有一点要澄清的是,在一些APIs中,一个“shader”要么是一个 vertex shader 要么是一个 fragment shader,两者组合在一起叫做一个“program”。在另一些APIs中刚好相反,一个“program”要么是一个 vertex program 要么是一个 fragment program,两者组合在一起的叫做一个“shader”。很遗憾,Unity 的文档中混淆了两种惯例。简单起见,我们在这里避免使用“program”,而用“shader”表示 vertex shader 和 fragment shader 的组合。
本节摘要
• 如何创建一个 Shader。
• 如何在 Unity 中定义一个CG vertex and fragment shader。
• 如何创建一个材质并将一个 Shader 赋予这个材质。
• 如何在 fragment shader 中操作一个带有 COLOR 语义的片段输出参数。
• 如何在 vertex shader 中变换一个带有 POSITION 语义的顶点输入参数。
• 如何创建一个游戏对象并赋予其材质。
扩展阅读
如果你还想知道更多
• 关于通常的 vertex and fragment shaders ,你可以阅读 附录A.1“可编程图形管线”。
• 关于顶点变换,诸如包含 model-view matrix 和 projection matrix 乘积的 UNITY_MATRIX_MVP,你可以阅读 附录A.2“顶点变换”。
• 关于在CG中操作向量(比如float4类型)和矩阵,你可以阅读 附录A.3“向量与矩阵操作”。
• 关于如何应用诸如 UNITY_MATRIX_MVP 的顶点变换,你可以阅读 附录A.4“应用矩阵转换”。
• 关于 Unity 的ShaderLab语言,你可以阅读 Unity’s ShaderLab 参考手册。
1.2 RGB立方体(原文参考)
这段教程讨论了顶点输出参数与片段输入参数。它基于 1.1“一个最简单的Shader”。
在这段教程中,我们将完成一个如下图所示的效果。每个立方体顶点的颜色将由它的坐标决定。也就是说,处在(x,y,z)坐标的顶点的颜色为(red,green,blue)=(x,y,z)。比如:顶点(x,y,z)=(0,0,1),映射到颜色(red,green,blue)=(0,0,1),也就是蓝色。
代码如下:
Shader "Cg shader for RGB cube" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // 为多个顶点输出参数定义一个输出结构 struct vertexOutput { float4 pos :SV_POSITION; float4 col :TEXCOORD0; }; vertexOutput vert(float4 vertexPos :POSITION) { vertexOutput output; output.pos = mul(UNITY_MATRIX_MVP, vertexPos); output.col = vertexPos + float4(0.5, 0.5, 0.5, 0.0); // 这里是顶点着色器将要输出的数据写入输出结构体中 // 由于直角坐标系的圆点在立方体的几何中心,所以我们要为(x,y,z)坐标加上0.5,因为立方体的坐标处于-0.5到0.5之间,而我们需要它在0到1之间。 return output; } float4 frag(vertexOutput input) :COLOR { return input.col; // 这里片段着色器返回带有TEXCOORD0语义的未命名的输入参数“col” // 输出参数带有COLOR语义 } ENDCG } } }
顶点着色器与片段着色器的通信
上面的Shader的主要目的是将片段着色器中的输出颜色的值设置为在顶点着色器中的顶点位置的值。当然实际上这么说不太准确:带有POSITION语义的顶点输入参数的坐标在 Unity 的默认立方体中处于-0.5到0.5之间,而我们想要的颜色组件的值处于0到1之间;因此,我们需要为立方体的坐标加上0.5。
然而我们的主要问题是:片段着色器如何从顶点着色器中得到所需的值?唯一的办法是利用带有相同语义的成对的顶点输出参数和片段输入参数(在本例中就是TEXCOORD0)。实际上,语义只是用来判断哪个顶点输出参数相当于哪个片段输入参数。我们也可以用其他的语义来代替TEXCOORD0,比如COLOR,在本例中用哪个语义并不重要,除了被限定在0到1之间的那个带有COLOR语义的参数。为各类参数使用诸如TEXCOORD0, TEXCOORD1, TEXCOORD2等等语义是很常见的。
下一个问题是指定多个顶点输出参数。由于 return 指令只能返回一个值,所以为所有需要输出的顶点参数定义一个结构体是很常见的。本例中的结构体名为 vertexOutput:
struct vertexOutput { float4 pos :SV_POSITION; float4 col :TEXCOORD0; };
利用结构体作为片段着色器的参数,我们要确保语义匹配。
out 修饰符
另一个不使用输出结构的方案是,使用带有 out 修饰符参数的顶点着色器函数,代码如下:
Shader "Cg shader for RGB cube" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag void vert( float4 vertexPos:POSITION, out float4 pos:SV_POSITION, out float4 col:TEXCOORD0) { pos = mul( UNITY_MATRIX_MVP,vertexPos); col = vertexPos + float4(0.5,0.5,0.5,0); return; } float4 frag( float4 pos:SV_POSITION, float4 col:TEXCOORD0):COLOR { return col; } ENDCG } } }
然而,在实际应用中使用输出结构更为普遍,它能确保顶点输出参数和片段输入参数的语义匹配。
这个着色器的变种
这个RGB立方体呈现出整个色域。因此,它也能用来展示颜色变化的效果。比如,灰度转换将计算红、绿、蓝三个值的平均值,也就是 (red + green + blue) /3,然后将这个平均值再赋给片段颜色中的红、绿、蓝,以得到相同饱和度的灰色。除了使用平均值,也可以使用相对的亮度,公式是 0.21 red + 0.72 green + 0.07 blue(扩展阅读1,扩展阅读2,扩展阅读3)。当然,颜色的其他任何变换(改变饱和度、对比度、色调等)也都适用。代码如下:
Shader "Cg shader for Gray cube 1" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag struct vertexOutput { float4 pos:SV_POSITION; float4 col:TEXCOORD0; }; vertexOutput vert(float4 vertexPos:POSITION) { vertexOutput output; output.pos = mul(UNITY_MATRIX_MVP,vertexPos); output.col = vertexPos + float4(0.5,0.5,0.5,0); return output; } float4 frag(vertexOutput input):COLOR { float mean = (input.col.x + input.col.y + input.col.z)/3; input.col.xyz = float3(mean,mean,mean); return input.col; } ENDCG } } }
效果如图:
Shader "Cg shader for Gray cube 2" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag struct vertexOutput { float4 pos:SV_POSITION; float4 col:TEXCOORD0; }; vertexOutput vert(float4 vertexPos:POSITION) { vertexOutput output; output.pos = mul(UNITY_MATRIX_MVP,vertexPos); output.col = vertexPos + float4(0.5,0.5,0.5,0); return output; } float4 frag(vertexOutput input):COLOR { float gray = float(0.21*input.col.x + 0.72*input.col.y + 0.07*input.col.z); return gray; } ENDCG } } }
效果如图:
可以看到,两种方法的效果还是有些不同的。
这个Shader的另一个变种,可以计算一个CMY(青色,品红,黄色)立方体:根据位置信息(x,y,z),你可以从纯白色中减去 x(红色分量)来产生青色,减去y(绿色分量)来产生品红色,减去z(蓝色分量)来产生黄色。代码如下:
Shader "Cg shader for CMY cube" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct vertexOutput { float4 pos :SV_POSITION; float4 col :TEXCOORD0; };
vertexOutput vert(float4 vertexPos :POSITION)
{
vertexOutput output;
output.pos = mul(UNITY_MATRIX_MVP, vertexPos);
output.col = vertexPos + float4(0.5, 0.5, 0.5, 0.0);
return output;
}
float4 frag(vertexOutput input) :COLOR
{
input.col = float4(1-input.col.x,1-input.col.y,1-input.col.z,input.col.w);
return input.col;
}
ENDCG
}
}
}
效果如图:
如果还想弄点有趣的,你可以计算一个HSV(色调,饱和度,亮度)圆柱体。因为 x 与 z 的坐标在 -0.5 到 0.5 之间,在 Cg 中,你可以通过公式 180.0 + degrees(atan2(z, x)) 来得到一个 0 到 360 度的夹角 H,可以通过公式 2.0 * sqrt(x * x + z * z) 在 y 轴上得到一个在 0 到 1 之间的距离 S。Unity 内置的圆柱体的 y 坐标在 -1 到 1 之间,可以通过公式 (y+1.0)/2.0 将其值转换为 0 到 1 之间的值 V。从 HSV 坐标来计算 RGB 颜色可以查看 article on HSV in Wikipedia。
1.3 调试Shader
1.4 在世界坐标空间里着色
相关文章推荐
- Programming in Emacs Lisp笔记(七)基础函数:car, cdr, cons
- Java2核心技术第七版的学习笔记(三) Fundamental Programming Structures in Java(Java语言的基础)(四):
- Unity基础知识学习笔记一
- Java2核心技术第七版的学习笔记(三) Fundamental Programming Structures in Java(Java语言的基础)(二)
- 【IOS基础知识】CGRectInset、CGRectOffset、frame、bounds对比整理
- Unity基础知识学习笔记二
- Java2核心技术第七版的学习笔记(三) Fundamental Programming Structures in Java(Java语言的基础)(三)
- 【IOS基础知识】CGRectInset、CGRectOffset、frame、bounds对比整理
- Cg Programming/Unity/Shading in World Space世界空间中的着色器
- Java2核心技术第七版的学习笔记(三) Fundamental Programming Structures in Java(Java语言的基础)(一)
- Programming in Objective-C 学习笔记07——Foundation框架基础01
- wiki/Cg Programming/Unity_shder/Shading in World Space
- Java基础知识笔记(四:多线程基础及生命周期)
- 嵌入式系统设计师考试笔记之嵌入式系统基础知识
- i2c bus 基础知识笔记
- Java基础知识强化之集合框架笔记80:HashMap的线程不安全性的体现
- python3学习笔记:1.基础知识
- Redis学习笔记之基础知识(三)
- 【2018.04.11学习笔记】【linux基础知识4.10-4.13】
- [unity基础知识]之Unity3d之Vector3 学习与应用