天天看点

shader编程学习(1)第一个shader

前言

简单说,shader是为渲染管线中的特定处理阶段提供算法的一段代码,在现在主流的3D游戏引擎中,shader已经无处不在,例如常见的镜头景深(PS:通过把背景模糊化来突出拍摄对象,还是拍出清晰的背景。),动态模糊,卡通渲染,以及各种特殊的材质效果和光照效果。同时unity提供了一些内建shader,让我们先体验下shader的魅力吧!

unity内建shader

打开unity,新建一个材质文件

shader编程学习(1)第一个shader

在inspector面板,你会发现在shader栏会有很多选项,可惜都是英文的。。。你需要自己查~

这里简单介绍些单词的意思

Unlit:仅使用纹理颜色,不受光照影响

VertexLit:顶点光照

Diffuse:漫反射

Specular:在漫反射的基础上增加高光

Bumped:凸起的意思,比如Bumped Diffuse,Bumped Specular,或让材质更有凹凸感和立体感,做法就是添加Normal mapped(法线贴图)

法线贴图:在原物体的凹凸表面的每个点上均作法线,通过RGB颜色通道来标记法线的方向,你可以把它理解成与原凹凸表面平行的另一个不同的表面,但实际上它又只是一个光滑的平面。对于视觉效果而言,它的效率比原有的凹凸表面更高,若在特定位置上应用光源,可以让细节程度较低的表面生成高细节程度的精确光照方向和反射效果。

目的就是让纹理看起来更有立体感

Parallax:视差 视差贴图应该算是法线贴图的改进版,可以比法线贴图有更加明显的图像深度与真实感。

可以尝试选择一些选择,比如选择了Bmuped Specular(法线高光)shader,修改下属性,效果如下

shader编程学习(1)第一个shader

感觉还是挺有质感的~~~

内建shader根据应用对象可以分为以下几类

shader编程学习(1)第一个shader

效果越好的shader,渲染开销越大,对硬件的要求就越高。

以下是不同光照效果从低到高的计算开销排序

shader编程学习(1)第一个shader

在移动平台方面,unity也提供了几种简单的shader,在Mobile类别下

unity中的三种自定义shader

1 表面着色器(surface shaders) 

通常情况下用户会使用这种shader,用什么语言编写呢?按照浅墨大神的话说:我们学的是HLSL,就是DirectX中的那个HLSL。

下面是理由阐述:

首先,Unity中编写Shader的语言叫做ShaderLab,而ShaderLab说白了就是裹着一层皮的CG着色器语言而已。Cg,即C forgraphics,即用于图形的C语言,是微软Microsoft和英伟达NVIDIA相互协作在标准硬件光照语言的语法和语义上达成的一种一致性协议。

HLSL和CG其实是同一种语言(参见Cg教程_可编程实时图形权威指南29页的致谢部分)。很多时候,我们会发现用HLSL写的代码可以直接当中Cg代码使用。

Microsoft和NVIDIA联手推出CG语言,想在经济和技术上实现双赢,从而通过这种方式联手打击他们共同的对手GLSL。

既然Unity主打Shader编程的语言ShaderLab是CG语言披上一层皮,而CG语言又约等于HLSL。这就是说,在Unity中写Shader约等于用HLSL写Shader,也就约等于给DirectX写Shader。虽然有点绕orz,最后总结一下也就是:

在Unity中写Shader约等于给DirectX写Shader

2 顶点和片段着色器(Vertex and fragment Shaders) 

可以做的事情更多,但是也比较难写。使用片段着色器的主要目的是可以在比较低的层级上进行更复杂(或者针对目标设备更高效)的开发。

3 固定功能管线着色器(Fixed Function Shaders)

如果游戏运行在不支持可编程管线的老旧硬件上,那么就需要编写这种Shader了,它可以作为顶点和片段着色器的备用选择

第一个Shader

由于是初级学习,目前我们只针对表面着色器进行学习,在unity中创建一个shader文件,用编辑器打开,会发现里面已经有一些内容了

Shader "Custom/NewShader" {//Shader的名称
	//定义shader中使用的属性,例如颜色,向量,纹理
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		//_MainTex是s属性的名称
		//Base (RGB)作为在inspector面板显示内容
		//2D代表这个属性的类型为2D,一张2的阶数大小的贴图
		//"white"是这个属性的默认值
	}
	/*子着色器 可以有多个,它是是代码的主体,每一个子着色器中包含一个或者多个的Pass。
	在计算着色时,平台先选择最优先可以使用的着色器,然后依次运行其中的Pass*/
	SubShader {
		Tags { "RenderType"="Opaque" }
		//标签,标记这个SubShader的作用,
		//"RenderType"="Opaque" 代表着渲染含有非透明效果的物体时调用。
		
		LOD 200
		//Level of Detail的缩写,这个数值决定了我们能用什么样的Shader。
		CGPROGRAM	//进入主要的代码部分
		#pragma surface surf Lambert

		sampler2D _MainTex;//声明变量

		struct Input {
			float2 uv_MainTex;
		};
		/*主要函数,它的两有参数,
		第一个是Input,我们已经明白了:在计算输出时Shader会多次调用surf函数,每次给入一个贴图上的点坐标,来计算输出。
		第二个参数是一个可写的SurfaceOutput,SurfaceOutput是预定义的输出结构,我们的surf函数的目标就是根据输入把这个输出结构填上。
		*/
		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	} 
	//回滚,用来处理所有Subshader都不能运行的情况
	FallBack "Diffuse"
}
           

SurfaceOutput结构体的定义如下

struct SurfaceOutput {  
    half3 Albedo;     //像素的颜色
    half3 Normal;     //像素的法向值
    half3 Emission;   //像素的发散颜色
    half Specular;    //像素的镜面高光
    half Gloss;       //像素的发光强度
    half Alpha;       //像素的透明度
};
           

总结

强烈推荐一篇文章 猫都能学会的Unity3D Shader入门指南(一)

作者极为详细且耐心的讲解了shader的每一行代码代表的意思和作用~初学者必看!(PS:感谢分享知识的作者!)

unity官方的shader案例: 链接

了解了这么多,对于shader编程也算入了门,想要进行下一步的精进,只有通过不断的学习和实践!