您的位置:首页 > 移动开发 > Unity3D

在unity3d中编写你的第一个着色器

2015-07-14 11:41 393 查看



你知道显卡是如何渲染图像的吗?

下面这个示意图展示了其一般的过程:







图形渲染管道--是一个在数据呈现在屏幕上之前对其进行传输的管道。老的电脑使用软件进行渲染。CPU通过以下的渲染管道进行所有的计算:





第一款如此使用的3D加速器被称为固定功能性管线,这种管线是严格固定且连续的。渲染的过程是不可能被中断的:







输入的数据作为一个对象以带有多个属性的独立点的形式传输:这些属性包括点的位置,颜色,法线,纹理坐标等。

转换和灯光。在这个阶段在对象上进行几何操作(移动,旋转,缩放),同时对每个顶点基于光源位置/类型及指定其表面(反射,折射)的参数进行光照计算。

原始安装是一个三角测量法的过程:顶点被组合成三角形。

光栅化。这个阶段的目的是基于已经准备好的数据计算像素颜色。由于我们只有顶点颜色相关的信息,所以要获得像素颜色,我们会在顶点的颜色值之间线性地插入值。

像素处理。就是像素着色。输入的数据被以以前的水平准备。也就是在这里我们可以对像素应用额外的效果,例如纹理化效果。

帧缓冲混合。这是创建的最后一帧。Z-缓冲数据被认为是用来确定哪个对象较接近相机。透明度测试被执行。对象在图层上位于最后一个图像层。也可以应用朦胧效果。然后完整的帧被放置于帧缓存中。

由于显卡有支持DirectX 8.0-8.1的硬件使得处理顶点和像素成为可编程的。下面这个图表显示了图形渲染管道:





现在让我们谈论一下3D对象。模型是一组顶点,除了它们连接起来的还有材质,动画等等。顶点是具有属性的。例如在展开纹理时定义顶点位置的UV-坐标。材质决定了对象的外观并且包括了一个用来渲染几何体或粒子的着色器的引用。因此,没有任何材质这些组成部分将无法显示。

着色器-是一个处理图形渲染管线阶段之一的程序。所有的着色器可被分为两组:顶点和片段(像素)。在Unity中有一个编写着色器的简单方法--表面着色器。它只是较高级别的抽象概念。在表面着色器编译期间,编译器会生成由顶点和像素阴影组成的着色器。Unity拥有其自己编写着色器的语言--支持插入CG和HLSL代码的ShaderLab。

实践

作为例子,我们来写一个着色器,这个着色器对象上添加了漫反射纹理,法线贴图,高光贴图(基于立方体贴图)并且在漫反射纹理中通过alpha通道来处理像素。

考虑到ShaderLab的一般语法。即使我们用CG或HLSL编写一个着色器,我们仍需要了解ShaderLab的语法,这样我们才可以在检查器中设置我们的着色器参数。



Shader"Group/SomeShader"

{

// properties that will be seen in the inspector

Properties

{

_Color("Main Color",Color)=(1,0.5,0.5,1)

}// define one subshader

SubShader

{

Pass{}

}

Fallback"Diffuse"

}

复制代码

第一个关键词是《Shader》。然后我们为着色器定义一个名称。我们可以用‘/’指定一个路径,在这个路径位置当设置材质时着色器就会显示在一个下拉菜单中。在检查器中会出现一个属性参数列表{},用户能够与其进行交互操作。



在Unity中每个着色器都由一系列子着色器组成。当Unity要显示一个网格,它就会找到要使用的着色器,并且选择第一个子着色器在用户的图形显卡上运行。这样着色器就可以在支持不同着色器模型的不同视频显卡上正确地显示了。通道阻塞导致对象的几何形状被渲染一次...着色器可以包含一个或多个通道。例如着色器在针对旧的硬件进行优化,或者实现特殊的效果的情况下就会用到多通道。

如果Unity在着色器内没有找到任何可以正确显示该几何体的子着色器,那么它就会回退到另一个定义在后备计划语句中的着色器。在上面的例子中,如果显卡无法正确地显示当前的着色器的话它将会采用漫反射着色器。

