文章著作权归作者所有。转载请联系作者,并在文中注明出处,给出原文链接。

本系列原更新于作者的github博客,这里给出链接

前言

经过前面两个章节的铺垫,我们对渲染以及Unity Shaderlab相关的知识已经有了大概的认识,接下来将要学习的就是Shader最重要的部分,SL(Shader Language),着色器语言。目前主流的着色器语言有HLSL,GLSL,Cg。三者在语法上也有诸多共通之处,选择一种学习即可。而在Unity中,主流是选择Cg作为着色器语言。在Shader编写的过程中,我们会经常穿梭在各个空间中,这里不对3D数学部分的前置知识作介绍,相关知识可从前面章节推荐的书籍学习。

在Shaderlab中,有三种着色器的书写方式。一种是Fixed-Function Shader,固定管线着色器。在这个着色器中,我们只能对渲染进行少量的配置,效果也很有限,在Unity 5.x之后的版本,Unity弃用了这种着色器。第二种是Surface Shader,表面着色器,这是Unity为我们提供的一种便于书写的方式,我们可以通过少量的代码,控制光照阴影等繁复的细节由Unity帮我们处理。新建一个Standard Surface Shader,可以看到里面只有50余行代码,但它包含了所有基础实现。最后一种,是Vertex/Fragment Shader,顶点/片元着色器,这是实现各种天马行空想象的最佳场所,当然,它的代码量以及复杂度也是最高的。而前两种shader也会被编译成对应的Vertex/Fragment Shader。这三种书写方式,都是在.shader文件中进行,组织方式上也是极为相似的。

这个系列的重点是Vertex/Fragment Shader。

CGPROGRAM

以之前模板的代码作为例子:

Shader "Blog/Start" {

    Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Color ("Color", Color) = (1, 1, 1, 1)
} SubShader {
CGINCLUDE
#include "UnityCG.cginc" sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color; struct a2v {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
}; struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
}; v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
} fixed4 frag(v2f i) : SV_Target {
fixed4 color;
color = tex2D(_MainTex, i.uv);
color *= _Color;
return color;
}
ENDCG Pass { CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
} FallBack "Diffuse"
}

在这个Shader中,出现了两个不同的代码块。首先第一个是CGINCLUDE代码块,它可以被放置在任何位置,甚至是整个Shader代码块的外部。在这个代码块中,我们可以编写那些需要重用的代码(如顶点着色器或片元着色器)。然后是CGPROGRAM代码块。这个代码块需要放在Pass块内,否则编译器会把这个Shader当成Surface Shader转而去检索surf()函数进而引起报错。这个代码块也是定义Vertex/Fragment Shader的地方。要保证,每个Pass都有且只有一个Vertex Shader和Fragment Shader。这两个Shader通过#pragma编译命令指定。接着是两个结构体:

struct a2v {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
}; struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};

第一个是顶点着色器的输入结构体,a2v即Application To Vertex(应用阶段到顶点着色),在每一个变量的后面都跟了一个冒号说明,冒号后的是这个变量的Semantic(语义),语义是和GPU通信的桥梁,告诉GPU在这个变量中填充什么数据。float4 vertex : POSITION;告诉GPU,把顶点数据的POSITION(模型空间下的顶点坐标)输入到vertex变量中,float2 texcoord : TEXCOORD0;的意思是,把纹理坐标集0给texcoord变量使用。在着色器之间的数据传递都是依靠语义实现的,使用结构体只是为了代码组织更有条理。

v2f即Vertex To Fragment,这是顶点着色器的输出结构体,也是片元着色器的输入结构体。float4 pos : SV_POSITION;:SV指System Value,带有SV前缀的语义在管线中都有特殊的含义,SV_POSITION的含义是裁剪空间下的坐标。

为什么输出裁剪空间下的顶点坐标?

因为这个坐标接下来用于片元着色器,片元着色器需要的是光栅化后的坐标,也就是裁剪空间的坐标。

然后是顶点着色器部分:

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}

这里只是做里简单的空间变换以及纹理映射。既然需要的是裁剪空间的坐标,那直接把输入的顶点坐标变换到裁剪空间即可。UnityObjectToClipPos()是Unity为我们提供的坐标空间转换函数。

