一、渐变纹理
也是由Value公司在制作<军团要塞2>提出,
可以保证物体的轮廓线相比之前使用的传统漫反射光照更加明显,能够提供多种色调变化,常用于卡通渲染中。
基本思想:
在顶点着色器中将法线、顶点转换到世界空间,计算平铺、偏移后的纹理坐标,传递到片元着色器中;
在片元着色器中,环境光如果没有使用单张纹理那么不必计算材质反射率,直接采用系统环境光。计算漫反射时采用半兰伯特模型,使用halfLambert构建纹理坐标对渐变纹理采样。高光反射使用Blinn-Phong模型h来计算。
代码:
Shader “Untiy Shaders Book/Chapter 7/Ramp Texture”
{
Properties
{
_Color{"Color TInt",Color}=(1,1,1,1)
_RampTex("Ramp Tex",2D)="white"{} //渐变纹理
_Specular("Specular",Color)=(1,1,1,1)
_Gloss("Gloss",Range(8.0,256))=20
}
Subshader
{
Pass
{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _RampTex;
float4 _RampTex_ST;
fixed4 _Specular;
float4 _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal ; NORMAL;
float4 texcoord : TEXCOORD0;
}
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
}
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
o.worldNormal = UntiyObjectToWorldNormal(v.normal);
o.worldPos = mul(_Object2World,v.vertex).xyz;
o.uv = TANSFORM_TEX(v.texcoord,_RampTex); //计算平铺、偏移后的纹理坐标
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //没有单张纹理,所以没有albedo
fixed halfLambert = 0.5*dot(worldNormal,worldLightDir)+0.5; //使用半兰伯特模型
fixed3 diffuseColor = tex2D(_RampTex,fixed2(halfLambert,halfLambert)).rgb*_Color.rgb; //使用halfLambert构造纹理坐标,对RampLambert进行采样。
fixed3 diffuse = _LightColor.rgb*diffuseColor; //计算半兰伯特的光照漫反射
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir+viewDir);
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(0,dot(worldNormal,halfDir)),_Gloss);
return fixed4(ambient+diffuse+specular,1.0);
}
}
}
}
需要注意的是:Wrap Mode需要设置成Clamp,Repeat可能会有取小数部分,从而有小黑点的情况。具体见158页。
二、遮罩纹理
遮罩纹理非常有用,它可以保护某个区域,免于某些修改。
可以使用一张遮罩纹理控制整体光照,某些区域的强或者弱,
还可以制作地形时,混合多种不同纹理。
总之,可以更加精准的(像素级别)控制模型表面的各种性质。
基本思想:
主要是在片元着色器中采样的到遮罩纹理的纹素值,然后使用某个通道的值,与表面属性相乘,通道为0时,就保护表面不受该属性的影响。
代码:
Shader "Untiy Shaders Book/Chapte3r 7/Normal Map In Tangent Space"
{
Properties
{
_Color("Color Tint",Color)=(1,1,1,1) //整体模型色调
_MainTex("Main Tex",2D)="white"{} //纹理的声明,初始值全白色。(大概是有了纹理之后,使用纹理来充当漫反射的颜色 _Diffuse)
_BumpMap("Normal Map",2D)="bump"{} //法线纹理,使用bump作为默认值,bump是模型自带的法线信息
_BumpScale("Bump Scale",Float)=1.0 //控制法线纹理作用下的凹凸程度
SpecularMask("Specular Mask",2D) = "white"{} //高光反射遮罩纹理
SpecularScale("Specular Scale",Float) = 1.0 //用来控制遮罩影响度的系数
_Specular("Specular",Color)=(1,1,1,1) //控制高光反射颜色
_Gloss("Gloss",Range(8.0,256))=20 //控制高光反射范围
}
SubShader
{
Pass
{
Tags{"LightMode"="ForwardBase"} //光照模式定义
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sample2D _MainTex;
float4 _MainTex_ST; //得到纹理平铺偏移属性,主纹理、法线纹理、遮罩纹理 共用,可以节省空间。
sample2D _BumpMap;
float _BumpScale;
sampler2D _SpecularMask; //遮罩纹理
float _SpecularScale; //遮罩影响系数
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT; //获取切线,4个分量是因为需要w分量来确定切线空间第三个坐标轴副切线的方向性。
float4 texcoord : TEXCOORD0; //存储纹理坐标
}
struct v2f
{
float4 pos : SV_POSITION; //描述输出的顶点位置
float2 uv : TEXCOORD0; //纹理坐标变量uv
float3 lightDir : TEXCOORD1; //传递顶点着色器中计算出的光源方向
float3 viewDir : TEXCOORD2; //传递顶点着色器中计算出的视角方向
}
v2f vert(a2v v)
{
v2f o;
o.pos=mul(UNTIY_MATRIX_MVP,v.vertex); //转换到裁剪空间中
o.uv.xy = v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw; //纹理的缩放平移计算
TANGENT_SPACE_ROTATION; //计算模型空间转换到切线空间的变换矩阵rotation(3×3),具体实现代码在150页。
o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz; //将光照方向转到切线空间内
o.viewDir=mul(rotation,ObjSpaceViewDir(v.vertex)).xyz; //将观察方向转到切线空间内
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 tangentLightDir = normalize(i.lightDir);//将切向空间内的矢量单位矢量化,下同
fixed3 tangentViewDir = normalize(i.viewDir);
fixed4 packedNormal = tex2D(_BumpMap,i.uv); //先对法线纹理进行采样
fixed3 tangentNormal; //创建变量从像素反映射回法线方向。
tangentNormal=UnpackNormal(packedNormal); //反映射
tangentNormal.xy* = _BumpScale; //乘以凹凸程度
tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
//运用直角三角形三边关系使用xy求得z
fixed3 albedo = tex2D(_MainTex,i.uv).rgb*_Color.rgb; //对纹理采样,乘颜色得到材质的反射率
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; //环境光部分
fixed3 diffuse = _LightColor0.rbg*albedo*max(0,dot(tangentNormal,tangentLightDir)); //漫反射
fixed3 halfDir = normalize(tangentLightDir+tangentViewDir); //计算BP模型中的h
fixed specularMask = tex2D(_SpecularMask,i.uv).r*_SpecularScale;
//对遮罩纹理采样后,rgb各个分量都一样,都是该点的高光反射强度,所以任选R分量进行计算掩码值。
//乘以系数_SpecularScale后,控制高光反射强度。
fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(tangentNormal,halfDir)),Gloss)*specularMask;
//计算附带遮罩的高光反射
return fixed4 (ambient+diffuse+specular,1.0); //相加
}
}
}
Fallback "Specular”
}
以上 这张遮罩纹理浪费了一些空间,因为rgb分量存储的是同一个值,
在实际的游戏制作中,我们会充分使用遮罩纹理的每一个颜色来存储表面属性。
比如r控制高光反射,g控制边缘光照强度,高光反射指数部分存储在b,自发光存储在a。
《Dota2》开发中,每个模型就是使用4张纹理:
一张控制定义颜色,一张控制定义表面法线,另外两张都是遮罩纹理。
这样两张遮罩纹理就会提供8种额外的表面属性,这使得人物材质自由度很强,可以支持很多的高级模型属性。
总结下前面不懂的:
tex2D采样怎么采,i.uv纹理坐标到底是什么,怎么用在这个函数的,可以查查tex2D函数。
Wrap Mode,Repeat和Clamp,为什么repeat纹理坐标超过1,舍弃整数部分然后会出现重复样子。然后为什么这样渐变纹理会有黑点。
遮罩纹理中,为什么_MainTex_ST会定义三个纹理的共同的偏移平铺系数,是如果不定义另外两个就会默认使用这个么。
就想起这些。。记下
这节看完突然就理解了,有时候为什么会看见那么乱七八糟,五颜六色,花了呼哨的没有意义的图片,原来都是控制信息,吊,谁研究的呢你说。