文明网站建设工作进度表,搜索引擎优化的实验结果分析,陕西手机网站建站,做网站知识大全本系列为作者学习UnityShader入门精要而作的笔记#xff0c;内容将包括#xff1a;
书本中句子照抄 个人批注项目源码一堆新手会犯的错误潜在的太监断更#xff0c;有始无终
我的GitHub仓库
总之适用于同样开始学习Shader的同学们进行有取舍的参考。 文章目录 表面着色器… 本系列为作者学习UnityShader入门精要而作的笔记内容将包括
书本中句子照抄 个人批注项目源码一堆新手会犯的错误潜在的太监断更有始无终
我的GitHub仓库
总之适用于同样开始学习Shader的同学们进行有取舍的参考。 文章目录 表面着色器表面着色器的一个例子编译指令表面函数光照模型其他可选参数 两个结构体数据来源Input 结构体表面属性SurfaceOutput结构体 Unity背后做了什么表面着色器实例分析 浅看了第十八章感觉没必要写。所以这一章表面着色器就是我们的最终章了接下来重心将会落实到一些深入引擎和优化技术上。
表面着色器
顶点片元着色器本质上是一种对硬件友好的方式但是对人类不友好。虽然计算机世界中这样的例子已经比比皆是了。出于拒绝反人类的目的一种新的着色器表面着色器(Surface Shader 被加入到Unity中。
表面着色shader包含了3个层次表面着色器光照模型和光照着色器
其中表面着色器定义了模型表面的反射率、法线和高光等光照模型则选择使用的光照模型类型是半兰伯特还是BlinnPhong或者其他光照模型光照着色器则由系统进行实现。
表面着色器大大减少了shader开发的工作量大多数时候我们只需要和表面着色器打交道定义一些属性选择要使用的光照模型即可。
表面着色器实际上就是对顶点片元着色器上的一层抽象使用表面着色器可以用一种更容易理解的方式编写shader不需要考虑前向渲染路径还是延迟渲染路径场景中的光源等等等等要素。 表面着色器的一个例子
要实现一个前向光照渲染的材质我们要定义光照模型定义前向渲染Pass定义阴影pass。总而言之如果用顶点片元着色器实现会很难很复杂。
但是现在我们可以使用表面着色器来直接实现
Shader Custom/BumpedDiffuse_Copy
{Properties{_Color(Main Color,Color) (1,1,1,1)_MainTex(Base,2D) white{}_BumpMap(Normalmap,2D) bump{}}SubShader{Tags{RenderType Opaque}LOD 300CGPROGRAM#pragma surface surf Lambert#pragma target 3.0sampler2D _MainTex;sampler2D _BumpMap;fixed4 _Color;struct Input{float2 uv_MainTex;float2 uv_BumpMap;};void surf(Input IN,inout SurfaceOutput o){fixed4 tex tex2D(_MainTex,IN.uv_MainTex);o.Albedo tex.rgb * _Color.rgb;o.Alpha tex.a * _Color.a;o.Normal UnpackNormal(tex2D(_BumpMap,IN.uv_BumpMap));}ENDCG}Fallback Diffuse
}
如此简单就能完成一个光照模型与顶点片元着色器需要包含到一个特定的Pass块中不同 表面着色器可以直接且必须写在SubShader块中Unity会自动生成多个Pass并在CGPROGRAM块中定义表面着色器的具体代码。 编译指令
在表面着色器中我们通过编译指令来和Unity进行沟通编译指令最重要的作用是指示该表面着色器使用的表面函数和光照函数并设置一些可选参数。表面着色器的CG块中的第一句代码往往就是它的编译指令编译指令的一般格式如下
#pragma surface surfaceFunction lightModel [optionalparams]指定表面函数和光照模型以及其他的一些可选参数来控制表面着色器的一些行为。
表面函数
表面着色器抽象出了表面这一概念并包含了表面属性一个对象的表面属性定义了它的反射率、光滑度、透明度等值。而编译指令中的surfaceFunction就用于定义这些表面属性。surfaceFunction通常就是名为surf的函数函数名可以任意它的函数格式是固定的
void surf(Input IN, inout SurfaceOutput o)
void surf(Input IN, inout SurfaceOutputStandard o)
void surf(Input IN, inout SurfaceOutputStandardSpecular o)其中输入结构体Input是我们自定义的用于设置各种表面属性。
输出结构体通常是SurfaceOutput ,SurfaceOutputStandard ,SurfaceOutputStandardSpecular ,这些类型它们是unity内置的结构体需要配合不同的光照模型使用。
光照模型
官方自定义光照文档
除了表面函数我们还需要指定光照函数光照函数会使用表面函数中设置的各种表面属性来应用某些光照模型。
unity中内置了基于物理的光照模型Standard和StandardSpecular以及简单的非基于物理的Lambert和BlinnPhong
当然我们也可以定义自己的光照函数例如用下列函数来定义前线渲染中的光照函数
// 根据官方文档的代码Lighting是统一的前缀后面字符部分才是光照模型的名称
// 一些常用的变量直接按规定属性名定义为函数入参并使用即可
// 用于不依赖视角的光照模型例如漫反射
half4 LightingName (SurfaceOutput s,half3 lightDir, half atten);
// 用于依赖视角的光照模型例如高光反射
half4 LightingName (SurfaceOutput s,half3 lightDir, half3 virwDir, half atten);其他可选参数
除了光照模型和表面着色器两个必需参数之外我们还可以设置一些可选参数
这些参数都在官方文档中记载了
简单的使用可选参数就可以定义一些功能
注意表面着色器只能在内置渲染管线Build-In Pipeline中使用而URP和HDRP是不能使用的在URP和HDRP中想要简单的实现Shader需要使用Shader Graph 两个结构体
表面着色器最多支持自定义4种关键的函数
表面函数用于设置各种表面性质如反射率法线等光照函数定义表面使用的光照模型顶点修改函数修改或传递顶点属性最后的颜色修改函数对最后的颜色进行修改
那么这些函数之间的信息传递是如何实现的呢
一个表面着色器需要使用两个结构体表面函数的输入结构体Input以及存储了表面属性的结构体SurfaceOutput
数据来源Input 结构体
Input结构体包含了许多表面属性的数据来源因此它会作为表面函数的输入结构体。Input支持很多内置的变量名通过这些变量名我们告诉Unity需要使用的数据信息例如在Input结构体种包含了主纹理和法线纹理的采样坐标uv_MainTex和uv_BumpMap。这些采样坐标必须以uv为前缀
因此纹理的采样直接用uv_sample2Dname这种格式定义即可而其他变量需要根据下表严格定义 只需要定义变量即可使用了
除了这些变量之外如果我们想要自定义变量也可以就像顶点片元着色器种实现的一样自定义变量并在顶点修改函数中进行处理将其传递到surface函数中去。 表面属性SurfaceOutput结构体
SurfaceOutput就是专门用于存储表面属性的。SurfaceOutput ,SurfaceOutputStandard ,SurfaceOutputStandardSpecular 等它们作为表面着色器的输出也作为光照函数的输入。这些结构体的变量都是提前声明好的直接使用即可。
剩下的就是光照模型可以用内置的光照模型我们也可以自定义光照函数 Unity背后做了什么
使用表面着色器我们只需要编译指令、自定义函数和两个结构体就可以生成一个表面着色器。而实际上Unity在背后为表面着色器生成了一系列的顶点/片元着色器
Unity在背后会根据表面着色器生成一个包含了很多Pass的顶点/片元着色器例如我们设置了不同的渲染路径则会生成对应渲染路径的Pass我们设置了不同LightMode则会生成不同的对应光照的Pass若使用了addshadow则会生成ShadowCaster的阴影Pass。 我们只需在Unity 的表面着色器的面板上点击Show generated code即可生成对应的顶点/片元着色器代码。 根据上图可以看到其实表面着色器的过程很简单。我们之前定义的部分都是属于片元着色器中的自定义部分。
Unity对Pass的自动生成过程如下
1直接将表面着色器中CGPROGRAM块部分的代码赋值并解析这部分代码包括了我们对预编译指令定义的变量以及表面函数光照函数等。这些函数和变量将在处理后进行调用
2unity分析代码并生成顶点着色器的输出v2f_surf结构体用于顶点着色器和片元着色器之间的变量传递。 v2f_surf结构体中的变量是根据我们定义的相应变量生成的注意变量名称要一模一样。若某些变量在编译时发现未使用也不会被生成带v2f_surf结构体中。
3接着生成顶点着色器vert_surf
若我们自定义了顶点修改函数则unity会首先调用顶点修改函数来修改顶点数据或填充自定义的Input结构体中的变量然后Unity会分析顶点修改函数中修改的数据并通过Input结构体将修改结果存储到v2f_surf相应的变量中。将顶点数据计算成一些其他的通用变量例如顶点坐标纹理坐标法线方向逐顶点光照光照纹理的采样坐标等。可以通过编译器控制某些变量是否需要计算最后将v2f_surf传递到下一片元着色器中
4生成片元着色器frag_surf
使用顶点着色器传递的v2f_surf结构体变量来填充Input结构体例如纹理坐标视角方向等调用自定义的表面函数填充SurfaceOutput结构体调用光照函数得到初始的颜色值如果使用的是内置的Lambert和BlinnPhong光照模型还会计算动态全局光照进行其他的颜色叠加例如若没有使用光照烘焙还会添加逐顶点光照最后如果自定义了最后的颜色修改函数unity会调用它进行最后的颜色修改 表面着色器实例分析
文中的表面着色器实例分析针对的是表面着色器生成的顶点/片元着色器代码具体可以详见书本
我们此处简单分析一下表面着色器的代码
Shader Unity Shaders Book/Chapter 17/Normal Extrusion {Properties {_ColorTint (Color Tint, Color) (1,1,1,1)_MainTex (Base (RGB), 2D) white {}_BumpMap (Normalmap, 2D) bump {}_Amount (Extrusion Amount, Range(-0.5, 0.5)) 0.1}SubShader {Tags { RenderTypeOpaque }LOD 300CGPROGRAM// surf - which surface function.// 自定义的光照模型// CustomLambert - which lighting model to use.// 自定义的顶点修改函数// vertex:myvert - use custom vertex modification function.// 自定义的颜色修改函数// finalcolor:mycolor - use custom final color modification function.// 编译选项——生成阴影// addshadow - generate a shadow caster pass. Because we modify the vertex position, the shder needs special shadows handling.// 编译选项——不为deferred/legacy deferred渲染路径生成pass// exclude_path:deferred/exclude_path:prepas - do not generate passes for deferred/legacy deferred rendering path.// 不生成用于全局动态光照的“meta” pass// nometa - do not generate a “meta” pass (that’s used by lightmapping dynamic global illumination to extract surface information).#pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa#pragma target 3.0fixed4 _ColorTint;sampler2D _MainTex;sampler2D _BumpMap;half _Amount;struct Input {float2 uv_MainTex;float2 uv_BumpMap;};// 自定义顶点修改函数void myvert (inout appdata_full v) {v.vertex.xyz v.normal * _Amount;}void surf (Input IN, inout SurfaceOutput o) {fixed4 tex tex2D(_MainTex, IN.uv_MainTex);o.Albedo tex.rgb;o.Alpha tex.a;o.Normal UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));}// 自定义的光照模型half4 LightingCustomLambert (SurfaceOutput s, half3 lightDir, half atten) {half NdotL dot(s.Normal, lightDir);half4 c;c.rgb s.Albedo * _LightColor0.rgb * (NdotL * atten);c.a s.Alpha;return c;}// 自定义的颜色修改函数void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) {color * _ColorTint;}ENDCG}FallBack Legacy Shaders/Diffuse
}