1. 概述

在上一篇文章《Unity3D学习笔记2——绘制一个带纹理的面》中介绍了如何绘制一个带纹理材质的面,并且通过调整光照,使得材质生效(变亮)。不过,上篇文章隐藏了一个很重要的细节——Unity Shader。Shader(着色器)是渲染管线中可被用户编程的阶段,依靠着色器可以控制渲染管线的细节。现代图像渲染技术,都把Shader封装成与Material(材质)相关的组件。所以这篇文章,我们就初步学习下在Unity中使用Shader。

2. 详论

2.1. 创建材质

在上一章中,材质、以及材质相关的资源是在Unity3D编辑器中创建,在C#脚本中直接引用的。这里为了学习使用Shader,我们使用自定义的Shader,可以在C#脚本中创建材质。修改上一章代码的材质部分:

Shader shader = Shader.Find("Custom/MainShader");
Material material = new Material(shader); Texture2D texture = Resources.Load<Texture2D>("ImageDemo");
material.mainTexture = texture; MeshRenderer meshRenderer = newGameObject.AddComponent<MeshRenderer>();
meshRenderer.material = material;

可以看到,要创建一个Material,首先得创建一个Shader。我们在Project视图中右键菜单->Create->Standard Surface Shader,创建一个标准表面着色器MainShader:

双击打开这个Shader,可以看到这个Shader的具体内容。标准着色器很复杂,我们清空里面的内容,填入我们这个更简单的着色器示例:

Shader "Custom/MainShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags{"Queue" = "Geometry"} Cull Back Pass
{
CGPROGRAM #pragma vertex vert
#pragma fragment frag sampler2D _MainTex; //顶点着色器输入
struct a2v
{
float4 position : POSITION;
float3 normal: NORMAL;
float2 texcoord : TEXCOORD0;
}; //顶点着色器输出
struct v2f
{
float4 position: SV_POSITION;
float2 texcoord: TEXCOORD0;
}; v2f vert(a2v v)
{
v2f o;
o.position = mul(UNITY_MATRIX_MVP, v.position);
o.texcoord = v.texcoord; return o;
} fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.texcoord);
} ENDCG
}
}
FallBack "Diffuse"
}

2.2. 着色器

Unity使用的着色器语言叫做ShaderLab,它是图形渲染中Shader(例如GLSL,HLSL以及CG)的更高级更抽象一级的封装。ShaderLab是个非常简单的说明性描述语言,通过嵌套在花括号中的语义来描述Unity Shader文件。

2.2.1. 名称

通过Shader语义指定Unity Shader的名称:

Shader "Custom/MainShader"
{ }

这个名称非常重要,在Unity编辑器中需要通过这个名字来引用Shader。

2.2.2. 属性

Shader语义块的第一个语义块是Properties语义块,它连接着材质和Unity3d编辑器,设置了这个属性就能够通过材质面板调整材质,调整材质的本质就是调整Shader。Properties的定义通常描述如下:

Properties {
Name ("display name",PropertyType) = DefaultValue
}

Name指的是在Shader中使用的名称,display name指的是显示在材质面板的名称。PropertyType则有点容易混淆,它指的是显示在材质面板中的属性类型,借用一下《Unity Shader入门精要》的图表:

2.2.3. SubShader

每个Unity Shader都至少包含一个SubShader语义块,Unity会优先选择第一个能够在当前平台下运行的SubShader作为最终渲染效果的Shader。

这个语义块下面又会包含三个语义块:

2.2.3.1. 标签(Tags)

SubShader的标签用于用于标识何时以何种方式被渲染到渲染引擎,它由一系列键值对组成。Queue是最常用的标签,用于标识渲染物体在渲染队列中的位置:

我们这里,把这个渲染物体放到Geometry队列中,这个位置通常放置不透明物体的渲染:

Tags{"Queue" = "Geometry"}

2.2.3.2. 渲染状态(RenderSetup)

渲染状态用于设置图形硬件的各种状态,例如是否应开启 Alpha 混合或是否应使用深度测试等。在像OpenGL这样的图形接口中,通常是以函数的形式进行调用的,Unity3d将其放在Shader里面,也有一定的道理。

这里的渲染状态设置成将背面裁剪掉:

Cull Back

2.2.3.3. 通道(Pass)