让我们考虑一下接下来的这个例子:



Shader "Example/Bumped Reflection Clip"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

_BumpMap ("Bumpmap", 2D) = "bump" {}

_Cube ("Cubemap", CUBE) = "" {}

_Value ("Reflection Power", Range(0,1)) = 0.5

}

SubShader

{

Tags { "RenderType" = "Opaque" }

Cull Off

CGPROGRAM

#pragma surface surf Lambert

struct Input

{

float2 uv_MainTex;

float2 uv_BumpMap;

float3 worldRefl;

INTERNAL_DATA

};

sampler2D _MainTex;

sampler2D _BumpMap;

samplerCUBE _Cube;

float _Value;

void surf (Input IN, inout SurfaceOutput o)

{

float4 tex = tex2D (_MainTex, IN.uv_MainTex);

clip (tex.a - 0.5);

o.Albedo = tex.rgb;

o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));

float4 refl = texCUBE (_Cube, WorldReflectionVector (IN, o.Normal));

o.Emission = refl.rgb * _Value * refl.a;

}

ENDCG

}

Fallback "Diffuse"

}

复制代码

区域属性包含四个变量,它们会显示在Unity的检查器中。_MainTex定义了一个显示的检查器中的名字,类型以及默认值。

_MainTex 和 _BumpMap --
纹理, _Cube -- 用于反射的立体贴图,_Value -- 反射量(水平)。

Tag {"RenderType" = "Opaque"}标志着着色器是不透明的。它会影响绘制序列。

Cull Off用来禁用拣选功能 -- 所有面的绘制都是独立于多边形所面对的方向的。这里有三个选项:

Back (背面) 不渲染背对观众的多边形(默认)。

Front(前面) 不渲染面对观众的多边形。用于由内而外地旋转对象。

Off (关闭) 禁用拣选 -- 对所有面进行绘制。用于特殊效果。

我们使用一段用两个关键词构成的CG代码:CGPROGRAM, ENDCG。

# pragma surface surf Lambert -- 声明表面着色器函数和其它参数。在这种情况下,函数被称为surf而Lambert光照模型被指定为一个额外的参数。

现在来考虑输入结构。所有可能的输入结构变量都可以在这里找到。但是我们只考虑那些我们例子中使用到的。

变量uv_MainTex 和 uv_BumpMap是用来准确为对象安置纹理所需的UV坐标。这些变量必须以纹理变量名字相同的方式命名,带有的前缀
uv_ 或 uv2_ 代表着第一和第二通道。worldRefl 和 INTERNAL_DATA是用于反射的。

现在让我们来阐明着色器函数surf。

通过第一步我们已经使得一个具有四个组成部分(RGB + Alpha)的像素载体输出了呈现UV-贴图的结构。变量tex将会保存它的相关信息。之后我们通过clip函数指定哪些像素在渲染过程中丢失。由于取舍点是基于存储在alpha通道中的信息,所以我们把tex.a作为一个参数。

cull off之后,我们使用下面这行代码来在对象上绘制我们的主纹理:o.Albedo = tex.rgb;变量o就是输出结构。所有的字段在有关SurfaceShaders(表面着色器)的帮助中都有所描述。

我们通过以下的步骤来应用法线贴图:

o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));

之后我们加入反射:

float4 refl = texCUBE (_Cube, WorldReflectionVector (IN, o.Normal));

o.Emission = refl.rgb * _Value * refl.a;

我要指出的是反射的信息是乘以_Value的值而得的,所以我们可以在检查器中控制效果的等级。

最后为显卡无法正确显示着色器的情况编写Fallback。

让我们看最终得到的效果:



下面是效果视频地址(需fanqiang观看):

http://youtube.googleapis.com/v/K4_pinJ7MaU&ap=%2526fmt%3D18

原文链接:http://blog.heyworks.com/tutorial-writing-your-first-shader-in-unity3d/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: