目录
一、unityshader简单制作
1.顶点/片元着色器
基本结构
顶点/片元着色器简单代码
结构体与模型数据
顶点着色器和片元着色器间的通信
属性使用
2.Unity提供的内置文件和变量
3.Unity提供的CG/HLSL语义
unity支持的语义
4.debug
5.渲染平台差异
二、unity基础光照
1.光照模型
1.BRDF光照模型
2.标准光照模型(经验模型)
3.计算:逐像素还是逐顶点
2.unity中的环境光和自发光
1.漫反射光照模型
2.高光反射光照模型
3.使用unity内置的函数
三、基础纹理
1.单张纹理
2.凹凸映射
3.渐变纹理
4.遮罩纹理
四、透明效果
1.Unity Shader的渲染顺序
2.透明度测试
3.透明度混合
4.开启深度写入的半透明效果
5.shaderlab的混合命令
shader ”MyShader“ {//命名
Properties{
Name{"display name",PropertyType}=DefaultValue//属性名字:Name 显示名字:display name 类型:PropertyType 默认值:DefaultValue
}
SubShader{
[Tags]//可选的标签,仅可在subshadeer中声明
Tags{"TagName1"="Value1" "TagName2"="Value2" }
[RenderSetup]//可选的状态,Cull、ZTest、ZWrite、Blend
Pass{
[Name]
Name "MyPassName"
[Tags]//LightMode/RequireOptions
[RenderSetup]
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
//UsePass 使用其他shader中的Pass,UsePass ”MyShader/MYPASSNAME“
//GrabPass负责抓取屏幕并将结果存储在一张纹理中
Fallback “name” / Fallback Off
}
Pass{
CGPROGRAM
#pragma vertex vert//哪个告诉unity,哪个函数包含了顶点着色器的代码:vert
#pragma fragment frag
float4 vert(float4 v :POSITION):SV_POSITION{//把模型顶点坐标填充到输入参数v,SV_POSITION告诉unity顶点着色器的输出是裁剪空间中的顶点坐标
return mul (UNITY _MATRIX_MVP,v);//顶点坐标从模型空间转换到裁剪空间,模型观察投影矩阵
}
fixed4 frag():SV_Target{//没有输入,输出是 fixed4 类型变量使用SV_Target语义限定,将用户的输出颜色存储到一个渲染目标中
return fixed4 (1.0,1.0,1.0,1.0);//表示白色的变量
}
ENDCG
}
Pass{
CGPROGRAM
#pragma vertex vert//哪个告诉unity,哪个函数包含了顶点着色器的代码:vert
#pragma fragment frag
struct a2v{
float4 vertex:POSITION;//POSITION告诉unity用模型空间的顶点坐标填充vertex
float3 normal:NORMAL;//NORMAL告诉unity用模型空间的法线方向填充normal
float4 texcoord:TEXCOORD0;//TEXCOORD0告诉unity用模型的第一套纹理坐标填充texcoord
}
float4 vert(a2v v):SV_POSITION{//SV_POSITION告诉unity顶点着色器的输出是裁剪空间中的顶点坐标
return mul (UNITY _MATRIX_MVP,v.vertex;//v.vertex来访问模型空间的顶点坐标
}
fixed4 frag():SV_Target{//没有输入,输出是 fixed4 类型变量使用SV_Target语义限定,将用户的输出颜色存储到一个渲染目标中
return fixed4 (1.0,1.0,1.0,1.0);//表示白色的变量
}
ENDCG
}
Pass{
CGPROGRAM
#pragma vertex vert//哪个告诉unity,哪个函数包含了顶点着色器的代码:vert
#pragma fragment frag
struct a2v{
float4 vertex:POSITION;//POSITION告诉unity用模型空间的顶点坐标填充vertex
float3 normal:NORMAL;//NORMAL告诉unity用模型空间的法线方向填充normal
float4 texcoord:TEXCOORD0;//TEXCOORD0告诉unity用模型的第一套纹理坐标填充texcoord
}
struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR0;
}
v2f vert(a2v v):SV_POSITION{//SV_POSITION告诉unity顶点着色器的输出是裁剪空间中的顶点坐标
v2f o;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.color=v.normal*0.5+fixed3(0.5,0.5,0.5);
return o;
}
fixed4 frag(v2f i):SV_Target{
return fixed4 (i.color,1.0);
}
ENDCG
}
shader ”MyShader“ {//命名
Properties{
_Color("Color Tint",Color)=(1.0,1.0,1.0,1.0);
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
fixed4 _Color;
struct a2v{
float4 vertex:POSITION;//POSITION告诉unity用模型空间的顶点坐标填充vertex
float3 normal:NORMAL;//NORMAL告诉unity用模型空间的法线方向填充normal
float4 texcoord:TEXCOORD0;//TEXCOORD0告诉unity用模型的第一套纹理坐标填充texcoord
}
struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR0;
}
v2f vert(a2v v):SV_POSITION{//SV_POSITION告诉unity顶点着色器的输出是裁剪空间中的顶点坐标
v2f o;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.color=v.normal*0.5+fixed3(0.5,0.5,0.5);
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 c=i.color;
c*=_Color.rgb;
return fixed4 (c,1.0);
}
ENDCG
}
}
内置的包含文件
CGPROGRAM
#include “UnityCG.cginc"
ENDCG
CGincludes文件夹包含了所有的内置包含文件
语义:赋给shader输入和输出的字符串,这个字符串表达了这个参数的含义,可以让shader知道从哪里读取数据,并把数据输出到哪里。
unity并不支持所有的语义。
系统数值语义SV_开头,被修饰的变量包含了可用于光栅化的变换胡的顶点坐标(即齐次裁剪空间中的坐标),默认会经过光栅化后显示在屏幕上
1.使用假彩色图像
把需要调试的变量映射到[0,1],作为颜色输出到屏幕上,然后通过屏幕上的像素颜色来判断这个值是否正确。
2.vs Graphics Debugger
3.帧调试器
window-Frame Debugger打开
1.渲染纹理的坐标差异
openGL进行屏幕映射 左下角为原点
DirectX右上角为原点
2.shader语法差异
3.shader语义差异
4.其他平台差异
5.float/half/fixed
辐照度:对于平行光,可通过计算在垂直于l的单位面积上单位时间内穿过的能量来得到。
散射:只改变光线的方向,但不改变光线的密度和颜色。
折射/透射(漫反射diffuse):散射到物体内部,与内部颗粒进行相交
反射(高光反射specular):散射到物体外部,
吸收(漫反射diffuse):只改变光线的密度和颜色,不改变方向
着色:根据材质属性、光源信息,使用一个等式去计算沿着某个观察方向的出射度的过程。这个等式称作光照模型lighting model
图形学中大多使用一个数学公式来表达,并且提供了一些参数来调整材质属性。
当给定入射光线的方向和辐照度后BRDF可以给出在某个出射方向上的光照能量分布。
自发光(emissive):Cmissive 当给定一个方向时,一个表面本身会向该方向发射多少辐射量。如果没有使用全局光照,并不会照亮周围的物体只是本身更亮。
高光反射(specular):Cspecular 当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
phong模型 r=2(n•I)n-I 表面法线n、视角方向v、光源方向I、反射方向r
Cspecular=(Clight•mspecular)max(0,v•r) mgloss是材质的光泽度也称为反光度,mspecular是材质的高光反射颜色,Clight光源颜色
Blinn模型 引入矢量h h=v+I/(|v+I|) Cspecular=(Clight•mspecular)max(0,n•h) //摄像机和光源足够远会快于phong,
//不能表现菲涅耳反射 各项同性:固定视角和光源反射不变,
漫反射(diffuse):Cdiffuse 当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
兰伯特模型 Cdiffuse=(Clight•mdiffuse)max(0,n•I) n表面法线,I指向光源的单位矢量,mdiffuse材质漫反射颜色,Clight光源颜色
半兰伯特模型 :Cdiffuse=(Clight•mdiffuse)(a(n•I)+β) 没有使用max来防止点积为负而是使用一个a倍缩放加上一个β偏移,一般都取0.5
Cdiffuse=(Clight•mdiffuse)(0.5(n•I)+0.5)
环境光(ambient):Cambient 描述其他所有的间接光照。
逐像素光照:片元着色器计算,以每个像素为基础,得到它的法线进行光照模型计算。Phong着色
逐顶点光照:顶点着色器,每个顶点上计算光照,渲染图元内部线性插值。高洛德着色Gouraud//计算量小但是不适合非线性计算,产生棱角
具体写在代码篇里了 https://blog.csdn.net/louxiasang/article/details/114458670
环境光控制:Window-lighting-Ambient Source/Ambient Color/Ambient Intensity
内置变量:UNITY_LIGHTMODEL_AMBIENT
具体写在代码篇里了 https://blog.csdn.net/louxiasang/article/details/114549162
利用纹理展开技术把纹理映射坐标存储在每个顶点上,纹理映射坐标(UV坐标)定义了该顶点在纹理中对应的2D坐标,使用(u,v)来表示。
我们在之前的Blinn-Phong光照模型基础上计算光照。
1.纹理面板中的第一个属性是纹理类型,Alpha form Grayscale复选框,勾选后透明通道的值会由每个像素的灰度值生成。
2.Wrap Mode它决定了当纹理坐标超过[0,1]范围后将会如何平铺:
Repeat、Clamp
3.Tilling平铺属性。
4.Filter Mode属性:当纹理由于变换而产生拉伸时将会采用哪种滤波模式。
Point Bilinear Trilinear三种模式效果依次提升但是耗费性能也依次增大,会影响放大缩小时纹理得到的图片质量。
Point:最近邻滤波,采样像素只有一个,看起来有像素风格
Bilinear:找到四个邻近像素进行线性插值混合,看起来被模糊了(常用)
Trilinear:比Bilinear多了一个在多级渐远纹理之间进行混合
纹理缩小更为复杂,多级渐远纹理技术:原纹理提前用滤波处理来得到更多更小的图像,当物体远离摄像机时可以直接使用较小的纹理。缺点是要空间存储这些纹理,通常是33%。
多级渐远纹理开启:纹理类型advanced勾选Generate Mip Maps。可以选择是否使用线性空间以及采用的滤波器。
5.纹理的最大尺寸和纹理模式
凹凸映射使用一张纹理来修改模型表面的法线。
1.高度纹理height map
模拟表面位移displacement然后得到一个修改后的法线,也称为高度映射。
高度图中存储的是强度值intensity,用于表示模型表面局部的海拔高度。
颜色越浅越向外凸起,颜色越深越凹。好处:直观 缺点:复杂,实时计算不能直接得到表面法线要根据灰度值计算。
2.法线纹理
存储表面法线方向,法线方向的分量范围[-1,1],而像素是[0,1]所以我们需要做一个映射。
pixel=(normal+1)/2 normal=pixel*2-1
模型空间的法线纹理(直接想法,更符合直观认识)、切线空间的法线纹理(实际制作,简单直观、在纹理坐标的缝合处和尖锐边角部分可见的突变较少更为平滑、自由度很高、可进行UV动画、可以重用法线纹理、可压缩)
3.Unity中的法线纹理类型
把纹理设置成normal map时,可以让unity根据不同平台对纹理进行压缩在通过UnpackNormal函数来针对不同的压缩格式对法线纹理进行正确的采样。
从高度图中生成法线纹理:导入高度图,设置成Normal map勾选Create from Grayscale。
使用渐变纹理来控制漫反射光照的结果。
遮罩允许我们保护某些区域,应用于某些反光强烈某些弱或者只做地形时混合多张图片。
一般流程:通过采样得到遮罩纹理纹素值,使用某个或几个通道的值来与某种表面属性进行相乘,当通道值为0就可以不受影响。
我们可以利用一张纹理的RDBA四个通道用以存储不同属性,例如高光反射强度在R,边缘光照强度在G,高光反射指数在B,自发光强度在A
具体写在代码篇里了https://blog.csdn.net/louxiasang/article/details/114681917
透明通道,当开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值还有透明度,透明度为1完全不透明0完全不现实。
两种方法:透明度测试、透明度混合
不透明物体:深度缓冲 z-buffer 用于解决可见性问题,基本思想是根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在于深度缓存中的值进行比较,更远的话不渲染,更近的话覆盖颜色缓冲中的值并更新到深度值。
透明度测试:片元透明值不满足条件就舍弃,否则就按不透明物体处理。透明度测试不需要关闭深度写入,只是根据透明度来舍弃一些片元。效果极端,要么完全透明要么不透明。
透明度混合:使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。需要关闭深度写入没有关闭深度测试。
渲染引擎一般会对物体进行排序再渲染:1.先渲染所有不透明物体并开启它们的深度测试和深度写入。2.把半透明物体按照它们距离摄像机的远近进行排序,按照从后向前的顺序渲染这些半透明,开启深度测试,关闭深度写入。
但是循环重叠的物体会出问题,凸面体、拆分成子模型、分割网格、开启深度写入的半透明效果可以减少错误。
渲染队列:用Subshader的Queue标签来决定我们的模型将归于哪个渲染队列。
索引号越小表示越早被渲染。
透明度测试:
Subshader{
Tags{“Queue”=“Transparent”}
Pass{
}
}
透明度混合:
Subshader{
Tags{“Queue”=“AlphaTest”}
Pass{
Zwrite Off;
}
}
混合是一个逐片元且不可编程但是可以高度可配置的。
混合等式:一个用于混合RGB通道、一个用于混合A通道。默认使用加操作。
1.Blend 混合因子A 混合因子B :使用同样的混合因子来混合RGB通道和A通道
2.Blend 混合因子A 混合因子B,混合因子C 混合因子D :Orgb=混合因子A x Srgb+混合因子B x Drgb;Oa=混合因子A x Sa+混合因子B x Da