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

本系列原更新于作者的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. C# 实现敏感词过滤

    实现 该 敏感词过滤 采用的是 DFA算法,参考文章:https://blog.csdn.net/chenssy/article/details/26961957 具体 实现 步骤 如下: 第一步,构 ...

  2. 关联mysql失败_Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezon' 时区错误

    时区错误,MySQL默认的时区是UTC时区,比北京时间晚8个小时. 所以要修改mysql的时长 在mysql的命令模式下,输入: set global time_zone='+8:00'; 再次连接成 ...

  3. JavaScript中的 JSON 和 JSONP

    JSON 和 JSONP JSONP是一种发送JSON数据的方法,无需担心跨域问题.JSONP不使用该XMLHttpRequest对象.JSONP使用<script>标签代替.由于跨域策略 ...

  4. (九)分布式服务----Zookeeper注册中心

    ==>>点击查看本系列文章目录 首先看一下几种注册中心: 最老的就是Zookeeper了, 比较新的有Eureka,Consul 都可以做注册中心.可以自行搜索对比三者的优缺点. Zook ...

  5. jQuery-ready与load

      // ready 在DOM加载完成时运行的代码 $(document).ready(function(){ // 在这里写代码... }) // 可以简写为 $(function(){ // 在这 ...

  6. linux 修改文件打开数量限制

    1.查看打开文件数量限制 ulimit -a ulimit -n 2.临时修改 ulimit -n 2048 3.永久修改 vi /etc/security/limits.conf 追加 * soft ...

  7. 结对编程(-java-实现)

    一 .Github项目地址:https://github.com/mushan520/Four-fundamental-rules-java.git                           ...

  8. 笔记5:Django知识一

    Django 1 MVC MVC的核心思想就是解耦. 2 Django介绍 2.1 MVT Django遵循MVC思想,其称为MVT.其中: M: Model模型:和MVC中的M相同和数据库交互 V: ...

  9. jmeter压测学习4-正则表达式提取

    前言 上一个接口返回的token作为下个接口的入参,除了前面一篇讲到的用json提取器提取,也可以用正则提取. json提取器只能提取json格式的数据,正则可以匹配任意的返回. 我现在有一个登陆接口 ...

  10. 链接(url)中不能有汉字,遇到汉字,需要使用quote转换之后使用

    from urllib.parse import quotename=quote("翻译")print(name)