shader 的入门

Shader "Custom/DiffuseTexture" {
Properties // 定义着色器的属性
{
_MainTex ("Base (RGB)", 2D) = "white" {}
//_MainTex 属性的名称,简单的说就是变量名,在之后整个shader代码中将使用这个名字来获取该属性的内容
//"Base (RGB)" 这个字符串将显示在Unity的材质编辑器中作为shader的使用者可读的内容
// 2D 这个属性的类型,可能的type 所表示的内容有以下几种:
//1. Vector:定义一个四维向量属性 写为 (x,y,z,w)
//2. Color : -- -- 颜色属性(取值为0-1的四维向量)以0~1定义的rgba颜色,比如(1,1,1,1);
//3. Float : -- -- 浮点数属性 某个指定的浮点数
//4. Range(min,max) :----浮点数范围属性,取值为min- max 
//5. 2D :---- 2D纹理属性
//6. Rect: ---- 矩形纹理属性(非2的n次幂)
//7. Cube: ---- 立方体纹理属性  对于贴图来说,默认值可以为一个代表默认tint颜色的字符串,可以是空字符串或者"white","black","gray","bump"中的一个


// "white"{} : 默认格式:defaultValue[{options}] 定义了这个属性的默认值,通过输入一个符合格式的默认值来制定对应属性的初始值
//(某些效果可能需要某些特定的参数值来达到需要的效果,虽然这些值可以在之后在进行调整,但是如果默认就指定为想要的值的话就省去了一个个调整的时间,方便很多
// 另外还有一个{option},它只对2D,Rect或者Cube贴图有关,在写输入时我们最少要在贴图之后写一对什么都不含的空白的{},当我们需要打开特定选项时可以把其写在这对花括号内。如果需要同时打开多个选项,可以使用空白分隔。可能的选择有ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormal中的一个,这些都是OpenGL中TexGen的模式,如果使用了自定义的顶点程序,,那么这些参数将被忽略




//所以,一组属性的申明看起来也许会是这个样子的


//Define a color with a default value of semi-transparent blue
//_MainColor ("Main Color", Color) = (0,0,1,0.5)  
//Define a texture with a default of white
//_Texture ("Texture", 2D) = "white" {}  
}




//现在看懂上面那段Shader(以及其他所有Shader)的Properties部分应该不会有任何问题了。接下来就是SubShader部分了。
SubShader {
//表面着色器可以被若干的标签(tags)所修饰,而硬件将通过判定这些标签来决定什么时候调用该着色器。
Tags { "RenderType"="Opaque" } //告诉系统应该在渲染非透明物体时调用我们,与之对应的是 “Transparent”,表示渲染含有透明效果的物体时调用。这里的Tags其实暗示你的shader输出的是什么,如果输出中都是非透明物体,那写在Opaque里;如果想渲染透明或者半透明的像素,那应该写在Transparent中。
//tags(标签) 是可选的,
//另外比较有用的标签还有"IgnoreProjector"="True"(不被Projectors影响),"ForceNoShadowCasting"="True"(从不产生阴影)以及"Queue"="xxx"(指定渲染顺序队列)。这里想要着重说一下的是Queue这个标签,如果你使用Unity做过一些透明和不透明物体的混合的话,很可能已经遇到过不透明物体无法呈现在透明物体之后的情况。这种情况很可能是由于Shader的渲染顺序不正确导致的。Queue指定了物体的渲染顺序,预定义的Queue有:


//Background - 最早被调用的渲染,用来渲染天空盒或者背景
//Geometry - 这是默认值,用来渲染非透明物体(普通情况下,场景中的绝大多数物体应该是非透明的)
//AlphaTest - 用来渲染经过Alpha Test的像素,单独为AlphaTest设定一个Queue是出于对效率的考虑
//Transparent - 以从后往前的顺序渲染透明物体
//Overlay - 用来渲染叠加的效果,是渲染的最后阶段(比如镜头光晕等特效)
//这些预定义的值本质上是一组定义整数,Background = 1000, Geometry = 2000, AlphaTest = 2450, Transparent = 3000,最后Overlay = 4000。在我们实际设置Queue值时,不仅能使用上面的几个预定义值,我们也可以指定自己的Queue值,写成类似这样:"Queue"="Transparent+100",表示一个在Transparent之后100的Queue上进行调用。通过调整Queue值,我们可以确保某些物体一定在另一些物体之前或者之后渲染,这个技巧有时候很有用处。
//比如说 加载粒子播放在ngui的界面之前 就需要修改粒子的 renderer.sharedMaterial.renderQueue = 3000;




LOD 200   //Shader Level of Detail    着色器的细节层次技术  以下是unity 官网给出的解释


//Shader Level of Detail (LOD) works by only using shaders or subshaders that have their LOD value less than a given number.
//着色器的细节层次效果只有在着色器或子着色器含有小于一定阈值的LOD值时才起作用。
//By default, allowed LOD level is infinite, that is, all shaders that are supported by the user's hardware can be used. However, in some cases you might want to drop shader details, even if the hardware can support them. For example, some cheap graphics cards might support all the features, but are too slow to use them. So you may want to not use parallax normal mapping on them.
//默认情况下,允许的 LOD 级别可以是无限的。也就是说,可以使用硬件所支持的所有的着色器。然而,在某些情况下即使硬件可以支持它们,你可能也要放弃着色器的详细信息。例如,一些廉价的图形卡可能支持所有功能,但使用这些功能的速度太慢了。所以,你可能就不想在这些卡上使用视差法线映射了。
//Shader LOD can be either set per individual shader (using Shader.maximumLOD), or globally for all shaders (using Shader.globalMaximumLOD).
//着色器的细节层次既可以针对单个着色器进行设定(使用Shader.maximumLOD),也可以针对所有着色器进行全局设定(使用Shader.globalMaximumLOD)。
//In your custom shaders, use LOD command to set up LOD value for any subshader.
//在你的自定义着色器中,使用LOD命令来为每个子着色器来设定LOD值。
//Built-in shaders in Unity have their LODs set up this way:
//Unity中内建的着色器的LOD设置参数如下:
//*       VertexLit kind of shaders = 100
//*       Decal, Reflective VertexLit = 150
//*       Diffuse = 200
//*       Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
//*       Bumped, Specular = 300
//*       Bumped Specular = 400
//*       Parallax = 500
//*       Parallax Specular = 600




// 首先是CGPROGRAM。这是一个开始标记,表明从这里开始是一段CG程序(我们在写Unity的Shader时用的是Cg/HLSL语言)。最后一行的ENDCG与CGPROGRAM是对应的,表明CG程序到此结束。而不是pass中,它会自己编译到各个pass中
// 使用 #pragma surface 命令来指明他是一个表面着色器 他的写法:#pragma surface surfaceFunction lightModel [optionalparams]
// surface 声明的是一个表面着色器
// surfaceFunction 着色器代码的方法名字
// lightModel 使用的光照模型


CGPROGRAM


#pragma surface surf Lambert


sampler2D _MainTex;


// 接下来一句sampler2D _MainTex;,sampler2D是个啥?其实在CG中,sampler2D就是和texture所绑定的一个数据容器接口。等等..这个说法还是太复杂了,简单理解的话,所谓加载以后的texture(贴图)说白了不过是一块内存存储的,使用了RGB(也许还有A)通道,且每个通道8bits的数据。而具体地想知道像素与坐标的对应关系,以及获取这些数据,我们总不能一次一次去自己计算内存地址或者偏移,因此可以通过sampler2D来对贴图进行操作。更简单地理解,sampler2D就是GLSL中的2D贴图的类型,相应的,还有sampler1D,sampler3D,samplerCube等等格式。
        // 解释通了sampler2D是什么之后,还需要解释下为什么在这里需要一句对_MainTex的声明,之前我们不是已经在Properties里声明过它是贴图了么。答案是我们用来实例的这个shader其实是由两个相对独立的块组成的,外层的属性声明,回滚等等是Unity可以直接使用和编译的ShaderLab;而现在我们是在CGPROGRAM...ENDCG这样一个代码块中,这是一段CG程序。对于这段CG程序,要想访问在Properties中所定义的变量的话,必须使用和之前变量相同的名字进行声明。于是其实sampler2D _MainTex;做的事情就是再次声明并链接了_MainTex,使得接下来的CG程序能够使用这个变量。


struct Input {
float2 uv_MainTex;
};


// 终于可以继续了。接下来是一个struct结构体。相信大家对于结构体已经很熟悉了,我们先跳过之,直接看下面的的surf函数。上面的#pragma段已经指出了我们的着色器代码的方法的名字叫做surf,那没跑儿了,就是这段代码是我们的着色器的工作核心。我们已经说过不止一次,着色器就是给定了输入,然后给出输出进行着色的代码。CG规定了声明为表面着色器的方法(就是我们这里的surf)的参数类型和名字,因此我们没有权利决定surf的输入输出参数的类型,只能按照规定写。这个规定就是第一个参数是一个Input结构,第二个参数是一个inout的SurfaceOutput结构。
        // 它们分别是什么呢?Input其实是需要我们去定义的结构,这给我们提供了一个机会,可以把所需要参与计算的数据都放到这个Input结构中,传入surf函数使用;SurfaceOutput是已经定义好了里面类型输出结构,但是一开始的时候内容暂时是空白的,我们需要向里面填写输出,这样就可以完成着色了。先仔细看看INPUT吧,现在可以跳回来看上面定义的INPUT结构体了:
        // 作为输入的结构体必须命名为Input,这个结构体中定义了一个float2的变量…你没看错我也没打错,就是float2,表示浮点数的float后面紧跟一个数字2,这又是什么意思呢?其实没什么魔法,float和vec都可以在之后加入一个2到4的数字,来表示被打包在一起的2到4个同类型数。比如下面的这些定义:
// Define a 2d vector variable
// vec2 coordinate;  
// Define a color variable
// float4 color;  
// Multiply out a color
// float3 multipliedColor = color.rgb * coordinate.x;  


        // 在访问这些值时,我们即可以只使用名称来获得整组值,也可以使用下标的方式(比如.xyzw,.rgba或它们的部分比如.x等等)来获得某个值。在这个例子里,我们声明了一个叫做uv_MainTex的包含两个浮点数的变量。
// 如果你对3D开发稍有耳闻的话,一定不会对uv这两个字母感到陌生。UV mapping的作用是将一个2D贴图上的点按照一定规则映射到3D模型上,是3D渲染中最常见的一种顶点处理手段。在CG程序中,我们有这样的约定,在一个贴图变量(在我们例子中是_MainTex)之前加上uv两个字母,就代表提取它的uv值(其实就是两个代表贴图上点的二维坐标 )。我们之后就可以在surf程序中直接通过访问uv_MainTex来取得这张贴图当前需要计算的点的坐标值了。
// 如果你坚持看到这里了,那要恭喜你,因为离最后成功读完一个Shader只有一步之遥。我们回到surf函数,它的两有参数,第一个是Input,我们已经明白了:在计算输出时Shader会多次调用surf函数,每次给入一个贴图上的点坐标,来计算输出。第二个参数是一个可写的SurfaceOutput,SurfaceOutput是预定义的输出结构,我们的surf函数的目标就是根据输入把这个输出结构填上。SurfaceOutput结构体的定义如下

struct SurfaceOutput {  
   half3 Albedo;     // 反射光
   half3 Normal;     // 法线
   half3 Emission;   // 自发光
   half Specular;    // 高光
   half Gloss;       // 光泽度
   half Alpha;       // 透明度
};


//这里的half和我们常见float与double类似,都表示浮点数,只不过精度不一样。也许你很熟悉单精度浮点数(float或者single)和双精度浮点数(double),这里的half指的是半精度浮点数,精度最低,运算性能相对比高精度浮点数高一些,因此被大量使用。
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}


// 这里用到了一个tex2d函数,这是CG程序中用来在一张贴图中对一个点进行采样的方法,返回一个float4。这里对_MainTex在输入点上进行了采样,并将其颜色的rbg值赋予了输出的像素颜色,将a值赋予透明度。于是,着色器就明白了应当怎样工作:即找到贴图上对应的uv点,直接使用颜色信息来进行着色,over。
ENDCG



FallBack "Diffuse"


// FallBack 语句的用法有两种
// 1. FallBack "备用着色器名称"。 如上例 
// 2. FallBack Off 显示生命不使用备用着色器,当没有子着色器能够运行的时候也不会有任何警告
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值