在Pass语义块中,才是像OpenGL/DirectX中使用的Shader。OpenGL使用的着色器语言叫做GLSL,DirectX使用的着色器语言叫做HLSL,Unity3D则推荐使用Cg语言,这是一种类C语言,与HLSL非常相似。Cg语言代码段在Pass语义块中被包裹在CGPROGRAM和ENDCG之间:

CGPROGRAM
//...
ENDCG

2.2.4. 回退(FallBack)

FallBack定义了一种退化策略,由于不同机器支持的性能特性不同,如果之前的子着色器都不生效,那么就使用这个着色器,通常这个着色器是内置的:

FallBack "Diffuse"

2.3. 渲染管线

图形渲染引擎的渲染管线其实是个内涵非常丰富的概念,再次借用《Unity Shader入门精要》的插图,渲染管线的描述大致如下:

当然只看这个图是不够的,但是我们可以直接从代码层面去了解它。镶嵌在CGPROGRAM和ENDCG之间的CG代码,体现的正是渲染管线的思维。

首先,通过编译指令,分别指定顶点着色器程序和片元着色器程序:

#pragma vertex vert
#pragma fragment frag

vert就是顶点着色器的函数,在这个着色器程序中指定了计算了顶点坐标和纹理坐标:

v2f vert(a2v v)
{
v2f o;
o.position = mul(UNITY_MATRIX_MVP, v.position);
o.texcoord = v.texcoord; return o;
}

传入参数是一个结构体,POSITION,NORMAL,TEXCOORD0是Unity Shader中固定的语义,分别代表这位置、法向量以及纹理坐标,他们也被称为顶点属性。还记得在上一篇文章《Unity3D学习笔记2——绘制一个带纹理的面》中创建Mesh时给Mesh创建的成员变量vertices、uv和normals吧?给他们传入的数据正是在这里用到了。

//顶点着色器输入
struct a2v
{
float4 position : POSITION;
float3 normal: NORMAL;
float2 texcoord : TEXCOORD0;
};

传出参数则是另外一个结构体:

//顶点着色器输出
struct v2f
{
float4 position: SV_POSITION;
float2 texcoord: TEXCOORD0;
};

SV_POSITION表示的是裁剪空间坐标,也就是在顶点着色器中计算的顶点值。这个计算内容的内涵也挺丰富的,简单来说,创建Mesh时的顶点坐标,经过一个模型变换(Model)、视图变换(View)、投影变换(Projection),最终变成了裁剪空间坐标系中的坐标,体现在着色器中,就是内置的MVP矩阵UNITY_MATRIX_MVP。

剩下的就是片元着色器函数的部分了。在这个着色器中,_MainTex也就是我们先前创建的,并且传递到材质中的纹理,通过将顶点着色器中传递过来的纹理坐标进行采样,得到具体的片元颜色:

sampler2D _MainTex;

fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.texcoord);
}

最终显示的效果如下:

可以看到这里显示的就是图片本身的颜色,这是因为在着色器中只是采样了图片的颜色,并没有光照计算的参与。也就是在图形引擎中,任何效果的设置只是表象,任何效果的实现都会归结到着色器中。

Unity3D学习笔记3——Unity Shader的初步使用的更多相关文章

  1. Unity3D学习笔记6——GPU实例化(1)

    目录 1. 概述 2. 详论 3. 参考 1. 概述 在之前的文章中说到,一种材质对应一次绘制调用的指令.即使是这种情况,两个三维物体使用同一种材质,但它们使用的材质参数不一样,那么最终仍然会造成两次 ...

  2. Unity3D学习笔记7——GPU实例化(2)

    目录 1. 概述 2. 详论 2.1. 实现 2.2. 解析 3. 参考 1. 概述 在上一篇文章<Unity3D学习笔记6--GPU实例化(1)>详细介绍了Unity3d中GPU实例化的 ...

  3. Unity3D学习笔记8——GPU实例化(3)

    目录 1. 概述 2. 详论 2.1. 自动实例化 2.2. MaterialPropertyBlock 3. 参考 1. 概述 在前两篇文章<Unity3D学习笔记6--GPU实例化(1)&g ...

  4. Unity3D学习笔记12——渲染纹理

    目录 1. 概述 2. 详论 3. 问题 1. 概述 在文章<Unity3D学习笔记11--后处理>中论述了后处理是帧缓存(Framebuffer)技术实现之一:而另外一个帧缓存技术实现就 ...

  5. Unity3D学习笔记4——创建Mesh高级接口

    目录 1. 概述 2. 详论 3. 其他 4. 参考 1. 概述 在文章Unity3D学习笔记2--绘制一个带纹理的面中使用代码的方式创建了一个Mesh,不过这套接口在Unity中被称为简单接口.与其 ...

  6. 【HLSL学习笔记】WPF Shader Effect Library算法解读之[DirectionalBlur]

    原文:[HLSL学习笔记]WPF Shader Effect Library算法解读之[DirectionalBlur] 方位模糊是一个按照指定角度循环位移并叠加纹理,最后平均颜色值并输出的一种特效. ...

  7. 【HLSL学习笔记】WPF Shader Effect Library算法解读之[Embossed]

    原文:[HLSL学习笔记]WPF Shader Effect Library算法解读之[Embossed] Embossed(浮雕效果)          浮雕效果主要有两个参数:Amount和Wid ...

  8. 【HLSL学习笔记】WPF Shader Effect Library算法解读之[BandedSwirl]

    原文:[HLSL学习笔记]WPF Shader Effect Library算法解读之[BandedSwirl] 因工作原因,需要在Silverlight中使用Pixel Shader技术,这对于我来 ...

  9. unity3d学习笔记(一) 第一人称视角实现和倒计时实现

    unity3d学习笔记(一) 第一人称视角实现和倒计时实现 1. 第一人称视角 (1)让mainCamera和player(视角对象)同步在一起 因为我们的player是生成的,所以不能把mainCa ...

随机推荐

  1. 惊呆了,Spring Boot居然这么耗内存!

    Spring Boot总体来说,搭建还是比较容易的,特别是Spring Cloud全家桶,简称亲民微服务,但在发展趋势中,容器化技术已经成熟,面对巨耗内存的Spring Boot,小公司表示用不起.如 ...

  2. 【模拟8.01】matrix(DP杂题,思维题)

    很神的题,感谢lnc大佬的指点. 先设1-LL[i]统称左区间,RR[i]-m为右区间 用L[i]统计从1-i列,出现的左区间端点的前缀和,R[i]是右区间.... f[i][j]中j表示当前在第i列 ...

  3. 『无为则无心』Python基础 — 8、Python中的数据类型(数值、布尔、字符串)

    目录 1.数据类型介绍 2.数值型(Number) 3.布尔型(bool) 4.None(空值) 5.常量 6.字符串(String) 1.数据类型介绍 (1)什么是数据类型 在生活中,我们日常使用的 ...

  4. Jenkins用户权限管理-Role-based Authorization Strategy插件

    02-Jenkins用户权限管理-Role-based Authorization Strategy插件 在jenkins的使用过程中,需要给用户分配只管理特定项目的权限来保证项目相关人员只能管理对应 ...

  5. DHCP的简单介绍与配置

    一.DHCP简介 二.DHCP报文类型 三.DHCP工作原理 四.实例操作 一.DHCP简介 DHCP(Dynamic Host Configuration Protocol),动态主机配置协议,是一 ...

  6. IP地址与子网的划分

    一.IP地址 1.IP地址的定义 (1).IP地址有32位二进制数组成,一般用点分十进制来表示 (2).IP地址由两部分组成 网络部分(NETWORK) 主机部分(HOST) 2.IP地址的分类 IP ...

  7. MiniSMB 专业网络性能测试仪表 英特尔82576 4*1GE 网卡性能测试报告

    MiniSMB 专业网络性能测试仪表英特尔82576 4*1GE网卡性能测试报告 一.测试环境 测试配置 ①工控机配置: CPU:Intel(R) Core(TM) i7-6800K CPU @ 3. ...

  8. Cobra框架使用手册

    cobra框架使用手册 cobra是go语言的一个库,可以用于编写命令行工具. 概念 Cobra 结构由三部分组成:命令 (commands).参数 (arguments).标志 (flags).最好 ...

  9. webpack(6)webpack处理图片

    图片处理url-loader(webpack5之前的处理方式) 在项目开发中,我们时长会需要使用到图片,比如在img文件夹中有图片test1.png,然后在normal.css中会引用到图片 body ...

  10. zset如何解决内部链表查找效率低下

    zset作为有序集合,内部基于跳表或者说索引的方式实现了数据的快速查找.解决了链表查询效率低下的痛点 前言 紧接前文我们学习了Redis中Hash结构.在里面我们梳理了字典这个重要的内部结构并分析了h ...