// Tranforms position from object to homogenous space
inline float4 UnityObjectToClipPos(in float3 pos)
{
#if defined(STEREO_CUBEMAP_RENDER_ON)
return UnityObjectToClipPosODS(pos);
#else
// More efficient than computing M*VP matrix product
return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
#endif
}
inline float4 UnityObjectToClipPos(float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
{
return UnityObjectToClipPos(pos.xyz);
}

可以看到,这个函数处理了一些差别并重载了两个版本,但本质上,都是MVP矩阵右乘顶点坐标的列向量形式。然后是TRANSFORM_TEX宏。

// Transforms 2D UV by scale/bias property
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

可以看到这个宏计算了顶点对应的纹理采样位置,计算方式也对应了我们之前说到的_ST(Scale & Tiling,纹理缩放和偏移)相关知识点。然后是片元着色器部分:

fixed4 frag(v2f i) : SV_Target {
fixed4 color;
color = tex2D(_MainTex, i.uv);
color *= _Color;
return color;
}

片元着色器的最终目的是确定片元的像素颜色,即一个RGBA值。首先注意到的是SV_Target,它的语义是:这个着色器只返回一个值,这个值也就是片元的像素颜色值。此外,片元着色器还可以返回多个颜色,这时我们需要用到SV_TargetN语义,在这个情境下,SV_Target0是对应片元的像素颜色。例:

struct frag_output {
fixed4 color0 : SV_Target0;
fixed4 color1 : SV_Target1;
fixed4 color2 : SV_Target2;
} frag_output frag(v2f i) {
frag_output output;
// ...
return output;
}

回到模板的片元着色器内部,首先是color = tex2D(_MainTex, i.uv);tex2D(sampler2D texture, float2 uv);是Cg为我们提供的一个纹理采样函数,它将按照输入的uv采样输入的纹理texture,最后返回采样颜色。然后是color *= _Color;这里只是简单的把采样颜色和shader外部给定的颜色做乘法处理(叠加)。在场景中新建一个sphere,把这个shader添加到一个新的material,再把这个material挂到sphere上,不出意外没有报错的话,即可得到一个纯白色的球(由于没有任何光照阴影计算,也没有给纹理赋值)。这是我们第一个生效的shader。

2.1:CGPROGRAM的更多相关文章

  1. 【unity shaders】:Unity中的Shader及其基本框架

    shader和Material的基本关系 Shader(着色器)实际上就是一小段程序,它负责将输入的Mesh(网格)以指定的方式和输入的贴图或者颜色等组合作用,然后输出.绘图单元可以依据这个输出来将图 ...

  2. Unity3D ShaderLab 基础的高光实现

    Unity3D ShaderLab 基础的高光实现 关于高光: 在一个物体表面的高光属性就是为了描述它是如何表现光泽.这种类型的效果在着色器的世界中通常称为视点相关效果. 之所以这样说,是因为为了实现 ...

  3. 【Unity Shaders】使用CgInclude让你的Shader模块化——创建CgInclude文件存储光照模型

    本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图. 这里是本书所需的代码 ...

  4. 【Unity Shaders】Mobile Shader Adjustment —— 为手机定制Shader

    本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图.这里是本书所需的代码和 ...

  5. Unity SurfaceShader 开始编程

    Unity SurfaceShader 开始编程 在14年年初的时候,以前给自己定下了今年要实现的三个目标.当中之中的一个就是学会编写自己的Shader,并可以投入到实际的项目应用之中.如今,转眼间日 ...

  6. Unity Shader入门教程(一)

    参考文献:http://www.360doc.com/content/13/0923/15/12282510_316492286.shtml Unity Shader是着色器,将纹理.网格信息输入,得 ...

  7. 【Unity Shaders】Mobile Shader Adjustment—— 什么是高效的Shader

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  8. 【Unity Shaders】Vertex Magic —— 访问顶点颜色

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  9. 【Unity Shaders】Transparency —— 透明的cutoff shader

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

随机推荐

  1. Java网上体育商城系统ssh

    网上体育商城的主要功能包括:前台用户登录退出.注册.在线购物.修改个人信息.后台商品管理等等.本系统结构如下:(1)商品浏览模块:        首页浏览最新上市商品,按销量排行显示商品        ...

  2. 关于PHP在企业级开发领域的访谈

    企业软件的一个关键元素就是互操作性,它可以让软件与其他平台交换信息.大家都认为PHP在这方面表现欠佳,因为它的WS-*支持相对来说比较新且功能较少,成熟度不高.关于这点我们从未手动开启过PHP的相关进 ...

  3. maven 学习---Maven依赖机制

    在 Maven 依赖机制的帮助下自动下载所有必需的依赖库,并保持版本升级. 案例分析 让我们看一个案例研究,以了解它是如何工作的.假设你想使用 Log4j 作为项目的日志.这里你要做什么? 1.在传统 ...

  4. python3类和实例

    面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可 ...

  5. Android Activity之间的数据传递

    1.向目标Activity传递数据: Intent intent=new Intent(this,Main2Activity.class); //可传递多种类型的数据 intent.putExtra( ...

  6. CentOS7 安装FastDFS单机版

    1. 下载 FastDFS https://github.com/happyfish100/fastdfs/releases libfastcommon https://github.com/happ ...

  7. 网络远程唤醒 WOL Magic Packet【转】

    转自:https://www.cnblogs.com/dcb3688/p/4608063.html Magic Packet Magic Packet白皮书介绍: The basic technica ...

  8. TensorFlow GPU版本的安装与调试

    笔者采用python3.6.7+TensorFlow1.12.0+CUDA10.0+CUDNN7.3.1构建环境 PC端配置为GTX 1050+Intel i7 7700HQ 4核心8线程@2.8GH ...

  9. Windows 局域网内共享

    前言 在局域网内,其它成员无需密码就可以访问某台计算机共享的某个磁盘或某个文件夹. 本文记录几个关键点, 共享端:需要共享的计算机 用户端:从共享计算机读取文件的计算机 共享端 开启guest用户 1 ...

  10. jenkins部署报404错误

    环境:tomcat 7+jdk1.7+win10 64 jenkins_1.5.23 部署完成后服务器启动输入网址:http://192.168.3.100:8080/jenkins打开无法访问报40 ...